]> git.lizzy.rs Git - PAKEs.git/commitdiff
spake2 code reorganization (#6)
authorArtyom Pavlov <newpavlov@gmail.com>
Wed, 17 Oct 2018 18:11:24 +0000 (18:11 +0000)
committerGitHub <noreply@github.com>
Wed, 17 Oct 2018 18:11:24 +0000 (18:11 +0000)
spake2/src/lib.rs
spake2/src/spake2.rs [deleted file]
spake2/src/tests.rs [new file with mode: 0644]
spake2/tests/mod.rs [new file with mode: 0644]
srp/src/lib.rs

index 55a85df3384aa597ca9cd8d0a6ff34f00bdcae20..163cb2616084e062f59586c06a29e42bf6bb1c1b 100644 (file)
@@ -1,7 +1,6 @@
-#![doc(html_logo_url =
-    "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")]
+#![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")]
+#![deny(warnings)]
 #![forbid(unsafe_code)]
-#![cfg_attr(test, deny(warnings))]
 
 extern crate curve25519_dalek;
 extern crate hex;
@@ -10,102 +9,542 @@ extern crate num_bigint;
 extern crate rand;
 extern crate sha2;
 
-mod spake2;
-pub use spake2::*;
+use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
+use curve25519_dalek::edwards::CompressedEdwardsY;
+use curve25519_dalek::edwards::EdwardsPoint as c2_Element;
+use curve25519_dalek::scalar::Scalar as c2_Scalar;
 
-#[cfg(test)]
-mod tests {
-    use spake2::{Ed25519Group, ErrorType, Identity, Password, SPAKEErr, SPAKE2};
-
-    #[test]
-    fn test_basic() {
-        let (s1, msg1) = SPAKE2::<Ed25519Group>::start_a(
-            &Password::new(b"password"),
-            &Identity::new(b"idA"),
-            &Identity::new(b"idB"),
-        );
-        let (s2, msg2) = SPAKE2::<Ed25519Group>::start_b(
-            &Password::new(b"password"),
-            &Identity::new(b"idA"),
-            &Identity::new(b"idB"),
-        );
-        let key1 = s1.finish(msg2.as_slice()).unwrap();
-        let key2 = s2.finish(msg1.as_slice()).unwrap();
-        assert_eq!(key1, key2);
+use hkdf::Hkdf;
+use rand::{CryptoRng, OsRng, Rng};
+use sha2::{Digest, Sha256};
+use std::fmt;
+use std::ops::Deref;
+
+/* "newtype pattern": it's a Vec<u8>, but only used for a specific argument
+ * type, to distinguish between ones that are meant as passwords, and ones
+ * that are meant as identity strings */
+
+#[derive(PartialEq, Eq, Clone)]
+pub struct Password(Vec<u8>);
+impl Password {
+    pub fn new(p: &[u8]) -> Password {
+        Password(p.to_vec())
+    }
+}
+impl Deref for Password {
+    type Target = Vec<u8>;
+    fn deref(&self) -> &Vec<u8> {
+        &self.0
     }
+}
 
-    #[test]
-    fn test_mismatch() {
-        let (s1, msg1) = SPAKE2::<Ed25519Group>::start_a(
-            &Password::new(b"password"),
-            &Identity::new(b"idA"),
-            &Identity::new(b"idB"),
-        );
-        let (s2, msg2) = SPAKE2::<Ed25519Group>::start_b(
-            &Password::new(b"password2"),
-            &Identity::new(b"idA"),
-            &Identity::new(b"idB"),
-        );
-        let key1 = s1.finish(msg2.as_slice()).unwrap();
-        let key2 = s2.finish(msg1.as_slice()).unwrap();
-        assert_ne!(key1, key2);
+#[derive(PartialEq, Eq, Clone)]
+pub struct Identity(Vec<u8>);
+impl Deref for Identity {
+    type Target = Vec<u8>;
+    fn deref(&self) -> &Vec<u8> {
+        &self.0
+    }
+}
+impl Identity {
+    pub fn new(p: &[u8]) -> Identity {
+        Identity(p.to_vec())
     }
+}
 
-    #[test]
-    fn test_reflected_message() {
-        let (s1, msg1) = SPAKE2::<Ed25519Group>::start_a(
-            &Password::new(b"password"),
-            &Identity::new(b"idA"),
-            &Identity::new(b"idB"),
-        );
-        let r = s1.finish(msg1.as_slice());
-        assert_eq!(
-            r.unwrap_err(),
-            SPAKEErr {
-                kind: ErrorType::BadSide,
-            }
-        );
+#[derive(Debug, PartialEq, Eq)]
+pub enum ErrorType {
+    BadSide,
+    WrongLength,
+    CorruptMessage,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct SPAKEErr {
+    pub kind: ErrorType,
+}
+
+pub trait Group {
+    type Scalar;
+    type Element;
+    //type Element: Add<Output=Self::Element>
+    //    + Mul<Self::Scalar, Output=Self::Element>;
+    // const element_length: usize; // in unstable, or u8
+    //type ElementBytes : Index<usize, Output=u8>+IndexMut<usize>; // later
+    type TranscriptHash;
+    fn const_m() -> Self::Element;
+    fn const_n() -> Self::Element;
+    fn const_s() -> Self::Element;
+    fn hash_to_scalar(s: &[u8]) -> Self::Scalar;
+    fn random_scalar<T>(cspring: &mut T) -> Self::Scalar
+    where
+        T: Rng + CryptoRng;
+    fn scalar_neg(s: &Self::Scalar) -> Self::Scalar;
+    fn element_to_bytes(e: &Self::Element) -> Vec<u8>;
+    fn bytes_to_element(b: &[u8]) -> Option<Self::Element>;
+    fn element_length() -> usize;
+    fn basepoint_mult(s: &Self::Scalar) -> Self::Element;
+    fn scalarmult(e: &Self::Element, s: &Self::Scalar) -> Self::Element;
+    fn add(a: &Self::Element, b: &Self::Element) -> Self::Element;
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Ed25519Group;
+
+impl Group for Ed25519Group {
+    type Scalar = c2_Scalar;
+    type Element = c2_Element;
+    //type ElementBytes = Vec<u8>;
+    //type ElementBytes = [u8; 32];
+    //type ScalarBytes
+    type TranscriptHash = Sha256;
+
+    fn const_m() -> c2_Element {
+        // python -c "import binascii, spake2; b=binascii.hexlify(spake2.ParamsEd25519.M.to_bytes()); print(', '.join(['0x'+b[i:i+2] for i in range(0,len(b),2)]))"
+        // 15cfd18e385952982b6a8f8c7854963b58e34388c8e6dae891db756481a02312
+        CompressedEdwardsY([
+            0x15, 0xcf, 0xd1, 0x8e, 0x38, 0x59, 0x52, 0x98, 0x2b, 0x6a, 0x8f, 0x8c, 0x78, 0x54,
+            0x96, 0x3b, 0x58, 0xe3, 0x43, 0x88, 0xc8, 0xe6, 0xda, 0xe8, 0x91, 0xdb, 0x75, 0x64,
+            0x81, 0xa0, 0x23, 0x12,
+        ]).decompress()
+        .unwrap()
+    }
+
+    fn const_n() -> c2_Element {
+        // python -c "import binascii, spake2; b=binascii.hexlify(spake2.ParamsEd25519.N.to_bytes()); print(', '.join(['0x'+b[i:i+2] for i in range(0,len(b),2)]))"
+        // f04f2e7eb734b2a8f8b472eaf9c3c632576ac64aea650b496a8a20ff00e583c3
+        CompressedEdwardsY([
+            0xf0, 0x4f, 0x2e, 0x7e, 0xb7, 0x34, 0xb2, 0xa8, 0xf8, 0xb4, 0x72, 0xea, 0xf9, 0xc3,
+            0xc6, 0x32, 0x57, 0x6a, 0xc6, 0x4a, 0xea, 0x65, 0x0b, 0x49, 0x6a, 0x8a, 0x20, 0xff,
+            0x00, 0xe5, 0x83, 0xc3,
+        ]).decompress()
+        .unwrap()
+    }
+
+    fn const_s() -> c2_Element {
+        // python -c "import binascii, spake2; b=binascii.hexlify(spake2.ParamsEd25519.S.to_bytes()); print(', '.join(['0x'+b[i:i+2] for i in range(0,len(b),2)]))"
+        // 6f00dae87c1be1a73b5922ef431cd8f57879569c222d22b1cd71e8546ab8e6f1
+        CompressedEdwardsY([
+            0x6f, 0x00, 0xda, 0xe8, 0x7c, 0x1b, 0xe1, 0xa7, 0x3b, 0x59, 0x22, 0xef, 0x43, 0x1c,
+            0xd8, 0xf5, 0x78, 0x79, 0x56, 0x9c, 0x22, 0x2d, 0x22, 0xb1, 0xcd, 0x71, 0xe8, 0x54,
+            0x6a, 0xb8, 0xe6, 0xf1,
+        ]).decompress()
+        .unwrap()
+    }
+
+    fn hash_to_scalar(s: &[u8]) -> c2_Scalar {
+        ed25519_hash_to_scalar(s)
+    }
+    fn random_scalar<T>(cspring: &mut T) -> c2_Scalar
+    where
+        T: Rng + CryptoRng,
+    {
+        c2_Scalar::random(cspring)
+    }
+    fn scalar_neg(s: &c2_Scalar) -> c2_Scalar {
+        -s
+    }
+    fn element_to_bytes(s: &c2_Element) -> Vec<u8> {
+        s.compress().as_bytes().to_vec()
+    }
+    fn element_length() -> usize {
+        32
+    }
+    fn bytes_to_element(b: &[u8]) -> Option<c2_Element> {
+        if b.len() != 32 {
+            return None;
+        }
+        //let mut bytes: [u8; 32] =
+        let mut bytes = [0u8; 32];
+        bytes.copy_from_slice(b);
+        let cey = CompressedEdwardsY(bytes);
+        // CompressedEdwardsY::new(b)
+        cey.decompress()
+    }
+
+    fn basepoint_mult(s: &c2_Scalar) -> c2_Element {
+        //c2_Element::basepoint_mult(s)
+        ED25519_BASEPOINT_POINT * s
+    }
+    fn scalarmult(e: &c2_Element, s: &c2_Scalar) -> c2_Element {
+        e * s
+        //e.scalar_mult(s)
+    }
+    fn add(a: &c2_Element, b: &c2_Element) -> c2_Element {
+        a + b
+        //a.add(b)
+    }
+}
+
+fn ed25519_hash_to_scalar(s: &[u8]) -> c2_Scalar {
+    //c2_Scalar::hash_from_bytes::<Sha512>(&s)
+    // spake2.py does:
+    //  h = HKDF(salt=b"", ikm=s, hash=SHA256, info=b"SPAKE2 pw", len=32+16)
+    //  i = int(h, 16)
+    //  i % q
+
+    let mut okm = [0u8; 32 + 16];
+    Hkdf::<Sha256>::extract(Some(b""), s)
+        .expand(b"SPAKE2 pw", &mut okm)
+        .unwrap();
+    //println!("expanded:   {}{}", "................................", okm.iter().to_hex()); // ok
+
+    let mut reducible = [0u8; 64]; // little-endian
+    for (i, x) in okm.iter().enumerate().take(32 + 16) {
+        reducible[32 + 16 - 1 - i] = *x;
+    }
+    //println!("reducible:  {}", reducible.iter().to_hex());
+    c2_Scalar::from_bytes_mod_order_wide(&reducible)
+    //let reduced = c2_Scalar::reduce(&reducible);
+    //println!("reduced:    {}", reduced.as_bytes().to_hex());
+    //println!("done");
+    //reduced
+}
+
+fn ed25519_hash_ab(
+    password_vec: &[u8],
+    id_a: &[u8],
+    id_b: &[u8],
+    first_msg: &[u8],
+    second_msg: &[u8],
+    key_bytes: &[u8],
+) -> Vec<u8> {
+    assert_eq!(first_msg.len(), 32);
+    assert_eq!(second_msg.len(), 32);
+    // the transcript is fixed-length, made up of 6 32-byte values:
+    // byte 0-31   : sha256(pw)
+    // byte 32-63  : sha256(idA)
+    // byte 64-95  : sha256(idB)
+    // byte 96-127 : X_msg
+    // byte 128-159: Y_msg
+    // byte 160-191: K_bytes
+    let mut transcript = [0u8; 6 * 32];
+
+    let mut pw_hash = Sha256::new();
+    pw_hash.input(password_vec);
+    transcript[0..32].copy_from_slice(&pw_hash.result());
+
+    let mut ida_hash = Sha256::new();
+    ida_hash.input(id_a);
+    transcript[32..64].copy_from_slice(&ida_hash.result());
+
+    let mut idb_hash = Sha256::new();
+    idb_hash.input(id_b);
+    transcript[64..96].copy_from_slice(&idb_hash.result());
+
+    transcript[96..128].copy_from_slice(first_msg);
+    transcript[128..160].copy_from_slice(second_msg);
+    transcript[160..192].copy_from_slice(key_bytes);
+
+    //println!("transcript: {:?}", transcript.iter().to_hex());
+
+    //let mut hash = G::TranscriptHash::default();
+    let mut hash = Sha256::new();
+    hash.input(&transcript);
+    hash.result().to_vec()
+}
+
+fn ed25519_hash_symmetric(
+    password_vec: &[u8],
+    id_s: &[u8],
+    msg_u: &[u8],
+    msg_v: &[u8],
+    key_bytes: &[u8],
+) -> Vec<u8> {
+    assert_eq!(msg_u.len(), 32);
+    assert_eq!(msg_v.len(), 32);
+    // # since we don't know which side is which, we must sort the messages
+    // first_msg, second_msg = sorted([msg1, msg2])
+    // transcript = b"".join([sha256(pw).digest(),
+    //                        sha256(idSymmetric).digest(),
+    //                        first_msg, second_msg, K_bytes])
+
+    // the transcript is fixed-length, made up of 5 32-byte values:
+    // byte 0-31   : sha256(pw)
+    // byte 32-63  : sha256(idSymmetric)
+    // byte 64-95  : X_msg
+    // byte 96-127 : Y_msg
+    // byte 128-159: K_bytes
+    let mut transcript = [0u8; 5 * 32];
+
+    let mut pw_hash = Sha256::new();
+    pw_hash.input(password_vec);
+    transcript[0..32].copy_from_slice(&pw_hash.result());
+
+    let mut ids_hash = Sha256::new();
+    ids_hash.input(id_s);
+    transcript[32..64].copy_from_slice(&ids_hash.result());
+
+    if msg_u < msg_v {
+        transcript[64..96].copy_from_slice(msg_u);
+        transcript[96..128].copy_from_slice(msg_v);
+    } else {
+        transcript[64..96].copy_from_slice(msg_v);
+        transcript[96..128].copy_from_slice(msg_u);
     }
+    transcript[128..160].copy_from_slice(key_bytes);
+
+    let mut hash = Sha256::new();
+    hash.input(&transcript);
+    hash.result().to_vec()
+}
+
+/* "session type pattern" */
+
+#[derive(Debug, PartialEq, Eq)]
+enum Side {
+    A,
+    B,
+    Symmetric,
+}
+
+// we implement a custom Debug below, to avoid revealing secrets in a dump
+#[derive(PartialEq, Eq)]
+pub struct SPAKE2<G: Group> {
+    //where &G::Scalar: Neg {
+    side: Side,
+    xy_scalar: G::Scalar,
+    password_vec: Vec<u8>,
+    id_a: Vec<u8>,
+    id_b: Vec<u8>,
+    id_s: Vec<u8>,
+    msg1: Vec<u8>,
+    password_scalar: G::Scalar,
+}
 
-    #[test]
-    fn test_bad_length() {
-        let (s1, msg1) = SPAKE2::<Ed25519Group>::start_a(
-            &Password::new(b"password"),
-            &Identity::new(b"idA"),
-            &Identity::new(b"idB"),
+impl<G: Group> SPAKE2<G> {
+    fn start_internal(
+        side: Side,
+        password: &Password,
+        id_a: &Identity,
+        id_b: &Identity,
+        id_s: &Identity,
+        xy_scalar: G::Scalar,
+    ) -> (SPAKE2<G>, Vec<u8>) {
+        //let password_scalar: G::Scalar = hash_to_scalar::<G::Scalar>(password);
+        let password_scalar: G::Scalar = G::hash_to_scalar(&password);
+
+        // a: X = B*x + M*pw
+        // b: Y = B*y + N*pw
+        // sym: X = B*x * S*pw
+        let blinding = match side {
+            Side::A => G::const_m(),
+            Side::B => G::const_n(),
+            Side::Symmetric => G::const_s(),
+        };
+        let m1: G::Element = G::add(
+            &G::basepoint_mult(&xy_scalar),
+            &G::scalarmult(&blinding, &password_scalar),
         );
-        let mut msg2 = Vec::<u8>::with_capacity(msg1.len() + 1);
-        msg2.resize(msg1.len() + 1, 0u8);
-        let r = s1.finish(&msg2);
-        assert_eq!(
-            r.unwrap_err(),
-            SPAKEErr {
+        //let m1: G::Element = &G::basepoint_mult(&x) + &(blinding * &password_scalar);
+        let msg1: Vec<u8> = G::element_to_bytes(&m1);
+        let mut password_vec = Vec::new();
+        password_vec.extend_from_slice(&password);
+        let mut id_a_copy = Vec::new();
+        id_a_copy.extend_from_slice(&id_a);
+        let mut id_b_copy = Vec::new();
+        id_b_copy.extend_from_slice(&id_b);
+        let mut id_s_copy = Vec::new();
+        id_s_copy.extend_from_slice(&id_s);
+
+        let mut msg_and_side = Vec::new();
+        msg_and_side.push(match side {
+            Side::A => 0x41,         // 'A'
+            Side::B => 0x42,         // 'B'
+            Side::Symmetric => 0x53, // 'S'
+        });
+        msg_and_side.extend_from_slice(&msg1);
+
+        (
+            SPAKE2 {
+                side,
+                xy_scalar,
+                password_vec, // string
+                id_a: id_a_copy,
+                id_b: id_b_copy,
+                id_s: id_s_copy,
+                msg1: msg1.clone(),
+                password_scalar, // scalar
+            },
+            msg_and_side,
+        )
+    }
+
+    fn start_a_internal(
+        password: &Password,
+        id_a: &Identity,
+        id_b: &Identity,
+        xy_scalar: G::Scalar,
+    ) -> (SPAKE2<G>, Vec<u8>) {
+        Self::start_internal(
+            Side::A,
+            &password,
+            &id_a,
+            &id_b,
+            &Identity::new(b""),
+            xy_scalar,
+        )
+    }
+
+    fn start_b_internal(
+        password: &Password,
+        id_a: &Identity,
+        id_b: &Identity,
+        xy_scalar: G::Scalar,
+    ) -> (SPAKE2<G>, Vec<u8>) {
+        Self::start_internal(
+            Side::B,
+            &password,
+            &id_a,
+            &id_b,
+            &Identity::new(b""),
+            xy_scalar,
+        )
+    }
+
+    fn start_symmetric_internal(
+        password: &Password,
+        id_s: &Identity,
+        xy_scalar: G::Scalar,
+    ) -> (SPAKE2<G>, Vec<u8>) {
+        Self::start_internal(
+            Side::Symmetric,
+            &password,
+            &Identity::new(b""),
+            &Identity::new(b""),
+            &id_s,
+            xy_scalar,
+        )
+    }
+
+    pub fn start_a(password: &Password, id_a: &Identity, id_b: &Identity) -> (SPAKE2<G>, Vec<u8>) {
+        let mut cspring: OsRng = OsRng::new().unwrap();
+        let xy_scalar: G::Scalar = G::random_scalar(&mut cspring);
+        Self::start_a_internal(&password, &id_a, &id_b, xy_scalar)
+    }
+
+    pub fn start_b(password: &Password, id_a: &Identity, id_b: &Identity) -> (SPAKE2<G>, Vec<u8>) {
+        let mut cspring: OsRng = OsRng::new().unwrap();
+        let xy_scalar: G::Scalar = G::random_scalar(&mut cspring);
+        Self::start_b_internal(&password, &id_a, &id_b, xy_scalar)
+    }
+
+    pub fn start_symmetric(password: &Password, id_s: &Identity) -> (SPAKE2<G>, Vec<u8>) {
+        let mut cspring: OsRng = OsRng::new().unwrap();
+        let xy_scalar: G::Scalar = G::random_scalar(&mut cspring);
+        Self::start_symmetric_internal(&password, &id_s, xy_scalar)
+    }
+
+    pub fn finish(self, msg2: &[u8]) -> Result<Vec<u8>, SPAKEErr> {
+        if msg2.len() != 1 + G::element_length() {
+            return Err(SPAKEErr {
                 kind: ErrorType::WrongLength,
+            });
+        }
+        let msg_side = msg2[0];
+
+        match self.side {
+            Side::A => match msg_side {
+                0x42 => (), // 'B'
+                _ => {
+                    return Err(SPAKEErr {
+                        kind: ErrorType::BadSide,
+                    })
+                }
+            },
+            Side::B => match msg_side {
+                0x41 => (), // 'A'
+                _ => {
+                    return Err(SPAKEErr {
+                        kind: ErrorType::BadSide,
+                    })
+                }
+            },
+            Side::Symmetric => match msg_side {
+                0x53 => (), // 'S'
+                _ => {
+                    return Err(SPAKEErr {
+                        kind: ErrorType::BadSide,
+                    })
+                }
+            },
+        }
+
+        let msg2_element = match G::bytes_to_element(&msg2[1..]) {
+            Some(x) => x,
+            None => {
+                return Err(SPAKEErr {
+                    kind: ErrorType::CorruptMessage,
+                })
             }
-        );
-    }
+        };
 
-    #[test]
-    fn test_basic_symmetric() {
-        let (s1, msg1) = SPAKE2::<Ed25519Group>::start_symmetric(
-            &Password::new(b"password"),
-            &Identity::new(b"idS"),
-        );
-        let (s2, msg2) = SPAKE2::<Ed25519Group>::start_symmetric(
-            &Password::new(b"password"),
-            &Identity::new(b"idS"),
-        );
-        let key1 = s1.finish(msg2.as_slice()).unwrap();
-        let key2 = s2.finish(msg1.as_slice()).unwrap();
-        assert_eq!(key1, key2);
+        // a: K = (Y+N*(-pw))*x
+        // b: K = (X+M*(-pw))*y
+        let unblinding = match self.side {
+            Side::A => G::const_n(),
+            Side::B => G::const_m(),
+            Side::Symmetric => G::const_s(),
+        };
+        let tmp1 = G::scalarmult(&unblinding, &G::scalar_neg(&self.password_scalar));
+        let tmp2 = G::add(&msg2_element, &tmp1);
+        let key_element = G::scalarmult(&tmp2, &self.xy_scalar);
+        let key_bytes = G::element_to_bytes(&key_element);
+
+        // key = H(H(pw) + H(idA) + H(idB) + X + Y + K)
+        //transcript = b"".join([sha256(pw).digest(),
+        //                       sha256(idA).digest(), sha256(idB).digest(),
+        //                       X_msg, Y_msg, K_bytes])
+        //key = sha256(transcript).digest()
+        // note that both sides must use the same order
+
+        Ok(match self.side {
+            Side::A => ed25519_hash_ab(
+                &self.password_vec,
+                &self.id_a,
+                &self.id_b,
+                self.msg1.as_slice(),
+                &msg2[1..],
+                &key_bytes,
+            ),
+            Side::B => ed25519_hash_ab(
+                &self.password_vec,
+                &self.id_a,
+                &self.id_b,
+                &msg2[1..],
+                self.msg1.as_slice(),
+                &key_bytes,
+            ),
+            Side::Symmetric => ed25519_hash_symmetric(
+                &self.password_vec,
+                &self.id_s,
+                &self.msg1,
+                &msg2[1..],
+                &key_bytes,
+            ),
+        })
     }
+}
 
-    #[test]
-    fn it_works() {}
+fn maybe_utf8(s: &[u8]) -> String {
+    match String::from_utf8(s.to_vec()) {
+        Ok(m) => format!("(s={})", m),
+        Err(_) => format!("(hex={})", hex::encode(s)),
+    }
+}
 
-    #[test]
-    #[should_panic(expected = "nope")]
-    fn it_panics() {
-        assert!(false, "nope");
+impl<G: Group> fmt::Debug for SPAKE2<G> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(
+            f,
+            "SPAKE2(G=?, side={:?}, idA={}, idB={}, idS={})",
+            self.side,
+            maybe_utf8(&self.id_a),
+            maybe_utf8(&self.id_b),
+            maybe_utf8(&self.id_s)
+        )
     }
 }
+
+#[cfg(test)]
+mod tests;
diff --git a/spake2/src/spake2.rs b/spake2/src/spake2.rs
deleted file mode 100644 (file)
index 8f90bdf..0000000
+++ /dev/null
@@ -1,741 +0,0 @@
-#![allow(dead_code)]
-
-use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
-use curve25519_dalek::edwards::CompressedEdwardsY;
-use curve25519_dalek::edwards::EdwardsPoint as c2_Element;
-use curve25519_dalek::scalar::Scalar as c2_Scalar;
-use hex;
-use hkdf::Hkdf;
-use num_bigint::BigUint;
-use rand::{CryptoRng, OsRng, Rng};
-use sha2::{Digest, Sha256};
-use std::fmt;
-use std::ops::Deref;
-
-//use hex::ToHex;
-
-/* "newtype pattern": it's a Vec<u8>, but only used for a specific argument
- * type, to distinguish between ones that are meant as passwords, and ones
- * that are meant as identity strings */
-
-#[derive(PartialEq, Eq, Clone)]
-pub struct Password(Vec<u8>);
-impl Password {
-    pub fn new(p: &[u8]) -> Password {
-        Password(p.to_vec())
-    }
-}
-impl Deref for Password {
-    type Target = Vec<u8>;
-    fn deref(&self) -> &Vec<u8> {
-        &self.0
-    }
-}
-
-#[derive(PartialEq, Eq, Clone)]
-pub struct Identity(Vec<u8>);
-impl Deref for Identity {
-    type Target = Vec<u8>;
-    fn deref(&self) -> &Vec<u8> {
-        &self.0
-    }
-}
-impl Identity {
-    pub fn new(p: &[u8]) -> Identity {
-        Identity(p.to_vec())
-    }
-}
-
-#[derive(Debug, PartialEq, Eq)]
-pub enum ErrorType {
-    BadSide,
-    WrongLength,
-    CorruptMessage,
-}
-
-#[derive(Debug, PartialEq, Eq)]
-pub struct SPAKEErr {
-    pub kind: ErrorType,
-}
-
-pub trait Group {
-    type Scalar;
-    type Element;
-    //type Element: Add<Output=Self::Element>
-    //    + Mul<Self::Scalar, Output=Self::Element>;
-    // const element_length: usize; // in unstable, or u8
-    //type ElementBytes : Index<usize, Output=u8>+IndexMut<usize>; // later
-    type TranscriptHash;
-    fn const_m() -> Self::Element;
-    fn const_n() -> Self::Element;
-    fn const_s() -> Self::Element;
-    fn hash_to_scalar(s: &[u8]) -> Self::Scalar;
-    fn random_scalar<T>(cspring: &mut T) -> Self::Scalar
-    where
-        T: Rng + CryptoRng;
-    fn scalar_neg(s: &Self::Scalar) -> Self::Scalar;
-    fn element_to_bytes(e: &Self::Element) -> Vec<u8>;
-    fn bytes_to_element(b: &[u8]) -> Option<Self::Element>;
-    fn element_length() -> usize;
-    fn basepoint_mult(s: &Self::Scalar) -> Self::Element;
-    fn scalarmult(e: &Self::Element, s: &Self::Scalar) -> Self::Element;
-    fn add(a: &Self::Element, b: &Self::Element) -> Self::Element;
-}
-
-#[derive(Debug, PartialEq, Eq)]
-pub struct Ed25519Group;
-
-impl Group for Ed25519Group {
-    type Scalar = c2_Scalar;
-    type Element = c2_Element;
-    //type ElementBytes = Vec<u8>;
-    //type ElementBytes = [u8; 32];
-    //type ScalarBytes
-    type TranscriptHash = Sha256;
-
-    fn const_m() -> c2_Element {
-        // python -c "import binascii, spake2; b=binascii.hexlify(spake2.ParamsEd25519.M.to_bytes()); print(', '.join(['0x'+b[i:i+2] for i in range(0,len(b),2)]))"
-        // 15cfd18e385952982b6a8f8c7854963b58e34388c8e6dae891db756481a02312
-        CompressedEdwardsY([
-            0x15, 0xcf, 0xd1, 0x8e, 0x38, 0x59, 0x52, 0x98, 0x2b, 0x6a, 0x8f, 0x8c, 0x78, 0x54,
-            0x96, 0x3b, 0x58, 0xe3, 0x43, 0x88, 0xc8, 0xe6, 0xda, 0xe8, 0x91, 0xdb, 0x75, 0x64,
-            0x81, 0xa0, 0x23, 0x12,
-        ]).decompress()
-        .unwrap()
-    }
-
-    fn const_n() -> c2_Element {
-        // python -c "import binascii, spake2; b=binascii.hexlify(spake2.ParamsEd25519.N.to_bytes()); print(', '.join(['0x'+b[i:i+2] for i in range(0,len(b),2)]))"
-        // f04f2e7eb734b2a8f8b472eaf9c3c632576ac64aea650b496a8a20ff00e583c3
-        CompressedEdwardsY([
-            0xf0, 0x4f, 0x2e, 0x7e, 0xb7, 0x34, 0xb2, 0xa8, 0xf8, 0xb4, 0x72, 0xea, 0xf9, 0xc3,
-            0xc6, 0x32, 0x57, 0x6a, 0xc6, 0x4a, 0xea, 0x65, 0x0b, 0x49, 0x6a, 0x8a, 0x20, 0xff,
-            0x00, 0xe5, 0x83, 0xc3,
-        ]).decompress()
-        .unwrap()
-    }
-
-    fn const_s() -> c2_Element {
-        // python -c "import binascii, spake2; b=binascii.hexlify(spake2.ParamsEd25519.S.to_bytes()); print(', '.join(['0x'+b[i:i+2] for i in range(0,len(b),2)]))"
-        // 6f00dae87c1be1a73b5922ef431cd8f57879569c222d22b1cd71e8546ab8e6f1
-        CompressedEdwardsY([
-            0x6f, 0x00, 0xda, 0xe8, 0x7c, 0x1b, 0xe1, 0xa7, 0x3b, 0x59, 0x22, 0xef, 0x43, 0x1c,
-            0xd8, 0xf5, 0x78, 0x79, 0x56, 0x9c, 0x22, 0x2d, 0x22, 0xb1, 0xcd, 0x71, 0xe8, 0x54,
-            0x6a, 0xb8, 0xe6, 0xf1,
-        ]).decompress()
-        .unwrap()
-    }
-
-    fn hash_to_scalar(s: &[u8]) -> c2_Scalar {
-        ed25519_hash_to_scalar(s)
-    }
-    fn random_scalar<T>(cspring: &mut T) -> c2_Scalar
-    where
-        T: Rng + CryptoRng,
-    {
-        c2_Scalar::random(cspring)
-    }
-    fn scalar_neg(s: &c2_Scalar) -> c2_Scalar {
-        -s
-    }
-    fn element_to_bytes(s: &c2_Element) -> Vec<u8> {
-        s.compress().as_bytes().to_vec()
-    }
-    fn element_length() -> usize {
-        32
-    }
-    fn bytes_to_element(b: &[u8]) -> Option<c2_Element> {
-        if b.len() != 32 {
-            return None;
-        }
-        //let mut bytes: [u8; 32] =
-        let mut bytes = [0u8; 32];
-        bytes.copy_from_slice(b);
-        let cey = CompressedEdwardsY(bytes);
-        // CompressedEdwardsY::new(b)
-        cey.decompress()
-    }
-
-    fn basepoint_mult(s: &c2_Scalar) -> c2_Element {
-        //c2_Element::basepoint_mult(s)
-        ED25519_BASEPOINT_POINT * s
-    }
-    fn scalarmult(e: &c2_Element, s: &c2_Scalar) -> c2_Element {
-        e * s
-        //e.scalar_mult(s)
-    }
-    fn add(a: &c2_Element, b: &c2_Element) -> c2_Element {
-        a + b
-        //a.add(b)
-    }
-}
-
-fn decimal_to_scalar(d: &[u8]) -> c2_Scalar {
-    let bytes = BigUint::parse_bytes(d, 10).unwrap().to_bytes_le();
-    assert_eq!(bytes.len(), 32);
-    let mut b2 = [0u8; 32];
-    b2.copy_from_slice(&bytes);
-    c2_Scalar::from_bytes_mod_order(b2)
-}
-
-fn ed25519_hash_to_scalar(s: &[u8]) -> c2_Scalar {
-    //c2_Scalar::hash_from_bytes::<Sha512>(&s)
-    // spake2.py does:
-    //  h = HKDF(salt=b"", ikm=s, hash=SHA256, info=b"SPAKE2 pw", len=32+16)
-    //  i = int(h, 16)
-    //  i % q
-
-    let mut okm = [0u8; 32 + 16];
-    Hkdf::<Sha256>::extract(Some(b""), s)
-        .expand(b"SPAKE2 pw", &mut okm)
-        .unwrap();
-    //println!("expanded:   {}{}", "................................", okm.iter().to_hex()); // ok
-
-    let mut reducible = [0u8; 64]; // little-endian
-    for (i, x) in okm.iter().enumerate().take(32 + 16) {
-        reducible[32 + 16 - 1 - i] = *x;
-    }
-    //println!("reducible:  {}", reducible.iter().to_hex());
-    c2_Scalar::from_bytes_mod_order_wide(&reducible)
-    //let reduced = c2_Scalar::reduce(&reducible);
-    //println!("reduced:    {}", reduced.as_bytes().to_hex());
-    //println!("done");
-    //reduced
-}
-
-fn ed25519_hash_ab(
-    password_vec: &[u8],
-    id_a: &[u8],
-    id_b: &[u8],
-    first_msg: &[u8],
-    second_msg: &[u8],
-    key_bytes: &[u8],
-) -> Vec<u8> {
-    assert_eq!(first_msg.len(), 32);
-    assert_eq!(second_msg.len(), 32);
-    // the transcript is fixed-length, made up of 6 32-byte values:
-    // byte 0-31   : sha256(pw)
-    // byte 32-63  : sha256(idA)
-    // byte 64-95  : sha256(idB)
-    // byte 96-127 : X_msg
-    // byte 128-159: Y_msg
-    // byte 160-191: K_bytes
-    let mut transcript = [0u8; 6 * 32];
-
-    let mut pw_hash = Sha256::new();
-    pw_hash.input(password_vec);
-    transcript[0..32].copy_from_slice(&pw_hash.result());
-
-    let mut ida_hash = Sha256::new();
-    ida_hash.input(id_a);
-    transcript[32..64].copy_from_slice(&ida_hash.result());
-
-    let mut idb_hash = Sha256::new();
-    idb_hash.input(id_b);
-    transcript[64..96].copy_from_slice(&idb_hash.result());
-
-    transcript[96..128].copy_from_slice(first_msg);
-    transcript[128..160].copy_from_slice(second_msg);
-    transcript[160..192].copy_from_slice(key_bytes);
-
-    //println!("transcript: {:?}", transcript.iter().to_hex());
-
-    //let mut hash = G::TranscriptHash::default();
-    let mut hash = Sha256::new();
-    hash.input(&transcript);
-    hash.result().to_vec()
-}
-
-fn ed25519_hash_symmetric(
-    password_vec: &[u8],
-    id_s: &[u8],
-    msg_u: &[u8],
-    msg_v: &[u8],
-    key_bytes: &[u8],
-) -> Vec<u8> {
-    assert_eq!(msg_u.len(), 32);
-    assert_eq!(msg_v.len(), 32);
-    // # since we don't know which side is which, we must sort the messages
-    // first_msg, second_msg = sorted([msg1, msg2])
-    // transcript = b"".join([sha256(pw).digest(),
-    //                        sha256(idSymmetric).digest(),
-    //                        first_msg, second_msg, K_bytes])
-
-    // the transcript is fixed-length, made up of 5 32-byte values:
-    // byte 0-31   : sha256(pw)
-    // byte 32-63  : sha256(idSymmetric)
-    // byte 64-95  : X_msg
-    // byte 96-127 : Y_msg
-    // byte 128-159: K_bytes
-    let mut transcript = [0u8; 5 * 32];
-
-    let mut pw_hash = Sha256::new();
-    pw_hash.input(password_vec);
-    transcript[0..32].copy_from_slice(&pw_hash.result());
-
-    let mut ids_hash = Sha256::new();
-    ids_hash.input(id_s);
-    transcript[32..64].copy_from_slice(&ids_hash.result());
-
-    if msg_u < msg_v {
-        transcript[64..96].copy_from_slice(msg_u);
-        transcript[96..128].copy_from_slice(msg_v);
-    } else {
-        transcript[64..96].copy_from_slice(msg_v);
-        transcript[96..128].copy_from_slice(msg_u);
-    }
-    transcript[128..160].copy_from_slice(key_bytes);
-
-    let mut hash = Sha256::new();
-    hash.input(&transcript);
-    hash.result().to_vec()
-}
-
-/* "session type pattern" */
-
-#[derive(Debug, PartialEq, Eq)]
-enum Side {
-    A,
-    B,
-    Symmetric,
-}
-
-// we implement a custom Debug below, to avoid revealing secrets in a dump
-#[derive(PartialEq, Eq)]
-pub struct SPAKE2<G: Group> {
-    //where &G::Scalar: Neg {
-    side: Side,
-    xy_scalar: G::Scalar,
-    password_vec: Vec<u8>,
-    id_a: Vec<u8>,
-    id_b: Vec<u8>,
-    id_s: Vec<u8>,
-    msg1: Vec<u8>,
-    password_scalar: G::Scalar,
-}
-
-impl<G: Group> SPAKE2<G> {
-    fn start_internal(
-        side: Side,
-        password: &Password,
-        id_a: &Identity,
-        id_b: &Identity,
-        id_s: &Identity,
-        xy_scalar: G::Scalar,
-    ) -> (SPAKE2<G>, Vec<u8>) {
-        //let password_scalar: G::Scalar = hash_to_scalar::<G::Scalar>(password);
-        let password_scalar: G::Scalar = G::hash_to_scalar(&password);
-
-        // a: X = B*x + M*pw
-        // b: Y = B*y + N*pw
-        // sym: X = B*x * S*pw
-        let blinding = match side {
-            Side::A => G::const_m(),
-            Side::B => G::const_n(),
-            Side::Symmetric => G::const_s(),
-        };
-        let m1: G::Element = G::add(
-            &G::basepoint_mult(&xy_scalar),
-            &G::scalarmult(&blinding, &password_scalar),
-        );
-        //let m1: G::Element = &G::basepoint_mult(&x) + &(blinding * &password_scalar);
-        let msg1: Vec<u8> = G::element_to_bytes(&m1);
-        let mut password_vec = Vec::new();
-        password_vec.extend_from_slice(&password);
-        let mut id_a_copy = Vec::new();
-        id_a_copy.extend_from_slice(&id_a);
-        let mut id_b_copy = Vec::new();
-        id_b_copy.extend_from_slice(&id_b);
-        let mut id_s_copy = Vec::new();
-        id_s_copy.extend_from_slice(&id_s);
-
-        let mut msg_and_side = Vec::new();
-        msg_and_side.push(match side {
-            Side::A => 0x41,         // 'A'
-            Side::B => 0x42,         // 'B'
-            Side::Symmetric => 0x53, // 'S'
-        });
-        msg_and_side.extend_from_slice(&msg1);
-
-        (
-            SPAKE2 {
-                side,
-                xy_scalar,
-                password_vec, // string
-                id_a: id_a_copy,
-                id_b: id_b_copy,
-                id_s: id_s_copy,
-                msg1: msg1.clone(),
-                password_scalar, // scalar
-            },
-            msg_and_side,
-        )
-    }
-
-    fn start_a_internal(
-        password: &Password,
-        id_a: &Identity,
-        id_b: &Identity,
-        xy_scalar: G::Scalar,
-    ) -> (SPAKE2<G>, Vec<u8>) {
-        Self::start_internal(
-            Side::A,
-            &password,
-            &id_a,
-            &id_b,
-            &Identity::new(b""),
-            xy_scalar,
-        )
-    }
-
-    fn start_b_internal(
-        password: &Password,
-        id_a: &Identity,
-        id_b: &Identity,
-        xy_scalar: G::Scalar,
-    ) -> (SPAKE2<G>, Vec<u8>) {
-        Self::start_internal(
-            Side::B,
-            &password,
-            &id_a,
-            &id_b,
-            &Identity::new(b""),
-            xy_scalar,
-        )
-    }
-
-    fn start_symmetric_internal(
-        password: &Password,
-        id_s: &Identity,
-        xy_scalar: G::Scalar,
-    ) -> (SPAKE2<G>, Vec<u8>) {
-        Self::start_internal(
-            Side::Symmetric,
-            &password,
-            &Identity::new(b""),
-            &Identity::new(b""),
-            &id_s,
-            xy_scalar,
-        )
-    }
-
-    pub fn start_a(password: &Password, id_a: &Identity, id_b: &Identity) -> (SPAKE2<G>, Vec<u8>) {
-        let mut cspring: OsRng = OsRng::new().unwrap();
-        let xy_scalar: G::Scalar = G::random_scalar(&mut cspring);
-        Self::start_a_internal(&password, &id_a, &id_b, xy_scalar)
-    }
-
-    pub fn start_b(password: &Password, id_a: &Identity, id_b: &Identity) -> (SPAKE2<G>, Vec<u8>) {
-        let mut cspring: OsRng = OsRng::new().unwrap();
-        let xy_scalar: G::Scalar = G::random_scalar(&mut cspring);
-        Self::start_b_internal(&password, &id_a, &id_b, xy_scalar)
-    }
-
-    pub fn start_symmetric(password: &Password, id_s: &Identity) -> (SPAKE2<G>, Vec<u8>) {
-        let mut cspring: OsRng = OsRng::new().unwrap();
-        let xy_scalar: G::Scalar = G::random_scalar(&mut cspring);
-        Self::start_symmetric_internal(&password, &id_s, xy_scalar)
-    }
-
-    pub fn finish(self, msg2: &[u8]) -> Result<Vec<u8>, SPAKEErr> {
-        if msg2.len() != 1 + G::element_length() {
-            return Err(SPAKEErr {
-                kind: ErrorType::WrongLength,
-            });
-        }
-        let msg_side = msg2[0];
-
-        match self.side {
-            Side::A => match msg_side {
-                0x42 => (), // 'B'
-                _ => {
-                    return Err(SPAKEErr {
-                        kind: ErrorType::BadSide,
-                    })
-                }
-            },
-            Side::B => match msg_side {
-                0x41 => (), // 'A'
-                _ => {
-                    return Err(SPAKEErr {
-                        kind: ErrorType::BadSide,
-                    })
-                }
-            },
-            Side::Symmetric => match msg_side {
-                0x53 => (), // 'S'
-                _ => {
-                    return Err(SPAKEErr {
-                        kind: ErrorType::BadSide,
-                    })
-                }
-            },
-        }
-
-        let msg2_element = match G::bytes_to_element(&msg2[1..]) {
-            Some(x) => x,
-            None => {
-                return Err(SPAKEErr {
-                    kind: ErrorType::CorruptMessage,
-                })
-            }
-        };
-
-        // a: K = (Y+N*(-pw))*x
-        // b: K = (X+M*(-pw))*y
-        let unblinding = match self.side {
-            Side::A => G::const_n(),
-            Side::B => G::const_m(),
-            Side::Symmetric => G::const_s(),
-        };
-        let tmp1 = G::scalarmult(&unblinding, &G::scalar_neg(&self.password_scalar));
-        let tmp2 = G::add(&msg2_element, &tmp1);
-        let key_element = G::scalarmult(&tmp2, &self.xy_scalar);
-        let key_bytes = G::element_to_bytes(&key_element);
-
-        // key = H(H(pw) + H(idA) + H(idB) + X + Y + K)
-        //transcript = b"".join([sha256(pw).digest(),
-        //                       sha256(idA).digest(), sha256(idB).digest(),
-        //                       X_msg, Y_msg, K_bytes])
-        //key = sha256(transcript).digest()
-        // note that both sides must use the same order
-
-        Ok(match self.side {
-            Side::A => ed25519_hash_ab(
-                &self.password_vec,
-                &self.id_a,
-                &self.id_b,
-                self.msg1.as_slice(),
-                &msg2[1..],
-                &key_bytes,
-            ),
-            Side::B => ed25519_hash_ab(
-                &self.password_vec,
-                &self.id_a,
-                &self.id_b,
-                &msg2[1..],
-                self.msg1.as_slice(),
-                &key_bytes,
-            ),
-            Side::Symmetric => ed25519_hash_symmetric(
-                &self.password_vec,
-                &self.id_s,
-                &self.msg1,
-                &msg2[1..],
-                &key_bytes,
-            ),
-        })
-    }
-}
-
-fn maybe_utf8(s: &[u8]) -> String {
-    match String::from_utf8(s.to_vec()) {
-        Ok(m) => format!("(s={})", m),
-        Err(_) => format!("(hex={})", hex::encode(s)),
-    }
-}
-
-impl<G: Group> fmt::Debug for SPAKE2<G> {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(
-            f,
-            "SPAKE2(G=?, side={:?}, idA={}, idB={}, idS={})",
-            self.side,
-            maybe_utf8(&self.id_a),
-            maybe_utf8(&self.id_b),
-            maybe_utf8(&self.id_s)
-        )
-    }
-}
-
-#[cfg(test)]
-mod test {
-    /* This compares results against the python compatibility tests:
-    spake2.test.test_compat.SPAKE2.test_asymmetric . The python test passes a
-    deterministic RNG (used only for tests, of course) into the per-Group
-    "random_scalar()" function, which results in some particular scalar.
-     */
-    use super::*;
-    use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
-    use hex;
-    use spake2::{Ed25519Group, SPAKE2};
-
-    // the python tests show the long-integer form of scalars. the rust code
-    // wants an array of bytes (little-endian). Make sure the way we convert
-    // things works correctly.
-
-    #[test]
-    fn test_convert() {
-        let t1_decimal =
-            b"2238329342913194256032495932344128051776374960164957527413114840482143558222";
-        let t1_scalar = decimal_to_scalar(t1_decimal);
-        let t1_bytes = t1_scalar.to_bytes();
-        let expected = [
-            0x4e, 0x5a, 0xb4, 0x34, 0x5d, 0x47, 0x08, 0x84, 0x59, 0x13, 0xb4, 0x64, 0x1b, 0xc2,
-            0x7d, 0x52, 0x52, 0xa5, 0x85, 0x10, 0x1b, 0xcc, 0x42, 0x44, 0xd4, 0x49, 0xf4, 0xa8,
-            0x79, 0xd9, 0xf2, 0x04,
-        ];
-        assert_eq!(t1_bytes, expected);
-        //println!("t1_scalar is {:?}", t1_scalar);
-    }
-
-    #[test]
-    fn test_serialize_basepoint() {
-        // make sure elements are serialized same as the python library
-        let exp = "5866666666666666666666666666666666666666666666666666666666666666";
-        let base_vec = ED25519_BASEPOINT_POINT.compress().as_bytes().to_vec();
-        let base_hex = hex::encode(base_vec);
-        println!("exp: {:?}", exp);
-        println!("got: {:?}", base_hex);
-        assert_eq!(exp, base_hex);
-    }
-
-    #[test]
-    fn test_password_to_scalar() {
-        let password = Password::new(b"password");
-        let expected_pw_scalar = decimal_to_scalar(
-            b"3515301705789368674385125653994241092664323519848410154015274772661223168839",
-        );
-        let pw_scalar = Ed25519Group::hash_to_scalar(&password);
-        println!("exp: {:?}", hex::encode(expected_pw_scalar.as_bytes()));
-        println!("got: {:?}", hex::encode(pw_scalar.as_bytes()));
-        assert_eq!(&pw_scalar, &expected_pw_scalar);
-    }
-
-    #[test]
-    fn test_sizes() {
-        let (s1, msg1) = SPAKE2::<Ed25519Group>::start_a(
-            &Password::new(b"password"),
-            &Identity::new(b"idA"),
-            &Identity::new(b"idB"),
-        );
-        assert_eq!(msg1.len(), 1 + 32);
-        let (s2, msg2) = SPAKE2::<Ed25519Group>::start_b(
-            &Password::new(b"password"),
-            &Identity::new(b"idA"),
-            &Identity::new(b"idB"),
-        );
-        assert_eq!(msg2.len(), 1 + 32);
-        let key1 = s1.finish(&msg2).unwrap();
-        let key2 = s2.finish(&msg1).unwrap();
-        assert_eq!(key1.len(), 32);
-        assert_eq!(key2.len(), 32);
-
-        let (s1, msg1) = SPAKE2::<Ed25519Group>::start_symmetric(
-            &Password::new(b"password"),
-            &Identity::new(b"idS"),
-        );
-        assert_eq!(msg1.len(), 1 + 32);
-        let (s2, msg2) = SPAKE2::<Ed25519Group>::start_symmetric(
-            &Password::new(b"password"),
-            &Identity::new(b"idS"),
-        );
-        assert_eq!(msg2.len(), 1 + 32);
-        let key1 = s1.finish(&msg2).unwrap();
-        let key2 = s2.finish(&msg1).unwrap();
-        assert_eq!(key1.len(), 32);
-        assert_eq!(key2.len(), 32);
-    }
-
-    #[test]
-    fn test_hash_ab() {
-        let key = ed25519_hash_ab(
-            b"pw",
-            b"idA",
-            b"idB",
-            b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", // len=32
-            b"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY",
-            b"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK",
-        );
-        let expected_key = "d59d9ba920f7092565cec747b08d5b2e981d553ac32fde0f25e5b4a4cfca3efd";
-        assert_eq!(hex::encode(key), expected_key);
-    }
-
-    #[test]
-    fn test_hash_symmetric() {
-        let key = ed25519_hash_symmetric(
-            b"pw",
-            b"idSymmetric",
-            b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
-            b"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY",
-            b"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK",
-        );
-        let expected_key = "b0b31e4401aae37d91a9a8bf6fbb1298cafc005ff9142e3ffc5b9799fb11128b";
-        assert_eq!(hex::encode(key), expected_key);
-    }
-
-    #[test]
-    fn test_asymmetric() {
-        let scalar_a = decimal_to_scalar(
-            b"2611694063369306139794446498317402240796898290761098242657700742213257926693",
-        );
-        let scalar_b = decimal_to_scalar(
-            b"7002393159576182977806091886122272758628412261510164356026361256515836884383",
-        );
-        let expected_pw_scalar = decimal_to_scalar(
-            b"3515301705789368674385125653994241092664323519848410154015274772661223168839",
-        );
-
-        println!("scalar_a is {}", hex::encode(scalar_a.as_bytes()));
-
-        let (s1, msg1) = SPAKE2::<Ed25519Group>::start_a_internal(
-            &Password::new(b"password"),
-            &Identity::new(b"idA"),
-            &Identity::new(b"idB"),
-            scalar_a,
-        );
-        let expected_msg1 = "416fc960df73c9cf8ed7198b0c9534e2e96a5984bfc5edc023fd24dacf371f2af9";
-
-        println!();
-        println!("xys1: {:?}", hex::encode(s1.xy_scalar.as_bytes()));
-        println!();
-        println!("pws1: {:?}", hex::encode(s1.password_scalar.as_bytes()));
-        println!("exp : {:?}", hex::encode(expected_pw_scalar.as_bytes()));
-        println!();
-        println!("msg1: {:?}", hex::encode(&msg1));
-        println!("exp : {:?}", expected_msg1);
-        println!();
-
-        assert_eq!(
-            hex::encode(expected_pw_scalar.as_bytes()),
-            hex::encode(s1.password_scalar.as_bytes())
-        );
-        assert_eq!(hex::encode(&msg1), expected_msg1);
-
-        let (s2, msg2) = SPAKE2::<Ed25519Group>::start_b_internal(
-            &Password::new(b"password"),
-            &Identity::new(b"idA"),
-            &Identity::new(b"idB"),
-            scalar_b,
-        );
-        assert_eq!(expected_pw_scalar, s2.password_scalar);
-        assert_eq!(
-            hex::encode(&msg2),
-            "42354e97b88406922b1df4bea1d7870f17aed3dba7c720b313edae315b00959309"
-        );
-
-        let key1 = s1.finish(&msg2).unwrap();
-        let key2 = s2.finish(&msg1).unwrap();
-        assert_eq!(key1, key2);
-        assert_eq!(
-            hex::encode(key1),
-            "712295de7219c675ddd31942184aa26e0a957cf216bc230d165b215047b520c1"
-        );
-    }
-
-    #[test]
-    fn test_debug() {
-        let (s1, _msg1) = SPAKE2::<Ed25519Group>::start_a(
-            &Password::new(b"password"),
-            &Identity::new(b"idA"),
-            &Identity::new(b"idB"),
-        );
-        println!("s1: {:?}", s1);
-        let (s2, _msg1) = SPAKE2::<Ed25519Group>::start_symmetric(
-            &Password::new(b"password"),
-            &Identity::new(b"idS"),
-        );
-        println!("s2: {:?}", s2);
-    }
-
-}
diff --git a/spake2/src/tests.rs b/spake2/src/tests.rs
new file mode 100644 (file)
index 0000000..b94bcdd
--- /dev/null
@@ -0,0 +1,193 @@
+//! This compares results against the python compatibility tests:
+//! spake2.test.test_compat.SPAKE2.test_asymmetric . The python test passes a
+//! deterministic RNG (used only for tests, of course) into the per-Group
+//! "random_scalar()" function, which results in some particular scalar.
+use super::*;
+use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
+use hex;
+use num_bigint::BigUint;
+
+// the python tests show the long-integer form of scalars. the rust code
+// wants an array of bytes (little-endian). Make sure the way we convert
+// things works correctly.
+fn decimal_to_scalar(d: &[u8]) -> c2_Scalar {
+    let bytes = BigUint::parse_bytes(d, 10).unwrap().to_bytes_le();
+    assert_eq!(bytes.len(), 32);
+    let mut b2 = [0u8; 32];
+    b2.copy_from_slice(&bytes);
+    c2_Scalar::from_bytes_mod_order(b2)
+}
+
+#[test]
+fn test_convert() {
+    let t1_decimal =
+        b"2238329342913194256032495932344128051776374960164957527413114840482143558222";
+    let t1_scalar = decimal_to_scalar(t1_decimal);
+    let t1_bytes = t1_scalar.to_bytes();
+    let expected = [
+        0x4e, 0x5a, 0xb4, 0x34, 0x5d, 0x47, 0x08, 0x84, 0x59, 0x13, 0xb4, 0x64, 0x1b, 0xc2, 0x7d,
+        0x52, 0x52, 0xa5, 0x85, 0x10, 0x1b, 0xcc, 0x42, 0x44, 0xd4, 0x49, 0xf4, 0xa8, 0x79, 0xd9,
+        0xf2, 0x04,
+    ];
+    assert_eq!(t1_bytes, expected);
+    //println!("t1_scalar is {:?}", t1_scalar);
+}
+
+#[test]
+fn test_serialize_basepoint() {
+    // make sure elements are serialized same as the python library
+    let exp = "5866666666666666666666666666666666666666666666666666666666666666";
+    let base_vec = ED25519_BASEPOINT_POINT.compress().as_bytes().to_vec();
+    let base_hex = hex::encode(base_vec);
+    println!("exp: {:?}", exp);
+    println!("got: {:?}", base_hex);
+    assert_eq!(exp, base_hex);
+}
+
+#[test]
+fn test_password_to_scalar() {
+    let password = Password::new(b"password");
+    let expected_pw_scalar = decimal_to_scalar(
+        b"3515301705789368674385125653994241092664323519848410154015274772661223168839",
+    );
+    let pw_scalar = Ed25519Group::hash_to_scalar(&password);
+    println!("exp: {:?}", hex::encode(expected_pw_scalar.as_bytes()));
+    println!("got: {:?}", hex::encode(pw_scalar.as_bytes()));
+    assert_eq!(&pw_scalar, &expected_pw_scalar);
+}
+
+#[test]
+fn test_sizes() {
+    let (s1, msg1) = SPAKE2::<Ed25519Group>::start_a(
+        &Password::new(b"password"),
+        &Identity::new(b"idA"),
+        &Identity::new(b"idB"),
+    );
+    assert_eq!(msg1.len(), 1 + 32);
+    let (s2, msg2) = SPAKE2::<Ed25519Group>::start_b(
+        &Password::new(b"password"),
+        &Identity::new(b"idA"),
+        &Identity::new(b"idB"),
+    );
+    assert_eq!(msg2.len(), 1 + 32);
+    let key1 = s1.finish(&msg2).unwrap();
+    let key2 = s2.finish(&msg1).unwrap();
+    assert_eq!(key1.len(), 32);
+    assert_eq!(key2.len(), 32);
+
+    let (s1, msg1) = SPAKE2::<Ed25519Group>::start_symmetric(
+        &Password::new(b"password"),
+        &Identity::new(b"idS"),
+    );
+    assert_eq!(msg1.len(), 1 + 32);
+    let (s2, msg2) = SPAKE2::<Ed25519Group>::start_symmetric(
+        &Password::new(b"password"),
+        &Identity::new(b"idS"),
+    );
+    assert_eq!(msg2.len(), 1 + 32);
+    let key1 = s1.finish(&msg2).unwrap();
+    let key2 = s2.finish(&msg1).unwrap();
+    assert_eq!(key1.len(), 32);
+    assert_eq!(key2.len(), 32);
+}
+
+#[test]
+fn test_hash_ab() {
+    let key = ed25519_hash_ab(
+        b"pw",
+        b"idA",
+        b"idB",
+        b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", // len=32
+        b"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY",
+        b"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK",
+    );
+    let expected_key = "d59d9ba920f7092565cec747b08d5b2e981d553ac32fde0f25e5b4a4cfca3efd";
+    assert_eq!(hex::encode(key), expected_key);
+}
+
+#[test]
+fn test_hash_symmetric() {
+    let key = ed25519_hash_symmetric(
+        b"pw",
+        b"idSymmetric",
+        b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+        b"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY",
+        b"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK",
+    );
+    let expected_key = "b0b31e4401aae37d91a9a8bf6fbb1298cafc005ff9142e3ffc5b9799fb11128b";
+    assert_eq!(hex::encode(key), expected_key);
+}
+
+#[test]
+fn test_asymmetric() {
+    let scalar_a = decimal_to_scalar(
+        b"2611694063369306139794446498317402240796898290761098242657700742213257926693",
+    );
+    let scalar_b = decimal_to_scalar(
+        b"7002393159576182977806091886122272758628412261510164356026361256515836884383",
+    );
+    let expected_pw_scalar = decimal_to_scalar(
+        b"3515301705789368674385125653994241092664323519848410154015274772661223168839",
+    );
+
+    println!("scalar_a is {}", hex::encode(scalar_a.as_bytes()));
+
+    let (s1, msg1) = SPAKE2::<Ed25519Group>::start_a_internal(
+        &Password::new(b"password"),
+        &Identity::new(b"idA"),
+        &Identity::new(b"idB"),
+        scalar_a,
+    );
+    let expected_msg1 = "416fc960df73c9cf8ed7198b0c9534e2e96a5984bfc5edc023fd24dacf371f2af9";
+
+    println!();
+    println!("xys1: {:?}", hex::encode(s1.xy_scalar.as_bytes()));
+    println!();
+    println!("pws1: {:?}", hex::encode(s1.password_scalar.as_bytes()));
+    println!("exp : {:?}", hex::encode(expected_pw_scalar.as_bytes()));
+    println!();
+    println!("msg1: {:?}", hex::encode(&msg1));
+    println!("exp : {:?}", expected_msg1);
+    println!();
+
+    assert_eq!(
+        hex::encode(expected_pw_scalar.as_bytes()),
+        hex::encode(s1.password_scalar.as_bytes())
+    );
+    assert_eq!(hex::encode(&msg1), expected_msg1);
+
+    let (s2, msg2) = SPAKE2::<Ed25519Group>::start_b_internal(
+        &Password::new(b"password"),
+        &Identity::new(b"idA"),
+        &Identity::new(b"idB"),
+        scalar_b,
+    );
+    assert_eq!(expected_pw_scalar, s2.password_scalar);
+    assert_eq!(
+        hex::encode(&msg2),
+        "42354e97b88406922b1df4bea1d7870f17aed3dba7c720b313edae315b00959309"
+    );
+
+    let key1 = s1.finish(&msg2).unwrap();
+    let key2 = s2.finish(&msg1).unwrap();
+    assert_eq!(key1, key2);
+    assert_eq!(
+        hex::encode(key1),
+        "712295de7219c675ddd31942184aa26e0a957cf216bc230d165b215047b520c1"
+    );
+}
+
+#[test]
+fn test_debug() {
+    let (s1, _msg1) = SPAKE2::<Ed25519Group>::start_a(
+        &Password::new(b"password"),
+        &Identity::new(b"idA"),
+        &Identity::new(b"idB"),
+    );
+    println!("s1: {:?}", s1);
+    let (s2, _msg1) = SPAKE2::<Ed25519Group>::start_symmetric(
+        &Password::new(b"password"),
+        &Identity::new(b"idS"),
+    );
+    println!("s2: {:?}", s2);
+}
diff --git a/spake2/tests/mod.rs b/spake2/tests/mod.rs
new file mode 100644 (file)
index 0000000..e85a9b6
--- /dev/null
@@ -0,0 +1,86 @@
+extern crate spake2;
+
+use spake2::{Ed25519Group, ErrorType, Identity, Password, SPAKEErr, SPAKE2};
+
+#[test]
+fn test_basic() {
+    let (s1, msg1) = SPAKE2::<Ed25519Group>::start_a(
+        &Password::new(b"password"),
+        &Identity::new(b"idA"),
+        &Identity::new(b"idB"),
+    );
+    let (s2, msg2) = SPAKE2::<Ed25519Group>::start_b(
+        &Password::new(b"password"),
+        &Identity::new(b"idA"),
+        &Identity::new(b"idB"),
+    );
+    let key1 = s1.finish(msg2.as_slice()).unwrap();
+    let key2 = s2.finish(msg1.as_slice()).unwrap();
+    assert_eq!(key1, key2);
+}
+
+#[test]
+fn test_mismatch() {
+    let (s1, msg1) = SPAKE2::<Ed25519Group>::start_a(
+        &Password::new(b"password"),
+        &Identity::new(b"idA"),
+        &Identity::new(b"idB"),
+    );
+    let (s2, msg2) = SPAKE2::<Ed25519Group>::start_b(
+        &Password::new(b"password2"),
+        &Identity::new(b"idA"),
+        &Identity::new(b"idB"),
+    );
+    let key1 = s1.finish(msg2.as_slice()).unwrap();
+    let key2 = s2.finish(msg1.as_slice()).unwrap();
+    assert_ne!(key1, key2);
+}
+
+#[test]
+fn test_reflected_message() {
+    let (s1, msg1) = SPAKE2::<Ed25519Group>::start_a(
+        &Password::new(b"password"),
+        &Identity::new(b"idA"),
+        &Identity::new(b"idB"),
+    );
+    let r = s1.finish(msg1.as_slice());
+    assert_eq!(
+        r.unwrap_err(),
+        SPAKEErr {
+            kind: ErrorType::BadSide,
+        }
+    );
+}
+
+#[test]
+fn test_bad_length() {
+    let (s1, msg1) = SPAKE2::<Ed25519Group>::start_a(
+        &Password::new(b"password"),
+        &Identity::new(b"idA"),
+        &Identity::new(b"idB"),
+    );
+    let mut msg2 = Vec::<u8>::with_capacity(msg1.len() + 1);
+    msg2.resize(msg1.len() + 1, 0u8);
+    let r = s1.finish(&msg2);
+    assert_eq!(
+        r.unwrap_err(),
+        SPAKEErr {
+            kind: ErrorType::WrongLength,
+        }
+    );
+}
+
+#[test]
+fn test_basic_symmetric() {
+    let (s1, msg1) = SPAKE2::<Ed25519Group>::start_symmetric(
+        &Password::new(b"password"),
+        &Identity::new(b"idS"),
+    );
+    let (s2, msg2) = SPAKE2::<Ed25519Group>::start_symmetric(
+        &Password::new(b"password"),
+        &Identity::new(b"idS"),
+    );
+    let key1 = s1.finish(msg2.as_slice()).unwrap();
+    let key2 = s2.finish(msg1.as_slice()).unwrap();
+    assert_eq!(key1, key2);
+}
index 6900ca0966875c06f5eb9ecbb7bea95b3f5b1871..3645a6f3a3cfc981e1e04ebad3b4a8905412965d 100644 (file)
@@ -63,8 +63,7 @@
 //!
 //! [1]: https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol
 //! [2]: https://tools.ietf.org/html/rfc5054
-#![doc(html_logo_url =
-    "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")]
+#![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")]
 extern crate digest;
 extern crate generic_array;
 extern crate num;