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
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}