deriveRatchetRoot function
Derives the shared ratchet root for one peer pair.
The input keying material is the group key followed by sharedSecret, the
X25519 agreement between the two sides' per-connection ephemeral keys
(ephemeralA, ephemeralB). The ephemeral private halves exist only in
memory and are wiped when the session ends, so a device stolen later —
with its identity, group and passphrase keys — cannot rebuild the root of
a recorded session: that is the forward secrecy. The group key keeps the
session bound to the group: a revoked device that still completes an
agreement derives a different root.
Deterministic and symmetric: both peers compute the same root from the same inputs, in either argument order.
Implementation
List<int> deriveRatchetRoot({
required List<int> groupKey,
required List<int> sharedSecret,
required List<int> ephemeralA,
required List<int> ephemeralB,
required String nodeA,
required String nodeB,
}) {
final (e1, e2) = _sortBytes(ephemeralA, ephemeralB);
final (id1, id2) =
nodeA.compareTo(nodeB) <= 0 ? (nodeA, nodeB) : (nodeB, nodeA);
final info = <int>[
..._ascii('passnote-ratchet-root-v2'),
0,
...utf8.encode(id1),
0,
...utf8.encode(id2),
0,
...e1,
0,
...e2,
];
return hkdfSha256([...groupKey, ...sharedSecret], const [], info, 32);
}