2 #![cfg_attr(docsrs, feature(doc_cfg))]
3 #![doc = include_str!("../README.md")]
5 html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
6 html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
8 #![forbid(unsafe_code)]
9 #![warn(missing_docs, rust_2018_idioms, unused_qualifications)]
13 //! Alice and Bob both initialize their SPAKE2 instances with the same (weak)
14 //! password. They will exchange messages to (hopefully) derive a shared secret
15 //! key. The protocol is symmetric: for each operation that Alice does, Bob will
18 //! However, there are two roles in the SPAKE2 protocol, "A" and "B". The two
19 //! sides must agree ahead of time which one will play which role (the
20 //! messages they generate depend upon which side they play). There are two
21 //! separate constructor functions, `start_a()` and `start_b()`, and a
22 //! complete interaction will use one of each (one `start_a` on one computer,
23 //! and one `start_b` on the other computer).
25 //! Each instance of a SPAKE2 protocol uses a set of shared parameters. These
26 //! include a group, a generator, and a pair of arbitrary group elements.
27 //! This library comes a single pre-generated parameter set, but could be
28 //! extended with others.
30 //! You start by calling `start_a()` (or `_b)` with the password and identity
31 //! strings for both sides. This gives you back a state object and the first
32 //! message, which you must send to your partner. Once you receive the
33 //! corresponding inbound message, you pass it into the state object
34 //! (consuming both in the process) by calling `s.finish()`, and you get back
35 //! the shared key as a bytestring.
37 //! The password and identity strings must each be wrapped in a "newtype",
38 //! which is a simple `struct` that protects against swapping the different
39 //! types of bytestrings.
41 //! Thus a client-side program start with:
44 //! use spake2::{Ed25519Group, Identity, Password, Spake2};
45 //! # fn send(msg: &[u8]) {}
46 //! let (s1, outbound_msg) = Spake2::<Ed25519Group>::start_a(
47 //! &Password::new(b"password"),
48 //! &Identity::new(b"client id string"),
49 //! &Identity::new(b"server id string"));
50 //! send(&outbound_msg);
52 //! # fn receive() -> Vec<u8> { let (s2, i2) = Spake2::<Ed25519Group>::start_b(&Password::new(b"password"), &Identity::new(b"client id string"), &Identity::new(b"server id string")); i2 }
53 //! let inbound_msg = receive();
54 //! let key1 = s1.finish(&inbound_msg).unwrap();
57 //! while the server-side might do:
60 //! # fn send(msg: &[u8]) {}
61 //! use spake2::{Ed25519Group, Identity, Password, Spake2};
62 //! let (s1, outbound_msg) = Spake2::<Ed25519Group>::start_b(
63 //! &Password::new(b"password"),
64 //! &Identity::new(b"client id string"),
65 //! &Identity::new(b"server id string"));
66 //! send(&outbound_msg);
68 //! # fn receive() -> Vec<u8> { let (s2, i2) = Spake2::<Ed25519Group>::start_a(&Password::new(b"password"), &Identity::new(b"client id string"), &Identity::new(b"server id string")); i2 }
69 //! let inbound_msg = receive();
70 //! let key2 = s1.finish(&inbound_msg).unwrap();
73 //! If both sides used the same password, and there is no man-in-the-middle,
74 //! then `key1` and `key2` will be identical. If not, the two sides will get
75 //! different keys. When one side encrypts with `key1`, and the other side
76 //! attempts to decrypt with `key2`, they'll get nothing but garbled noise.
78 //! The shared key can be used as an HMAC key to provide data integrity on
79 //! subsequent messages, or as an authenticated-encryption key (e.g.
80 //! nacl.secretbox). It can also be fed into [HKDF][1] to derive other
81 //! session keys as necessary.
83 //! The `SPAKE2` instances, and the messages they create, are single-use. Create
84 //! a new one for each new session. `finish` consumes the instance.
88 //! A single SPAKE2 instance must be used asymmetrically: the two sides must
89 //! somehow decide (ahead of time) which role they will each play. The
90 //! implementation includes the side identifier in the exchanged message to
91 //! guard against a `start_a` talking to another `start_a`. Typically a
92 //! "client" will take on the `A` role, and the "server" will be `B`.
94 //! This is a nuisance for more egalitarian protocols, where there's no clear
95 //! way to assign these roles ahead of time. In this case, use
96 //! `start_symmetric()` on both sides. This uses a different set of
97 //! parameters (so it is not interoperable with `start_A` or `start_b`), but
98 //! should otherwise behave the same way. The symmetric mode uses only one
99 //! identity string, not two.
104 //! # fn send(msg: &[u8]) {}
105 //! use spake2::{Ed25519Group, Identity, Password, Spake2};
106 //! let (s1, outbound_msg) = Spake2::<Ed25519Group>::start_symmetric(
107 //! &Password::new(b"password"),
108 //! &Identity::new(b"shared id string"));
109 //! send(&outbound_msg);
111 //! # fn receive() -> Vec<u8> { let (s2, i2) = Spake2::<Ed25519Group>::start_symmetric(&Password::new(b"password"), &Identity::new(b"shared id string")); i2 }
112 //! let inbound_msg = receive();
113 //! let key1 = s1.finish(&inbound_msg).unwrap();
116 //! Dave does exactly the same:
119 //! # fn send(msg: &[u8]) {}
120 //! use spake2::{Ed25519Group, Identity, Password, Spake2};
121 //! let (s1, outbound_msg) = Spake2::<Ed25519Group>::start_symmetric(
122 //! &Password::new(b"password"),
123 //! &Identity::new(b"shared id string"));
124 //! send(&outbound_msg);
126 //! # fn receive() -> Vec<u8> { let (s2, i2) = Spake2::<Ed25519Group>::start_symmetric(&Password::new(b"password"), &Identity::new(b"shared id string")); i2 }
127 //! let inbound_msg = receive();
128 //! let key1 = s1.finish(&inbound_msg).unwrap();
131 //! # Identifier Strings
133 //! The SPAKE2 protocol includes a pair of "identity strings" `idA` and `idB`
134 //! that are included in the final key-derivation hash. This binds the key to a
135 //! single pair of parties, or for some specific purpose.
137 //! For example, when user "alice" logs into "example.com", both sides should set
138 //! `idA = b"alice"` and `idB = b"example.com"`. This prevents an attacker from
139 //! substituting messages from unrelated login sessions (other users on the same
140 //! server, or other servers for the same user).
142 //! This also makes sure the session is established with the correct service. If
143 //! Alice has one password for "example.com" but uses it for both login and
144 //! file-transfer services, `idB` should be different for the two services.
145 //! Otherwise if Alice is simultaneously connecting to both services, and
146 //! attacker could rearrange the messages and cause her login client to connect
147 //! to the file-transfer server, and vice versa.
149 //! `idA` and `idB` must be bytestrings (slices of `<u8>`).
151 //! `start_symmetric` uses a single `idSymmetric=` string, instead of `idA`
152 //! and `idB`. Both sides must provide the same `idSymmetric=`, or leave it
157 //! Sometimes, you can't hold the SPAKE2 instance in memory for the whole
158 //! negotiation: perhaps all your program state is stored in a database, and
159 //! nothing lives in RAM for more than a few moments.
161 //! Unfortunately the Rust implementation does not yet provide serialization
162 //! of the state object. A future version should correct this.
166 //! This library is probably not constant-time, and does not protect against
167 //! timing attacks. Do not allow attackers to measure how long it takes you
168 //! to create or respond to a message. This matters somewhat less for pairing
169 //! protocols, because their passwords are single-use randomly-generated
170 //! keys, so an attacker has much less to work with.
172 //! This library depends upon a strong source of random numbers. Do not use it on
173 //! a system where os.urandom() is weak.
177 //! To run the built-in speed tests, just run `cargo bench`.
179 //! SPAKE2 consists of two phases, separated by a single message exchange.
180 //! The time these phases take is split roughly 50/50. On my 2.8GHz Core-i7
181 //! (i7-7600U) cpu, the built-in Ed25519Group parameters take about 112
182 //! microseconds for each phase, and the message exchanged is 33 bytes long.
186 //! Run `cargo test` to run the built-in test suite.
190 //! The protocol was described as "PAKE2" in ["cryptobook"] [2] from Dan Boneh
191 //! and Victor Shoup. This is a form of "SPAKE2", defined by Abdalla and
192 //! Pointcheval at [RSA 2005] [3]. Additional recommendations for groups and
193 //! distinguished elements were published in [Ladd's IETF draft] [4].
195 //! The Ed25519 implementation uses code adapted from Daniel Bernstein (djb),
196 //! Matthew Dempsky, Daniel Holth, Ron Garret, with further optimizations by
197 //! Brian Warner[5]. The "arbitrary element" computation, which must be the same
198 //! for both participants, is from python-pure25519 version 0.5.
200 //! The Boneh/Shoup chapter that defines PAKE2 also defines an augmented variant
201 //! named "PAKE2+", which changes one side (typically a server) to record a
202 //! derivative of the password instead of the actual password. In PAKE2+, a
203 //! server compromise does not immediately give access to the passwords: instead,
204 //! the attacker must perform an offline dictionary attack against the stolen
205 //! data before they can learn the passwords. PAKE2+ support is planned, but not
208 //! The security of the symmetric case was proved by Kobara/Imai[6] in 2003, and
209 //! uses different (slightly weaker?) reductions than that of the asymmetric
210 //! form. See also Mike Hamburg's analysis[7] from 2015.
212 //! Brian Warner first wrote the Python version in July 2010. He wrote this
213 //! Rust version in in May 2017.
217 //! [1]: https://tools.ietf.org/html/rfc5869 "HKDF"
218 //! [2]: http://crypto.stanford.edu/~dabo/cryptobook/ "cryptobook"
219 //! [3]: http://www.di.ens.fr/~pointche/Documents/Papers/2005_rsa.pdf "RSA 2005"
220 //! [4]: https://tools.ietf.org/html/draft-ladd-spake2-01 "Ladd's IETF draft"
221 //! [5]: https://github.com/warner/python-pure25519
222 //! [6]: http://eprint.iacr.org/2003/038.pdf "Pretty-Simple Password-Authenticated Key-Exchange Under Standard Assumptions"
223 //! [7]: https://moderncrypto.org/mail-archive/curves/2015/000419.html "PAKE questions"
225 #[allow(unused_imports)]
229 #[cfg(feature = "std")]
230 #[cfg_attr(test, macro_use)]
238 ed25519::Ed25519Group,
239 error::{Error, Result},
244 use core::{fmt, ops::Deref, str};
245 use curve25519_dalek::{edwards::EdwardsPoint as c2_Element, scalar::Scalar as c2_Scalar};
246 use rand_core::{CryptoRng, RngCore};
248 #[cfg(feature = "getrandom")]
249 use rand_core::OsRng;
252 // TODO(tarcieri): avoid allocation?
253 #[derive(PartialEq, Eq, Clone)]
254 pub struct Password(Vec<u8>);
257 /// Create a new password.
258 pub fn new(p: impl AsRef<[u8]>) -> Password {
259 Password(p.as_ref().to_vec())
263 impl Deref for Password {
264 type Target = Vec<u8>;
266 fn deref(&self) -> &Vec<u8> {
272 // TODO(tarcieri): avoid allocation?
273 #[derive(PartialEq, Eq, Clone)]
274 pub struct Identity(Vec<u8>);
276 impl Deref for Identity {
277 type Target = Vec<u8>;
279 fn deref(&self) -> &Vec<u8> {
285 /// Create a new identity.
286 pub fn new(p: &[u8]) -> Identity {
291 /// Session type identifying the "side" in a SPAKE2 exchange.
292 #[derive(Debug, PartialEq, Eq)]
299 /// SPAKE2 algorithm.
300 #[derive(Eq, PartialEq)]
301 pub struct Spake2<G: Group> {
302 //where &G::Scalar: Neg {
304 xy_scalar: G::Scalar,
305 password_vec: Vec<u8>,
310 password_scalar: G::Scalar,
313 impl<G: Group> Spake2<G> {
314 /// Start with identity `idA`.
316 /// Uses the system RNG.
317 #[cfg(feature = "getrandom")]
318 #[cfg_attr(docsrs, doc(cfg(feature = "getrandom")))]
319 pub fn start_a(password: &Password, id_a: &Identity, id_b: &Identity) -> (Spake2<G>, Vec<u8>) {
320 Self::start_a_with_rng(password, id_a, id_b, OsRng)
323 /// Start with identity `idB`.
325 /// Uses the system RNG.
326 #[cfg(feature = "getrandom")]
327 #[cfg_attr(docsrs, doc(cfg(feature = "getrandom")))]
328 pub fn start_b(password: &Password, id_a: &Identity, id_b: &Identity) -> (Spake2<G>, Vec<u8>) {
329 Self::start_b_with_rng(password, id_a, id_b, OsRng)
332 /// Start with symmetric identity.
334 /// Uses the system RNG.
335 #[cfg(feature = "getrandom")]
336 #[cfg_attr(docsrs, doc(cfg(feature = "getrandom")))]
337 pub fn start_symmetric(password: &Password, id_s: &Identity) -> (Spake2<G>, Vec<u8>) {
338 Self::start_symmetric_with_rng(password, id_s, OsRng)
341 /// Start with identity `idA` and the provided cryptographically secure RNG.
342 pub fn start_a_with_rng(
346 mut csrng: impl CryptoRng + RngCore,
347 ) -> (Spake2<G>, Vec<u8>) {
348 let xy_scalar: G::Scalar = G::random_scalar(&mut csrng);
349 Self::start_a_internal(password, id_a, id_b, xy_scalar)
352 /// Start with identity `idB` and the provided cryptographically secure RNG.
353 pub fn start_b_with_rng(
357 mut csrng: impl CryptoRng + RngCore,
358 ) -> (Spake2<G>, Vec<u8>) {
359 let xy_scalar: G::Scalar = G::random_scalar(&mut csrng);
360 Self::start_b_internal(password, id_a, id_b, xy_scalar)
363 /// Start with symmetric identity and the provided cryptographically secure RNG.
364 pub fn start_symmetric_with_rng(
367 mut csrng: impl CryptoRng + RngCore,
368 ) -> (Spake2<G>, Vec<u8>) {
369 let xy_scalar: G::Scalar = G::random_scalar(&mut csrng);
370 Self::start_symmetric_internal(password, id_s, xy_scalar)
374 pub fn finish(self, msg2: &[u8]) -> Result<Vec<u8>> {
375 if msg2.len() != 1 + G::element_length() {
376 return Err(Error::WrongLength);
378 let msg_side = msg2[0];
381 Side::A => match msg_side {
383 _ => return Err(Error::BadSide),
385 Side::B => match msg_side {
387 _ => return Err(Error::BadSide),
389 Side::Symmetric => match msg_side {
391 _ => return Err(Error::BadSide),
395 let msg2_element = match G::bytes_to_element(&msg2[1..]) {
397 None => return Err(Error::CorruptMessage),
400 // a: K = (Y+N*(-pw))*x
401 // b: K = (X+M*(-pw))*y
402 let unblinding = match self.side {
403 Side::A => G::const_n(),
404 Side::B => G::const_m(),
405 Side::Symmetric => G::const_s(),
407 let tmp1 = G::scalarmult(&unblinding, &G::scalar_neg(&self.password_scalar));
408 let tmp2 = G::add(&msg2_element, &tmp1);
409 let key_element = G::scalarmult(&tmp2, &self.xy_scalar);
410 let key_bytes = G::element_to_bytes(&key_element);
412 // key = H(H(pw) + H(idA) + H(idB) + X + Y + K)
413 //transcript = b"".join([sha256(pw).digest(),
414 // sha256(idA).digest(), sha256(idB).digest(),
415 // X_msg, Y_msg, K_bytes])
416 //key = sha256(transcript).digest()
417 // note that both sides must use the same order
420 Side::A => ed25519::hash_ab(
424 self.msg1.as_slice(),
428 Side::B => ed25519::hash_ab(
433 self.msg1.as_slice(),
436 Side::Symmetric => ed25519::hash_symmetric(
452 xy_scalar: G::Scalar,
453 ) -> (Spake2<G>, Vec<u8>) {
454 //let password_scalar: G::Scalar = hash_to_scalar::<G::Scalar>(password);
455 let password_scalar: G::Scalar = G::hash_to_scalar(password);
459 // sym: X = B*x * S*pw
460 let blinding = match side {
461 Side::A => G::const_m(),
462 Side::B => G::const_n(),
463 Side::Symmetric => G::const_s(),
465 let m1: G::Element = G::add(
466 &G::basepoint_mult(&xy_scalar),
467 &G::scalarmult(&blinding, &password_scalar),
469 //let m1: G::Element = &G::basepoint_mult(&x) + &(blinding * &password_scalar);
470 let msg1: Vec<u8> = G::element_to_bytes(&m1);
471 let mut password_vec = Vec::new();
472 password_vec.extend_from_slice(password);
473 let mut id_a_copy = Vec::new();
474 id_a_copy.extend_from_slice(id_a);
475 let mut id_b_copy = Vec::new();
476 id_b_copy.extend_from_slice(id_b);
477 let mut id_s_copy = Vec::new();
478 id_s_copy.extend_from_slice(id_s);
480 let mut msg_and_side = vec![match side {
481 Side::A => 0x41, // 'A'
482 Side::B => 0x42, // 'B'
483 Side::Symmetric => 0x53, // 'S'
485 msg_and_side.extend_from_slice(&msg1);
491 password_vec, // string
496 password_scalar, // scalar
506 xy_scalar: G::Scalar,
507 ) -> (Spake2<G>, Vec<u8>) {
508 Self::start_internal(
522 xy_scalar: G::Scalar,
523 ) -> (Spake2<G>, Vec<u8>) {
524 Self::start_internal(
534 fn start_symmetric_internal(
537 xy_scalar: G::Scalar,
538 ) -> (Spake2<G>, Vec<u8>) {
539 Self::start_internal(
550 impl<G: Group> fmt::Debug for Spake2<G> {
551 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
552 fmt.debug_struct("SPAKE2")
553 .field("group", &G::name())
554 .field("side", &self.side)
555 .field("idA", &MaybeUtf8(&self.id_a))
556 .field("idB", &MaybeUtf8(&self.id_b))
557 .field("idS", &MaybeUtf8(&self.id_s))
562 struct MaybeUtf8<'a>(&'a [u8]);
564 impl fmt::Debug for MaybeUtf8<'_> {
565 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
566 if let Ok(s) = str::from_utf8(self.0) {
567 write!(fmt, "(s={})", s)
569 write!(fmt, "(hex=")?;
572 write!(fmt, "{:x}", byte)?;
580 /// This compares results against the python compatibility tests:
581 /// spake2.test.test_compat.SPAKE2.test_asymmetric . The python test passes a
582 /// deterministic RNG (used only for tests, of course) into the per-Group
583 /// "random_scalar()" function, which results in some particular scalar.
584 #[cfg(all(test, feature = "std"))]
587 use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
588 use num_bigint::BigUint;
590 // the python tests show the long-integer form of scalars. the rust code
591 // wants an array of bytes (little-endian). Make sure the way we convert
592 // things works correctly.
593 fn decimal_to_scalar(d: &[u8]) -> c2_Scalar {
594 let bytes = BigUint::parse_bytes(d, 10).unwrap().to_bytes_le();
595 assert_eq!(bytes.len(), 32);
596 let mut b2 = [0u8; 32];
597 b2.copy_from_slice(&bytes);
598 c2_Scalar::from_bytes_mod_order(b2)
604 b"2238329342913194256032495932344128051776374960164957527413114840482143558222";
605 let t1_scalar = decimal_to_scalar(t1_decimal);
606 let t1_bytes = t1_scalar.to_bytes();
608 0x4e, 0x5a, 0xb4, 0x34, 0x5d, 0x47, 0x08, 0x84, 0x59, 0x13, 0xb4, 0x64, 0x1b, 0xc2,
609 0x7d, 0x52, 0x52, 0xa5, 0x85, 0x10, 0x1b, 0xcc, 0x42, 0x44, 0xd4, 0x49, 0xf4, 0xa8,
610 0x79, 0xd9, 0xf2, 0x04,
612 assert_eq!(t1_bytes, expected);
613 //println!("t1_scalar is {:?}", t1_scalar);
617 fn test_serialize_basepoint() {
618 // make sure elements are serialized same as the python library
619 let exp = "5866666666666666666666666666666666666666666666666666666666666666";
620 let base_vec = ED25519_BASEPOINT_POINT.compress().as_bytes().to_vec();
621 let base_hex = hex::encode(base_vec);
622 println!("exp: {:?}", exp);
623 println!("got: {:?}", base_hex);
624 assert_eq!(exp, base_hex);
628 fn test_password_to_scalar() {
629 let password = Password::new(b"password");
630 let expected_pw_scalar = decimal_to_scalar(
631 b"3515301705789368674385125653994241092664323519848410154015274772661223168839",
633 let pw_scalar = Ed25519Group::hash_to_scalar(&password);
634 println!("exp: {:?}", hex::encode(expected_pw_scalar.as_bytes()));
635 println!("got: {:?}", hex::encode(pw_scalar.as_bytes()));
636 assert_eq!(&pw_scalar, &expected_pw_scalar);
641 let (s1, msg1) = Spake2::<Ed25519Group>::start_a(
642 &Password::new(b"password"),
643 &Identity::new(b"idA"),
644 &Identity::new(b"idB"),
646 assert_eq!(msg1.len(), 1 + 32);
647 let (s2, msg2) = Spake2::<Ed25519Group>::start_b(
648 &Password::new(b"password"),
649 &Identity::new(b"idA"),
650 &Identity::new(b"idB"),
652 assert_eq!(msg2.len(), 1 + 32);
653 let key1 = s1.finish(&msg2).unwrap();
654 let key2 = s2.finish(&msg1).unwrap();
655 assert_eq!(key1.len(), 32);
656 assert_eq!(key2.len(), 32);
658 let (s1, msg1) = Spake2::<Ed25519Group>::start_symmetric(
659 &Password::new(b"password"),
660 &Identity::new(b"idS"),
662 assert_eq!(msg1.len(), 1 + 32);
663 let (s2, msg2) = Spake2::<Ed25519Group>::start_symmetric(
664 &Password::new(b"password"),
665 &Identity::new(b"idS"),
667 assert_eq!(msg2.len(), 1 + 32);
668 let key1 = s1.finish(&msg2).unwrap();
669 let key2 = s2.finish(&msg1).unwrap();
670 assert_eq!(key1.len(), 32);
671 assert_eq!(key2.len(), 32);
676 let key = ed25519::hash_ab(
680 b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", // len=32
681 b"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY",
682 b"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK",
684 let expected_key = "d59d9ba920f7092565cec747b08d5b2e981d553ac32fde0f25e5b4a4cfca3efd";
685 assert_eq!(hex::encode(key), expected_key);
689 fn test_hash_symmetric() {
690 let key = ed25519::hash_symmetric(
693 b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
694 b"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY",
695 b"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK",
697 let expected_key = "b0b31e4401aae37d91a9a8bf6fbb1298cafc005ff9142e3ffc5b9799fb11128b";
698 assert_eq!(hex::encode(key), expected_key);
702 fn test_asymmetric() {
703 let scalar_a = decimal_to_scalar(
704 b"2611694063369306139794446498317402240796898290761098242657700742213257926693",
706 let scalar_b = decimal_to_scalar(
707 b"7002393159576182977806091886122272758628412261510164356026361256515836884383",
709 let expected_pw_scalar = decimal_to_scalar(
710 b"3515301705789368674385125653994241092664323519848410154015274772661223168839",
713 println!("scalar_a is {}", hex::encode(scalar_a.as_bytes()));
715 let (s1, msg1) = Spake2::<Ed25519Group>::start_a_internal(
716 &Password::new(b"password"),
717 &Identity::new(b"idA"),
718 &Identity::new(b"idB"),
721 let expected_msg1 = "416fc960df73c9cf8ed7198b0c9534e2e96a5984bfc5edc023fd24dacf371f2af9";
724 println!("xys1: {:?}", hex::encode(s1.xy_scalar.as_bytes()));
726 println!("pws1: {:?}", hex::encode(s1.password_scalar.as_bytes()));
727 println!("exp : {:?}", hex::encode(expected_pw_scalar.as_bytes()));
729 println!("msg1: {:?}", hex::encode(&msg1));
730 println!("exp : {:?}", expected_msg1);
734 hex::encode(expected_pw_scalar.as_bytes()),
735 hex::encode(s1.password_scalar.as_bytes())
737 assert_eq!(hex::encode(&msg1), expected_msg1);
739 let (s2, msg2) = Spake2::<Ed25519Group>::start_b_internal(
740 &Password::new(b"password"),
741 &Identity::new(b"idA"),
742 &Identity::new(b"idB"),
745 assert_eq!(expected_pw_scalar, s2.password_scalar);
748 "42354e97b88406922b1df4bea1d7870f17aed3dba7c720b313edae315b00959309"
751 let key1 = s1.finish(&msg2).unwrap();
752 let key2 = s2.finish(&msg1).unwrap();
753 assert_eq!(key1, key2);
756 "712295de7219c675ddd31942184aa26e0a957cf216bc230d165b215047b520c1"
762 let (s1, _msg1) = Spake2::<Ed25519Group>::start_a(
763 &Password::new(b"password"),
764 &Identity::new(b"idA"),
765 &Identity::new(b"idB"),
767 println!("s1: {:?}", s1);
770 "SPAKE2 { group: \"Ed25519\", side: A, idA: (s=idA), idB: (s=idB), idS: (s=) }"
773 let (s2, _msg1) = Spake2::<Ed25519Group>::start_symmetric(
774 &Password::new(b"password"),
775 &Identity::new(b"idS"),
777 println!("s2: {:?}", s2);
780 "SPAKE2 { group: \"Ed25519\", side: Symmetric, idA: (s=), idB: (s=), idS: (s=idS) }"