Skip to main content

kcode_k1_terms/
lib.rs

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}