deriveRatchetRoot function

List<int> deriveRatchetRoot({
  1. required List<int> groupKey,
  2. required List<int> sharedSecret,
  3. required List<int> ephemeralA,
  4. required List<int> ephemeralB,
  5. required String nodeA,
  6. required String nodeB,
})

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);
}