s3p_core/
pod.rs

1//! Proof-of-Delivery (PoD) для S³P.
2//!
3//! Узел подписывает факт доставки конкретного шард-пакета, привязанного к серии (`scid`)
4//! и к хэшу содержимого (`leaf_hash`). Получатель может верифицировать подпись и
5//! агрегировать квитанции для расчёта вознаграждения.
6
7use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
8use serde::{Deserialize, Serialize};
9use sha2::{Digest, Sha256};
10use std::time::{SystemTime, UNIX_EPOCH};
11
12/// Каноничное сообщение, которое подписываем/проверяем.
13/// Формат (v1):
14///   "s3p-pod-v1" || scid(UTF-8) || shard_index(u32 LE) || ts_unix_ms(u64 LE) || leaf_hash(32)
15fn pod_message(scid: &str, shard_index: u32, ts_unix_ms: u64, leaf_hash: [u8; 32]) -> Vec<u8> {
16    let mut m = Vec::with_capacity(7 + scid.len() + 4 + 8 + 32);
17    m.extend_from_slice(b"s3p-pod-v1");
18    m.extend_from_slice(scid.as_bytes());
19    m.extend_from_slice(&shard_index.to_le_bytes());
20    m.extend_from_slice(&ts_unix_ms.to_le_bytes());
21    m.extend_from_slice(&leaf_hash);
22    m
23}
24
25#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
26pub struct ProofOfDelivery {
27    pub version: u8,             // 1
28    pub scid: String,            // серия/объект
29    pub shard_index: u32,        // какой шард/пакет подтверждаем
30    pub ts_unix_ms: u64,         // время выдачи PoD
31    pub signer_pubkey: [u8; 32], // Ed25519 public key (raw)
32    pub sig: Vec<u8>,            // подпись Ed25519 (ровно 64 байта)
33    pub leaf_hash: [u8; 32],     // коммит к данным (хэш тела шарда/пакета)
34}
35
36impl ProofOfDelivery {
37    /// Подписать PoD. Если `ts_unix_ms` не задан, берётся `SystemTime::now()`.
38    pub fn sign(
39        sk: &SigningKey,
40        scid: &str,
41        shard_index: u32,
42        leaf_hash: [u8; 32],
43        ts_unix_ms: Option<u64>,
44    ) -> Self {
45        let ts = ts_unix_ms.unwrap_or_else(|| {
46            SystemTime::now()
47                .duration_since(UNIX_EPOCH)
48                .expect("time")
49                .as_millis() as u64
50        });
51        let msg = pod_message(scid, shard_index, ts, leaf_hash);
52        let sig: Signature = sk.sign(&msg);
53        let pk: VerifyingKey = sk.verifying_key();
54        Self {
55            version: 1,
56            scid: scid.to_string(),
57            shard_index,
58            ts_unix_ms: ts,
59            signer_pubkey: pk.to_bytes(),
60            sig: sig.to_bytes().to_vec(),
61            leaf_hash,
62        }
63    }
64
65    /// Проверка подписи (и привязки к полям).
66    pub fn verify(&self) -> bool {
67        if self.sig.len() != 64 {
68            return false;
69        }
70        let pk = match VerifyingKey::from_bytes(&self.signer_pubkey) {
71            Ok(v) => v,
72            Err(_) => return false,
73        };
74        // В ed25519-dalek v2 — парсим подпись из среза:
75        let sig = match Signature::from_slice(&self.sig) {
76            Ok(s) => s,
77            Err(_) => return false,
78        };
79        let msg = pod_message(
80            &self.scid,
81            self.shard_index,
82            self.ts_unix_ms,
83            self.leaf_hash,
84        );
85        pk.verify(&msg, &sig).is_ok()
86    }
87
88    /// Упрощённый ID самой квитанции (хэш JSON-представления).
89    pub fn pod_id(&self) -> [u8; 32] {
90        let bytes = serde_json::to_vec(self).expect("serialize");
91        let mut h = Sha256::new();
92        h.update(&bytes);
93        h.finalize().into()
94    }
95}