use axum::{http::header, routing::get};
use ed25519_dalek::{Signature, VerifyingKey};
use sha2::{Digest, Sha256};
pub const REVISION: u64 = 1;
const TERMS: &str = r#"K1 TERMS OF SERVICE — 2026-08-27
SIGNING THESE EXACT UTF-8 BYTES WITH YOUR K1 ACCOUNT KEY MEANS YOU ACCEPT THESE TERMS.
“K1 Operator” means the person or entity operating the K1 server that provided these terms.
1. 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.
2. 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.
3. 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.
4. 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.
5. 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.
6. 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.
7. Disclaimer. K1 is provided “as is” and “as available,” without warranties of any kind, to the maximum extent permitted by law.
8. 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.
9. Changes. If the exact text of these terms changes, continued account use may require a new signature over the replacement text."#;
pub fn text() -> &'static str {
TERMS
}
pub fn sha256() -> [u8; 32] {
Sha256::digest(text().as_bytes()).into()
}
pub fn verify_acceptance(
public_key: &[u8; 32],
signature: &[u8; 64],
) -> Result<(), AcceptanceError> {
let public_key =
VerifyingKey::from_bytes(public_key).map_err(|_| AcceptanceError::MalformedPublicKey)?;
let signature = Signature::from_bytes(signature);
public_key
.verify_strict(text().as_bytes(), &signature)
.map_err(|_| AcceptanceError::VerificationFailed)
}
pub fn endpoint() -> axum::routing::MethodRouter {
get(|| async {
(
[(header::CONTENT_TYPE, "text/plain; charset=utf-8")],
text(),
)
})
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AcceptanceError {
MalformedPublicKey,
VerificationFailed,
}
impl std::fmt::Display for AcceptanceError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let message = match self {
Self::MalformedPublicKey => "malformed Ed25519 public key",
Self::VerificationFailed => "acceptance signature verification failed",
};
formatter.write_str(message)
}
}
impl std::error::Error for AcceptanceError {}
#[cfg(test)]
mod tests {
use super::{AcceptanceError, REVISION, endpoint, sha256, text, verify_acceptance};
use axum::{
Router,
body::{Body, to_bytes},
http::{Method, Request, StatusCode, header::CONTENT_TYPE},
response::Response,
};
use ed25519_dalek::{Signer, SigningKey};
use std::{
hint::black_box,
time::{Duration, Instant},
};
use tower::ServiceExt;
const EXPECTED_LENGTH: usize = 2_490;
const EXPECTED_SHA256: [u8; 32] = [
0xa1, 0x0e, 0x62, 0x4c, 0xe2, 0x9b, 0x45, 0x94, 0x1c, 0xd9, 0x61, 0x8b, 0x9a, 0xa8, 0x3b,
0x53, 0x09, 0x70, 0xb9, 0x32, 0xa6, 0x97, 0x05, 0x50, 0x07, 0xde, 0x74, 0x28, 0xda, 0xc2,
0x6d, 0x96,
];
const BODY_LIMIT: usize = 8 * 1024;
const SEED: [u8; 32] = [0x42; 32];
fn signed(message: &[u8]) -> ([u8; 32], [u8; 64]) {
let signing_key = SigningKey::from_bytes(&SEED);
let public_key = signing_key.verifying_key().to_bytes();
let signature = signing_key.sign(message).to_bytes();
(public_key, signature)
}
async fn request(method: Method) -> Response {
Router::new()
.route("/terms", endpoint())
.oneshot(
Request::builder()
.method(method)
.uri("/terms")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap()
}
fn assert_content_type(response: &Response) {
assert_eq!(
response.headers().get(CONTENT_TYPE).unwrap(),
"text/plain; charset=utf-8"
);
}
#[test]
fn canonical_revision_bytes_and_digest_are_stable() {
assert_eq!(REVISION, 1);
assert_eq!(text().len(), EXPECTED_LENGTH);
assert_eq!(sha256(), EXPECTED_SHA256);
assert!(!text().as_bytes().ends_with(b"\n"));
}
#[test]
fn fixed_seed_signature_over_exact_text_is_valid() {
let (public_key, signature) = signed(text().as_bytes());
assert_eq!(verify_acceptance(&public_key, &signature), Ok(()));
}
#[test]
fn altered_signature_fails_without_detail() {
let (public_key, mut signature) = signed(text().as_bytes());
signature[0] ^= 1;
assert_eq!(
verify_acceptance(&public_key, &signature),
Err(AcceptanceError::VerificationFailed)
);
}
#[test]
fn trailing_lf_signature_fails_against_canonical_text() {
let mut wrong_message = text().as_bytes().to_vec();
wrong_message.push(b'\n');
let (public_key, signature) = signed(&wrong_message);
assert_eq!(
verify_acceptance(&public_key, &signature),
Err(AcceptanceError::VerificationFailed)
);
}
#[test]
fn changed_content_signature_fails_against_canonical_text() {
let mut changed_message = text().as_bytes().to_vec();
changed_message[0] = b'X';
let (public_key, signature) = signed(&changed_message);
assert_eq!(
verify_acceptance(&public_key, &signature),
Err(AcceptanceError::VerificationFailed)
);
}
#[test]
fn malformed_public_key_is_distinct() {
let (_, signature) = signed(text().as_bytes());
let mut malformed_key = [0; 32];
malformed_key[0] = 2;
assert_eq!(
verify_acceptance(&malformed_key, &signature),
Err(AcceptanceError::MalformedPublicKey)
);
}
#[tokio::test]
async fn get_returns_exact_text_and_content_type() {
let response = request(Method::GET).await;
assert_eq!(response.status(), StatusCode::OK);
assert_content_type(&response);
let body = to_bytes(response.into_body(), BODY_LIMIT).await.unwrap();
assert_eq!(body.as_ref(), text().as_bytes());
}
#[tokio::test]
async fn head_succeeds_without_body_and_keeps_content_type() {
let response = request(Method::HEAD).await;
assert_eq!(response.status(), StatusCode::OK);
assert_content_type(&response);
let body = to_bytes(response.into_body(), BODY_LIMIT).await.unwrap();
assert!(body.is_empty());
}
#[test]
fn broad_managed_linux_performance_canary() {
let (public_key, signature) = signed(text().as_bytes());
let started = Instant::now();
for _ in 0..1_000 {
black_box(sha256());
}
for _ in 0..16 {
black_box(verify_acceptance(&public_key, &signature)).unwrap();
}
assert!(started.elapsed() < Duration::from_secs(30));
}
}