@@ -184,6 +184,7 @@ fn suite_id<D: DiffieHellman>() -> [u8; 5] {
184184 s
185185}
186186
187+ #[ inline]
187188fn extract_and_expand < D : DiffieHellman , H : Kdf > (
188189 dh : & [ u8 ] ,
189190 kem_context : & [ & [ u8 ] ] ,
@@ -200,6 +201,7 @@ fn extract_and_expand<D: DiffieHellman, H: Kdf>(
200201 )
201202}
202203
204+ #[ inline]
203205fn extract_and_expand_pieces < D : DiffieHellman , H : Kdf > (
204206 dh_pieces : & [ & [ u8 ] ] ,
205207 kem_context : & [ & [ u8 ] ] ,
@@ -429,14 +431,14 @@ impl<D: DiffieHellman, H: Kdf> Kem for DhKem<D, H> {
429431 enc : & Self :: EncappedKey ,
430432 sk_r : & Self :: PrivateKey ,
431433 ) -> Result < Self :: SharedSecret , HpkeError > {
432- let pk_e = D :: pk_from_bytes ( & enc. 0 ) ?;
434+ let pk_e = D :: pk_from_bytes ( enc. as_ref ( ) ) ?;
433435 let dh = Zeroizing :: new ( D :: dh ( & sk_r. sk , & pk_e) ?) ;
434436 // `sk_r.pk_bytes` was cached at construction time; using it here
435437 // saves the base-point scalar mult that `sk_to_pk` would otherwise
436438 // perform on every decap.
437439 Ok ( DhSharedSecret ( extract_and_expand :: < D , H > (
438440 & dh,
439- & [ & enc. 0 , & sk_r. pk_bytes ] ,
441+ & [ enc. as_ref ( ) , & sk_r. pk_bytes ] ,
440442 ) ?) )
441443 }
442444
@@ -474,28 +476,28 @@ impl<D: DiffieHellman, H: Kdf> AuthKem for DhKem<D, H> {
474476 sk_r : & Self :: PrivateKey ,
475477 pk_s : & Self :: PublicKey ,
476478 ) -> Result < Self :: SharedSecret , HpkeError > {
477- let pk_e = D :: pk_from_bytes ( & enc. 0 ) ?;
479+ let pk_e = D :: pk_from_bytes ( enc. as_ref ( ) ) ?;
478480 let dh1 = Zeroizing :: new ( D :: dh ( & sk_r. sk , & pk_e) ?) ;
479481 let dh2 = Zeroizing :: new ( D :: dh ( & sk_r. sk , & pk_s. 0 ) ?) ;
480482 let pk_sender = D :: pk_to_bytes ( & pk_s. 0 ) ;
481483 Ok ( DhSharedSecret ( extract_and_expand_pieces :: < D , H > (
482484 & [ & dh1, & dh2] ,
483- & [ & enc. 0 , & sk_r. pk_bytes , & pk_sender] ,
485+ & [ enc. as_ref ( ) , & sk_r. pk_bytes , & pk_sender] ,
484486 ) ?) )
485487 }
486488}
487489
488490fn encap_with < D : DiffieHellman , H : Kdf , R : CryptoRng + RngCore > (
489491 rng : & mut R ,
490492 pk_r : & DhPublicKey < D > ,
491- sk_s_authed : Option < & DhPrivateKey < D > > ,
493+ sk_sender : Option < & DhPrivateKey < D > > ,
492494) -> Result < ( DhSharedSecret , DhEncappedKey ) , HpkeError > {
493495 let ( sk_e, pk_e) = D :: generate ( rng) ;
494496 let dh1 = Zeroizing :: new ( D :: dh ( & sk_e, & pk_r. 0 ) ?) ;
495497 let enc = D :: pk_to_bytes ( & pk_e) ;
496498 let pk_recipient = D :: pk_to_bytes ( & pk_r. 0 ) ;
497499
498- let ss = match sk_s_authed {
500+ let ss = match sk_sender {
499501 None => extract_and_expand :: < D , H > ( & dh1, & [ & enc, & pk_recipient] ) ?,
500502 Some ( sk_s) => {
501503 let dh2 = Zeroizing :: new ( D :: dh ( & sk_s. sk , & pk_r. 0 ) ?) ;
@@ -996,6 +998,48 @@ mod tests {
996998 ) ;
997999 }
9981000
1001+ /// Verify that `extract_and_expand_pieces` produces the same output
1002+ /// as `extract_and_expand` when given two DH slices that together
1003+ /// equal the single concatenated input.
1004+ #[ test]
1005+ fn extract_and_expand_pieces_matches_concat ( ) {
1006+ let dh1 = [ 0x01u8 ; 32 ] ;
1007+ let dh2 = [ 0x02u8 ; 32 ] ;
1008+ let mut dh_concat = Vec :: new ( ) ;
1009+ dh_concat. extend_from_slice ( & dh1) ;
1010+ dh_concat. extend_from_slice ( & dh2) ;
1011+
1012+ let kem_context: & [ & [ u8 ] ] = & [ b"enc_bytes" , b"pk_recipient" ] ;
1013+
1014+ let single =
1015+ extract_and_expand :: < X25519 , crate :: HkdfSha256 > ( & dh_concat, kem_context) . unwrap ( ) ;
1016+
1017+ let pieces =
1018+ extract_and_expand_pieces :: < X25519 , crate :: HkdfSha256 > ( & [ & dh1, & dh2] , kem_context)
1019+ . unwrap ( ) ;
1020+
1021+ assert_eq ! ( single, pieces) ;
1022+ }
1023+
1024+ // Unit test that specifically calls `auth_encap_with_ikm` and `auth_decap`
1025+ // directly (bypassing the full HPKE stack), so that failures point exactly
1026+ // at the KEM layer (rather than somewhere in the key schedule or AEAD on top of it).
1027+ #[ test]
1028+ fn x25519_auth_encap_decap_roundtrip ( ) {
1029+ type Suite = DhKem < X25519 , crate :: HkdfSha256 > ;
1030+
1031+ let ( sk_r, pk_r) = Suite :: derive_key_pair ( b"auth-test-recipient-ikm" ) . unwrap ( ) ;
1032+ let ( sk_s, pk_s) = Suite :: derive_key_pair ( b"auth-test-sender-ikm" ) . unwrap ( ) ;
1033+
1034+ let ikm_e = b"auth-test-ephemeral-ikm" ;
1035+ let ( ss_enc, enc) =
1036+ auth_encap_with_ikm :: < X25519 , crate :: HkdfSha256 > ( & pk_r, & sk_s, ikm_e) . unwrap ( ) ;
1037+
1038+ let ss_dec = Suite :: auth_decap ( & enc, & sk_r, & pk_s) . unwrap ( ) ;
1039+
1040+ assert_eq ! ( ss_enc. as_ref( ) , ss_dec. as_ref( ) ) ;
1041+ }
1042+
9991043 /// All public `DhKem` aliases satisfy both `Kem` and `AuthKem`. Compile-only.
10001044 #[ test]
10011045 fn aliases_implement_kem_and_authkem ( ) {
0 commit comments