1use axum::{http::header, routing::get};
2use ed25519_dalek::{Signature, VerifyingKey};
3use sha2::{Digest, Sha256};
4
5const TERMS: &str = r#"K1 TERMS OF SERVICE — 2026-08-27
6
7SIGNING THESE EXACT UTF-8 BYTES WITH YOUR K1 ACCOUNT KEY MEANS YOU ACCEPT THESE TERMS.
8
9“K1 Operator” means the person or entity operating the K1 server that provided these terms.
10
111. 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.
12
132. 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.
14
153. 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.
16
174. 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.
18
195. 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.
20
216. 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.
22
237. Disclaimer. K1 is provided “as is” and “as available,” without warranties of any kind, to the maximum extent permitted by law.
24
258. 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.
26
279. Changes. If the exact text of these terms changes, continued account use may require a new signature over the replacement text."#;
28
29pub fn text() -> &'static str {
30 TERMS
31}
32
33pub fn sha256() -> [u8; 32] {
34 Sha256::digest(text().as_bytes()).into()
35}
36
37pub fn verify_acceptance(
38 public_key: &[u8; 32],
39 signature: &[u8; 64],
40) -> Result<(), AcceptanceError> {
41 let public_key =
42 VerifyingKey::from_bytes(public_key).map_err(|_| AcceptanceError::MalformedPublicKey)?;
43 let signature = Signature::from_bytes(signature);
44 public_key
45 .verify_strict(text().as_bytes(), &signature)
46 .map_err(|_| AcceptanceError::VerificationFailed)
47}
48
49pub fn endpoint() -> axum::routing::MethodRouter {
50 get(|| async {
51 (
52 [(header::CONTENT_TYPE, "text/plain; charset=utf-8")],
53 text(),
54 )
55 })
56}
57
58#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub enum AcceptanceError {
60 MalformedPublicKey,
61 VerificationFailed,
62}
63
64impl std::fmt::Display for AcceptanceError {
65 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 let message = match self {
67 Self::MalformedPublicKey => "malformed Ed25519 public key",
68 Self::VerificationFailed => "acceptance signature verification failed",
69 };
70 formatter.write_str(message)
71 }
72}
73
74impl std::error::Error for AcceptanceError {}
75
76#[cfg(test)]
77mod tests {
78 use super::{AcceptanceError, endpoint, sha256, text, verify_acceptance};
79 use axum::{
80 Router,
81 body::{Body, to_bytes},
82 http::{Method, Request, StatusCode, header::CONTENT_TYPE},
83 response::Response,
84 };
85 use ed25519_dalek::{Signer, SigningKey};
86 use std::{
87 hint::black_box,
88 time::{Duration, Instant},
89 };
90 use tower::ServiceExt;
91
92 const EXPECTED_LENGTH: usize = 2_490;
93 const EXPECTED_SHA256: [u8; 32] = [
94 0xa1, 0x0e, 0x62, 0x4c, 0xe2, 0x9b, 0x45, 0x94, 0x1c, 0xd9, 0x61, 0x8b, 0x9a, 0xa8, 0x3b,
95 0x53, 0x09, 0x70, 0xb9, 0x32, 0xa6, 0x97, 0x05, 0x50, 0x07, 0xde, 0x74, 0x28, 0xda, 0xc2,
96 0x6d, 0x96,
97 ];
98 const BODY_LIMIT: usize = 8 * 1024;
99 const SEED: [u8; 32] = [0x42; 32];
100
101 fn signed(message: &[u8]) -> ([u8; 32], [u8; 64]) {
102 let signing_key = SigningKey::from_bytes(&SEED);
103 let public_key = signing_key.verifying_key().to_bytes();
104 let signature = signing_key.sign(message).to_bytes();
105 (public_key, signature)
106 }
107
108 async fn request(method: Method) -> Response {
109 Router::new()
110 .route("/terms", endpoint())
111 .oneshot(
112 Request::builder()
113 .method(method)
114 .uri("/terms")
115 .body(Body::empty())
116 .unwrap(),
117 )
118 .await
119 .unwrap()
120 }
121
122 fn assert_content_type(response: &Response) {
123 assert_eq!(
124 response.headers().get(CONTENT_TYPE).unwrap(),
125 "text/plain; charset=utf-8"
126 );
127 }
128
129 #[test]
130 fn canonical_bytes_and_digest_are_stable() {
131 assert_eq!(text().len(), EXPECTED_LENGTH);
132 assert_eq!(sha256(), EXPECTED_SHA256);
133 assert!(!text().as_bytes().ends_with(b"\n"));
134 }
135
136 #[test]
137 fn fixed_seed_signature_over_exact_text_is_valid() {
138 let (public_key, signature) = signed(text().as_bytes());
139 assert_eq!(verify_acceptance(&public_key, &signature), Ok(()));
140 }
141
142 #[test]
143 fn altered_signature_fails_without_detail() {
144 let (public_key, mut signature) = signed(text().as_bytes());
145 signature[0] ^= 1;
146 assert_eq!(
147 verify_acceptance(&public_key, &signature),
148 Err(AcceptanceError::VerificationFailed)
149 );
150 }
151
152 #[test]
153 fn trailing_lf_signature_fails_against_canonical_text() {
154 let mut wrong_message = text().as_bytes().to_vec();
155 wrong_message.push(b'\n');
156 let (public_key, signature) = signed(&wrong_message);
157 assert_eq!(
158 verify_acceptance(&public_key, &signature),
159 Err(AcceptanceError::VerificationFailed)
160 );
161 }
162
163 #[test]
164 fn changed_content_signature_fails_against_canonical_text() {
165 let mut changed_message = text().as_bytes().to_vec();
166 changed_message[0] = b'X';
167 let (public_key, signature) = signed(&changed_message);
168 assert_eq!(
169 verify_acceptance(&public_key, &signature),
170 Err(AcceptanceError::VerificationFailed)
171 );
172 }
173
174 #[test]
175 fn malformed_public_key_is_distinct() {
176 let (_, signature) = signed(text().as_bytes());
177 let mut malformed_key = [0; 32];
178 malformed_key[0] = 2;
179 assert_eq!(
180 verify_acceptance(&malformed_key, &signature),
181 Err(AcceptanceError::MalformedPublicKey)
182 );
183 }
184
185 #[tokio::test]
186 async fn get_returns_exact_text_and_content_type() {
187 let response = request(Method::GET).await;
188 assert_eq!(response.status(), StatusCode::OK);
189 assert_content_type(&response);
190 let body = to_bytes(response.into_body(), BODY_LIMIT).await.unwrap();
191 assert_eq!(body.as_ref(), text().as_bytes());
192 }
193
194 #[tokio::test]
195 async fn head_succeeds_without_body_and_keeps_content_type() {
196 let response = request(Method::HEAD).await;
197 assert_eq!(response.status(), StatusCode::OK);
198 assert_content_type(&response);
199 let body = to_bytes(response.into_body(), BODY_LIMIT).await.unwrap();
200 assert!(body.is_empty());
201 }
202
203 #[test]
204 fn broad_managed_linux_performance_canary() {
205 let (public_key, signature) = signed(text().as_bytes());
206 let started = Instant::now();
207 for _ in 0..1_000 {
208 black_box(sha256());
209 }
210 for _ in 0..16 {
211 black_box(verify_acceptance(&public_key, &signature)).unwrap();
212 }
213 assert!(started.elapsed() < Duration::from_secs(30));
214 }
215}