1use crate::storage::blockchain::{
28 compute_block_hash, verify_chain, Block, SignedFields, VerifyReport, GENESIS_PREV_HASH,
29};
30use crate::storage::schema::Value;
31use crate::storage::signed_writes::{
32 reverify_row, RESERVED_SIGNATURE_COL, RESERVED_SIGNER_PUBKEY_COL, SIGNATURE_LEN,
33 SIGNER_PUBKEY_LEN,
34};
35
36use super::blockchain_kind::{
37 COL_BLOCK_HEIGHT, COL_HASH, COL_PREV_HASH, COL_TIMESTAMP,
38};
39
40pub const GENESIS_SIGNER_PUBKEY: [u8; SIGNER_PUBKEY_LEN] = [0u8; SIGNER_PUBKEY_LEN];
44
45pub const GENESIS_SIGNATURE: [u8; SIGNATURE_LEN] = [0u8; SIGNATURE_LEN];
48
49pub const RESERVED_COLUMNS_SIGNED_CHAIN: &[&str] = &[
53 COL_BLOCK_HEIGHT,
54 COL_PREV_HASH,
55 COL_TIMESTAMP,
56 COL_HASH,
57 RESERVED_SIGNER_PUBKEY_COL,
58 RESERVED_SIGNATURE_COL,
59];
60
61pub fn is_genesis_signed_marker(pubkey: &[u8; SIGNER_PUBKEY_LEN], signature: &[u8]) -> bool {
65 pubkey == &GENESIS_SIGNER_PUBKEY && signature.iter().all(|b| *b == 0)
66}
67
68pub fn make_signed_block_reserved_fields(
81 prev_hash: [u8; 32],
82 height: u64,
83 timestamp_ms: u64,
84 payload_canonical: &[u8],
85 signer_pubkey: [u8; SIGNER_PUBKEY_LEN],
86 signature: Vec<u8>,
87) -> (Vec<(String, Value)>, [u8; 32]) {
88 let signed = SignedFields {
89 signer_pubkey,
90 signature: signature.clone(),
91 };
92 let hash = compute_block_hash(
93 &prev_hash,
94 height,
95 timestamp_ms,
96 payload_canonical,
97 Some(&signed),
98 );
99 let fields = vec![
100 (COL_BLOCK_HEIGHT.to_string(), Value::UnsignedInteger(height)),
101 (COL_PREV_HASH.to_string(), Value::Blob(prev_hash.to_vec())),
102 (
103 COL_TIMESTAMP.to_string(),
104 Value::UnsignedInteger(timestamp_ms),
105 ),
106 (
107 RESERVED_SIGNER_PUBKEY_COL.to_string(),
108 Value::Blob(signer_pubkey.to_vec()),
109 ),
110 (
111 RESERVED_SIGNATURE_COL.to_string(),
112 Value::Blob(signature),
113 ),
114 (COL_HASH.to_string(), Value::Blob(hash.to_vec())),
115 ];
116 (fields, hash)
117}
118
119pub fn genesis_signed_fields(timestamp_ms: u64) -> Vec<(String, Value)> {
123 make_signed_block_reserved_fields(
124 GENESIS_PREV_HASH,
125 0,
126 timestamp_ms,
127 &[],
128 GENESIS_SIGNER_PUBKEY,
129 GENESIS_SIGNATURE.to_vec(),
130 )
131 .0
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct SignedChainVerifyOutcome {
140 pub checked: u64,
141 pub ok: bool,
142 pub first_bad_height: Option<u64>,
143 pub signature_failure: bool,
146}
147
148impl SignedChainVerifyOutcome {
149 pub fn ok(checked: u64) -> Self {
150 Self {
151 checked,
152 ok: true,
153 first_bad_height: None,
154 signature_failure: false,
155 }
156 }
157}
158
159pub fn verify_chain_with_signatures(blocks: &[Block]) -> SignedChainVerifyOutcome {
176 let checked = blocks.len() as u64;
177 match verify_chain(blocks) {
178 VerifyReport::Inconsistent { block_height, .. } => SignedChainVerifyOutcome {
179 checked,
180 ok: false,
181 first_bad_height: Some(block_height),
182 signature_failure: false,
183 },
184 VerifyReport::Ok => {
185 for block in blocks {
186 let Some(signed) = &block.signed else {
187 continue;
191 };
192 if block.block_height == 0
193 && is_genesis_signed_marker(&signed.signer_pubkey, &signed.signature)
194 {
195 continue;
196 }
197 if signed.signature.len() != SIGNATURE_LEN {
198 return SignedChainVerifyOutcome {
199 checked,
200 ok: false,
201 first_bad_height: Some(block.block_height),
202 signature_failure: true,
203 };
204 }
205 let mut sig_arr = [0u8; SIGNATURE_LEN];
206 sig_arr.copy_from_slice(&signed.signature);
207 if reverify_row(&signed.signer_pubkey, &sig_arr, &block.payload).is_err() {
208 return SignedChainVerifyOutcome {
209 checked,
210 ok: false,
211 first_bad_height: Some(block.block_height),
212 signature_failure: true,
213 };
214 }
215 }
216 SignedChainVerifyOutcome::ok(checked)
217 }
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use crate::storage::signed_writes::{
225 verify_insert, InsertSignatureFields, SignedWriteError, SignerRegistry,
226 };
227 use ed25519_dalek::{Signer, SigningKey};
228
229 fn signing_key(seed: u8) -> SigningKey {
230 SigningKey::from_bytes(&[seed; 32])
231 }
232
233 fn pubkey_of(sk: &SigningKey) -> [u8; SIGNER_PUBKEY_LEN] {
234 sk.verifying_key().to_bytes()
235 }
236
237 fn build_signed_chain<const N: usize>(sk: &SigningKey, payloads: [&[u8]; N]) -> Vec<Block> {
239 let mut out: Vec<Block> = Vec::new();
240 let mut prev = GENESIS_PREV_HASH;
241 let g_hash = compute_block_hash(
243 &prev,
244 0,
245 1_000,
246 &[],
247 Some(&SignedFields {
248 signer_pubkey: GENESIS_SIGNER_PUBKEY,
249 signature: GENESIS_SIGNATURE.to_vec(),
250 }),
251 );
252 out.push(Block {
253 block_height: 0,
254 prev_hash: prev,
255 timestamp_ms: 1_000,
256 payload: Vec::new(),
257 signed: Some(SignedFields {
258 signer_pubkey: GENESIS_SIGNER_PUBKEY,
259 signature: GENESIS_SIGNATURE.to_vec(),
260 }),
261 hash: g_hash,
262 });
263 prev = g_hash;
264 let pk = pubkey_of(sk);
265 for (i, &payload) in payloads.iter().enumerate() {
266 let height = (i + 1) as u64;
267 let ts = 1_000 + height;
268 let sig = sk.sign(payload).to_bytes();
269 let signed = SignedFields {
270 signer_pubkey: pk,
271 signature: sig.to_vec(),
272 };
273 let hash = compute_block_hash(&prev, height, ts, payload, Some(&signed));
274 out.push(Block {
275 block_height: height,
276 prev_hash: prev,
277 timestamp_ms: ts,
278 payload: payload.to_vec(),
279 signed: Some(signed),
280 hash,
281 });
282 prev = hash;
283 }
284 out
285 }
286
287 #[test]
288 fn reserved_columns_signed_chain_is_union() {
289 assert_eq!(RESERVED_COLUMNS_SIGNED_CHAIN.len(), 6);
291 for col in [
292 COL_BLOCK_HEIGHT,
293 COL_PREV_HASH,
294 COL_TIMESTAMP,
295 COL_HASH,
296 RESERVED_SIGNER_PUBKEY_COL,
297 RESERVED_SIGNATURE_COL,
298 ] {
299 assert!(
300 RESERVED_COLUMNS_SIGNED_CHAIN.contains(&col),
301 "missing reserved column {col}"
302 );
303 }
304 }
305
306 #[test]
307 fn genesis_uses_null_pubkey_and_signature() {
308 let fields = genesis_signed_fields(1_700_000_000_000);
311 let pk = fields
312 .iter()
313 .find(|(k, _)| k == RESERVED_SIGNER_PUBKEY_COL)
314 .unwrap();
315 match &pk.1 {
316 Value::Blob(b) => assert_eq!(&b[..], &GENESIS_SIGNER_PUBKEY[..]),
317 other => panic!("signer_pubkey must be Blob, got {other:?}"),
318 }
319 let sig = fields
320 .iter()
321 .find(|(k, _)| k == RESERVED_SIGNATURE_COL)
322 .unwrap();
323 match &sig.1 {
324 Value::Blob(b) => {
325 assert_eq!(b.len(), SIGNATURE_LEN);
326 assert!(b.iter().all(|x| *x == 0));
327 }
328 other => panic!("signature must be Blob, got {other:?}"),
329 }
330 let height = fields
331 .iter()
332 .find(|(k, _)| k == COL_BLOCK_HEIGHT)
333 .unwrap();
334 assert_eq!(height.1, Value::UnsignedInteger(0));
335 }
336
337 #[test]
338 fn hash_binds_signer_pubkey_and_signature() {
339 let sk = signing_key(7);
341 let pk = pubkey_of(&sk);
342 let payload = b"row=a;";
343 let sig = sk.sign(payload).to_bytes().to_vec();
344 let (_fields, hash_with_sig) =
345 make_signed_block_reserved_fields(GENESIS_PREV_HASH, 1, 42, payload, pk, sig.clone());
346 let mut sig_tampered = sig.clone();
348 sig_tampered[0] ^= 0x01;
349 let (_f2, hash_tampered) = make_signed_block_reserved_fields(
350 GENESIS_PREV_HASH,
351 1,
352 42,
353 payload,
354 pk,
355 sig_tampered,
356 );
357 assert_ne!(hash_with_sig, hash_tampered);
358 let mut pk_tampered = pk;
360 pk_tampered[0] ^= 0x01;
361 let (_f3, hash_pk_tampered) = make_signed_block_reserved_fields(
362 GENESIS_PREV_HASH,
363 1,
364 42,
365 payload,
366 pk_tampered,
367 sig,
368 );
369 assert_ne!(hash_with_sig, hash_pk_tampered);
370 }
371
372 #[test]
373 fn valid_signed_chain_verifies_ok() {
374 let sk = signing_key(3);
375 let chain = build_signed_chain(&sk, [b"a".as_slice(), b"b".as_slice(), b"c".as_slice()]);
376 let out = verify_chain_with_signatures(&chain);
377 assert!(out.ok, "{out:?}");
378 assert_eq!(out.checked, 4);
379 assert!(out.first_bad_height.is_none());
380 }
381
382 #[test]
383 fn tampering_signer_pubkey_fails_at_block_height() {
384 let sk = signing_key(4);
387 let mut chain = build_signed_chain(&sk, [b"a".as_slice(), b"b".as_slice(), b"c".as_slice()]);
388 if let Some(signed) = chain[2].signed.as_mut() {
391 signed.signer_pubkey[0] ^= 0x55;
392 }
393 let out = verify_chain_with_signatures(&chain);
394 assert!(!out.ok);
395 assert_eq!(out.first_bad_height, Some(2));
396 }
397
398 #[test]
399 fn tampering_signature_with_recomputed_hash_caught_by_sig_reverify() {
400 let sk = signing_key(5);
404 let attacker = signing_key(6);
405 let mut chain = build_signed_chain(&sk, [b"a".as_slice(), b"b".as_slice(), b"c".as_slice()]);
406 let target = &mut chain[2];
411 let bad_sig = attacker.sign(&target.payload).to_bytes().to_vec();
412 target.signed = Some(SignedFields {
413 signer_pubkey: pubkey_of(&sk),
414 signature: bad_sig,
415 });
416 let recomputed = compute_block_hash(
419 &target.prev_hash,
420 target.block_height,
421 target.timestamp_ms,
422 &target.payload,
423 target.signed.as_ref(),
424 );
425 target.hash = recomputed;
426 let mut prev = recomputed;
429 for i in 3..chain.len() {
430 chain[i].prev_hash = prev;
431 chain[i].hash = compute_block_hash(
432 &chain[i].prev_hash,
433 chain[i].block_height,
434 chain[i].timestamp_ms,
435 &chain[i].payload,
436 chain[i].signed.as_ref(),
437 );
438 prev = chain[i].hash;
439 }
440 let out = verify_chain_with_signatures(&chain);
441 assert!(!out.ok);
442 assert_eq!(out.first_bad_height, Some(2));
443 assert!(
444 out.signature_failure,
445 "expected signature_failure, got {out:?}"
446 );
447 }
448
449 #[test]
450 fn composition_chain_fail_then_sig_fail_atomic_reject() {
451 let sk = signing_key(8);
462 let pk = pubkey_of(&sk);
463 let payload = b"payload";
464 let sig = sk.sign(payload).to_bytes();
465 let registry = SignerRegistry::from_initial(&[pk], "@system", 0);
466
467 let attacker = signing_key(9);
469 let bad_sig = attacker.sign(payload).to_bytes();
470 let err = verify_insert(
471 ®istry,
472 &InsertSignatureFields {
473 signer_pubkey: Some(&pk),
474 signature: Some(&bad_sig),
475 },
476 payload,
477 )
478 .unwrap_err();
479 assert_eq!(err, SignedWriteError::InvalidSignature);
480
481 verify_insert(
483 ®istry,
484 &InsertSignatureFields {
485 signer_pubkey: Some(&pk),
486 signature: Some(&sig),
487 },
488 payload,
489 )
490 .unwrap();
491 }
492
493 #[test]
494 fn missing_signature_fields_typed_error() {
495 let registry = SignerRegistry::default();
498 let err = verify_insert(
499 ®istry,
500 &InsertSignatureFields::default(),
501 b"payload",
502 )
503 .unwrap_err();
504 match err {
505 SignedWriteError::MissingSignatureFields { fields } => {
506 assert!(fields.contains(&RESERVED_SIGNER_PUBKEY_COL));
507 assert!(fields.contains(&RESERVED_SIGNATURE_COL));
508 }
509 other => panic!("expected MissingSignatureFields, got {other:?}"),
510 }
511 }
512
513 #[test]
514 fn genesis_marker_recognised() {
515 assert!(is_genesis_signed_marker(
516 &GENESIS_SIGNER_PUBKEY,
517 &GENESIS_SIGNATURE
518 ));
519 assert!(!is_genesis_signed_marker(&[1u8; 32], &GENESIS_SIGNATURE));
520 let nonzero = [1u8; SIGNATURE_LEN];
521 assert!(!is_genesis_signed_marker(&GENESIS_SIGNER_PUBKEY, &nonzero));
522 }
523}