init: cryptographic append-only ledger
This commit is contained in:
177
crates/ledger-core/tests/receipt.rs
Normal file
177
crates/ledger-core/tests/receipt.rs
Normal file
@@ -0,0 +1,177 @@
|
||||
use base64::Engine as _;
|
||||
use ed25519_dalek::{Signer, SigningKey};
|
||||
|
||||
use ledger_core::attestation::{
|
||||
checkpoint_attestation_signing_message_v0, checkpoint_attestation_signing_message_v1,
|
||||
};
|
||||
use ledger_core::{
|
||||
CHECKPOINT_ATTESTATION_V0_FORMAT, CHECKPOINT_ATTESTATION_V1_FORMAT, CheckpointAttestation,
|
||||
CheckpointAttestationV0, CheckpointAttestationV1, EntryUnsigned, RECEIPT_V0_FORMAT,
|
||||
ReadProofV0, ReceiptV0, verify_receipt_v0,
|
||||
};
|
||||
|
||||
fn hex(bytes: &[u8]) -> String {
|
||||
bytes.iter().map(|b| format!("{:02x}", b)).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receipt_v0_verifies_with_attestation() {
|
||||
let author_key = SigningKey::from_bytes(&[7u8; 32]);
|
||||
let author_pubkey = author_key.verifying_key().to_bytes();
|
||||
|
||||
let unsigned1 = EntryUnsigned {
|
||||
prev_hash: [0u8; 32],
|
||||
ts_ms: 1,
|
||||
namespace: "law".to_string(),
|
||||
payload_cbor: vec![0x61, 0x41], // "A"
|
||||
author_pubkey,
|
||||
};
|
||||
let sig1 = author_key.sign(&unsigned1.signing_message()).to_bytes();
|
||||
let entry1 = unsigned1.to_entry(sig1);
|
||||
let h1 = entry1.hash();
|
||||
|
||||
let unsigned2 = EntryUnsigned {
|
||||
prev_hash: h1,
|
||||
ts_ms: 2,
|
||||
namespace: "law".to_string(),
|
||||
payload_cbor: vec![0x61, 0x42], // "B"
|
||||
author_pubkey,
|
||||
};
|
||||
let sig2 = author_key.sign(&unsigned2.signing_message()).to_bytes();
|
||||
let entry2 = unsigned2.to_entry(sig2);
|
||||
let h2 = entry2.hash();
|
||||
|
||||
let hashes = vec![h1, h2];
|
||||
let root = ledger_core::merkle::merkle_root(&hashes);
|
||||
let head = h2;
|
||||
let proof = ReadProofV0::from_tree(&hashes, 1).expect("build proof");
|
||||
|
||||
let witness_key = SigningKey::from_bytes(&[0x44u8; 32]);
|
||||
let witness_pubkey = witness_key.verifying_key().to_bytes();
|
||||
let ts_seen_ms = 123_456u64;
|
||||
let msg = checkpoint_attestation_signing_message_v0(&h1, 2, &root, &head, ts_seen_ms);
|
||||
let witness_sig = witness_key.sign(&msg).to_bytes();
|
||||
let att = CheckpointAttestationV0 {
|
||||
format: CHECKPOINT_ATTESTATION_V0_FORMAT.to_string(),
|
||||
ledger_genesis_hash_hex: h1,
|
||||
checkpoint_entry_count: 2,
|
||||
checkpoint_merkle_root_hex: root,
|
||||
checkpoint_head_hash_hex: head,
|
||||
ts_seen_ms,
|
||||
witness_pubkey_hex: witness_pubkey,
|
||||
witness_sig_hex: witness_sig,
|
||||
};
|
||||
|
||||
let entry_cbor = serde_cbor::to_vec(&entry2).expect("encode entry");
|
||||
let receipt = ReceiptV0 {
|
||||
format: RECEIPT_V0_FORMAT.to_string(),
|
||||
entry_cbor_b64: base64::engine::general_purpose::STANDARD_NO_PAD.encode(entry_cbor),
|
||||
entry_hash_hex: hex(&h2),
|
||||
read_proof: proof,
|
||||
attestations: vec![CheckpointAttestation::V0(att)],
|
||||
};
|
||||
|
||||
verify_receipt_v0(&receipt, true).expect("receipt verifies");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receipt_v0_verifies_with_attestation_v1() {
|
||||
let author_key = SigningKey::from_bytes(&[7u8; 32]);
|
||||
let author_pubkey = author_key.verifying_key().to_bytes();
|
||||
|
||||
let unsigned1 = EntryUnsigned {
|
||||
prev_hash: [0u8; 32],
|
||||
ts_ms: 1,
|
||||
namespace: "law".to_string(),
|
||||
payload_cbor: vec![0x61, 0x41], // "A"
|
||||
author_pubkey,
|
||||
};
|
||||
let sig1 = author_key.sign(&unsigned1.signing_message()).to_bytes();
|
||||
let entry1 = unsigned1.to_entry(sig1);
|
||||
let h1 = entry1.hash();
|
||||
|
||||
let unsigned2 = EntryUnsigned {
|
||||
prev_hash: h1,
|
||||
ts_ms: 2,
|
||||
namespace: "law".to_string(),
|
||||
payload_cbor: vec![0x61, 0x42], // "B"
|
||||
author_pubkey,
|
||||
};
|
||||
let sig2 = author_key.sign(&unsigned2.signing_message()).to_bytes();
|
||||
let entry2 = unsigned2.to_entry(sig2);
|
||||
let h2 = entry2.hash();
|
||||
|
||||
let hashes = vec![h1, h2];
|
||||
let root = ledger_core::merkle::merkle_root(&hashes);
|
||||
let head = h2;
|
||||
let proof = ReadProofV0::from_tree(&hashes, 1).expect("build proof");
|
||||
|
||||
let witness_key = SigningKey::from_bytes(&[0x44u8; 32]);
|
||||
let witness_pubkey = witness_key.verifying_key().to_bytes();
|
||||
let checkpoint_ts_ms = 100u64;
|
||||
let ts_seen_ms = 123_456u64;
|
||||
let msg = checkpoint_attestation_signing_message_v1(
|
||||
&h1,
|
||||
2,
|
||||
&root,
|
||||
&head,
|
||||
checkpoint_ts_ms,
|
||||
ts_seen_ms,
|
||||
);
|
||||
let witness_sig = witness_key.sign(&msg).to_bytes();
|
||||
let att = CheckpointAttestationV1 {
|
||||
format: CHECKPOINT_ATTESTATION_V1_FORMAT.to_string(),
|
||||
ledger_genesis_hash_hex: h1,
|
||||
checkpoint_entry_count: 2,
|
||||
checkpoint_merkle_root_hex: root,
|
||||
checkpoint_head_hash_hex: head,
|
||||
checkpoint_ts_ms,
|
||||
ts_seen_ms,
|
||||
witness_pubkey_hex: witness_pubkey,
|
||||
witness_sig_hex: witness_sig,
|
||||
};
|
||||
|
||||
let entry_cbor = serde_cbor::to_vec(&entry2).expect("encode entry");
|
||||
let receipt = ReceiptV0 {
|
||||
format: RECEIPT_V0_FORMAT.to_string(),
|
||||
entry_cbor_b64: base64::engine::general_purpose::STANDARD_NO_PAD.encode(entry_cbor),
|
||||
entry_hash_hex: hex(&h2),
|
||||
read_proof: proof,
|
||||
attestations: vec![CheckpointAttestation::V1(att)],
|
||||
};
|
||||
|
||||
verify_receipt_v0(&receipt, true).expect("receipt verifies");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receipt_v0_rejects_tampered_hash() {
|
||||
let author_key = SigningKey::from_bytes(&[7u8; 32]);
|
||||
let author_pubkey = author_key.verifying_key().to_bytes();
|
||||
|
||||
let unsigned = EntryUnsigned {
|
||||
prev_hash: [0u8; 32],
|
||||
ts_ms: 1,
|
||||
namespace: "law".to_string(),
|
||||
payload_cbor: vec![0x61, 0x41], // "A"
|
||||
author_pubkey,
|
||||
};
|
||||
let sig = author_key.sign(&unsigned.signing_message()).to_bytes();
|
||||
let entry = unsigned.to_entry(sig);
|
||||
let h = entry.hash();
|
||||
|
||||
let hashes = vec![h];
|
||||
let proof = ReadProofV0::from_tree(&hashes, 0).expect("build proof");
|
||||
|
||||
let entry_cbor = serde_cbor::to_vec(&entry).expect("encode entry");
|
||||
let mut receipt = ReceiptV0 {
|
||||
format: RECEIPT_V0_FORMAT.to_string(),
|
||||
entry_cbor_b64: base64::engine::general_purpose::STANDARD_NO_PAD.encode(entry_cbor),
|
||||
entry_hash_hex: "00".repeat(32),
|
||||
read_proof: proof,
|
||||
attestations: vec![],
|
||||
};
|
||||
assert!(verify_receipt_v0(&receipt, false).is_err());
|
||||
|
||||
receipt.entry_hash_hex = hex(&h);
|
||||
assert!(verify_receipt_v0(&receipt, false).is_ok());
|
||||
}
|
||||
Reference in New Issue
Block a user