Function protect_from_forgery
pub fn protect_from_forgery(
secret: EmbeddedCurvePoint,
eph_pk: EmbeddedCurvePoint,
recipient_point: EmbeddedCurvePoint,
) -> EmbeddedCurvePoint
pub fn protect_from_forgery(
secret: EmbeddedCurvePoint,
eph_pk: EmbeddedCurvePoint,
recipient_point: EmbeddedCurvePoint,
) -> EmbeddedCurvePoint
Protects a handshake's shared secret from recipient forgery.
The handshake's ECDH shared secret is symmetric:
S = eph_sk * recipient_pub, where(eph_sk, eph_pk)is the sender's ephemeral key pair, also equalsrecipient_priv * eph_pk. The recipient recomputingSthis way is normal: it is how they derive the handshake's tags when scanning. The attack is minting a second handshake with the same secret: the ephemeral key is unconstrained, so a malicious recipient can register its own handshake crafted to land onSagain, with a different sender-only secret, and emit logs under the honest handshake's constrained-delivery tag. That breaks the invariant that a constrained-delivery log sequence is tied to a single sender: by colliding on the tag it blocks the honest sender, who can no longer emit the next note in their sequence.The protection multiplies
Sby the scalark = hash(eph_pk, recipient_point): a forgery must announce a differenteph_pk, so the protected secrets diverge. The result is still a point and silos likeS.The recipient point must be part of
k: werekindependent of it, an attacker choosing the fake recipient point as a multiple of the honest protected secret could solve for the multiplier that makes the forged secret land exactly on it.Concretely, on the sender's side: S = eph_sk * recipient_point S' = protect_from_forgery(S, eph_pk, recipient_point) = k * S
See
forgery_protected_eph_pkfor how that arrives at the sameS'.