1use axum::{http::header, routing::get};
2use ed25519_dalek::{Signature, VerifyingKey};
3use sha2::{Digest, Sha256};
4
5pub const REVISION: u64 = 1;
6
7const TERMS: &str = r#"K1 TERMS OF SERVICE — 2026-08-27
8
9SIGNING THESE EXACT UTF-8 BYTES WITH YOUR K1 ACCOUNT KEY MEANS YOU ACCEPT THESE TERMS.
10
11“K1 Operator” means the person or entity operating the K1 server that provided these terms.
12
131. Eligibility and lawful use. You represent that you can enter this agreement and will use K1 only lawfully. You are responsible for your account keys, activity, and submitted data.
14
152. Data rights. To the maximum extent permitted by law, you assign to the K1 Operator all right, title, and interest in data you submit to or generate through K1. To the extent any right cannot be assigned, you grant the K1 Operator a perpetual, irrevocable, worldwide, royalty-free, transferable, sublicensable license to store, reproduce, modify, analyze, combine, publish, commercialize, create derivative works from, and otherwise use that data for any purpose.
16
173. Artificial-intelligence development. The K1 Operator may use your data, interactions, feedback, and generated material to develop, train, fine-tune, evaluate, and improve artificial-intelligence systems, models, datasets, services, and products.
18
194. No confidentiality or privacy promise. K1 is not a confidential or private service. Data may be retained indefinitely, inspected by people or machines, combined with other data, disclosed, published, lost, or compromised. Do not submit secrets, regulated information, or data you are not authorized to provide.
20
215. Generated results. Artificial-intelligence outputs may be incomplete, inaccurate, offensive, or harmful. You are responsible for reviewing outputs and for decisions or actions based on them.
22
236. Service availability. The K1 Operator may change, suspend, restrict, or discontinue any part of K1 at any time. K1 may lose data and provides no guarantee of availability, compatibility, retention, or recovery.
24
257. Disclaimer. K1 is provided “as is” and “as available,” without warranties of any kind, to the maximum extent permitted by law.
26
278. Limitation of liability. To the maximum extent permitted by law, the K1 Operator and its affiliates will not be liable for indirect, incidental, special, consequential, exemplary, or punitive damages, lost data, lost profits, or business interruption. Their aggregate liability will not exceed the amount you paid to use K1 during the twelve months preceding the claim.
28
299. Changes. If the exact text of these terms changes, continued account use may require a new signature over the replacement text."#;
30
31pub fn text() -> &'static str {
32 TERMS
33}
34
35pub fn sha256() -> [u8; 32] {
36 Sha256::digest(text().as_bytes()).into()
37}
38
39pub fn verify_acceptance(
40 public_key: &[u8; 32],
41 signature: &[u8; 64],
42) -> Result<(), AcceptanceError> {
43 let public_key =
44 VerifyingKey::from_bytes(public_key).map_err(|_| AcceptanceError::MalformedPublicKey)?;
45 let signature = Signature::from_bytes(signature);
46 public_key
47 .verify_strict(text().as_bytes(), &signature)
48 .map_err(|_| AcceptanceError::VerificationFailed)
49}
50
51pub fn endpoint() -> axum::routing::MethodRouter {
52 get(|| async {
53 (
54 [(header::CONTENT_TYPE, "text/plain; charset=utf-8")],
55 text(),
56 )
57 })
58}
59
60#[derive(Clone, Copy, Debug, Eq, PartialEq)]
61pub enum AcceptanceError {
62 MalformedPublicKey,
63 VerificationFailed,
64}
65
66impl std::fmt::Display for AcceptanceError {
67 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 let message = match self {
69 Self::MalformedPublicKey => "malformed Ed25519 public key",
70 Self::VerificationFailed => "acceptance signature verification failed",
71 };
72 formatter.write_str(message)
73 }
74}
75
76impl std::error::Error for AcceptanceError {}
77
78#[cfg(test)]
79mod tests {
80 use super::{AcceptanceError, REVISION, endpoint, sha256, text, verify_acceptance};
81 use axum::{
82 Router,
83 body::{Body, to_bytes},
84 http::{Method, Request, StatusCode, header::CONTENT_TYPE},
85 response::Response,
86 };
87 use ed25519_dalek::{Signer, SigningKey};
88 use std::{
89 hint::black_box,
90 time::{Duration, Instant},
91 };
92 use tower::ServiceExt;
93
94 const EXPECTED_LENGTH: usize = 2_490;
95 const EXPECTED_SHA256: [u8; 32] = [
96 0xa1, 0x0e, 0x62, 0x4c, 0xe2, 0x9b, 0x45, 0x94, 0x1c, 0xd9, 0x61, 0x8b, 0x9a, 0xa8, 0x3b,
97 0x53, 0x09, 0x70, 0xb9, 0x32, 0xa6, 0x97, 0x05, 0x50, 0x07, 0xde, 0x74, 0x28, 0xda, 0xc2,
98 0x6d, 0x96,
99 ];
100 const BODY_LIMIT: usize = 8 * 1024;
101 const SEED: [u8; 32] = [0x42; 32];
102
103 fn signed(message: &[u8]) -> ([u8; 32], [u8; 64]) {
104 let signing_key = SigningKey::from_bytes(&SEED);
105 let public_key = signing_key.verifying_key().to_bytes();
106 let signature = signing_key.sign(message).to_bytes();
107 (public_key, signature)
108 }
109
110 async fn request(method: Method) -> Response {
111 Router::new()
112 .route("/terms", endpoint())
113 .oneshot(
114 Request::builder()
115 .method(method)
116 .uri("/terms")
117 .body(Body::empty())
118 .unwrap(),
119 )
120 .await
121 .unwrap()
122 }
123
124 fn assert_content_type(response: &Response) {
125 assert_eq!(
126 response.headers().get(CONTENT_TYPE).unwrap(),
127 "text/plain; charset=utf-8"
128 );
129 }
130
131 #[test]
132 fn canonical_revision_bytes_and_digest_are_stable() {
133 assert_eq!(REVISION, 1);
134 assert_eq!(text().len(), EXPECTED_LENGTH);
135 assert_eq!(sha256(), EXPECTED_SHA256);
136 assert!(!text().as_bytes().ends_with(b"\n"));
137 }
138
139 #[test]
140 fn fixed_seed_signature_over_exact_text_is_valid() {
141 let (public_key, signature) = signed(text().as_bytes());
142 assert_eq!(verify_acceptance(&public_key, &signature), Ok(()));
143 }
144
145 #[test]
146 fn altered_signature_fails_without_detail() {
147 let (public_key, mut signature) = signed(text().as_bytes());
148 signature[0] ^= 1;
149 assert_eq!(
150 verify_acceptance(&public_key, &signature),
151 Err(AcceptanceError::VerificationFailed)
152 );
153 }
154
155 #[test]
156 fn trailing_lf_signature_fails_against_canonical_text() {
157 let mut wrong_message = text().as_bytes().to_vec();
158 wrong_message.push(b'\n');
159 let (public_key, signature) = signed(&wrong_message);
160 assert_eq!(
161 verify_acceptance(&public_key, &signature),
162 Err(AcceptanceError::VerificationFailed)
163 );
164 }
165
166 #[test]
167 fn changed_content_signature_fails_against_canonical_text() {
168 let mut changed_message = text().as_bytes().to_vec();
169 changed_message[0] = b'X';
170 let (public_key, signature) = signed(&changed_message);
171 assert_eq!(
172 verify_acceptance(&public_key, &signature),
173 Err(AcceptanceError::VerificationFailed)
174 );
175 }
176
177 #[test]
178 fn malformed_public_key_is_distinct() {
179 let (_, signature) = signed(text().as_bytes());
180 let mut malformed_key = [0; 32];
181 malformed_key[0] = 2;
182 assert_eq!(
183 verify_acceptance(&malformed_key, &signature),
184 Err(AcceptanceError::MalformedPublicKey)
185 );
186 }
187
188 #[tokio::test]
189 async fn get_returns_exact_text_and_content_type() {
190 let response = request(Method::GET).await;
191 assert_eq!(response.status(), StatusCode::OK);
192 assert_content_type(&response);
193 let body = to_bytes(response.into_body(), BODY_LIMIT).await.unwrap();
194 assert_eq!(body.as_ref(), text().as_bytes());
195 }
196
197 #[tokio::test]
198 async fn head_succeeds_without_body_and_keeps_content_type() {
199 let response = request(Method::HEAD).await;
200 assert_eq!(response.status(), StatusCode::OK);
201 assert_content_type(&response);
202 let body = to_bytes(response.into_body(), BODY_LIMIT).await.unwrap();
203 assert!(body.is_empty());
204 }
205
206 #[test]
207 fn broad_managed_linux_performance_canary() {
208 let (public_key, signature) = signed(text().as_bytes());
209 let started = Instant::now();
210 for _ in 0..1_000 {
211 black_box(sha256());
212 }
213 for _ in 0..16 {
214 black_box(verify_acceptance(&public_key, &signature)).unwrap();
215 }
216 assert!(started.elapsed() < Duration::from_secs(30));
217 }
218}