Skip to main content

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}