The field layer, an extension type over BigInt that a later implementation with fixed limbs can replace alone; the tower Fp2, Fp6 and Fp12 with the formulas of kilic; G1 and G2 with their compressed encodings and the verdicts of FromCompressed (flags, the point at infinity, coordinates below p, the curve and the subgroup, checked in G2 by psi(P) = [x]P); the optimal ate pairing with the final exponentiation of kilic, GT serialized c1 before c0 at every level; and the hash to G1 of RFC 9380 with the DST of Quicknet. tool/bls12381_go_vectors.go writes test/vectors/bls12381_vectors.json with kilic and kyber-bls12381: the frozen edge cases of datekeys-ts with their Go verdicts recomputed, and decodings, sums, multiples, pairings, hashes, maps and BLS signatures drawn from a fixed seed. BigInt is not constant time: the README says where that matters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>v0.11
parent
82fa7145d0
commit
cdd1c89b1e
@ -0,0 +1,169 @@
|
||||
/// The base field Fp of BLS12-381 and its scalar field order r: the one
|
||||
/// layer of the curve code that touches the representation of a field
|
||||
/// element.
|
||||
///
|
||||
/// An element is an extension type over a [BigInt] in 0..p-1, so that it
|
||||
/// costs no wrapper at run time and the rest of the code (the tower of
|
||||
/// bls12381_tower.dart, the curves, the pairing and the hash to curve) uses
|
||||
/// only the operations declared here. A later implementation with fixed
|
||||
/// limbs replaces this file alone.
|
||||
///
|
||||
/// [BigInt] is not constant time: its operations take time and allocate
|
||||
/// memory that depend on the values. That is acceptable where the values are
|
||||
/// public, as in the verification of a release, whose signature is public
|
||||
/// once drand publishes it, and in the decryption of a tlock stanza, whose
|
||||
/// inputs are that signature and the stanza. It is not where they are
|
||||
/// secret: in the encryption of a tlock stanza, sigma and r leak through the
|
||||
/// timing of this code (see ibe.dart).
|
||||
///
|
||||
/// The [BigInt] of Dart is exact on the VM and compiled to JavaScript alike.
|
||||
library;
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'bytes.dart';
|
||||
|
||||
/// The modulus p of the base field (spec §12.2).
|
||||
final BigInt fpModulus = BigInt.parse(
|
||||
'1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb1'
|
||||
'53ffffb9feffffffffaaab',
|
||||
radix: 16,
|
||||
);
|
||||
|
||||
/// The prime order r of G1, G2 and GT, the order of the scalar field (spec
|
||||
/// §12.2).
|
||||
final BigInt groupOrder = BigInt.parse(
|
||||
'73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001',
|
||||
radix: 16,
|
||||
);
|
||||
|
||||
final BigInt _p = fpModulus;
|
||||
// (p + 1) / 4: the exponent of the square root, as p = 3 mod 4.
|
||||
final BigInt _sqrtExponent = (_p + BigInt.one) >> 2;
|
||||
// (p - 1) / 2: the largest y that is not lexicographically larger than -y.
|
||||
final BigInt _halfP = (_p - BigInt.one) >> 1;
|
||||
|
||||
/// The size of an element of Fp in bytes, big-endian.
|
||||
const fpByteLength = 48;
|
||||
|
||||
/// An element of Fp, the integers modulo p.
|
||||
extension type const Fp._(BigInt _v) {
|
||||
/// The element [v], which must be in 0..p-1.
|
||||
factory Fp(BigInt v) {
|
||||
if (v.isNegative || v >= _p) {
|
||||
throw ArgumentError.value(v, 'v', 'not in 0..p-1');
|
||||
}
|
||||
return Fp._(v);
|
||||
}
|
||||
|
||||
/// The element [v] modulo p, for any integer [v].
|
||||
factory Fp.reduce(BigInt v) => Fp._(v % _p);
|
||||
|
||||
/// The element of the hexadecimal [hex], which must be below p: the
|
||||
/// constants of the curves.
|
||||
factory Fp.hex(String hex) => Fp(BigInt.parse(hex, radix: 16));
|
||||
|
||||
/// The element of the 48 big-endian bytes [b], or null when they encode p
|
||||
/// or more: the coordinate decoders of spec §12.2 reject it rather than
|
||||
/// reduce it.
|
||||
static Fp? fromBytes(List<int> b) {
|
||||
if (b.length != fpByteLength) {
|
||||
throw ArgumentError.value(b.length, 'b', 'want $fpByteLength bytes');
|
||||
}
|
||||
final v = _bigFromBytes(b);
|
||||
return v < _p ? Fp._(v) : null;
|
||||
}
|
||||
|
||||
/// The element of any number of big-endian bytes, reduced modulo p: the
|
||||
/// 64 bytes of hash_to_field of RFC 9380.
|
||||
static Fp fromBytesReduced(List<int> b) => Fp._(_bigFromBytes(b) % _p);
|
||||
|
||||
/// 0.
|
||||
static final Fp zero = Fp._(BigInt.zero);
|
||||
|
||||
/// 1.
|
||||
static final Fp one = Fp._(BigInt.one);
|
||||
|
||||
/// 1/2, which is (p + 1)/2.
|
||||
static final Fp half = Fp._((_p + BigInt.one) >> 1);
|
||||
|
||||
/// The integer in 0..p-1 of this element.
|
||||
BigInt toBigInt() => _v;
|
||||
|
||||
/// The 48 big-endian bytes of this element.
|
||||
Uint8List toBytes() =>
|
||||
fromHex(_v.toRadixString(16).padLeft(2 * fpByteLength, '0'));
|
||||
|
||||
/// Whether this is 0.
|
||||
bool get isZero => _v.sign == 0;
|
||||
|
||||
/// Whether this is 1.
|
||||
bool get isOne => _v == BigInt.one;
|
||||
|
||||
/// Whether this and [other] are the same element.
|
||||
bool equals(Fp other) => _v == other._v;
|
||||
|
||||
/// The parity of the integer of this element: sgn0 of RFC 9380 for m = 1.
|
||||
bool get isOdd => _v.isOdd;
|
||||
|
||||
/// Whether this element is larger than its negation as an integer in
|
||||
/// 0..p-1, which sets the sign bit of a compressed point (spec §12.2).
|
||||
bool get isLexicographicallyLargest => _v > _halfP;
|
||||
|
||||
/// The sum.
|
||||
Fp operator +(Fp o) {
|
||||
final s = _v + o._v;
|
||||
return Fp._(s >= _p ? s - _p : s);
|
||||
}
|
||||
|
||||
/// The difference.
|
||||
Fp operator -(Fp o) {
|
||||
final d = _v - o._v;
|
||||
return Fp._(d.isNegative ? d + _p : d);
|
||||
}
|
||||
|
||||
/// The negation.
|
||||
Fp operator -() => _v.sign == 0 ? this : Fp._(_p - _v);
|
||||
|
||||
/// The product.
|
||||
Fp operator *(Fp o) => Fp._((_v * o._v) % _p);
|
||||
|
||||
/// The square.
|
||||
Fp square() => Fp._((_v * _v) % _p);
|
||||
|
||||
/// Twice this.
|
||||
Fp double() => this + this;
|
||||
|
||||
/// The inverse, or 0 for 0, as the inverse of kilic/bls12-381.
|
||||
Fp inverse() => _v.sign == 0 ? this : Fp._(_v.modInverse(_p));
|
||||
|
||||
/// This to the power [e], e >= 0.
|
||||
Fp pow(BigInt e) => Fp._(_v.modPow(e, _p));
|
||||
|
||||
/// A square root, or null when this is not a square. Of the two roots
|
||||
/// it returns this^((p + 1) / 4); when this is not a square, that power is
|
||||
/// a root of its negation instead, which [sqrtOrNegatedRoot] exposes.
|
||||
Fp? sqrt() {
|
||||
final (root, isSquare) = sqrtOrNegatedRoot();
|
||||
return isSquare ? root : null;
|
||||
}
|
||||
|
||||
/// this^((p + 1) / 4) and whether it is a square root of this. When it is
|
||||
/// not, it is a square root of the negation of this, since -1 is not a
|
||||
/// square in Fp: the one exponentiation that the map of RFC 9380 needs.
|
||||
(Fp, bool) sqrtOrNegatedRoot() {
|
||||
final s = _v.modPow(_sqrtExponent, _p);
|
||||
return (Fp._(s), (s * s) % _p == _v);
|
||||
}
|
||||
|
||||
/// (a * b - c * d) mod p, reduced once.
|
||||
static Fp mulSub(Fp a, Fp b, Fp c, Fp d) =>
|
||||
Fp._((a._v * b._v - c._v * d._v) % _p);
|
||||
|
||||
/// (a * b + c * d) mod p, reduced once.
|
||||
static Fp mulAdd(Fp a, Fp b, Fp c, Fp d) =>
|
||||
Fp._((a._v * b._v + c._v * d._v) % _p);
|
||||
}
|
||||
|
||||
BigInt _bigFromBytes(List<int> b) =>
|
||||
b.isEmpty ? BigInt.zero : BigInt.parse(toHex(b), radix: 16);
|
||||
@ -0,0 +1,36 @@
|
||||
// Go reference values of BLS12-381 that the tests running on Node.js use:
|
||||
// they read no file. Each is copied from the JSON that Go wrote,
|
||||
// testdata/vectors/tlock_ibe.json or test/vectors/bls12381_vectors.json, and
|
||||
// bls12381_constants_test.dart checks it against that JSON on the VM.
|
||||
library;
|
||||
|
||||
/// The generator of G1, compressed (tlock_ibe.json).
|
||||
const generatorG1 =
|
||||
'97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83f'
|
||||
'f97a1aeffb3af00adb22c6bb';
|
||||
|
||||
/// The generator of G2, compressed (tlock_ibe.json).
|
||||
const generatorG2 =
|
||||
'93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf112'
|
||||
'13945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02'
|
||||
'b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8';
|
||||
|
||||
/// H2(e(G1, G2)) truncated to 16 bytes (tlock_ibe.json).
|
||||
const h2OfGenerators = 'cb87319f24560b5231579a09ad79f12e';
|
||||
|
||||
/// The identity of round 1000 hashed to G1 with the DST of Quicknet
|
||||
/// (bls12381_vectors.json).
|
||||
const hashOfRound1000 =
|
||||
'8f5a32d53837b00fbc0ee31ce9966435a41c5188a80ce9934d3c80588b6ad6f643ebda1b'
|
||||
'83ef89e44da9ced6205cdecf';
|
||||
|
||||
/// hash_to_curve of "abc" with the DST of RFC 9380, appendix J.9.1: x
|
||||
/// (bls12381_vectors.json).
|
||||
const hashOfAbcX =
|
||||
'03567bc5ef9c690c2ab2ecdf6a96ef1c139cc0b2f284dca0a9a7943388a49a3aee664ba5'
|
||||
'379a7655d3c68900be2f6903';
|
||||
|
||||
/// The same: y.
|
||||
const hashOfAbcY =
|
||||
'0b9c15f3fe6e5cf4211f346271d7b01c8f3b28be689c8429c85b67af215533311f0b8dfa'
|
||||
'aa154fa6b88176c229f2885d';
|
||||
@ -0,0 +1,46 @@
|
||||
// The Go values that the tests on Node.js carry in bls12381_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 'bls12381_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 generators and H2 are those of tlock_ibe.json', () {
|
||||
final v = section(
|
||||
readJson('testdata/vectors/tlock_ibe.json'),
|
||||
'vectors',
|
||||
).first;
|
||||
expect(
|
||||
[generatorG1, generatorG2, h2OfGenerators],
|
||||
[v['g1'], v['g2'], v['h2']],
|
||||
);
|
||||
});
|
||||
|
||||
test('the hashes to G1 are those of bls12381_vectors.json', () {
|
||||
final hashes = section(
|
||||
readJson('test/vectors/bls12381_vectors.json'),
|
||||
'hash_to_g1',
|
||||
);
|
||||
final round = hashes.firstWhere(
|
||||
(v) => v['label'] == 'the identity of round 1000',
|
||||
);
|
||||
expect(hashOfRound1000, round['point']);
|
||||
final abc = hashes.firstWhere(
|
||||
(v) => (v['label']! as String).contains('msg "abc"'),
|
||||
);
|
||||
expect([hashOfAbcX, hashOfAbcY], [abc['x'], abc['y']]);
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,219 @@
|
||||
// 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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,546 @@
|
||||
//go:build ignore
|
||||
|
||||
// Prints test/vectors/bls12381_vectors.json: the Go reference values for the
|
||||
// BLS12-381 code of lib/src/bls12381_*.dart, from the libraries that drand
|
||||
// and tlock use for Quicknet: github.com/kilic/bls12-381 and
|
||||
// github.com/drand/kyber-bls12381 over it.
|
||||
//
|
||||
// - points: the frozen edge-case encodings of datekeys-ts
|
||||
// (src/lib/dkc/testing/bls12381-vectors.json; valid points, sign-bit
|
||||
// flips, identity encodings with stray flags or payload, missing
|
||||
// compression flag, wrong lengths, uncompressed forms, x + p, points on
|
||||
// the curve outside the subgroup, cofactor torsion), each with the
|
||||
// verdict of the reference recomputed here: the KeyGroup of the drand
|
||||
// crypto schemes (G1 for pedersen-bls-unchained, G2 for
|
||||
// bls-unchained-g1-rfc9380), whose UnmarshalBinary is FromCompressed of
|
||||
// kilic, and Equal(Null()) for the identity, as profile.Validate uses it.
|
||||
// - decode: encodings drawn from a fixed seed: multiples of the generators,
|
||||
// their negations, one flipped bit, random x with the compression flag,
|
||||
// and random x of points on the curve outside the subgroup; each with the
|
||||
// verdict, and for an invalid one the class of the error of kilic
|
||||
// ("format", "curve" or "subgroup").
|
||||
// - add, multiply: sums and multiples of points drawn from the seed, the
|
||||
// special cases included (P + P, P + (-P), the point at infinity, the
|
||||
// scalars 0, 1, r - 1, r, r + 1 and 2^256 - 1), computed by kilic.
|
||||
// - pairing: e(P, Q) for points drawn from the seed, serialized by
|
||||
// kyber-bls12381 (the order of kilic: c1 before c0 at every level).
|
||||
// - hash_to_g1: HashToCurve of kilic for the DST of Quicknet and of tlock
|
||||
// (BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_) on messages drawn from
|
||||
// the seed and on the identities of some rounds, and for the DST of the
|
||||
// test vectors of RFC 9380, appendix J.9.1, on their messages, with the
|
||||
// affine coordinates.
|
||||
// - map_to_g1: MapToCurve of kilic (the simplified SWU map, the isogeny and
|
||||
// the clearing of the cofactor) on elements u drawn from the seed and on
|
||||
// the exceptional ones, u = 0 and Z·u² = -1, which no hash reaches.
|
||||
// - signatures: BLS signatures on G1 of kyber's sign/bls (NewSchemeOnG1,
|
||||
// the scheme of Quicknet) under keys drawn from the seed, and the verdict
|
||||
// of its Verify on each and on edited copies.
|
||||
//
|
||||
// The seed is fixed: the output is the same on every run. 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 bls12381_go_vectors.go path/to/bls12381-vectors.json > bls12381_vectors.json
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/drand/drand/v2/common"
|
||||
"github.com/drand/drand/v2/crypto"
|
||||
"github.com/drand/kyber"
|
||||
bls "github.com/drand/kyber-bls12381"
|
||||
signbls "github.com/drand/kyber/sign/bls"
|
||||
bls12381 "github.com/kilic/bls12-381"
|
||||
)
|
||||
|
||||
const quicknetDST = "BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_"
|
||||
|
||||
var (
|
||||
p, _ = new(big.Int).SetString("1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", 16)
|
||||
order, _ = new(big.Int).SetString("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", 16)
|
||||
g1 = bls12381.NewG1()
|
||||
g2 = bls12381.NewG2()
|
||||
)
|
||||
|
||||
func must[T any](v T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// stream is a deterministic byte source: SHA-256 of the label, a counter
|
||||
// and a block index.
|
||||
type stream struct {
|
||||
label string
|
||||
n uint32
|
||||
}
|
||||
|
||||
func (s *stream) bytes(n int) []byte {
|
||||
s.n++
|
||||
var out []byte
|
||||
for i := uint32(0); len(out) < n; i++ {
|
||||
h := sha256.Sum256(binary.BigEndian.AppendUint32(binary.BigEndian.AppendUint32([]byte("DateKeys BLS12-381 vectors "+s.label), s.n), i))
|
||||
out = append(out, h[:]...)
|
||||
}
|
||||
return out[:n]
|
||||
}
|
||||
|
||||
// scalar is a nonzero scalar below r.
|
||||
func (s *stream) scalar() *big.Int {
|
||||
k := new(big.Int).SetBytes(s.bytes(64))
|
||||
k.Mod(k, new(big.Int).Sub(order, big.NewInt(1)))
|
||||
return k.Add(k, big.NewInt(1))
|
||||
}
|
||||
|
||||
func (s *stream) fp() *big.Int {
|
||||
x := new(big.Int).SetBytes(s.bytes(64))
|
||||
return x.Mod(x, p)
|
||||
}
|
||||
|
||||
func fp48(x *big.Int) []byte { return x.FillBytes(make([]byte, 48)) }
|
||||
|
||||
func hx(b []byte) string { return hex.EncodeToString(b) }
|
||||
|
||||
func g1Mul(k *big.Int) *bls12381.PointG1 {
|
||||
return g1.MulScalarBig(g1.New(), g1.One(), k)
|
||||
}
|
||||
|
||||
func g2Mul(k *big.Int) *bls12381.PointG2 {
|
||||
return g2.MulScalarBig(g2.New(), g2.One(), k)
|
||||
}
|
||||
|
||||
func g1Enc(q *bls12381.PointG1) []byte { return g1.ToCompressed(g1.New().Set(q)) }
|
||||
|
||||
func g2Enc(q *bls12381.PointG2) []byte { return g2.ToCompressed(g2.New().Set(q)) }
|
||||
|
||||
// errorClass names the check of FromCompressed of kilic that failed.
|
||||
func errorClass(err error) string {
|
||||
switch {
|
||||
case strings.Contains(err.Error(), "not on curve"):
|
||||
return "curve"
|
||||
case strings.Contains(err.Error(), "correct subgroup"):
|
||||
return "subgroup"
|
||||
default:
|
||||
return "format"
|
||||
}
|
||||
}
|
||||
|
||||
type decodeVector struct {
|
||||
Label string `json:"label"`
|
||||
Group string `json:"group"`
|
||||
Hex string `json:"hex"`
|
||||
Go string `json:"go"`
|
||||
Class string `json:"class,omitempty"`
|
||||
}
|
||||
|
||||
func verdict(group string, b []byte) decodeVector {
|
||||
v := decodeVector{Group: group, Hex: hx(b)}
|
||||
var err error
|
||||
var zero bool
|
||||
if group == "G1" {
|
||||
var q *bls12381.PointG1
|
||||
if q, err = g1.FromCompressed(b); err == nil {
|
||||
zero = g1.IsZero(q)
|
||||
}
|
||||
} else {
|
||||
var q *bls12381.PointG2
|
||||
if q, err = g2.FromCompressed(b); err == nil {
|
||||
zero = g2.IsZero(q)
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case err != nil:
|
||||
v.Go, v.Class = "invalid", errorClass(err)
|
||||
case zero:
|
||||
v.Go = "identity"
|
||||
default:
|
||||
v.Go = "point"
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// schemeVerdict is the verdict of the KeyGroup of the drand scheme, as
|
||||
// scripts/bls12381-go-verdicts.go of datekeys-ts computes it.
|
||||
func schemeVerdict(group string, b []byte) string {
|
||||
id := crypto.UnchainedSchemeID
|
||||
if group == "G2" {
|
||||
id = crypto.SigsOnG1ID
|
||||
}
|
||||
s := must(crypto.SchemeFromName(id))
|
||||
k := s.KeyGroup.Point()
|
||||
switch {
|
||||
case k.UnmarshalBinary(b) != nil:
|
||||
return "invalid"
|
||||
case k.Equal(k.Null()):
|
||||
return "identity"
|
||||
default:
|
||||
return "point"
|
||||
}
|
||||
}
|
||||
|
||||
type addVector struct {
|
||||
Label string `json:"label"`
|
||||
Group string `json:"group"`
|
||||
A string `json:"a"`
|
||||
B string `json:"b"`
|
||||
Sum string `json:"sum"`
|
||||
}
|
||||
|
||||
type mulVector struct {
|
||||
Label string `json:"label"`
|
||||
Group string `json:"group"`
|
||||
Point string `json:"point"`
|
||||
Scalar string `json:"scalar"`
|
||||
Product string `json:"product"`
|
||||
}
|
||||
|
||||
type pairingVector struct {
|
||||
Label string `json:"label"`
|
||||
G1 string `json:"g1"`
|
||||
G2 string `json:"g2"`
|
||||
GT string `json:"gt"`
|
||||
}
|
||||
|
||||
type hashVector struct {
|
||||
Label string `json:"label"`
|
||||
DST string `json:"dst"`
|
||||
Msg string `json:"msg"`
|
||||
Point string `json:"point"`
|
||||
X string `json:"x"`
|
||||
Y string `json:"y"`
|
||||
}
|
||||
|
||||
type mapVector struct {
|
||||
Label string `json:"label"`
|
||||
U string `json:"u"`
|
||||
Point string `json:"point"`
|
||||
}
|
||||
|
||||
type signatureVector struct {
|
||||
Label string `json:"label"`
|
||||
PublicKey string `json:"public_key"`
|
||||
Msg string `json:"msg"`
|
||||
Signature string `json:"signature"`
|
||||
Go bool `json:"go"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
out := struct {
|
||||
Description string `json:"description"`
|
||||
Generator string `json:"generator"`
|
||||
Libraries string `json:"libraries"`
|
||||
PointsFrom string `json:"points_from"`
|
||||
Points []decodeVector `json:"points"`
|
||||
Decode []decodeVector `json:"decode"`
|
||||
Add []addVector `json:"add"`
|
||||
Multiply []mulVector `json:"multiply"`
|
||||
Pairing []pairingVector `json:"pairing"`
|
||||
HashToG1 []hashVector `json:"hash_to_g1"`
|
||||
MapToG1 []mapVector `json:"map_to_g1"`
|
||||
Signatures []signatureVector `json:"signatures"`
|
||||
}{
|
||||
Description: "Go reference values for the BLS12-381 code of lib/src/bls12381_*.dart: decoding verdicts, " +
|
||||
"sums, multiples, pairings, hashes to G1 and BLS signatures on G1; see tool/bls12381_go_vectors.go.",
|
||||
Generator: "tool/bls12381_go_vectors.go",
|
||||
Libraries: libraries(),
|
||||
PointsFrom: "the encodings of src/lib/dkc/testing/bls12381-vectors.json of datekeys-ts at 289fe71, " +
|
||||
"with the verdicts recomputed by this generator",
|
||||
}
|
||||
|
||||
// The frozen edge cases of datekeys-ts.
|
||||
var frozen struct {
|
||||
Vectors []struct{ Label, Group, Hex, Go string }
|
||||
}
|
||||
if err := json.Unmarshal(must(os.ReadFile(os.Args[1])), &frozen); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, f := range frozen.Vectors {
|
||||
b := must(hex.DecodeString(f.Hex))
|
||||
v := verdict(f.Group, b)
|
||||
if (len(b) == 48 && f.Group == "G1") || (len(b) == 96 && f.Group == "G2") {
|
||||
if s := schemeVerdict(f.Group, b); s != v.Go {
|
||||
panic(f.Label + ": kilic and the drand scheme disagree")
|
||||
}
|
||||
} else {
|
||||
v.Go, v.Class = schemeVerdict(f.Group, b), "format"
|
||||
}
|
||||
if v.Go != f.Go {
|
||||
panic(f.Label + ": the verdict of datekeys-ts is not the one of Go")
|
||||
}
|
||||
v.Label = f.Label
|
||||
out.Points = append(out.Points, v)
|
||||
}
|
||||
|
||||
// Decoding.
|
||||
s := &stream{label: "decode"}
|
||||
for i := 0; i < 24; i++ {
|
||||
b := g1Enc(g1Mul(s.scalar()))
|
||||
neg := bytes.Clone(b)
|
||||
neg[0] ^= 0x20
|
||||
flip := bytes.Clone(b)
|
||||
bit := 3 + int(binary.BigEndian.Uint16(s.bytes(2)))%(48*8-3)
|
||||
flip[bit/8] ^= 0x80 >> (bit % 8)
|
||||
for j, c := range [][]byte{b, neg, flip} {
|
||||
v := verdict("G1", c)
|
||||
v.Label = fmt.Sprintf("G1 multiple %d, %s", i, []string{"as encoded", "negated", fmt.Sprintf("bit %d flipped", bit)}[j])
|
||||
out.Decode = append(out.Decode, v)
|
||||
}
|
||||
}
|
||||
for i := 0; i < 8; i++ {
|
||||
b := g2Enc(g2Mul(s.scalar()))
|
||||
neg := bytes.Clone(b)
|
||||
neg[0] ^= 0x20
|
||||
flip := bytes.Clone(b)
|
||||
bit := 3 + int(binary.BigEndian.Uint16(s.bytes(2)))%(96*8-3)
|
||||
flip[bit/8] ^= 0x80 >> (bit % 8)
|
||||
for j, c := range [][]byte{b, neg, flip} {
|
||||
v := verdict("G2", c)
|
||||
v.Label = fmt.Sprintf("G2 multiple %d, %s", i, []string{"as encoded", "negated", fmt.Sprintf("bit %d flipped", bit)}[j])
|
||||
out.Decode = append(out.Decode, v)
|
||||
}
|
||||
}
|
||||
// Random x, with the compression flag and either sign: on the curve or
|
||||
// not, and then outside the subgroup.
|
||||
classes := map[string]int{}
|
||||
for i := 0; classes["G1 subgroup"] < 16 || classes["G1 curve"] < 8; i++ {
|
||||
b := fp48(s.fp())
|
||||
b[0] |= 0x80 | s.bytes(1)[0]&0x20
|
||||
v := verdict("G1", b)
|
||||
key := "G1 " + v.Class
|
||||
if classes[key] >= 16 {
|
||||
continue
|
||||
}
|
||||
classes[key]++
|
||||
v.Label = fmt.Sprintf("G1 random x %d", i)
|
||||
out.Decode = append(out.Decode, v)
|
||||
}
|
||||
for i := 0; classes["G2 subgroup"] < 8 || classes["G2 curve"] < 4; i++ {
|
||||
b := append(fp48(s.fp()), fp48(s.fp())...)
|
||||
b[0] |= 0x80 | s.bytes(1)[0]&0x20
|
||||
v := verdict("G2", b)
|
||||
key := "G2 " + v.Class
|
||||
if classes[key] >= 8 {
|
||||
continue
|
||||
}
|
||||
classes[key]++
|
||||
v.Label = fmt.Sprintf("G2 random x %d", i)
|
||||
out.Decode = append(out.Decode, v)
|
||||
}
|
||||
|
||||
// Sums.
|
||||
s = &stream{label: "add"}
|
||||
inf1, inf2 := g1.Zero(), g2.Zero()
|
||||
for i := 0; i < 16; i++ {
|
||||
a, b := g1Mul(s.scalar()), g1Mul(s.scalar())
|
||||
out.Add = append(out.Add, addVector{fmt.Sprintf("G1 sum %d", i), "G1", hx(g1Enc(a)), hx(g1Enc(b)), hx(g1Enc(g1.Add(g1.New(), a, b)))})
|
||||
}
|
||||
a1 := g1Mul(s.scalar())
|
||||
for _, c := range []struct {
|
||||
label string
|
||||
a, b *bls12381.PointG1
|
||||
}{
|
||||
{"G1 P + P", a1, a1},
|
||||
{"G1 P + (-P)", a1, g1.Neg(g1.New(), a1)},
|
||||
{"G1 P + infinity", a1, inf1},
|
||||
{"G1 infinity + P", inf1, a1},
|
||||
{"G1 infinity + infinity", inf1, inf1},
|
||||
} {
|
||||
out.Add = append(out.Add, addVector{c.label, "G1", hx(g1Enc(c.a)), hx(g1Enc(c.b)), hx(g1Enc(g1.Add(g1.New(), c.a, c.b)))})
|
||||
}
|
||||
for i := 0; i < 8; i++ {
|
||||
a, b := g2Mul(s.scalar()), g2Mul(s.scalar())
|
||||
out.Add = append(out.Add, addVector{fmt.Sprintf("G2 sum %d", i), "G2", hx(g2Enc(a)), hx(g2Enc(b)), hx(g2Enc(g2.Add(g2.New(), a, b)))})
|
||||
}
|
||||
a2 := g2Mul(s.scalar())
|
||||
for _, c := range []struct {
|
||||
label string
|
||||
a, b *bls12381.PointG2
|
||||
}{
|
||||
{"G2 P + P", a2, a2},
|
||||
{"G2 P + (-P)", a2, g2.Neg(g2.New(), a2)},
|
||||
{"G2 P + infinity", a2, inf2},
|
||||
{"G2 infinity + P", inf2, a2},
|
||||
} {
|
||||
out.Add = append(out.Add, addVector{c.label, "G2", hx(g2Enc(c.a)), hx(g2Enc(c.b)), hx(g2Enc(g2.Add(g2.New(), c.a, c.b)))})
|
||||
}
|
||||
|
||||
// Multiples.
|
||||
s = &stream{label: "multiply"}
|
||||
max256 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1))
|
||||
special := []struct {
|
||||
label string
|
||||
k *big.Int
|
||||
}{
|
||||
{"0", big.NewInt(0)},
|
||||
{"1", big.NewInt(1)},
|
||||
{"2", big.NewInt(2)},
|
||||
{"r - 1", new(big.Int).Sub(order, big.NewInt(1))},
|
||||
{"r", new(big.Int).Set(order)},
|
||||
{"r + 1", new(big.Int).Add(order, big.NewInt(1))},
|
||||
{"2^256 - 1", max256},
|
||||
}
|
||||
for i := 0; i < 12; i++ {
|
||||
q, k := g1Mul(s.scalar()), new(big.Int).SetBytes(s.bytes(32))
|
||||
out.Multiply = append(out.Multiply, mulVector{fmt.Sprintf("G1 multiple %d", i), "G1", hx(g1Enc(q)), k.Text(16), hx(g1Enc(g1.MulScalarBig(g1.New(), q, k)))})
|
||||
}
|
||||
q1 := g1Mul(s.scalar())
|
||||
for _, c := range special {
|
||||
out.Multiply = append(out.Multiply, mulVector{"G1 by " + c.label, "G1", hx(g1Enc(q1)), c.k.Text(16), hx(g1Enc(g1.MulScalarBig(g1.New(), q1, c.k)))})
|
||||
}
|
||||
for i := 0; i < 6; i++ {
|
||||
q, k := g2Mul(s.scalar()), new(big.Int).SetBytes(s.bytes(32))
|
||||
out.Multiply = append(out.Multiply, mulVector{fmt.Sprintf("G2 multiple %d", i), "G2", hx(g2Enc(q)), k.Text(16), hx(g2Enc(g2.MulScalarBig(g2.New(), q, k)))})
|
||||
}
|
||||
q2 := g2Mul(s.scalar())
|
||||
for _, c := range special {
|
||||
out.Multiply = append(out.Multiply, mulVector{"G2 by " + c.label, "G2", hx(g2Enc(q2)), c.k.Text(16), hx(g2Enc(g2.MulScalarBig(g2.New(), q2, c.k)))})
|
||||
}
|
||||
|
||||
// Pairings, through kyber-bls12381.
|
||||
s = &stream{label: "pairing"}
|
||||
suite := bls.NewBLS12381Suite()
|
||||
for i := 0; i < 6; i++ {
|
||||
a := suite.G1().Point().Mul(scalarOf(s.scalar()), nil)
|
||||
b := suite.G2().Point().Mul(scalarOf(s.scalar()), nil)
|
||||
out.Pairing = append(out.Pairing, pairingVector{fmt.Sprintf("e(P, Q) %d", i), marshal(a), marshal(b), marshal(suite.Pair(a, b))})
|
||||
}
|
||||
{
|
||||
a := suite.G1().Point().Mul(scalarOf(s.scalar()), nil)
|
||||
b := suite.G2().Point().Mul(scalarOf(s.scalar()), nil)
|
||||
out.Pairing = append(out.Pairing,
|
||||
pairingVector{"e(infinity, Q)", marshal(suite.G1().Point().Null()), marshal(b), marshal(suite.Pair(suite.G1().Point().Null(), b))},
|
||||
pairingVector{"e(P, infinity)", marshal(a), marshal(suite.G2().Point().Null()), marshal(suite.Pair(a, suite.G2().Point().Null()))})
|
||||
}
|
||||
|
||||
// Hashes to G1.
|
||||
s = &stream{label: "hash"}
|
||||
hashOne := func(label, dst string, msg []byte) hashVector {
|
||||
q := must(g1.HashToCurve(msg, []byte(dst)))
|
||||
u := g1.ToUncompressed(g1.New().Set(q))
|
||||
return hashVector{label, dst, hx(msg), hx(g1Enc(q)), hx(u[:48]), hx(u[48:])}
|
||||
}
|
||||
for i := 0; i < 24; i++ {
|
||||
n := int(s.bytes(1)[0]) % 80
|
||||
out.HashToG1 = append(out.HashToG1, hashOne(fmt.Sprintf("a message of %d bytes", n), quicknetDST, s.bytes(n)))
|
||||
}
|
||||
sch := must(crypto.SchemeFromName(crypto.SigsOnG1ID))
|
||||
for _, round := range []uint64{1, 1000, 1001, 1004, 2000, 83903165811, 1<<53 - 1} {
|
||||
id := sch.DigestBeacon(&common.Beacon{Round: round})
|
||||
out.HashToG1 = append(out.HashToG1, hashOne(fmt.Sprintf("the identity of round %d", round), quicknetDST, id))
|
||||
}
|
||||
const rfcDST = "QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_"
|
||||
for _, m := range []string{"", "abc", "abcdef0123456789", "q128_" + strings.Repeat("q", 128), "a512_" + strings.Repeat("a", 512)} {
|
||||
out.HashToG1 = append(out.HashToG1, hashOne(fmt.Sprintf("RFC 9380 J.9.1, msg %q", shorten(m)), rfcDST, []byte(m)))
|
||||
}
|
||||
|
||||
// The map to G1 of one element.
|
||||
s = &stream{label: "map"}
|
||||
z := big.NewInt(11)
|
||||
// Z·u² = -1: u² = -1/Z, a square since -Z is one.
|
||||
minusInvZ := new(big.Int).Sub(p, new(big.Int).ModInverse(z, p))
|
||||
root := new(big.Int).ModSqrt(minusInvZ, p)
|
||||
if root == nil {
|
||||
panic("-1/Z is not a square")
|
||||
}
|
||||
us := []struct {
|
||||
label string
|
||||
u *big.Int
|
||||
}{
|
||||
{"u = 0, exceptional", big.NewInt(0)},
|
||||
{"Z·u² = -1, exceptional", root},
|
||||
{"Z·u² = -1, the other root, exceptional", new(big.Int).Sub(p, root)},
|
||||
{"u = 1", big.NewInt(1)},
|
||||
{"u = p - 1", new(big.Int).Sub(p, big.NewInt(1))},
|
||||
}
|
||||
for i := 0; i < 8; i++ {
|
||||
us = append(us, struct {
|
||||
label string
|
||||
u *big.Int
|
||||
}{fmt.Sprintf("u drawn from the seed %d", i), s.fp()})
|
||||
}
|
||||
for _, c := range us {
|
||||
q := must(g1.MapToCurve(fp48(c.u)))
|
||||
out.MapToG1 = append(out.MapToG1, mapVector{c.label, hx(fp48(c.u)), hx(g1Enc(q))})
|
||||
}
|
||||
|
||||
// BLS signatures on G1, the scheme of Quicknet.
|
||||
s = &stream{label: "signatures"}
|
||||
scheme := signbls.NewSchemeOnG1(suite)
|
||||
for i := 0; i < 6; i++ {
|
||||
sk := scalarOf(s.scalar())
|
||||
pk := suite.G2().Point().Mul(sk, nil)
|
||||
msg := s.bytes(32)
|
||||
sig := must(scheme.Sign(sk, msg))
|
||||
other := s.bytes(32)
|
||||
for _, c := range []struct {
|
||||
label string
|
||||
msg, sig []byte
|
||||
}{
|
||||
{fmt.Sprintf("signature %d", i), msg, sig},
|
||||
{fmt.Sprintf("signature %d of another message", i), other, sig},
|
||||
{fmt.Sprintf("signature %d negated", i), msg, negate(sig)},
|
||||
} {
|
||||
out.Signatures = append(out.Signatures, signatureVector{c.label, marshal(pk), hx(c.msg), hx(c.sig), scheme.Verify(pk, c.msg, c.sig) == nil})
|
||||
}
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
enc.SetEscapeHTML(false)
|
||||
if err := enc.Encode(out); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func scalarOf(k *big.Int) kyber.Scalar {
|
||||
return bls.NewKyberScalar().SetBytes(k.FillBytes(make([]byte, 32)))
|
||||
}
|
||||
|
||||
func marshal(m interface{ MarshalBinary() ([]byte, error) }) string {
|
||||
return hex.EncodeToString(must(m.MarshalBinary()))
|
||||
}
|
||||
|
||||
func negate(sig []byte) []byte {
|
||||
c := bytes.Clone(sig)
|
||||
c[0] ^= 0x20
|
||||
return c
|
||||
}
|
||||
|
||||
func shorten(m string) string {
|
||||
if len(m) > 20 {
|
||||
return m[:20] + "…"
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// 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 "github.com/kilic/bls12-381", "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…
Reference in new issue