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

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',
]);
});
}

Powered by TurnKey Linux.