Skip to main content

commonware_cryptography/handshake/sake/
protocol.rs

1use super::{
2    Error,
3    exchange::{EphemeralPublicKey, SecretKey},
4};
5use crate::{
6    PublicKey, Signature, Signer, Verifier,
7    transcript::{self, Summary, Transcript},
8};
9use commonware_codec::{Buf, Encode, FixedSize, Mode, Read, ReadExt, Write, mode, modes};
10use core::ops::Range;
11use rand_core::CryptoRng;
12
13const LABEL_CONFIRMATION_L2D: &[u8] = b"confirmation_l2d";
14const LABEL_CONFIRMATION_D2L: &[u8] = b"confirmation_d2l";
15
16/// Transcript schema used by a SAKE handshake.
17///
18/// The version is part of the protocol definition: both peers must agree on it out of band.
19#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
20#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
21pub enum Version {
22    /// Commits the dialer identity after the [Syn] signature. If the signature scheme lacks
23    /// conservative exclusive ownership (it admits key substitution), a dialer can complete a
24    /// handshake under a public key other than its own under which its [Syn] signature also
25    /// verifies.
26    V0,
27    /// Commits both identities before every signature and uses injective transcript framing.
28    V1,
29}
30
31impl From<Version> for Mode {
32    fn from(version: Version) -> Self {
33        match version {
34            Version::V0 => mode!(0),
35            Version::V1 => mode!(1),
36        }
37    }
38}
39
40impl Version {
41    /// Returns the protocol namespace forked from the application namespace.
42    ///
43    /// V0 has its own namespace. Subsequent versions share the SAKE namespace and commit their
44    /// mode before the handshake fields.
45    const fn namespace(self) -> &'static [u8] {
46        match self {
47            Self::V0 => b"_COMMONWARE_CRYPTOGRAPHY_HANDSHAKE",
48            Self::V1 => b"_COMMONWARE_CRYPTOGRAPHY_SAKE",
49        }
50    }
51
52    /// Returns the transcript framing used by this version.
53    ///
54    /// V0 framing is safe for [Version::V0] because the application namespace is summarized as a
55    /// single packet before SAKE commits a fixed sequence of canonical encodings at fixed
56    /// positions.
57    const fn transcript(self) -> transcript::Version {
58        match self {
59            Self::V0 => transcript::Version::V0,
60            Self::V1 => transcript::Version::V1,
61        }
62    }
63
64    /// Returns whether a peer's own identity is committed before it signs [Syn].
65    const fn binds_identity_before_syn(self) -> bool {
66        match self {
67            Self::V0 => false,
68            Self::V1 => true,
69        }
70    }
71}
72
73/// First handshake message sent by the dialer.
74/// Contains the dialer's timestamp, ephemeral key, and transcript signature.
75#[cfg_attr(test, derive(Debug, PartialEq))]
76pub struct Syn<S: Signature> {
77    time_ms: u64,
78    epk: EphemeralPublicKey,
79    sig: S,
80}
81
82impl<S: Signature> FixedSize for Syn<S> {
83    const SIZE: usize = u64::SIZE + EphemeralPublicKey::SIZE + S::SIZE;
84}
85
86impl<S: Signature> Write for Syn<S> {
87    fn write(&self, buf: &mut impl bytes::BufMut) {
88        self.time_ms.write(buf);
89        self.epk.write(buf);
90        self.sig.write(buf);
91    }
92}
93
94impl<S: Signature> Read for Syn<S> {
95    type Cfg = S::Cfg;
96
97    fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
98        Ok(Self {
99            time_ms: ReadExt::read(buf)?,
100            epk: ReadExt::read(buf)?,
101            sig: Read::read_cfg(buf, cfg)?,
102        })
103    }
104}
105
106#[cfg(feature = "arbitrary")]
107impl<S: Signature> arbitrary::Arbitrary<'_> for Syn<S>
108where
109    S: for<'a> arbitrary::Arbitrary<'a>,
110{
111    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
112        Ok(Self {
113            time_ms: u.arbitrary()?,
114            epk: u.arbitrary()?,
115            sig: u.arbitrary()?,
116        })
117    }
118}
119
120/// Second handshake message sent by the listener.
121/// Contains the listener's timestamp, ephemeral key, transcript signature, and confirmation tag.
122#[cfg_attr(test, derive(Debug, PartialEq))]
123pub struct SynAck<S: Signature> {
124    time_ms: u64,
125    epk: EphemeralPublicKey,
126    sig: S,
127    confirmation: Summary,
128}
129
130impl<S: Signature> FixedSize for SynAck<S> {
131    const SIZE: usize = u64::SIZE + EphemeralPublicKey::SIZE + S::SIZE + Summary::SIZE;
132}
133
134impl<S: Signature> Write for SynAck<S> {
135    fn write(&self, buf: &mut impl bytes::BufMut) {
136        self.time_ms.write(buf);
137        self.epk.write(buf);
138        self.sig.write(buf);
139        self.confirmation.write(buf);
140    }
141}
142
143impl<S: Signature> Read for SynAck<S> {
144    type Cfg = S::Cfg;
145
146    fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
147        Ok(Self {
148            time_ms: ReadExt::read(buf)?,
149            epk: ReadExt::read(buf)?,
150            sig: Read::read_cfg(buf, cfg)?,
151            confirmation: ReadExt::read(buf)?,
152        })
153    }
154}
155
156#[cfg(feature = "arbitrary")]
157impl<S: Signature> arbitrary::Arbitrary<'_> for SynAck<S>
158where
159    S: for<'a> arbitrary::Arbitrary<'a>,
160{
161    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
162        Ok(Self {
163            time_ms: u.arbitrary()?,
164            epk: u.arbitrary()?,
165            sig: u.arbitrary()?,
166            confirmation: u.arbitrary()?,
167        })
168    }
169}
170
171/// Third handshake message sent by the dialer.
172/// Contains dialer's confirmation tag to complete the handshake.
173#[cfg_attr(test, derive(PartialEq))]
174#[cfg_attr(feature = "arbitrary", derive(Debug, arbitrary::Arbitrary))]
175pub struct Ack {
176    confirmation: Summary,
177}
178
179impl FixedSize for Ack {
180    const SIZE: usize = Summary::SIZE;
181}
182
183impl Write for Ack {
184    fn write(&self, buf: &mut impl bytes::BufMut) {
185        self.confirmation.write(buf);
186    }
187}
188
189impl Read for Ack {
190    type Cfg = ();
191
192    fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
193        Ok(Self {
194            confirmation: ReadExt::read(buf)?,
195        })
196    }
197}
198
199/// State maintained by the dialer during handshake.
200/// Tracks ephemeral secret, peer identity, and protocol transcript.
201pub struct DialState<P> {
202    esk: SecretKey,
203    peer_identity: P,
204    transcript: Transcript,
205    ok_timestamps: Range<u64>,
206}
207
208/// State maintained by the listener during handshake.
209/// Retains the secret transcript until the dialer's confirmation is verified.
210pub struct ListenState {
211    transcript: Transcript,
212}
213
214/// Inputs that start a dialer or listener: the namespace, timing bounds, identities, and
215/// [Version].
216pub struct Context<S, P> {
217    version: Version,
218    transcript: Transcript,
219    current_time: u64,
220    ok_timestamps: Range<u64>,
221    my_identity: S,
222    peer_identity: P,
223}
224
225impl<S, P> Context<S, P> {
226    /// Creates a context from the application namespace, the local time, the accepted range of
227    /// peer timestamps (both in milliseconds), the local signer, the expected peer identity, and
228    /// the [Version].
229    pub fn new(
230        namespace: &[u8],
231        current_time_ms: u64,
232        ok_timestamps: Range<u64>,
233        my_identity: S,
234        peer_identity: P,
235        version: Version,
236    ) -> Self {
237        let mut transcript =
238            Transcript::new(namespace, version.transcript()).fork(version.namespace());
239        if let Some(modes) = modes![version] {
240            transcript.commit(modes.encode());
241        }
242        Self {
243            version,
244            transcript,
245            current_time: current_time_ms,
246            ok_timestamps,
247            my_identity,
248            peer_identity,
249        }
250    }
251}
252
253/// Initiates a handshake as the dialer.
254/// Returns the dialer state and the first message to send.
255pub fn dial_start<S: Signer, P: PublicKey>(
256    rng: impl CryptoRng,
257    ctx: Context<S, P>,
258) -> (DialState<P>, Syn<<S as Signer>::Signature>) {
259    let Context {
260        version,
261        current_time,
262        ok_timestamps,
263        my_identity,
264        peer_identity,
265        mut transcript,
266    } = ctx;
267
268    // Generate an ephemeral key and commit the timestamp and listener identity.
269    let esk = SecretKey::new(rng);
270    let epk = esk.public();
271    let dialer_identity = my_identity.public_key().encode();
272    transcript
273        .commit(current_time.encode())
274        .commit(peer_identity.encode());
275
276    // V1 commits the dialer identity before signing so the [Syn] signature covers it. V0 commits it
277    // after.
278    if version.binds_identity_before_syn() {
279        transcript.commit(&dialer_identity[..]);
280    }
281    let sig = transcript.commit(epk.encode()).sign(&my_identity);
282    if !version.binds_identity_before_syn() {
283        transcript.commit(dialer_identity);
284    }
285    (
286        DialState {
287            esk,
288            peer_identity,
289            transcript,
290            ok_timestamps,
291        },
292        Syn {
293            time_ms: current_time,
294            epk,
295            sig,
296        },
297    )
298}
299
300/// Completes a handshake as the dialer.
301/// Verifies the listener's [SynAck] and returns the [Ack] to send and the confirmed secret
302/// transcript. The caller must send [Ack] before using the transcript for application data.
303pub fn dial_end<P: PublicKey>(
304    state: DialState<P>,
305    msg: SynAck<<P as Verifier>::Signature>,
306) -> Result<(Ack, Transcript), Error> {
307    let DialState {
308        esk,
309        peer_identity,
310        mut transcript,
311        ok_timestamps,
312    } = state;
313
314    // Check the listener's timestamp and verify its signature over the transcript.
315    if !ok_timestamps.contains(&msg.time_ms) {
316        return Err(Error::InvalidTimestamp(msg.time_ms, ok_timestamps));
317    }
318    if !transcript
319        .commit(msg.time_ms.encode())
320        .commit(msg.epk.encode())
321        .verify(&peer_identity, &msg.sig)
322    {
323        return Err(Error::InvalidSignature);
324    }
325
326    // Commit the shared secret, then derive the confirmations from the transcript.
327    let Some(shared) = esk.exchange(&msg.epk) else {
328        return Err(Error::InvalidEphemeralKey);
329    };
330    shared
331        .secret
332        .expose(|secret| transcript.commit(secret.as_ref()));
333    let confirmation_l2d = transcript.fork(LABEL_CONFIRMATION_L2D).summarize();
334    let confirmation_d2l = transcript.fork(LABEL_CONFIRMATION_D2L).summarize();
335
336    // Accept the listener only if it derived the same transcript.
337    if msg.confirmation != confirmation_l2d {
338        return Err(Error::InvalidConfirmation);
339    }
340
341    Ok((
342        Ack {
343            confirmation: confirmation_d2l,
344        },
345        transcript,
346    ))
347}
348
349/// Processes the first handshake message as the listener.
350/// Verifies the dialer's [Syn] and returns the listener state and the [SynAck] to send.
351pub fn listen_start<S: Signer, P: PublicKey>(
352    rng: impl CryptoRng,
353    ctx: Context<S, P>,
354    msg: Syn<<P as Verifier>::Signature>,
355) -> Result<(ListenState, SynAck<<S as Signer>::Signature>), Error> {
356    let Context {
357        version,
358        current_time,
359        my_identity,
360        peer_identity,
361        ok_timestamps,
362        mut transcript,
363    } = ctx;
364
365    // Check the dialer's timestamp and commit it with the listener identity.
366    if !ok_timestamps.contains(&msg.time_ms) {
367        return Err(Error::InvalidTimestamp(msg.time_ms, ok_timestamps));
368    }
369    let dialer_identity = peer_identity.encode();
370    transcript
371        .commit(msg.time_ms.encode())
372        .commit(my_identity.public_key().encode());
373
374    // Commit the dialer identity where the dialer did: before verifying the [Syn] signature under
375    // V1 and after under V0.
376    if version.binds_identity_before_syn() {
377        transcript.commit(&dialer_identity[..]);
378    }
379    if !transcript
380        .commit(msg.epk.encode())
381        .verify(&peer_identity, &msg.sig)
382    {
383        return Err(Error::InvalidSignature);
384    }
385    if !version.binds_identity_before_syn() {
386        transcript.commit(dialer_identity);
387    }
388
389    // Sign the listener's timestamp and ephemeral key.
390    let esk = SecretKey::new(rng);
391    let epk = esk.public();
392    let sig = transcript
393        .commit(current_time.encode())
394        .commit(epk.encode())
395        .sign(&my_identity);
396
397    // Commit the shared secret and derive the confirmation the dialer must match.
398    let Some(shared) = esk.exchange(&msg.epk) else {
399        return Err(Error::InvalidEphemeralKey);
400    };
401    shared
402        .secret
403        .expose(|secret| transcript.commit(secret.as_ref()));
404    let confirmation_l2d = transcript.fork(LABEL_CONFIRMATION_L2D).summarize();
405
406    Ok((
407        ListenState { transcript },
408        SynAck {
409            time_ms: current_time,
410            epk,
411            sig,
412            confirmation: confirmation_l2d,
413        },
414    ))
415}
416
417/// Completes the handshake as the listener.
418/// Verifies the dialer's confirmation and returns the confirmed secret transcript.
419pub fn listen_end(state: ListenState, msg: Ack) -> Result<Transcript, Error> {
420    let confirmation_d2l = state.transcript.fork(LABEL_CONFIRMATION_D2L).summarize();
421    if msg.confirmation != confirmation_d2l {
422        return Err(Error::InvalidConfirmation);
423    }
424
425    Ok(state.transcript)
426}
427
428#[cfg(test)]
429mod test {
430    use super::*;
431    use crate::{Signer, ed25519::PrivateKey, secp256r1::standard};
432    use commonware_codec::{Codec, Copying, DecodeExt};
433    use commonware_math::algebra::Random;
434    use commonware_utils::{test_rng, union_unique};
435    use p256::{
436        AffinePoint, FieldBytes, ProjectivePoint, Scalar,
437        elliptic_curve::{ops::Reduce, sec1::ToSec1Point as _},
438    };
439    use sha2::{Digest, Sha256};
440
441    const VERSIONS: [Version; 2] = [Version::V0, Version::V1];
442
443    fn test_encode_roundtrip<T: Codec<Cfg = ()> + PartialEq>(value: &T) {
444        assert!(value == &<T as DecodeExt<_>>::decode(value.encode()).unwrap());
445    }
446
447    /// Completes a handshake under each [Version] and checks that both peers return the same
448    /// transcript.
449    #[test]
450    fn test_can_setup() -> Result<(), Error> {
451        for version in VERSIONS {
452            let mut rng = test_rng();
453            let dialer_crypto = PrivateKey::random(&mut rng);
454            let listener_crypto = PrivateKey::random(&mut rng);
455
456            // Run the three-message handshake and check each message round-trips through its codec.
457            let (d_state, msg1) = dial_start(
458                &mut rng,
459                Context::new(
460                    b"test_namespace",
461                    0,
462                    0..1,
463                    dialer_crypto.clone(),
464                    listener_crypto.public_key(),
465                    version,
466                ),
467            );
468            test_encode_roundtrip(&msg1);
469            let (l_state, msg2) = listen_start(
470                &mut rng,
471                Context::new(
472                    b"test_namespace",
473                    0,
474                    0..1,
475                    listener_crypto,
476                    dialer_crypto.public_key(),
477                    version,
478                ),
479                msg1,
480            )?;
481            test_encode_roundtrip(&msg2);
482            let (msg3, dialer) = dial_end(d_state, msg2)?;
483            test_encode_roundtrip(&msg3);
484            let listener = listen_end(l_state, msg3)?;
485            assert_eq!(dialer.summarize(), listener.summarize());
486        }
487
488        Ok(())
489    }
490
491    /// Rejects a [SynAck] or [Ack] whose confirmation does not match the transcript.
492    #[test]
493    fn test_mismatched_confirmation_fails() {
494        for version in VERSIONS {
495            let mut rng = test_rng();
496            let dialer_crypto = PrivateKey::random(&mut rng);
497            let listener_crypto = PrivateKey::random(&mut rng);
498            let wrong = Transcript::new(b"wrong", version.transcript()).summarize();
499            let start = |rng: &mut _| {
500                let (d_state, msg1) = dial_start(
501                    &mut *rng,
502                    Context::new(
503                        b"test_namespace",
504                        0,
505                        0..1,
506                        dialer_crypto.clone(),
507                        listener_crypto.public_key(),
508                        version,
509                    ),
510                );
511                let (l_state, msg2) = listen_start(
512                    &mut *rng,
513                    Context::new(
514                        b"test_namespace",
515                        0,
516                        0..1,
517                        listener_crypto.clone(),
518                        dialer_crypto.public_key(),
519                        version,
520                    ),
521                    msg1,
522                )
523                .unwrap();
524                (d_state, l_state, msg2)
525            };
526
527            // The dialer rejects a SynAck carrying the wrong confirmation.
528            let (d_state, _, mut msg2) = start(&mut rng);
529            msg2.confirmation = wrong;
530            assert!(matches!(
531                dial_end(d_state, msg2),
532                Err(Error::InvalidConfirmation)
533            ));
534
535            // The listener rejects an Ack carrying the wrong confirmation.
536            let (d_state, l_state, msg2) = start(&mut rng);
537            let (mut msg3, _) = dial_end(d_state, msg2).unwrap();
538            msg3.confirmation = wrong;
539            assert!(matches!(
540                listen_end(l_state, msg3),
541                Err(Error::InvalidConfirmation)
542            ));
543        }
544    }
545
546    /// Rejects a [Syn] signed under a different application namespace.
547    #[test]
548    fn test_mismatched_namespace_fails() {
549        for version in VERSIONS {
550            let mut rng = test_rng();
551            let dialer_crypto = PrivateKey::random(&mut rng);
552            let listener_crypto = PrivateKey::random(&mut rng);
553
554            let (_, msg1) = dial_start(
555                &mut rng,
556                Context::new(
557                    b"namespace_a",
558                    0,
559                    0..1,
560                    dialer_crypto.clone(),
561                    listener_crypto.public_key(),
562                    version,
563                ),
564            );
565
566            let result = listen_start(
567                &mut rng,
568                Context::new(
569                    b"namespace_b",
570                    0,
571                    0..1,
572                    listener_crypto,
573                    dialer_crypto.public_key(),
574                    version,
575                ),
576                msg1,
577            );
578
579            assert!(matches!(result, Err(Error::InvalidSignature)));
580        }
581    }
582
583    /// Rejects a [Syn] from a dialer running a different [Version].
584    #[test]
585    fn test_mismatched_version_fails() {
586        for (dialer_version, listener_version) in
587            [(Version::V0, Version::V1), (Version::V1, Version::V0)]
588        {
589            let mut rng = test_rng();
590            let dialer_crypto = PrivateKey::random(&mut rng);
591            let listener_crypto = PrivateKey::random(&mut rng);
592
593            let (_, msg1) = dial_start(
594                &mut rng,
595                Context::new(
596                    b"test_namespace",
597                    0,
598                    0..1,
599                    dialer_crypto.clone(),
600                    listener_crypto.public_key(),
601                    dialer_version,
602                ),
603            );
604
605            let result = listen_start(
606                &mut rng,
607                Context::new(
608                    b"test_namespace",
609                    0,
610                    0..1,
611                    listener_crypto,
612                    dialer_crypto.public_key(),
613                    listener_version,
614                ),
615                msg1,
616            );
617
618            assert!(matches!(result, Err(Error::InvalidSignature)));
619        }
620    }
621
622    /// Rejects a [Syn] when the listener expects a different dialer identity.
623    #[test]
624    fn test_mismatched_dialer_identity_fails() {
625        for version in VERSIONS {
626            let mut rng = test_rng();
627            let dialer_crypto = PrivateKey::random(&mut rng);
628            let listener_crypto = PrivateKey::random(&mut rng);
629            let impostor_crypto = PrivateKey::random(&mut rng);
630
631            let (_, msg1) = dial_start(
632                &mut rng,
633                Context::new(
634                    b"test_namespace",
635                    0,
636                    0..1,
637                    dialer_crypto,
638                    listener_crypto.public_key(),
639                    version,
640                ),
641            );
642
643            let result = listen_start(
644                &mut rng,
645                Context::new(
646                    b"test_namespace",
647                    0,
648                    0..1,
649                    listener_crypto,
650                    impostor_crypto.public_key(),
651                    version,
652                ),
653                msg1,
654            );
655
656            assert!(matches!(result, Err(Error::InvalidSignature)));
657        }
658    }
659
660    /// Reconstructs the transcript a listener verifies a [Syn] against, with the dialer identity
661    /// included or omitted before the ephemeral key.
662    fn syn_transcript<P: PublicKey>(
663        version: Version,
664        syn: &Syn<P::Signature>,
665        listener: &P,
666        dialer: Option<&P>,
667    ) -> Transcript {
668        let mut transcript =
669            Transcript::new(b"test_namespace", version.transcript()).fork(version.namespace());
670        if version == Version::V1 {
671            transcript.commit(&[1][..]);
672        }
673        transcript
674            .commit(syn.time_ms.encode())
675            .commit(listener.encode());
676        if let Some(dialer) = dialer {
677            transcript.commit(dialer.encode());
678        }
679        transcript.commit(syn.epk.encode());
680        transcript
681    }
682
683    /// Checks that a V1 [Syn] signature covers the dialer identity and that a V0 [Syn] signature
684    /// omits it.
685    #[test]
686    fn test_syn_signature_dialer_identity() {
687        for (version, covers) in [(Version::V0, false), (Version::V1, true)] {
688            let mut rng = test_rng();
689            let dialer_crypto = PrivateKey::random(&mut rng);
690            let listener_crypto = PrivateKey::random(&mut rng);
691            let dialer = dialer_crypto.public_key();
692            let listener = listener_crypto.public_key();
693
694            let (_, syn) = dial_start(
695                &mut rng,
696                Context::new(
697                    b"test_namespace",
698                    0,
699                    0..1,
700                    dialer_crypto,
701                    listener.clone(),
702                    version,
703                ),
704            );
705
706            // The signature verifies only over the transcript layout of its version.
707            let with = syn_transcript(version, &syn, &listener, Some(&dialer));
708            let without = syn_transcript(version, &syn, &listener, None);
709            assert_eq!(with.verify(&dialer, &syn.sig), covers, "{version:?}");
710            assert_eq!(without.verify(&dialer, &syn.sig), !covers, "{version:?}");
711        }
712    }
713
714    /// Derives the second public key under which an ECDSA signature over `summary` verifies.
715    ///
716    /// Replacing the signature's nonce point `R` with `-R` yields `Q' = -Q - 2 e r^-1 G`.
717    fn substitute(
718        key: &standard::PublicKey,
719        summary: &Summary,
720        sig: &standard::Signature,
721    ) -> standard::PublicKey {
722        // Transcript signatures use an empty namespace, so the signed payload is the summary
723        // behind a zero-length namespace prefix.
724        let payload = union_unique(b"", summary.as_ref());
725        let hash: [u8; 32] = Sha256::digest(&payload).into();
726        let e = <Scalar as Reduce<FieldBytes>>::reduce(&FieldBytes::from(hash));
727        let sig = p256::ecdsa::Signature::from_slice(&sig.encode()).unwrap();
728        let r: Scalar = *sig.r();
729        let r_inv = r.invert().unwrap();
730        let q = p256::PublicKey::from_sec1_bytes(&key.encode())
731            .unwrap()
732            .to_projective();
733        let derived: AffinePoint = (-q - ProjectivePoint::GENERATOR * (e * r_inv).double()).into();
734        standard::PublicKey::decode(Copying(derived.to_sec1_point(true).as_bytes())).unwrap()
735    }
736
737    /// V1 rejects a [Syn] whose signature verifies under a derived identity.
738    ///
739    /// Some signature schemes let anyone derive a second public key under which an existing
740    /// signature verifies. Under V0 the [Syn] signature does not cover the dialer identity, so a
741    /// dialer that signs with its own key can complete the handshake while claiming the derived
742    /// key. V1 commits the dialer identity before signing, so the claim fails verification.
743    #[test]
744    fn test_v1_rejects_derived_identity() {
745        for version in VERSIONS {
746            let mut rng = test_rng();
747            let dialer = standard::PrivateKey::random(&mut rng);
748            let listener = standard::PrivateKey::random(&mut rng);
749
750            // The dialer signs a Syn with its own key.
751            let (state, syn) = dial_start(
752                &mut rng,
753                Context::new(
754                    b"test_namespace",
755                    0,
756                    0..1,
757                    dialer.clone(),
758                    listener.public_key(),
759                    version,
760                ),
761            );
762
763            // It derives a second identity under which that signature verifies and claims it.
764            let (dialer_key, listener_key) = (dialer.public_key(), listener.public_key());
765            let signed = syn_transcript(
766                version,
767                &syn,
768                &listener_key,
769                version.binds_identity_before_syn().then_some(&dialer_key),
770            )
771            .summarize();
772            let derived = substitute(&dialer_key, &signed, &syn.sig);
773            assert_ne!(derived, dialer_key);
774            assert!(signed.verify(&derived, &syn.sig));
775
776            // Rebuild the dialer transcript with the derived identity in its V0 position.
777            let mut claimed = syn_transcript(version, &syn, &listener_key, None);
778            claimed.commit(derived.encode());
779            let result = listen_start(
780                &mut rng,
781                Context::new(b"test_namespace", 0, 0..1, listener, derived, version),
782                syn,
783            );
784
785            // V1 fails the [Syn] signature check because the signature covers the real dialer
786            // identity.
787            if version == Version::V1 {
788                assert!(matches!(result, Err(Error::InvalidSignature)));
789                continue;
790            }
791
792            // Under V0 the dialer finishes the exchange under the derived identity.
793            let (listen_state, syn_ack) = result.unwrap();
794            let state = DialState {
795                transcript: claimed,
796                ..state
797            };
798            let (ack, dialer) = dial_end(state, syn_ack).unwrap();
799            let listener = listen_end(listen_state, ack).unwrap();
800            assert_eq!(dialer.summarize(), listener.summarize());
801        }
802    }
803}