mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-03-26 22:23:14 +00:00
parent
cd737ea058
commit
57fe656728
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -18,6 +18,12 @@ version = "1.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.8.3"
|
||||
@ -97,7 +103,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "chat-proto"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/logos-messaging/chat_proto#21cd52b0649c959f43eec19e1edad12451ccc382"
|
||||
source = "git+https://github.com/logos-messaging/chat_proto#44d5360c41d721a011d20ee69a75a85357b33b0e"
|
||||
dependencies = [
|
||||
"prost",
|
||||
]
|
||||
@ -137,6 +143,7 @@ dependencies = [
|
||||
"hkdf",
|
||||
"rand_core",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
"x25519-dalek",
|
||||
"xeddsa",
|
||||
"zeroize",
|
||||
@ -503,6 +510,7 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
name = "logos-chat"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"blake2",
|
||||
"chat-proto",
|
||||
"crypto",
|
||||
|
||||
@ -7,6 +7,7 @@ edition = "2024"
|
||||
crate-type = ["staticlib","dylib"]
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.22"
|
||||
blake2.workspace = true
|
||||
chat-proto = { git = "https://github.com/logos-messaging/chat_proto" }
|
||||
crypto = { path = "../crypto" }
|
||||
|
||||
@ -41,7 +41,7 @@ impl Context {
|
||||
.invite_to_private_convo(remote_bundle, content)
|
||||
.unwrap_or_else(|_| todo!("Log/Surface Error"));
|
||||
|
||||
let remote_id = Inbox::inbox_identifier_for_key(remote_bundle.installation_key);
|
||||
let remote_id = Inbox::inbox_identifier_for_key(*remote_bundle.installation_key());
|
||||
let payload_bytes = payloads
|
||||
.into_iter()
|
||||
.map(|p| p.into_envelope(remote_id.clone()))
|
||||
@ -107,8 +107,7 @@ impl Context {
|
||||
}
|
||||
|
||||
pub fn create_intro_bundle(&mut self) -> Result<Vec<u8>, ChatError> {
|
||||
let pkb = self.inbox.create_bundle();
|
||||
Ok(Introduction::from(pkb).into())
|
||||
Ok(self.inbox.create_intro_bundle().into())
|
||||
}
|
||||
|
||||
fn add_convo(&mut self, convo: Box<dyn Convo>) -> ConversationIdOwned {
|
||||
|
||||
@ -51,19 +51,14 @@ impl Inbox {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_bundle(&mut self) -> PrekeyBundle {
|
||||
pub fn create_intro_bundle(&mut self) -> Introduction {
|
||||
let ephemeral = StaticSecret::random();
|
||||
|
||||
let signed_prekey = PublicKey::from(&ephemeral);
|
||||
let ephemeral_key: PublicKey = (&ephemeral).into();
|
||||
self.ephemeral_keys
|
||||
.insert(hex::encode(signed_prekey.as_bytes()), ephemeral);
|
||||
.insert(hex::encode(ephemeral_key.as_bytes()), ephemeral);
|
||||
|
||||
PrekeyBundle {
|
||||
identity_key: self.ident.public_key(),
|
||||
signed_prekey,
|
||||
signature: [0u8; 64],
|
||||
onetime_prekey: None,
|
||||
}
|
||||
Introduction::new(self.ident.secret(), ephemeral_key, OsRng)
|
||||
}
|
||||
|
||||
pub fn invite_to_private_convo(
|
||||
@ -73,18 +68,17 @@ impl Inbox {
|
||||
) -> Result<(PrivateV1Convo, Vec<AddressedEncryptedPayload>), ChatError> {
|
||||
let mut rng = OsRng;
|
||||
|
||||
// TODO: Include signature in introduction bundle. Manaully fill for now
|
||||
let pkb = PrekeyBundle {
|
||||
identity_key: remote_bundle.installation_key,
|
||||
signed_prekey: remote_bundle.ephemeral_key,
|
||||
signature: [0u8; 64],
|
||||
identity_key: *remote_bundle.installation_key(),
|
||||
signed_prekey: *remote_bundle.ephemeral_key(),
|
||||
signature: *remote_bundle.signature(),
|
||||
onetime_prekey: None,
|
||||
};
|
||||
|
||||
let (seed_key, ephemeral_pub) =
|
||||
InboxHandshake::perform_as_initiator(self.ident.secret(), &pkb, &mut rng);
|
||||
|
||||
let mut convo = PrivateV1Convo::new_initiator(seed_key, remote_bundle.ephemeral_key);
|
||||
let mut convo = PrivateV1Convo::new_initiator(seed_key, *remote_bundle.ephemeral_key());
|
||||
|
||||
let mut payloads = convo.send_message(initial_message)?;
|
||||
|
||||
@ -99,8 +93,8 @@ impl Inbox {
|
||||
let header = proto::InboxHeaderV1 {
|
||||
initiator_static: self.ident.public_key().copy_to_bytes(),
|
||||
initiator_ephemeral: ephemeral_pub.copy_to_bytes(),
|
||||
responder_static: remote_bundle.installation_key.copy_to_bytes(),
|
||||
responder_ephemeral: remote_bundle.ephemeral_key.copy_to_bytes(),
|
||||
responder_static: remote_bundle.installation_key().copy_to_bytes(),
|
||||
responder_ephemeral: remote_bundle.ephemeral_key().copy_to_bytes(),
|
||||
};
|
||||
|
||||
let handshake = proto::InboxHandshakeV1 {
|
||||
@ -110,7 +104,7 @@ impl Inbox {
|
||||
|
||||
// Update the address field with the Inbox delivery_Address
|
||||
first_message.delivery_address =
|
||||
delivery_address_for_installation(remote_bundle.installation_key);
|
||||
delivery_address_for_installation(*remote_bundle.installation_key());
|
||||
// Update the data field with new Payload
|
||||
first_message.data = proto::EncryptedPayload {
|
||||
encryption: Some(proto::Encryption::InboxHandshake(handshake)),
|
||||
@ -244,9 +238,9 @@ mod tests {
|
||||
let raya_ident = Identity::new();
|
||||
let mut raya_inbox = Inbox::new(raya_ident.into());
|
||||
|
||||
let bundle = raya_inbox.create_bundle();
|
||||
let bundle = raya_inbox.create_intro_bundle();
|
||||
let (_, mut payloads) = saro_inbox
|
||||
.invite_to_private_convo(&bundle.into(), "hello".as_bytes())
|
||||
.invite_to_private_convo(&bundle, "hello".as_bytes())
|
||||
.unwrap();
|
||||
|
||||
let payload = payloads.remove(0);
|
||||
|
||||
@ -97,7 +97,7 @@ mod tests {
|
||||
let bob_bundle = PrekeyBundle {
|
||||
identity_key: PublicKey::from(&bob_identity),
|
||||
signed_prekey: bob_signed_prekey_pub,
|
||||
signature: [0u8; 64],
|
||||
signature: crypto::Ed25519Signature([0u8; 64]),
|
||||
onetime_prekey: None,
|
||||
};
|
||||
|
||||
|
||||
@ -1,33 +1,92 @@
|
||||
use crypto::PrekeyBundle;
|
||||
use x25519_dalek::PublicKey;
|
||||
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
|
||||
use chat_proto::logoschat::intro::IntroBundle;
|
||||
use crypto::Ed25519Signature;
|
||||
use prost::Message;
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
|
||||
use crate::errors::ChatError;
|
||||
|
||||
/// Supplies remote participants with the required keys to use Inbox protocol
|
||||
pub struct Introduction {
|
||||
pub installation_key: PublicKey,
|
||||
pub ephemeral_key: PublicKey,
|
||||
const BUNDLE_PREFIX: &str = "logos_chatintro_1_";
|
||||
|
||||
fn intro_binding_message(ephemeral: &PublicKey) -> Vec<u8> {
|
||||
let mut message = Vec::with_capacity(BUNDLE_PREFIX.len() + 32);
|
||||
message.extend_from_slice(BUNDLE_PREFIX.as_bytes());
|
||||
message.extend_from_slice(ephemeral.as_bytes());
|
||||
message
|
||||
}
|
||||
|
||||
impl From<PrekeyBundle> for Introduction {
|
||||
fn from(value: PrekeyBundle) -> Self {
|
||||
Introduction {
|
||||
installation_key: value.identity_key,
|
||||
ephemeral_key: value.signed_prekey,
|
||||
pub(crate) fn sign_intro_binding<R: RngCore + CryptoRng>(
|
||||
secret: &StaticSecret,
|
||||
ephemeral: &PublicKey,
|
||||
rng: R,
|
||||
) -> Ed25519Signature {
|
||||
let message = intro_binding_message(ephemeral);
|
||||
crypto::xeddsa_sign(secret, &message, rng)
|
||||
}
|
||||
|
||||
pub(crate) fn verify_intro_binding(
|
||||
pubkey: &PublicKey,
|
||||
ephemeral: &PublicKey,
|
||||
signature: &Ed25519Signature,
|
||||
) -> Result<(), crypto::SignatureError> {
|
||||
let message = intro_binding_message(ephemeral);
|
||||
crypto::xeddsa_verify(pubkey, &message, signature)
|
||||
}
|
||||
|
||||
/// Supplies remote participants with the required keys to use Inbox protocol
|
||||
pub struct Introduction {
|
||||
installation_key: PublicKey,
|
||||
ephemeral_key: PublicKey,
|
||||
signature: Ed25519Signature,
|
||||
}
|
||||
|
||||
impl Introduction {
|
||||
/// Create a new `Introduction` by signing the ephemeral key with the installation secret.
|
||||
pub(crate) fn new<R: RngCore + CryptoRng>(
|
||||
installation_secret: &StaticSecret,
|
||||
ephemeral_key: PublicKey,
|
||||
rng: R,
|
||||
) -> Self {
|
||||
let installation_key = installation_secret.into();
|
||||
let signature = sign_intro_binding(installation_secret, &ephemeral_key, rng);
|
||||
Self {
|
||||
installation_key,
|
||||
ephemeral_key,
|
||||
signature,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn installation_key(&self) -> &PublicKey {
|
||||
&self.installation_key
|
||||
}
|
||||
|
||||
pub fn ephemeral_key(&self) -> &PublicKey {
|
||||
&self.ephemeral_key
|
||||
}
|
||||
|
||||
pub fn signature(&self) -> &Ed25519Signature {
|
||||
&self.signature
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Introduction> for Vec<u8> {
|
||||
fn from(val: Introduction) -> Self {
|
||||
// TODO: avoid copies, via writing directly to slice
|
||||
let link = format!(
|
||||
"Bundle:{}:{}",
|
||||
hex::encode(val.installation_key.as_bytes()),
|
||||
hex::encode(val.ephemeral_key.as_bytes()),
|
||||
);
|
||||
fn from(intro: Introduction) -> Vec<u8> {
|
||||
let bundle = IntroBundle {
|
||||
installation_pubkey: prost::bytes::Bytes::copy_from_slice(
|
||||
intro.installation_key.as_bytes(),
|
||||
),
|
||||
ephemeral_pubkey: prost::bytes::Bytes::copy_from_slice(intro.ephemeral_key.as_bytes()),
|
||||
signature: prost::bytes::Bytes::copy_from_slice(intro.signature.as_ref()),
|
||||
};
|
||||
|
||||
link.into_bytes()
|
||||
let base64_encoded = URL_SAFE_NO_PAD.encode(bundle.encode_to_vec());
|
||||
|
||||
let mut result = String::with_capacity(BUNDLE_PREFIX.len() + base64_encoded.len());
|
||||
result.push_str(BUNDLE_PREFIX);
|
||||
result.push_str(&base64_encoded);
|
||||
|
||||
result.into_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,30 +94,103 @@ impl TryFrom<&[u8]> for Introduction {
|
||||
type Error = ChatError;
|
||||
|
||||
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
||||
let str_value = String::from_utf8_lossy(value);
|
||||
let parts: Vec<&str> = str_value.splitn(3, ':').collect();
|
||||
let str_value = std::str::from_utf8(value)
|
||||
.map_err(|_| ChatError::BadBundleValue("invalid UTF-8".into()))?;
|
||||
|
||||
if parts[0] != "Bundle" {
|
||||
return Err(ChatError::BadBundleValue(
|
||||
"not recognized as an introduction bundle".into(),
|
||||
));
|
||||
}
|
||||
let base64_part = str_value.strip_prefix(BUNDLE_PREFIX).ok_or_else(|| {
|
||||
ChatError::BadBundleValue("not recognized as an introduction bundle".into())
|
||||
})?;
|
||||
|
||||
let installation_bytes: [u8; 32] = hex::decode(parts[1])
|
||||
.map_err(|_| ChatError::BadParsing("installation_key"))?
|
||||
let proto_bytes = URL_SAFE_NO_PAD
|
||||
.decode(base64_part)
|
||||
.map_err(|_| ChatError::BadBundleValue("invalid base64".into()))?;
|
||||
|
||||
let bundle = IntroBundle::decode(proto_bytes.as_slice())
|
||||
.map_err(|_| ChatError::BadBundleValue("invalid protobuf".into()))?;
|
||||
|
||||
let installation_bytes: [u8; 32] = bundle
|
||||
.installation_pubkey
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.map_err(|_| ChatError::InvalidKeyLength)?;
|
||||
|
||||
let ephemeral_bytes: [u8; 32] = bundle
|
||||
.ephemeral_pubkey
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.map_err(|_| ChatError::InvalidKeyLength)?;
|
||||
|
||||
let signature_bytes: [u8; 64] = bundle
|
||||
.signature
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.map_err(|_| ChatError::BadBundleValue("invalid signature length".into()))?;
|
||||
|
||||
let installation_key = PublicKey::from(installation_bytes);
|
||||
|
||||
let ephemeral_bytes: [u8; 32] = hex::decode(parts[2])
|
||||
.map_err(|_| ChatError::BadParsing("ephemeral_key"))?
|
||||
.try_into()
|
||||
.map_err(|_| ChatError::InvalidKeyLength)?;
|
||||
let ephemeral_key = PublicKey::from(ephemeral_bytes);
|
||||
let signature = Ed25519Signature(signature_bytes);
|
||||
|
||||
verify_intro_binding(&installation_key, &ephemeral_key, &signature)
|
||||
.map_err(|_| ChatError::BadBundleValue("invalid signature".into()))?;
|
||||
|
||||
Ok(Introduction {
|
||||
installation_key,
|
||||
ephemeral_key,
|
||||
signature,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand_core::OsRng;
|
||||
|
||||
fn create_test_introduction() -> Introduction {
|
||||
let install_secret = StaticSecret::random_from_rng(OsRng);
|
||||
|
||||
let ephemeral_secret = StaticSecret::random_from_rng(OsRng);
|
||||
let ephemeral_pub: PublicKey = (&ephemeral_secret).into();
|
||||
|
||||
Introduction::new(&install_secret, ephemeral_pub, OsRng)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialization_roundtrip() {
|
||||
let intro = create_test_introduction();
|
||||
let original_install = *intro.installation_key();
|
||||
let original_ephemeral = *intro.ephemeral_key();
|
||||
let original_signature = *intro.signature();
|
||||
|
||||
let encoded: Vec<u8> = intro.into();
|
||||
let decoded = Introduction::try_from(encoded.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(*decoded.installation_key(), original_install);
|
||||
assert_eq!(*decoded.ephemeral_key(), original_ephemeral);
|
||||
assert_eq!(*decoded.signature(), original_signature);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_prefix_rejected() {
|
||||
assert!(Introduction::try_from(b"wrong_prefix_AAAA".as_slice()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_base64_rejected() {
|
||||
assert!(Introduction::try_from(b"logos_chatintro_1_!!!invalid!!!".as_slice()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_truncated_payload_rejected() {
|
||||
let intro = create_test_introduction();
|
||||
let encoded: Vec<u8> = intro.into();
|
||||
let encoded_str = String::from_utf8(encoded).unwrap();
|
||||
|
||||
let truncated = format!(
|
||||
"logos_chatintro_1_{}",
|
||||
&encoded_str[BUNDLE_PREFIX.len()..][..10]
|
||||
);
|
||||
|
||||
assert!(Introduction::try_from(truncated.as_bytes()).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,3 +12,4 @@ ed25519-dalek = "2.2.0"
|
||||
xeddsa = "1.0.2"
|
||||
zeroize = {version = "1.8.2", features= ["derive"]}
|
||||
generic-array = "1.3.5"
|
||||
thiserror = "2"
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
mod keys;
|
||||
mod x3dh;
|
||||
mod xeddsa_sign;
|
||||
|
||||
pub use keys::{GenericArray, SecretKey};
|
||||
pub use x3dh::{DomainSeparator, PrekeyBundle, X3Handshake};
|
||||
pub use xeddsa_sign::{Ed25519Signature, SignatureError, xeddsa_sign, xeddsa_verify};
|
||||
|
||||
@ -6,13 +6,14 @@ use sha2::Sha256;
|
||||
use x25519_dalek::{PublicKey, SharedSecret, StaticSecret};
|
||||
|
||||
use crate::keys::SecretKey;
|
||||
use crate::xeddsa_sign::Ed25519Signature;
|
||||
|
||||
/// A prekey bundle containing the public keys needed to initiate an X3DH key exchange.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PrekeyBundle {
|
||||
pub identity_key: PublicKey,
|
||||
pub signed_prekey: PublicKey,
|
||||
pub signature: [u8; 64],
|
||||
pub signature: Ed25519Signature,
|
||||
pub onetime_prekey: Option<PublicKey>,
|
||||
}
|
||||
|
||||
@ -151,7 +152,7 @@ mod tests {
|
||||
let bob_bundle = PrekeyBundle {
|
||||
identity_key: bob_identity_pub,
|
||||
signed_prekey: bob_signed_prekey_pub,
|
||||
signature: [0u8; 64], // Placeholder for signature
|
||||
signature: Ed25519Signature::empty(),
|
||||
onetime_prekey: Some(bob_onetime_prekey_pub),
|
||||
};
|
||||
|
||||
@ -191,7 +192,7 @@ mod tests {
|
||||
let bob_bundle = PrekeyBundle {
|
||||
identity_key: bob_identity_pub,
|
||||
signed_prekey: bob_signed_prekey_pub,
|
||||
signature: [0u8; 64], // Placeholder for signature
|
||||
signature: Ed25519Signature::empty(),
|
||||
onetime_prekey: None,
|
||||
};
|
||||
|
||||
|
||||
135
crypto/src/xeddsa_sign.rs
Normal file
135
crypto/src/xeddsa_sign.rs
Normal file
@ -0,0 +1,135 @@
|
||||
//! XEdDSA signing using X25519 keys.
|
||||
//!
|
||||
//! This module provides generic XEdDSA sign and verify functions
|
||||
//! that allow signing arbitrary messages with X25519 keys.
|
||||
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
use xeddsa::{Sign, Verify, xed25519};
|
||||
|
||||
/// A 64-byte XEdDSA signature over an Ed25519-compatible curve.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Ed25519Signature(pub [u8; 64]);
|
||||
|
||||
impl Ed25519Signature {
|
||||
pub fn empty() -> Self {
|
||||
Self([0u8; 64])
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8; 64]> for Ed25519Signature {
|
||||
fn as_ref(&self) -> &[u8; 64] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 64]> for Ed25519Signature {
|
||||
fn from(bytes: [u8; 64]) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Error type for signature verification failures.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||
#[error("signature verification failed")]
|
||||
pub struct SignatureError;
|
||||
|
||||
/// Sign a message using XEdDSA with an X25519 secret key.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `secret` - The X25519 secret key to sign with
|
||||
/// * `message` - The message to sign
|
||||
/// * `rng` - A cryptographically secure random number generator
|
||||
///
|
||||
/// # Returns
|
||||
/// An `Ed25519Signature`
|
||||
pub fn xeddsa_sign<R: RngCore + CryptoRng>(
|
||||
secret: &StaticSecret,
|
||||
message: &[u8],
|
||||
mut rng: R,
|
||||
) -> Ed25519Signature {
|
||||
let signing_key = xed25519::PrivateKey::from(secret);
|
||||
Ed25519Signature(signing_key.sign(message, &mut rng))
|
||||
}
|
||||
|
||||
/// Verify an XEdDSA signature using an X25519 public key.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `pubkey` - The X25519 public key to verify with
|
||||
/// * `message` - The message that was signed
|
||||
/// * `signature` - The 64-byte XEdDSA signature to verify
|
||||
///
|
||||
/// # Returns
|
||||
/// `Ok(())` if the signature is valid, `Err(SignatureError)` otherwise
|
||||
pub fn xeddsa_verify(
|
||||
pubkey: &PublicKey,
|
||||
message: &[u8],
|
||||
signature: &Ed25519Signature,
|
||||
) -> Result<(), SignatureError> {
|
||||
let verify_key = xed25519::PublicKey::from(pubkey);
|
||||
verify_key
|
||||
.verify(message, &signature.0)
|
||||
.map_err(|_| SignatureError)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand_core::OsRng;
|
||||
|
||||
#[test]
|
||||
fn test_sign_and_verify_roundtrip() {
|
||||
let secret = StaticSecret::random_from_rng(OsRng);
|
||||
let public = PublicKey::from(&secret);
|
||||
let message = b"test message";
|
||||
|
||||
let signature = xeddsa_sign(&secret, message, OsRng);
|
||||
|
||||
assert!(xeddsa_verify(&public, message, &signature).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrong_key_fails() {
|
||||
let secret = StaticSecret::random_from_rng(OsRng);
|
||||
let message = b"test message";
|
||||
|
||||
let signature = xeddsa_sign(&secret, message, OsRng);
|
||||
|
||||
let wrong_secret = StaticSecret::random_from_rng(OsRng);
|
||||
let wrong_public = PublicKey::from(&wrong_secret);
|
||||
|
||||
assert_eq!(
|
||||
xeddsa_verify(&wrong_public, message, &signature),
|
||||
Err(SignatureError)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrong_message_fails() {
|
||||
let secret = StaticSecret::random_from_rng(OsRng);
|
||||
let public = PublicKey::from(&secret);
|
||||
let message = b"test message";
|
||||
|
||||
let signature = xeddsa_sign(&secret, message, OsRng);
|
||||
|
||||
assert_eq!(
|
||||
xeddsa_verify(&public, b"wrong message", &signature),
|
||||
Err(SignatureError)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_corrupted_signature_fails() {
|
||||
let secret = StaticSecret::random_from_rng(OsRng);
|
||||
let public = PublicKey::from(&secret);
|
||||
let message = b"test message";
|
||||
|
||||
let mut signature = xeddsa_sign(&secret, message, OsRng);
|
||||
signature.0[0] ^= 0xFF;
|
||||
|
||||
assert_eq!(
|
||||
xeddsa_verify(&public, message, &signature),
|
||||
Err(SignatureError)
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user