1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//! 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>>;
/// The public key half of this signer's key material (DC-53 Stage 1). The signer is the only
/// party that holds it, so recording AUTHOR key material at authoring time -- the mechanism
/// verification depends on, since Ed25519 signatures are not public-key-recoverable -- requires
/// it from here, not from anything persisted in the `Signature` itself (`key_id` is a label, not
/// key material).
fn public_key_bytes(&self) -> [u8; prikk_crypto::ED25519_KEY_LEN];
}
/// 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()?;
signature.validate_shape()?;
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.
pub fn new(key_id: impl Into<String>, keypair: Ed25519KeyPair) -> Result<Self> {
let key_id = key_id.into();
Signature::validate_key_id(&key_id)?;
Ok(Self { key_id, keypair })
}
/// Construct from a non-empty key id and a 32-byte secret seed (caller-provided key material).
pub fn from_seed(
key_id: impl Into<String>,
seed: &[u8; prikk_crypto::ED25519_KEY_LEN],
) -> Result<Self> {
Self::new(key_id, Ed25519KeyPair::from_seed(seed))
}
}
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())
}
fn public_key_bytes(&self) -> [u8; prikk_crypto::ED25519_KEY_LEN] {
self.keypair.public_key_bytes()
}
}