fix(train): no row's ID tells the instance a date: the days' IDs are keyed by the account (ADR 0030, D57)
A day's, night's and heart chunk's ID was a UUID of version 5 of the person and the date, which the instance can hash for every date itself: each ID told it which days the band was worn. halkyon_keys' keyedRowId hashes the name under the account's X25519 secret key (BLAKE2b, a UUID of version 8), the same on every device of the account and unreadable without its keys; ContentCipher.rowId and TrainKeys.rowId hand it on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
9b9b8d08ca
commit
839d49c4cc
1 changed files with 249 additions and 36 deletions
55
packages/halkyon_keys/test/row_ids_test.dart
Normal file
55
packages/halkyon_keys/test/row_ids_test.dart
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import 'package:halkyon_keys/halkyon_keys.dart';
|
||||
import 'package:sodium/sodium_sumo.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
final sodium = await SodiumSumoInit.init();
|
||||
|
||||
group('row IDs keyed by the account', () {
|
||||
final account = AccountKeys.generate(sodium);
|
||||
final secrets = (
|
||||
encryption: account.encryption.secretKey.extractBytes(),
|
||||
signing: account.signing.secretKey.extractBytes(),
|
||||
);
|
||||
|
||||
test('every device of the account names a row alike: its keys, unlocked '
|
||||
'anew, give the same ID', () {
|
||||
final elsewhere = AccountKeys.fromSecretKeys(
|
||||
sodium,
|
||||
encryptionSecretKey: secrets.encryption,
|
||||
signingSecretKey: secrets.signing,
|
||||
);
|
||||
expect(
|
||||
keyedRowId(sodium, elsewhere, 'train/day/2026-10-30'),
|
||||
keyedRowId(sodium, account, 'train/day/2026-10-30'),
|
||||
);
|
||||
});
|
||||
|
||||
test('another name, or the same name of another account, gives another '
|
||||
'ID', () {
|
||||
final other = AccountKeys.generate(sodium);
|
||||
final id = keyedRowId(sodium, account, 'train/day/2026-10-30');
|
||||
expect(keyedRowId(sodium, account, 'train/day/2026-10-31'), isNot(id));
|
||||
expect(keyedRowId(sodium, account, 'train/night/2026-10-30'), isNot(id));
|
||||
expect(keyedRowId(sodium, other, 'train/day/2026-10-30'), isNot(id));
|
||||
});
|
||||
|
||||
test("the ID is a UUID of version 8, RFC 9562's for IDs of one's own "
|
||||
'making, and not a name hashed in the open (version 5)', () {
|
||||
final id = keyedRowId(sodium, account, 'train/day/2026-10-30');
|
||||
expect(
|
||||
id,
|
||||
matches(
|
||||
RegExp(
|
||||
'^[0-9a-f]{8}-[0-9a-f]{4}-8[0-9a-f]{3}-[89ab][0-9a-f]{3}-'
|
||||
r'[0-9a-f]{12}$',
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('a row without a name has no ID', () {
|
||||
expect(() => keyedRowId(sodium, account, ''), throwsArgumentError);
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue