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/ibe_test.dart

278 lines
8.7 KiB

// 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');
test('unwraps the tlock stanza of the encryption of Go', () {
final args = ['1000', quicknetChainHash];
final body = fromHex(encrypt1000U + encrypt1000V + encrypt1000W);
final r = Release(1000, sig1000);
expect(toHex(unwrapTlockStanza(p, 1000, r, args, body)), encrypt1000Msg);
expect(
() => unwrapTlockStanza(p, 1000, r, args, body.sublist(1)),
dateKeysError(
ErrorCode.integrity,
'agewrap: tlock stanza body of 127 bytes, want 128: ERR_INTEGRITY',
),
);
expect(
() => unwrapTlockStanza(p, 1000, r, ['1001', quicknetChainHash], body),
dateKeysError(
ErrorCode.roundMismatch,
'agewrap: tlock stanza round "1001", DateKey round 1000: '
'ERR_ROUND_MISMATCH',
),
);
});
test('never takes the point at infinity for a signature, whatever the key '
'(Go does, with the point at infinity for key)', () {
final infinity = Uint8List(96)..[0] = 0xc0;
expect(
() => verifyRelease(
p.copyWith(publicKey: infinity),
1000,
Release(1000, Uint8List(48)..[0] = 0xc0),
),
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',
),
);
});
test('reports whatever a source throws as ERR_RELEASE_UNAVAILABLE', () async {
await expectLater(
fetchRelease(suppliedRelease(), p, 1000),
throwsA(
isA<DateKeysException>().having(
(e) => e.message,
'message',
'release: no release supplied for round 1000: '
'ERR_RELEASE_UNAVAILABLE',
),
),
);
await expectLater(
fetchRelease(_Failing(), p, 1000),
dateKeysError(
ErrorCode.releaseUnavailable,
'capsule: release source: relay: bad signature: ERR_RELEASE_INVALID: '
'ERR_RELEASE_UNAVAILABLE',
),
);
});
}
final class _Failing implements ReleaseSource {
@override
Future<Release> fetch(PinnedProfile p, int round) async =>
throw DateKeysException(ErrorCode.releaseInvalid, 'relay: bad signature');
}

Powered by TurnKey Linux.