prikk-store 0.4.0

Prikk storage crate scaffold.
Documentation
//! Author signing boundary for node-addressed worktree authoring (DC-09 Phase 4.4a, R1).
//!
//! Authoring no longer attaches a placeholder signature. It builds the role-bound preimage via
//! [`prikk_object::Signature::signed_bytes`] over the unsigned patch object id and delegates the
//! actual signing to an injected [`AuthorSigner`]. The production implementation
//! ([`Ed25519AuthorSigner`]) produces a real Ed25519 signature through `prikk-crypto`; tests inject a
//! deterministic Ed25519 signer. There is no placeholder signer on the production Patch AUTHOR
//! signing paths; rollback drafts use this same boundary after marking their payload purpose.
//!
//! Out of scope here (later phases): trust stores, key persistence, rotation, and signature policy.

use prikk_crypto::Ed25519KeyPair;
use prikk_error::Result;
use prikk_object::{ObjectId, ObjectType, Signature, SignatureAlgorithm, SignerRole};

/// A provider that produces the detached signature bytes for an authored patch.
///
/// The authoring engine builds the role-bound preimage and calls [`AuthorSigner::sign`]; the signer
/// owns the secret key material and the key identifier recorded in the resulting [`Signature`].
pub trait AuthorSigner {
    /// The non-empty key identifier recorded in the produced signature.
    fn key_id(&self) -> &str;
    /// Produce the detached signature bytes over the role-bound `preimage`.
    fn sign(&self, preimage: &[u8]) -> Result<Vec<u8>>;
}

/// Build a role-bound AUTHOR [`Signature`] for the unsigned patch `object_id` using `signer`.
///
/// The preimage binds algorithm (`Ed25519`), object type (`Patch`), the object id, the signer role
/// (`Author`), and the signer's key id, exactly as verification reconstructs it. This is the single
/// author-signing entry point used by node-addressed worktree/commit authoring.
pub fn author_signature(signer: &impl AuthorSigner, object_id: ObjectId) -> Result<Signature> {
    let preimage = Signature::signed_bytes(
        SignatureAlgorithm::Ed25519,
        ObjectType::Patch,
        object_id,
        SignerRole::Author,
        signer.key_id(),
    );
    let signature_bytes = signer.sign(&preimage)?;
    let signature = Signature {
        algorithm: SignatureAlgorithm::Ed25519,
        key_id: signer.key_id().to_string(),
        signature_bytes,
        // Advisory only (never used as authoritative audit time); fixed for reproducible identity.
        created_at: 0,
        signer_role: SignerRole::Author,
    };
    signature.validate()?;
    Ok(signature)
}

/// Production AUTHOR signer: a real Ed25519 keypair plus a caller-provided key id.
pub struct Ed25519AuthorSigner {
    key_id: String,
    keypair: Ed25519KeyPair,
}

impl Ed25519AuthorSigner {
    /// Construct from a non-empty key id and an Ed25519 keypair.
    #[must_use]
    pub fn new(key_id: impl Into<String>, keypair: Ed25519KeyPair) -> Self {
        Self {
            key_id: key_id.into(),
            keypair,
        }
    }

    /// Construct from a non-empty key id and a 32-byte secret seed (caller-provided key material).
    #[must_use]
    pub fn from_seed(
        key_id: impl Into<String>,
        seed: &[u8; prikk_crypto::ED25519_KEY_LEN],
    ) -> Self {
        Self::new(key_id, Ed25519KeyPair::from_seed(seed))
    }

    /// The 32-byte public key, for verifying patches this signer produced.
    #[must_use]
    pub fn public_key_bytes(&self) -> [u8; prikk_crypto::ED25519_KEY_LEN] {
        self.keypair.public_key_bytes()
    }
}

impl AuthorSigner for Ed25519AuthorSigner {
    fn key_id(&self) -> &str {
        &self.key_id
    }

    fn sign(&self, preimage: &[u8]) -> Result<Vec<u8>> {
        Ok(self.keypair.sign(preimage).to_vec())
    }
}