feat(delivery): add server-signed delivery proof on enqueue
The server now produces a 96-byte Ed25519-signed delivery proof for every enqueued message: SHA-256(seq || recipient_key || timestamp_ms) followed by the server's Ed25519 signature. Clients can verify the proof using verify_delivery_proof() in quicproquo-core to get cryptographic evidence the server accepted their message.
This commit is contained in:
@@ -7,6 +7,7 @@ use prost::Message;
|
||||
use quicproquo_proto::qpq::v1;
|
||||
use quicproquo_rpc::error::RpcStatus;
|
||||
use quicproquo_rpc::method::{HandlerResult, RequestContext};
|
||||
use sha2::{Digest, Sha256};
|
||||
use tokio::sync::Notify;
|
||||
|
||||
use crate::domain::delivery::DeliveryService;
|
||||
@@ -15,6 +16,29 @@ use crate::hooks::{HookAction, MessageEvent};
|
||||
|
||||
use super::{require_auth, ServerState};
|
||||
|
||||
/// Build a 96-byte delivery proof: `SHA-256(seq || recipient_key || timestamp_ms) || Ed25519(hash)`.
|
||||
///
|
||||
/// The sender stores this as cryptographic evidence that the server enqueued the message.
|
||||
fn build_delivery_proof(
|
||||
signing_key: &quicproquo_core::IdentityKeypair,
|
||||
seq: u64,
|
||||
recipient_key: &[u8],
|
||||
timestamp_ms: u64,
|
||||
) -> Vec<u8> {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(seq.to_le_bytes());
|
||||
hasher.update(recipient_key);
|
||||
hasher.update(timestamp_ms.to_le_bytes());
|
||||
let hash: [u8; 32] = hasher.finalize().into();
|
||||
|
||||
let sig = signing_key.sign_raw(&hash);
|
||||
|
||||
let mut proof = vec![0u8; 96];
|
||||
proof[..32].copy_from_slice(&hash);
|
||||
proof[32..].copy_from_slice(&sig);
|
||||
proof
|
||||
}
|
||||
|
||||
pub async fn handle_enqueue(state: Arc<ServerState>, ctx: RequestContext) -> HandlerResult {
|
||||
let identity_key = match require_auth(&state, &ctx) {
|
||||
Ok(ik) => ik,
|
||||
@@ -71,7 +95,7 @@ pub async fn handle_enqueue(state: Arc<ServerState>, ctx: RequestContext) -> Han
|
||||
// Fire hook.
|
||||
let action = state.hooks.on_message_enqueue(&MessageEvent {
|
||||
sender_identity: Some(identity_key),
|
||||
recipient_key: req.recipient_key,
|
||||
recipient_key: req.recipient_key.clone(),
|
||||
channel_id: req.channel_id,
|
||||
payload_len: req.payload.len(),
|
||||
seq: resp.seq,
|
||||
@@ -80,9 +104,18 @@ pub async fn handle_enqueue(state: Arc<ServerState>, ctx: RequestContext) -> Han
|
||||
return HandlerResult::err(RpcStatus::Forbidden, &reason);
|
||||
}
|
||||
|
||||
// Build server-signed delivery proof.
|
||||
let timestamp_ms = crate::auth::current_timestamp();
|
||||
let delivery_proof = build_delivery_proof(
|
||||
&state.signing_key,
|
||||
resp.seq,
|
||||
&req.recipient_key,
|
||||
timestamp_ms,
|
||||
);
|
||||
|
||||
let proto = v1::EnqueueResponse {
|
||||
seq: resp.seq,
|
||||
delivery_proof: resp.delivery_proof,
|
||||
delivery_proof,
|
||||
duplicate: false,
|
||||
};
|
||||
HandlerResult::ok(Bytes::from(proto.encode_to_vec()))
|
||||
|
||||
Reference in New Issue
Block a user