From 58154b6deec9d73940577878bed43ea41c466079 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Wed, 11 Sep 2024 06:27:16 +0200 Subject: [PATCH] wip on ristretto group --- Cargo.lock | 74 +++++++++++++++++++++++++++++- Cargo.toml | 1 + src/encrypt.rs | 119 ++++++++++++++++++++++--------------------------- 3 files changed, 127 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a6366a3..d406e9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,6 +225,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cc" version = "1.0.98" @@ -363,6 +369,28 @@ dependencies = [ "cipher", ] +[[package]] +name = "curve25519-dalek-ng" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core", + "subtle-ng", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" @@ -662,6 +690,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rust-elgamal" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85aed98a392f04fd23d5d3f5797c0fac3a572a15a15fd90a5a6d878d309a0e46" +dependencies = [ + "curve25519-dalek-ng", + "rand_core", + "serde", +] + [[package]] name = "s2id" version = "0.3.0-alpha.1" @@ -679,6 +718,7 @@ dependencies = [ "percent-encoding", "rand", "rpassword", + "rust-elgamal", "secp256k1", "sha2", "shellexpand", @@ -704,6 +744,26 @@ dependencies = [ "cc", ] +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "sha2" version = "0.10.8" @@ -712,7 +772,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -759,6 +819,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + [[package]] name = "syn" version = "1.0.109" @@ -1042,3 +1108,9 @@ name = "windows_x86_64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 32166be..7a91afd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ percent-encoding = "2.3.1" # Cli-specific rpassword = { version = "7.3.1", optional = true } aes-gcm = { version = "0.10.3", optional = true } +rust-elgamal = "0.4.0" crossbeam-channel = { version = "0.5.12", optional = true } [features] diff --git a/src/encrypt.rs b/src/encrypt.rs index bfba19f..1a511a3 100644 --- a/src/encrypt.rs +++ b/src/encrypt.rs @@ -24,15 +24,16 @@ use std::str::FromStr; use aes_gcm::aead::{Aead, Nonce, OsRng}; use aes_gcm::{AeadCore, Aes256Gcm, KeyInit}; use amplify::confinement::{Confined, SmallOrdMap, U64 as U64MAX}; -use amplify::hex::ToHex; use amplify::Bytes32; use armor::{ArmorHeader, ArmorParseError, AsciiArmor}; -use ec25519::{edwards25519, KeyPair, Seed}; -use rand::random; +use rand::thread_rng; +use rust_elgamal::{ + Ciphertext, CompressedRistretto, DecryptionKey, EncryptionKey, RistrettoPoint, Scalar, +}; use sha2::{Digest, Sha256}; use strict_encoding::{StrictDeserialize, StrictSerialize}; -use crate::{Algo, InvalidPubkey, SsiPair, SsiPub, LIB_NAME_SSI}; +use crate::{Algo, SsiPair, SsiPub, LIB_NAME_SSI}; #[derive(Copy, Clone, Debug, Display, Error)] pub enum EncryptionError { @@ -46,8 +47,8 @@ pub enum EncryptionError { pub enum DecryptionError { #[display("the message can't be decrypted using key {0}.")] KeyMismatch(SsiPub), - #[display("invalid public key {0}.")] - InvalidPubkey(SsiPub), + #[display("non-canonical points")] + NonCanonical, #[from(aes_gcm::Error)] #[display("unable to decrypt data.")] Decrypt, @@ -68,8 +69,12 @@ impl AsRef<[u8]> for SymmetricKey { impl SymmetricKey { pub fn new() -> Self { - let key = random::<[u8; 32]>(); - Self(Bytes32::from_byte_array(key)) + Self( + RistrettoPoint::random(&mut thread_rng()) + .compress() + .to_bytes() + .into(), + ) } } @@ -121,10 +126,8 @@ impl Encrypted { let key = SymmetricKey::new(); let mut keys = bmap![]; for pk in receivers { - let (msg, c1) = pk - .encrypt_key(&key) - .map_err(|_| EncryptionError::InvalidPubkey(pk))?; - keys.insert(pk, (msg, Bytes32::from_slice_unsafe(c1.as_slice()))); + let (c1, c2) = pk.encrypt_key(&key).inner(); + keys.insert(pk, (c1.compress().to_bytes().into(), c2.compress().to_bytes().into())); } let (nonce, msg) = encrypt(source, key); Ok(Self { @@ -136,81 +139,65 @@ impl Encrypted { pub fn decrypt(&self, pair: impl Into) -> Result, DecryptionError> { let pair = pair.into(); - let (msg, c1) = self + let (c1, c2) = self .keys .iter() .find(|(pk, _)| *pk == &pair.pk) .map(|(_, secret)| secret) .ok_or(DecryptionError::KeyMismatch(pair.pk))?; - let c1 = ec25519::PublicKey::new(c1.to_byte_array()); - let key = pair - .decrypt_key(*msg, c1) - .map_err(|_| DecryptionError::InvalidPubkey(pair.pk))?; + let c1 = CompressedRistretto::from_slice(c1.as_slice()) + .decompress() + .ok_or(DecryptionError::NonCanonical)?; + let c2 = CompressedRistretto::from_slice(c2.as_slice()) + .decompress() + .ok_or(DecryptionError::NonCanonical)?; + let key = pair.decrypt_key(Ciphertext::from((c1, c2))); Ok(decrypt(self.data.as_slice(), self.nonce.into(), key)?) } } impl SsiPub { - pub fn encrypt_key( - &self, - key: &SymmetricKey, - ) -> Result<(Bytes32, ec25519::PublicKey), InvalidPubkey> { + pub fn encrypt_key(&self, key: &SymmetricKey) -> Ciphertext { match self.algo() { Algo::Ed25519 => self.encrypt_key_ed25519(key), - Algo::Bip340 | Algo::Other(_) => Err(InvalidPubkey), + Algo::Bip340 | Algo::Other(_) => { + todo!("only encryption with Ed25519 keys is currently supported") + } } } - pub fn encrypt_key_ed25519( - &self, - message: &SymmetricKey, - ) -> Result<(Bytes32, ec25519::PublicKey), InvalidPubkey> { - let pair = KeyPair::from_seed(Seed::generate()); - let y = pair.sk; - let c1 = pair.pk; - - let h = - edwards25519::GeP3::from_bytes_vartime(&self.to_byte_array()).ok_or(InvalidPubkey)?; - let s = edwards25519::ge_scalarmult(&y.seed().scalar(), &h); - - println!("Shared secret generated: {}", s.to_bytes().to_hex()); - let c2 = edwards25519::sc_mul(message.as_ref(), &s.to_bytes()); - { - let s_neg = edwards25519::sc_invert(&s.to_bytes()); - let key = edwards25519::sc_mul(&c2, &s_neg); - assert_eq!(key, message.0.to_byte_array()) - } - - // let c2 = edwards25519::ge_scalarmult(message.as_ref(), &s); - Ok((c2.into(), c1)) + pub fn encrypt_key_ed25519(&self, message: &SymmetricKey) -> Ciphertext { + let enc_key = EncryptionKey::from( + CompressedRistretto::from_slice(&self.to_byte_array()) + .decompress() + .expect("invalid pubkey"), + ); + let point = CompressedRistretto::from_slice(&message.0.to_byte_array()) + .decompress() + .expect("invalid symmetric key"); + enc_key.encrypt(point, &mut thread_rng()) } } impl SsiPair { - pub fn decrypt_key( - &self, - encrypted_message: Bytes32, - c1: ec25519::PublicKey, - ) -> Result { + pub fn decrypt_key(&self, encrypted_message: Ciphertext) -> SymmetricKey { match self.pk.algo() { - Algo::Ed25519 => self.decrypt_key_ed25519(encrypted_message, c1), - Algo::Bip340 | Algo::Other(_) => Err(InvalidPubkey), + Algo::Ed25519 => self.decrypt_key_ed25519(encrypted_message), + Algo::Bip340 | Algo::Other(_) => { + todo!("only encryption with Ed25519 keys is currently supported") + } } } - pub fn decrypt_key_ed25519( - &self, - encrypted_message: Bytes32, - c1: ec25519::PublicKey, - ) -> Result { - let c1 = edwards25519::GeP3::from_bytes_vartime(&c1).ok_or(InvalidPubkey)?; - let s = edwards25519::ge_scalarmult(&self.sk.secret_bytes(), &c1); - println!("Shared secret recovered: {}", s.to_bytes().to_hex()); - let s_neg = - edwards25519::GeP3::from_bytes_negate_vartime(&s.to_bytes()).expect("valid pubkey"); - let key = edwards25519::sc_mul(encrypted_message.as_ref(), &s_neg.to_bytes()); - // let key = edwards25519::ge_scalarmult(encrypted_message.as_ref(), &s_neg); - Ok(SymmetricKey::from(key)) + pub fn decrypt_key_ed25519(&self, encrypted_message: Ciphertext) -> SymmetricKey { + let dec_key = DecryptionKey::from( + Scalar::from_canonical_bytes(self.sk.secret_bytes()).expect("invalid secret key"), + ); + dec_key + .decrypt(encrypted_message) + .compress() + .to_bytes() + .into() } } @@ -258,8 +245,8 @@ mod test { let sk = SsiSecret::new(Algo::Ed25519, Chain::Bitcoin); let pair = SsiPair::from(sk); let key = SymmetricKey::new(); - let (encrypted, c1) = pair.pk.encrypt_key(&key).unwrap(); - let decrypted = pair.decrypt_key(encrypted, c1).unwrap(); + let encrypted = pair.pk.encrypt_key(&key); + let decrypted = pair.decrypt_key(encrypted); assert_eq!(key.0, decrypted.0); }