vyre_sigstore/lib.rs
1#![deny(unsafe_code)]
2#![deny(missing_docs)]
3
4//! # vyre-sigstore — ed25519 signing + verification for vyre conformance certificates
5//!
6//! This crate operates on serialized certificate bytes and ed25519 keys. It has
7//! zero dependency on `vyre-conform` internals — downstream auditors can verify
8//! certificates without pulling the maintainer harness.
9//!
10//! ## Example
11//!
12//! ```ignore
13//! use vyre_sigstore::{sign, verify, SigningKey};
14//! use rand::rngs::OsRng;
15//!
16//! let key = SigningKey::generate(&mut OsRng);
17//! let cert_bytes = std::fs::read("certificate.cbor")?;
18//!
19//! let signature = sign(&cert_bytes, &key);
20//! verify(&cert_bytes, &signature, &key.verifying_key())?;
21//! # Ok::<(), Box<dyn std::error::Error>>(())
22//! ```
23
24use ed25519_dalek::{Signer, Verifier};
25use thiserror::Error;
26
27pub use ed25519_dalek::{Signature, SigningKey, VerifyingKey};
28
29/// Verification failure modes.
30#[derive(Debug, Error)]
31#[non_exhaustive]
32pub enum VerifyError {
33 /// The signature does not verify against the public key and certificate
34 /// bytes. Either the cert has been tampered, the signature is stale, or
35 /// the wrong public key was supplied.
36 #[error("Fix: signature does not match certificate + public key. Check cert bytes are unmodified and public key matches the signing key.")]
37 BadSignature,
38}
39
40/// Sign certificate bytes with an ed25519 key.
41///
42/// The returned [`Signature`] is detached — pair it with the certificate
43/// bytes and the matching [`VerifyingKey`] to verify.
44#[must_use]
45pub fn sign(cert_bytes: &[u8], key: &SigningKey) -> Signature {
46 key.sign(cert_bytes)
47}
48
49/// Verify a detached signature against certificate bytes and a public key.
50///
51/// # Errors
52/// Returns [`VerifyError::BadSignature`] if the signature does not match.
53pub fn verify(
54 cert_bytes: &[u8],
55 sig: &Signature,
56 pubkey: &VerifyingKey,
57) -> Result<(), VerifyError> {
58 pubkey
59 .verify(cert_bytes, sig)
60 .map_err(|_| VerifyError::BadSignature)
61}
62
63/// Compute the canonical blake3 digest of certificate bytes.
64///
65/// Callers sign this digest rather than raw bytes when the certificate format
66/// may evolve: the signature stays stable across cert-format migrations that
67/// preserve semantic content.
68#[must_use]
69pub fn canonical_digest(cert_bytes: &[u8]) -> [u8; 32] {
70 *blake3::hash(cert_bytes).as_bytes()
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 fn sample_key() -> SigningKey {
78 // deterministic key for tests — not for production use
79 let seed: [u8; 32] = [7u8; 32];
80 SigningKey::from_bytes(&seed)
81 }
82
83 #[test]
84 fn round_trip_signs_and_verifies() {
85 let key = sample_key();
86 let cert = b"example certificate bytes";
87 let sig = sign(cert, &key);
88 verify(cert, &sig, &key.verifying_key()).expect("Fix: signature should verify");
89 }
90
91 #[test]
92 fn tampered_cert_fails_verify() {
93 let key = sample_key();
94 let cert = b"original bytes";
95 let sig = sign(cert, &key);
96 let tampered = b"tampered bytes";
97 let err = verify(tampered, &sig, &key.verifying_key()).unwrap_err();
98 assert!(matches!(err, VerifyError::BadSignature));
99 }
100
101 #[test]
102 fn canonical_digest_is_stable() {
103 let a = canonical_digest(b"same input");
104 let b = canonical_digest(b"same input");
105 assert_eq!(a, b);
106 }
107}