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.
147 lines
4.9 KiB
147 lines
4.9 KiB
// The key of words (spec §38.1) against test/vectors/wordkey_vectors.json,
|
|
// whose expected values the Go reference computed
|
|
// (tool/wordkey_go_vectors.go): the words of wordkey.Normalize, the refusals
|
|
// of wordkey.Check with their texts, and the salt and the password of
|
|
// wordkey.Key, with PBKDF2 at 1000 iterations. The vectors come from a Dart
|
|
// constant, so that these tests also run compiled to JavaScript; the keys of
|
|
// 600 000 iterations and their identities are in wordkey_vm_test.dart.
|
|
|
|
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:datekeys/src/bytes.dart';
|
|
import 'package:datekeys/src/sha256.dart';
|
|
import 'package:datekeys/src/wordkey.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
import 'vectors/wordkey_vectors.g.dart';
|
|
import 'wordkey_support.dart';
|
|
|
|
final Map<String, Object?> vectors =
|
|
jsonDecode(wordkeyVectorsJson) as Map<String, Object?>;
|
|
|
|
List<Map<String, Object?>> cases(String name) =>
|
|
(vectors[name]! as List).cast<Map<String, Object?>>();
|
|
|
|
/// "ok", or the message of the [WordKeyException] that [f] throws.
|
|
String outcome(void Function() f) {
|
|
try {
|
|
f();
|
|
return 'ok';
|
|
} on WordKeyException catch (e) {
|
|
return e.message;
|
|
}
|
|
}
|
|
|
|
String label(Map<String, Object?> c) =>
|
|
c['name'] as String? ?? '${c['in'] ?? c['words']}';
|
|
|
|
void main() {
|
|
test('the constants are those of Go', () {
|
|
expect(vectors['rounds'], wordKeyRounds);
|
|
expect(vectors['min_words'], minWords);
|
|
expect(vectors['min_letters'], minLetters);
|
|
});
|
|
|
|
test('normalizeWords reads the words as wordkey.Normalize', () {
|
|
final all = cases('normalize');
|
|
expect(all, hasLength(greaterThan(350)));
|
|
var strings = 0;
|
|
for (final c in all) {
|
|
final input = fromHex(c['in']! as String);
|
|
final want = [
|
|
for (final w in (c['words']! as List).cast<String>())
|
|
decodeUtf8(fromHex(w))!,
|
|
];
|
|
expect(normalizeWordsUtf8(input), want, reason: label(c));
|
|
final s = fromWtf8(input);
|
|
if (s != null) {
|
|
strings++;
|
|
expect(normalizeWords(s), want, reason: label(c));
|
|
}
|
|
}
|
|
expect(strings, greaterThan(300));
|
|
});
|
|
|
|
test('checkWords refuses words as wordkey.Check, with its texts', () {
|
|
final all = cases('check');
|
|
expect(all, hasLength(greaterThan(450)));
|
|
var refused = 0;
|
|
for (final c in all) {
|
|
final words = wordBytes(c);
|
|
final want = c['result']! as String;
|
|
expect(outcome(() => checkWordsUtf8(words)), want, reason: label(c));
|
|
if (want != 'ok') refused++;
|
|
final strings = [for (final w in words) fromWtf8(w)];
|
|
if (strings.every((s) => s != null)) {
|
|
expect(
|
|
outcome(() => checkWords([for (final s in strings) s!])),
|
|
want,
|
|
reason: label(c),
|
|
);
|
|
}
|
|
}
|
|
expect(refused, greaterThan(400));
|
|
});
|
|
|
|
test('the salt and the password of wordkey.Key', () {
|
|
for (final c in cases('keys')) {
|
|
final words = [for (final w in wordBytes(c)) fromWtf8(w)!];
|
|
final chain = fromHex(c['chain_hash']! as String);
|
|
final round = c['round']! as int;
|
|
final capsuleId = fromHex(c['capsule_id']! as String);
|
|
final salt = wordKeySalt(chain, round, capsuleId);
|
|
expect(salt, utf8Bytes(c['salt']! as String), reason: label(c));
|
|
// The password is the words joined by one U+0020, in UTF-8: with the
|
|
// salt and 1000 iterations, the PBKDF2 of Go. wordKey derives with
|
|
// the same two and 600 000 iterations, on the VM in
|
|
// wordkey_vm_test.dart.
|
|
final password = wordKeyPassword(words);
|
|
expect(toHex(password), c['password'], reason: label(c));
|
|
expect(
|
|
toHex(pbkdf2HmacSha256(password, salt, 1000, 32)),
|
|
c['key_1000'],
|
|
reason: label(c),
|
|
);
|
|
}
|
|
});
|
|
|
|
test('a round is an int of 0 to 2^53-1', () {
|
|
final chain = Uint8List(32);
|
|
final id = Uint8List(16);
|
|
expect(() => wordKeySalt(chain, -1, id), throwsRangeError);
|
|
expect(
|
|
() => wordKeySalt(chain, 9007199254740991 + 1, id),
|
|
throwsRangeError,
|
|
);
|
|
expect(
|
|
utf8.decode(wordKeySalt(chain, 9007199254740991, id)),
|
|
contains('|9007199254740991|'),
|
|
);
|
|
});
|
|
|
|
test('the examples of spec §38.1 give the same words', () {
|
|
// The three spellings of «abaco arbol», by the names of their cases, and
|
|
// the punctuation that counts.
|
|
final examples = [
|
|
for (final c in cases('normalize'))
|
|
if ((c['name'] as String? ?? '').contains('38.1: ')) c,
|
|
];
|
|
final spellings = [
|
|
for (final c in examples)
|
|
if ((c['name']! as String).contains('baco')) c['words'],
|
|
];
|
|
expect(spellings, hasLength(3));
|
|
expect(spellings[0], hasLength(2));
|
|
expect(spellings[1], spellings[0]);
|
|
expect(spellings[2], spellings[0]);
|
|
final punctuation = examples.singleWhere(
|
|
(c) => (c['name']! as String).endsWith('punctuation counts'),
|
|
);
|
|
expect(normalizeWordsUtf8(fromHex(punctuation['in']! as String)), [
|
|
'perro,',
|
|
'perro',
|
|
]);
|
|
});
|
|
}
|