1use commonware_codec::{DecodeExt, Encode};
12use commonware_cryptography::{
13 Signer as _, Verifier as _,
14 ed25519::{PrivateKey, PublicKey, Signature},
15};
16
17pub mod approval;
18pub mod canon;
19pub mod grant;
20pub mod handoff;
21pub mod hex;
22pub mod mandate;
23pub mod sensitive;
24pub mod subagent;
25pub mod tls;
26pub mod toolcall;
27
28pub const NAMESPACE: &[u8] = b"polychrome.v1";
30
31#[derive(Debug, thiserror::Error)]
35pub enum SignerError {
36 #[error("invalid ed25519 private key material: {0}")]
39 InvalidKeyMaterial(#[from] commonware_codec::Error),
40}
41
42#[derive(Clone)]
44pub struct Signer(PrivateKey);
45
46impl Signer {
47 #[must_use]
52 pub fn from_seed(seed: u64) -> Self {
53 Self(PrivateKey::from_seed(seed))
54 }
55
56 pub fn from_key_bytes(bytes: &[u8]) -> Result<Self, SignerError> {
66 Ok(Self(PrivateKey::decode(bytes)?))
67 }
68
69 #[must_use]
71 pub fn public_key_bytes(&self) -> Vec<u8> {
72 self.0.public_key().encode().to_vec()
73 }
74
75 #[must_use]
78 pub fn sign(&self, msg: &[u8]) -> Vec<u8> {
79 self.0.sign(NAMESPACE, msg).encode().to_vec()
80 }
81}
82
83#[must_use]
87pub fn verify(public_key: &[u8], msg: &[u8], sig: &[u8]) -> bool {
88 let Ok(pk) = PublicKey::decode(public_key) else {
89 return false;
90 };
91 let Ok(sig) = Signature::decode(sig) else {
92 return false;
93 };
94 pk.verify(NAMESPACE, msg, &sig)
95}
96
97#[cfg(test)]
98mod tests {
99 #![allow(clippy::pedantic, clippy::nursery, missing_docs)]
100
101 use super::*;
102
103 #[test]
104 fn sign_then_verify_round_trips() {
105 let signer = Signer::from_seed(1);
106 let pk = signer.public_key_bytes();
107 let msg = b"tool-call canonical bytes";
108 let sig = signer.sign(msg);
109 assert!(verify(&pk, msg, &sig));
110 }
111
112 #[test]
113 fn wrong_message_fails() {
114 let signer = Signer::from_seed(1);
115 let pk = signer.public_key_bytes();
116 let sig = signer.sign(b"original");
117 assert!(!verify(&pk, b"tampered", &sig));
118 }
119
120 #[test]
121 fn wrong_key_fails() {
122 let signer = Signer::from_seed(1);
123 let other = Signer::from_seed(2);
124 let msg = b"msg";
125 let sig = signer.sign(msg);
126 assert!(!verify(&other.public_key_bytes(), msg, &sig));
127 }
128
129 #[test]
130 fn garbage_inputs_return_false_not_panic() {
131 assert!(!verify(b"not-a-key", b"m", b"not-a-sig"));
132 assert!(!verify(&[], &[], &[]));
133 }
134
135 #[test]
140 fn from_key_bytes_round_trips_and_rejects_bad_length() {
141 let key_bytes = [7u8; 32];
142 let signer = Signer::from_key_bytes(&key_bytes).expect("valid 32-byte key material");
143 let pk = signer.public_key_bytes();
144 let msg = b"approval-response canonical bytes";
145 let sig = signer.sign(msg);
146 assert!(verify(&pk, msg, &sig));
147
148 assert!(Signer::from_key_bytes(&[7u8; 31]).is_err());
150 assert!(Signer::from_key_bytes(&[7u8; 33]).is_err());
151 assert!(Signer::from_key_bytes(&[]).is_err());
152 }
153}