]> git.lizzy.rs Git - PAKEs.git/commitdiff
Add 'spake2/' from commit 'f6e9d07dffa9a9b39f203c23043f93337ebe1ab6'
authorBrian Warner <warner@lothar.com>
Tue, 16 Oct 2018 00:13:55 +0000 (17:13 -0700)
committerBrian Warner <warner@lothar.com>
Tue, 16 Oct 2018 00:19:56 +0000 (17:19 -0700)
git-subtree-dir: spake2
git-subtree-mainline: 1afa40d15b0c2cecc989bdfc67a5251aa5cd5954
git-subtree-split: f6e9d07dffa9a9b39f203c23043f93337ebe1ab6

This moves https://github.com/warner/spake2.rs into the "spake2/"
subdirectory of https://github.com/RustCrypto/PAKEs .

I'll move the release tags over next, then update the travis config and
READMEs.

1  2 
spake2/.gitignore
spake2/.travis.yml
spake2/Cargo.toml
spake2/LICENSE
spake2/README.md
spake2/benches/spake2.rs
spake2/src/lib.rs
spake2/src/spake2.rs

index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..80061583945f891aaa4a848dcbb2d3226f11af62
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,11 @@@
++# Generated by Cargo
++# will have compiled files and executables
++/target/
++
++# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
++# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
++Cargo.lock
++
++# These are backup files generated by rustfmt
++**/*.rs.bk
++/cobertura.xml
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..3998753caf31bbbf40a3915a92c38f7085fc76b1
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,40 @@@
++language: rust
++
++rust:
++  - stable
++  - beta
++  - nightly
++
++os:
++  - linux
++  - osx
++
++branches:
++  except:
++    - /^WIP-.*$/
++
++matrix:
++  include:
++    - rust: 1.26.0 # lock down for consistent rustfmt behavior
++      env: RUSTFMT
++      install:
++        - rustup component add rustfmt-preview
++      script:
++        - cargo fmt -- --write-mode=diff
++  allow_failures:
++    - rust: nightly
++
++script:
++  - cargo clean
++  - cargo build --verbose --all
++#  - cargo run --verbose --example XYZ
++#  - cargo test --verbose --features "test" --all
++  - cargo test --verbose --all
++
++after_success: |
++  if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then
++    bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
++    # Uncomment the following two lines create and upload a report for codecov.io
++    cargo tarpaulin --out Xml
++    bash <(curl -s https://codecov.io/bash)
++  fi
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8736f35804934031e3c470501297a2edfd41cc3c
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,33 @@@
++[package]
++name = "spake2"
++version = "0.1.1-alpha.0"
++authors = ["Brian Warner <warner@lothar.com>"]
++description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust."
++documentation = "https://docs.rs/spake2"
++homepage = "https://github.com/warner/spake2.rs"
++repository = "https://github.com/warner/spake2.rs"
++license = "MIT"
++categories = ["cryptography"]
++exclude = [
++    ".gitignore"
++]
++
++[badges]
++travis-ci = { repository = "warner/spake2.rs" }
++is-it-maintained-issue-resolution = { repository = "warner/spake2.rs" }
++is-it-maintained-open-issues = { repository = "warner/spake2.rs" }
++
++[dependencies]
++curve25519-dalek = "0.19"
++rand = "0.5"
++sha2 = "0.7"
++hkdf = "0.6"
++num-bigint = "0.2"
++hex = "0.3"
++
++[dev-dependencies]
++bencher = "0.1"
++
++[[bench]]
++name = "spake2"
++harness = false
diff --cc spake2/LICENSE
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..59531257a55bd83988e4f957045125cd3ed7ac6d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,21 @@@
++MIT License
++
++Copyright (c) 2017 Brian Warner
++
++Permission is hereby granted, free of charge, to any person obtaining a copy
++of this software and associated documentation files (the "Software"), to deal
++in the Software without restriction, including without limitation the rights
++to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++copies of the Software, and to permit persons to whom the Software is
++furnished to do so, subject to the following conditions:
++
++The above copyright notice and this permission notice shall be included in all
++copies or substantial portions of the Software.
++
++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++SOFTWARE.
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..0d32749682af772b044bcfc71fe9defa0eb7a335
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,29 @@@
++# spake2.rs
++The SPAKE2 password-authenticated key-exchange algorithm, in Rust.
++
++[![Build Status][build-status-image]][build-status-url]
++[![Codecov][codecov-image]][codecov-url]
++[![Is-It-Maintained-Resolution-Time][iim-resolution-image]][iim-resolution-url]
++[![Is-It-Maintained-Open-Issues][iim-open-image]][iim-open-url]
++[![Crates.io][crates-io-image]][crates-io-url]
++[![Docs.rs][docs-image]][docs-url]
++[![License][license-image]][license-url]
++
++This is still pretty early, but seems to do the job. It needs a proper security review before you should consider using it for anything serious.
++
++Note that the API has changed since 0.0.8 . I released 0.0.9 by mistake.
++
++[build-status-image]: https://travis-ci.org/warner/spake2.rs.svg?branch=master
++[build-status-url]: https://travis-ci.org/warner/spake2.rs
++[codecov-image]: https://codecov.io/gh/warner/spake2.rs/branch/master/graph/badge.svg
++[codecov-url]: https://codecov.io/gh/warner/spake2.rs
++[crates-io-image]: https://img.shields.io/crates/v/spake2.svg
++[crates-io-url]: https://crates.io/crates/spake2
++[docs-image]: https://docs.rs/spake2/badge.svg
++[docs-url]: https://docs.rs/spake2
++[license-image]: https://img.shields.io/badge/License-MIT-blue.svg
++[license-url]: LICENSE
++[iim-resolution-image]: http://isitmaintained.com/badge/resolution/warner/spake2.rs.svg
++[iim-resolution-url]: http://isitmaintained.com/project/warner/spake2.rs
++[iim-open-image]: http://isitmaintained.com/badge/open/warner/spake2.rs.svg
++[iim-open-url]: http://isitmaintained.com/project/warner/spake2.rs
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8323342a758460db419d01f9a74559764fd4130d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,60 @@@
++#[macro_use]
++extern crate bencher;
++
++extern crate spake2;
++
++use bencher::Bencher;
++use spake2::{Ed25519Group, Identity, Password, SPAKE2};
++
++fn spake2_start(bench: &mut Bencher) {
++    bench.iter(|| {
++        let (_, _) = SPAKE2::<Ed25519Group>::start_a(
++            &Password::new(b"password"),
++            &Identity::new(b"idA"),
++            &Identity::new(b"idB"),
++        );
++    })
++}
++
++/*
++fn spake2_finish(bench: &mut Bencher) {
++    // this doesn't work, because s1 is consumed by doing finish()
++    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 msg2_slice = msg2.as_slice();
++    bench.iter(|| s1.finish(msg2_slice))
++}
++*/
++
++fn spake2_start_and_finish(bench: &mut Bencher) {
++    let (_, msg2) = SPAKE2::<Ed25519Group>::start_b(
++        &Password::new(b"password"),
++        &Identity::new(b"idA"),
++        &Identity::new(b"idB"),
++    );
++    let msg2_slice = msg2.as_slice();
++    bench.iter(|| {
++        let (s1, _) = SPAKE2::<Ed25519Group>::start_a(
++            &Password::new(b"password"),
++            &Identity::new(b"idA"),
++            &Identity::new(b"idB"),
++        );
++        s1.finish(msg2_slice)
++    })
++}
++
++benchmark_group!(
++    benches,
++    spake2_start,
++    //spake2_finish,
++    spake2_start_and_finish
++);
++benchmark_main!(benches);
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..c2858ba5b155ab902a71c73ffafc239eeba6e2cd
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,109 @@@
++#![forbid(unsafe_code)]
++#![cfg_attr(test, deny(warnings))]
++
++extern crate curve25519_dalek;
++extern crate hex;
++extern crate hkdf;
++extern crate num_bigint;
++extern crate rand;
++extern crate sha2;
++
++mod spake2;
++pub use spake2::*;
++
++#[cfg(test)]
++mod tests {
++    use spake2::{Ed25519Group, ErrorType, Identity, Password, SPAKE2, SPAKEErr};
++
++    #[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);
++    }
++
++    #[test]
++    fn it_works() {}
++
++    #[test]
++    #[should_panic(expected = "nope")]
++    fn it_panics() {
++        assert!(false, "nope");
++    }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a258c4c86bc10cf2a840f518cace85f65727bcfb
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,741 @@@
++#![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);
++    }
++
++}