1#[cfg(not(feature = "std"))]
11use alloc::{format, string::String, string::ToString, vec::Vec};
12
13use ed25519_dalek::{Signature as EdSignature, Signer as _, SigningKey, Verifier, VerifyingKey};
14use fips204::ml_dsa_65;
15use fips204::traits::{SerDes, Signer as MlSigner, Verifier as MlVerifier};
16use hmac::{Hmac, Mac};
17use sha2::{Digest, Sha256};
18
19use crate::canonical::{
20 encode_bytes_b64, encode_constraints, encode_hybrid_pub_key, encode_i32, encode_i64,
21 encode_str, encode_str_array,
22};
23use crate::types::{
24 DelegationCert, HybridPrivateKey, HybridPublicKey, HybridSignature, KeyRotationStatement,
25 ProofBundle, ReceiptPartySignature, RevocationList, RevocationPush, SessionToken,
26 TransactionReceipt, VerifyResult, WitnessEntry,
27};
28#[cfg(feature = "std")]
29use crate::types::{AgentIdentity, HumanRoot};
30
31type HmacSha256 = Hmac<Sha256>;
32
33pub fn derive_id(pub_key: &HybridPublicKey) -> String {
39 let mut hasher = Sha256::new();
40 hasher.update(&pub_key.ed25519);
41 hasher.update(&pub_key.ml_dsa_65);
42 let digest = hasher.finalize();
43 hex::encode(&digest[..16])
44}
45
46pub fn generate_hybrid_keypair() -> (HybridPublicKey, HybridPrivateKey) {
52 use rand_core::OsRng;
53 let mut seed = [0u8; 32];
54 use rand_core::RngCore;
55 OsRng.fill_bytes(&mut seed);
56 let ed_sk = SigningKey::from_bytes(&seed);
57 let ed_pk = ed_sk.verifying_key();
58
59 let (ml_pk, ml_sk) = ml_dsa_65::try_keygen().expect("ML-DSA-65 keygen");
60
61 (
62 HybridPublicKey {
63 ed25519: ed_pk.to_bytes().to_vec(),
64 ml_dsa_65: ml_pk.into_bytes().to_vec(),
65 },
66 HybridPrivateKey {
67 ed25519: seed.to_vec(),
68 ml_dsa_65: ml_sk.into_bytes().to_vec(),
69 },
70 )
71}
72
73#[cfg(feature = "std")]
79pub fn generate_human_root() -> (HumanRoot, HybridPrivateKey) {
80 let (pub_key, priv_key) = generate_hybrid_keypair();
81 let id = derive_id(&pub_key);
82 (
83 HumanRoot {
84 id,
85 public_key: pub_key,
86 created_at: now_unix(),
87 anchors: None,
88 },
89 priv_key,
90 )
91}
92
93#[cfg(feature = "std")]
99pub fn generate_agent(name: &str, agent_type: &str) -> (AgentIdentity, HybridPrivateKey) {
100 let (pub_key, priv_key) = generate_hybrid_keypair();
101 let id = derive_id(&pub_key);
102 (
103 AgentIdentity {
104 id,
105 public_key: pub_key,
106 name: name.to_string(),
107 agent_type: agent_type.to_string(),
108 created_at: now_unix(),
109 },
110 priv_key,
111 )
112}
113
114#[cfg(feature = "std")]
116fn now_unix() -> i64 {
117 use std::time::{SystemTime, UNIX_EPOCH};
118 SystemTime::now()
119 .duration_since(UNIX_EPOCH)
120 .unwrap_or_default()
121 .as_secs() as i64
122}
123
124pub fn delegation_sign_bytes(cert: &DelegationCert) -> Vec<u8> {
135 let mut out = String::new();
136 out.push('{');
137 out.push_str("\"cert_id\":"); encode_str(&cert.cert_id, &mut out);
138 out.push_str(",\"constraints\":"); encode_constraints(&cert.constraints, &mut out);
139 out.push_str(",\"expires_at\":"); encode_i64(cert.expires_at, &mut out);
140 out.push_str(",\"issued_at\":"); encode_i64(cert.issued_at, &mut out);
141 out.push_str(",\"issuer_id\":"); encode_str(&cert.issuer_id, &mut out);
142 out.push_str(",\"issuer_pub_key\":"); encode_hybrid_pub_key(&cert.issuer_pub_key, &mut out);
143 out.push_str(",\"scope\":"); encode_str_array(&cert.scope, &mut out);
144 out.push_str(",\"subject_id\":"); encode_str(&cert.subject_id, &mut out);
145 out.push_str(",\"subject_pub_key\":"); encode_hybrid_pub_key(&cert.subject_pub_key, &mut out);
146 out.push_str(",\"version\":"); encode_i32(cert.version, &mut out);
147 out.push('}');
148 out.into_bytes()
149}
150
151pub fn challenge_sign_bytes(challenge: &[u8], ts: i64) -> Vec<u8> {
155 challenge_sign_bytes_with_stream(challenge, ts, &[], &[], 0)
156}
157
158pub fn challenge_sign_bytes_with_session_context(
161 challenge: &[u8],
162 ts: i64,
163 session_context: &[u8],
164) -> Vec<u8> {
165 challenge_sign_bytes_with_stream(challenge, ts, session_context, &[], 0)
166}
167
168pub fn challenge_sign_bytes_with_stream(
175 challenge: &[u8],
176 ts: i64,
177 session_context: &[u8],
178 stream_id: &[u8],
179 stream_seq: i64,
180) -> Vec<u8> {
181 let stream_len = if stream_id.is_empty() {
182 0
183 } else {
184 stream_id.len() + 8
185 };
186 let mut out = Vec::with_capacity(challenge.len() + 8 + session_context.len() + stream_len);
187 out.extend_from_slice(challenge);
188 out.extend_from_slice(&(ts as u64).to_be_bytes());
189 out.extend_from_slice(session_context);
190 if !stream_id.is_empty() {
191 out.extend_from_slice(stream_id);
192 out.extend_from_slice(&(stream_seq as u64).to_be_bytes());
193 }
194 out
195}
196
197pub fn revocation_sign_bytes(list: &RevocationList) -> Vec<u8> {
200 let mut out = String::new();
201 out.push('{');
202 out.push_str("\"issuer_id\":"); encode_str(&list.issuer_id, &mut out);
203 out.push_str(",\"revoked_certs\":"); encode_str_array(&list.revoked_certs, &mut out);
204 out.push_str(",\"updated_at\":"); encode_i64(list.updated_at, &mut out);
205 out.push('}');
206 out.into_bytes()
207}
208
209pub fn key_rotation_sign_bytes(stmt: &KeyRotationStatement) -> Vec<u8> {
212 let mut out = String::new();
213 out.push('{');
214 out.push_str("\"new_id\":"); encode_str(&stmt.new_id, &mut out);
215 out.push_str(",\"new_pub_key\":"); encode_hybrid_pub_key(&stmt.new_pub_key, &mut out);
216 out.push_str(",\"old_id\":"); encode_str(&stmt.old_id, &mut out);
217 out.push_str(",\"old_pub_key\":"); encode_hybrid_pub_key(&stmt.old_pub_key, &mut out);
218 out.push_str(",\"reason\":"); encode_str(&stmt.reason, &mut out);
219 out.push_str(",\"rotated_at\":"); encode_i64(stmt.rotated_at, &mut out);
220 out.push_str(",\"version\":"); encode_i32(stmt.version, &mut out);
221 out.push('}');
222 out.into_bytes()
223}
224
225pub fn sign_both(msg: &[u8], priv_key: &HybridPrivateKey) -> HybridSignature {
231 let mut ed_seed = [0u8; 32];
232 ed_seed.copy_from_slice(&priv_key.ed25519[..32]);
233 let ed_sk = SigningKey::from_bytes(&ed_seed);
234 let ed_sig = ed_sk.sign(msg);
235
236 let mut ml_sk_bytes = [0u8; ml_dsa_65::SK_LEN];
237 ml_sk_bytes.copy_from_slice(&priv_key.ml_dsa_65);
238 let ml_sk = ml_dsa_65::PrivateKey::try_from_bytes(ml_sk_bytes)
239 .expect("ML-DSA-65 secret key malformed");
240 let ml_sig = ml_sk.try_sign(msg, &[]).expect("ML-DSA-65 sign");
241
242 HybridSignature {
243 ed25519: ed_sig.to_bytes().to_vec(),
244 ml_dsa_65: ml_sig.to_vec(),
245 }
246}
247
248pub fn verify_both(
250 msg: &[u8],
251 sig: &HybridSignature,
252 pub_key: &HybridPublicKey,
253) -> Result<(), String> {
254 if pub_key.ed25519.len() != 32 {
255 return Err(format!(
256 "Ed25519 public key wrong length: {}",
257 pub_key.ed25519.len()
258 ));
259 }
260 if pub_key.ml_dsa_65.len() != 1952 {
261 return Err(format!(
262 "ML-DSA-65 public key wrong length: {}",
263 pub_key.ml_dsa_65.len()
264 ));
265 }
266 if sig.ed25519.len() != 64 {
267 return Err(format!(
268 "Ed25519 signature wrong length: {}",
269 sig.ed25519.len()
270 ));
271 }
272 if sig.ml_dsa_65.len() != 3309 {
273 return Err(format!(
274 "ML-DSA-65 signature wrong length: {}",
275 sig.ml_dsa_65.len()
276 ));
277 }
278
279 let mut ed_pk_bytes = [0u8; 32];
280 ed_pk_bytes.copy_from_slice(&pub_key.ed25519);
281 let ed_pk = VerifyingKey::from_bytes(&ed_pk_bytes)
282 .map_err(|_| "Ed25519 public key invalid".to_string())?;
283 let ed_sig = EdSignature::from_slice(&sig.ed25519)
284 .map_err(|_| "Ed25519 signature invalid".to_string())?;
285 ed_pk
286 .verify(msg, &ed_sig)
287 .map_err(|_| "Ed25519 signature invalid".to_string())?;
288
289 let mut ml_pk_bytes = [0u8; ml_dsa_65::PK_LEN];
290 ml_pk_bytes.copy_from_slice(&pub_key.ml_dsa_65);
291 let ml_pk = ml_dsa_65::PublicKey::try_from_bytes(ml_pk_bytes)
292 .map_err(|_| "ML-DSA-65 public key malformed".to_string())?;
293 let mut ml_sig_bytes = [0u8; ml_dsa_65::SIG_LEN];
294 ml_sig_bytes.copy_from_slice(&sig.ml_dsa_65);
295 if !ml_pk.verify(msg, &ml_sig_bytes, &[]) {
296 return Err("ML-DSA-65 signature invalid".to_string());
297 }
298
299 Ok(())
300}
301
302pub fn issue_delegation(cert: &mut DelegationCert, issuer_priv: &HybridPrivateKey) {
307 cert.signature = sign_both(&delegation_sign_bytes(cert), issuer_priv);
308}
309
310pub fn verify_delegation_signature(cert: &DelegationCert) -> bool {
311 verify_delegation_signature_e(cert).is_ok()
312}
313
314pub fn verify_delegation_signature_e(cert: &DelegationCert) -> Result<(), String> {
315 verify_both(
316 &delegation_sign_bytes(cert),
317 &cert.signature,
318 &cert.issuer_pub_key,
319 )
320}
321
322pub fn sign_challenge(challenge: &[u8], ts: i64, agent_priv: &HybridPrivateKey) -> HybridSignature {
323 sign_challenge_with_session_context(challenge, ts, &[], agent_priv)
324}
325
326pub fn sign_challenge_with_session_context(
327 challenge: &[u8],
328 ts: i64,
329 session_context: &[u8],
330 agent_priv: &HybridPrivateKey,
331) -> HybridSignature {
332 assert!(
333 session_context.is_empty() || session_context.len() == 32,
334 "session_context must be 32 bytes"
335 );
336 sign_both(
337 &challenge_sign_bytes_with_session_context(challenge, ts, session_context),
338 agent_priv,
339 )
340}
341
342pub fn sign_challenge_with_stream(
343 challenge: &[u8],
344 ts: i64,
345 session_context: &[u8],
346 stream_id: &[u8],
347 stream_seq: i64,
348 agent_priv: &HybridPrivateKey,
349) -> HybridSignature {
350 assert!(
351 session_context.is_empty() || session_context.len() == 32,
352 "session_context must be 32 bytes"
353 );
354 assert_eq!(stream_id.len(), 32, "stream_id must be 32 bytes");
355 assert!(stream_seq >= 1, "stream_seq must be >=1");
356 sign_both(
357 &challenge_sign_bytes_with_stream(challenge, ts, session_context, stream_id, stream_seq),
358 agent_priv,
359 )
360}
361
362pub fn verify_challenge_signature(
363 challenge: &[u8],
364 ts: i64,
365 sig: &HybridSignature,
366 agent_pub: &HybridPublicKey,
367) -> Result<(), String> {
368 verify_challenge_signature_with_stream(challenge, ts, &[], &[], 0, sig, agent_pub)
369}
370
371pub fn verify_challenge_signature_with_session_context(
372 challenge: &[u8],
373 ts: i64,
374 session_context: &[u8],
375 sig: &HybridSignature,
376 agent_pub: &HybridPublicKey,
377) -> Result<(), String> {
378 verify_challenge_signature_with_stream(challenge, ts, session_context, &[], 0, sig, agent_pub)
379}
380
381pub fn verify_challenge_signature_with_stream(
382 challenge: &[u8],
383 ts: i64,
384 session_context: &[u8],
385 stream_id: &[u8],
386 stream_seq: i64,
387 sig: &HybridSignature,
388 agent_pub: &HybridPublicKey,
389) -> Result<(), String> {
390 if !session_context.is_empty() && session_context.len() != 32 {
391 return Err(format!(
392 "session_context must be 32 bytes, got {}",
393 session_context.len()
394 ));
395 }
396 if !stream_id.is_empty() && stream_id.len() != 32 {
397 return Err(format!(
398 "stream_id must be 32 bytes, got {}",
399 stream_id.len()
400 ));
401 }
402 if !stream_id.is_empty() && stream_seq < 1 {
403 return Err(format!("stream_seq must be >=1, got {}", stream_seq));
404 }
405 verify_both(
406 &challenge_sign_bytes_with_stream(challenge, ts, session_context, stream_id, stream_seq),
407 sig,
408 agent_pub,
409 )
410}
411
412pub fn issue_revocation_list(list: &mut RevocationList, issuer_priv: &HybridPrivateKey) {
413 list.signature = sign_both(&revocation_sign_bytes(list), issuer_priv);
414}
415
416pub fn verify_revocation_list(list: &RevocationList, issuer_pub: &HybridPublicKey) -> bool {
417 verify_both(&revocation_sign_bytes(list), &list.signature, issuer_pub).is_ok()
418}
419
420pub fn revocation_push_sign_bytes(push: &RevocationPush) -> Vec<u8> {
423 let mut out = String::new();
424 out.push('{');
425 out.push_str("\"entries\":"); encode_str_array(&push.entries, &mut out);
426 out.push_str(",\"issuer_id\":"); encode_str(&push.issuer_id, &mut out);
427 out.push_str(",\"pushed_at\":"); encode_i64(push.pushed_at, &mut out);
428 out.push_str(",\"seq_no\":"); encode_i64(push.seq_no, &mut out);
429 out.push('}');
430 out.into_bytes()
431}
432
433pub fn issue_revocation_push(push: &mut RevocationPush, issuer_priv: &HybridPrivateKey) {
434 push.signature = sign_both(&revocation_push_sign_bytes(push), issuer_priv);
435}
436
437pub fn verify_revocation_push(push: &RevocationPush, issuer_pub: &HybridPublicKey) -> bool {
438 verify_both(
439 &revocation_push_sign_bytes(push),
440 &push.signature,
441 issuer_pub,
442 )
443 .is_ok()
444}
445
446pub fn witness_entry_sign_bytes(entry: &WitnessEntry) -> Vec<u8> {
449 let mut out = String::new();
450 out.push('{');
451 out.push_str("\"entry_data\":"); encode_bytes_b64(&entry.entry_data, &mut out);
452 out.push_str(",\"prev_hash\":"); encode_bytes_b64(&entry.prev_hash, &mut out);
453 out.push_str(",\"timestamp\":"); encode_i64(entry.timestamp, &mut out);
454 out.push_str(",\"witness_id\":"); encode_str(&entry.witness_id, &mut out);
455 out.push('}');
456 out.into_bytes()
457}
458
459pub fn issue_witness_entry(entry: &mut WitnessEntry, witness_priv: &HybridPrivateKey) {
460 entry.signature = sign_both(&witness_entry_sign_bytes(entry), witness_priv);
461}
462
463pub fn verify_witness_entry(entry: &WitnessEntry, witness_pub: &HybridPublicKey) -> bool {
464 verify_both(
465 &witness_entry_sign_bytes(entry),
466 &entry.signature,
467 witness_pub,
468 )
469 .is_ok()
470}
471
472pub fn issue_key_rotation_statement(
473 stmt: &mut KeyRotationStatement,
474 old_priv: &HybridPrivateKey,
475 new_priv: &HybridPrivateKey,
476) {
477 let bytes = key_rotation_sign_bytes(stmt);
478 stmt.signature_old = sign_both(&bytes, old_priv);
479 stmt.signature_new = sign_both(&bytes, new_priv);
480}
481
482pub fn verify_key_rotation_statement(stmt: &KeyRotationStatement) -> Result<(), String> {
483 if stmt.version != 1 {
484 return Err(format!(
485 "version_mismatch: unsupported version {}",
486 stmt.version
487 ));
488 }
489 if stmt.old_id != derive_id(&stmt.old_pub_key) {
490 return Err("old_id does not match old_pub_key".to_string());
491 }
492 if stmt.new_id != derive_id(&stmt.new_pub_key) {
493 return Err("new_id does not match new_pub_key".to_string());
494 }
495 if stmt.old_id == stmt.new_id {
496 return Err("old_id and new_id must differ".to_string());
497 }
498 if !is_key_rotation_reason_known(&stmt.reason) {
499 return Err(format!("unknown key rotation reason: {}", stmt.reason));
500 }
501 let bytes = key_rotation_sign_bytes(stmt);
502 verify_both(&bytes, &stmt.signature_old, &stmt.old_pub_key)
503 .map_err(|e| format!("old signature invalid: {}", e))?;
504 verify_both(&bytes, &stmt.signature_new, &stmt.new_pub_key)
505 .map_err(|e| format!("new signature invalid: {}", e))?;
506 Ok(())
507}
508
509fn is_key_rotation_reason_known(reason: &str) -> bool {
510 matches!(
511 reason,
512 "routine" | "compromise_suspected" | "device_lost" | "recovery" | "other"
513 )
514}
515
516pub fn generate_challenge() -> Vec<u8> {
518 use rand_core::{OsRng, RngCore};
519 let mut b = [0u8; 32];
520 OsRng.fill_bytes(&mut b);
521 b.to_vec()
522}
523
524pub fn transaction_receipt_sign_bytes(receipt: &TransactionReceipt) -> Vec<u8> {
534 let mut sorted_parties: Vec<&crate::types::ReceiptParty> = receipt.parties.iter().collect();
536 sorted_parties.sort_by(|a, b| a.party_id.cmp(&b.party_id));
537
538 let mut out = String::new();
539 out.push('{');
540 out.push_str("\"created_at\":"); encode_i64(receipt.created_at, &mut out);
541 out.push_str(",\"parties\":[");
542 for (i, p) in sorted_parties.iter().enumerate() {
543 if i > 0 {
544 out.push(',');
545 }
546 out.push('{');
547 out.push_str("\"agent_id\":"); encode_str(&p.agent_id, &mut out);
548 out.push_str(",\"agent_pub_key\":"); encode_hybrid_pub_key(&p.agent_pub_key, &mut out);
549 out.push_str(",\"party_id\":"); encode_str(&p.party_id, &mut out);
550 out.push_str(",\"role\":"); encode_str(&p.role, &mut out);
551 out.push('}');
552 }
553 out.push(']');
554 out.push_str(",\"terms_canonical_json\":"); encode_bytes_b64(&receipt.terms_canonical_json, &mut out);
555 out.push_str(",\"terms_schema_uri\":"); encode_str(&receipt.terms_schema_uri, &mut out);
556 out.push_str(",\"transaction_id\":"); encode_str(&receipt.transaction_id, &mut out);
557 out.push_str(",\"version\":"); encode_i32(receipt.version, &mut out);
558 out.push('}');
559 out.into_bytes()
560}
561
562pub fn sign_transaction_receipt_party(
564 receipt: &TransactionReceipt,
565 party_id: &str,
566 agent_priv: &HybridPrivateKey,
567) -> ReceiptPartySignature {
568 let data = transaction_receipt_sign_bytes(receipt);
569 let sig = sign_both(&data, agent_priv);
570 ReceiptPartySignature {
571 party_id: party_id.to_string(),
572 signature: sig,
573 }
574}
575
576pub fn chain_hash(chain: &[DelegationCert]) -> Vec<u8> {
584 let mut hasher = Sha256::new();
585 for cert in chain {
586 hasher.update(delegation_sign_bytes(cert));
587 }
588 hasher.finalize().to_vec()
589}
590
591pub fn session_token_sign_bytes(token: &SessionToken) -> Vec<u8> {
596 let mut scope = token.granted_scope.clone();
597 scope.sort();
598 let mut out = String::new();
599 out.push('{');
600 out.push_str("\"agent_id\":"); encode_str(&token.agent_id, &mut out);
601 out.push_str(",\"agent_pub_key\":"); encode_hybrid_pub_key(&token.agent_pub_key, &mut out);
602 out.push_str(",\"chain_hash\":"); encode_bytes_b64(&token.chain_hash, &mut out);
603 out.push_str(",\"granted_scope\":"); encode_str_array(&scope, &mut out);
604 out.push_str(",\"human_id\":"); encode_str(&token.human_id, &mut out);
605 out.push_str(",\"issued_at\":"); encode_i64(token.issued_at, &mut out);
606 out.push_str(",\"session_id\":"); encode_str(&token.session_id, &mut out);
607 out.push_str(",\"valid_until\":"); encode_i64(token.valid_until, &mut out);
608 out.push_str(",\"version\":"); encode_i32(token.version, &mut out);
609 out.push('}');
610 out.into_bytes()
611}
612
613pub fn issue_session_token(
616 bundle: &ProofBundle,
617 result: &VerifyResult,
618 session_id: &str,
619 issued_at: i64,
620 valid_until: i64,
621 session_secret: &[u8],
622) -> Result<SessionToken, String> {
623 if session_secret.is_empty() {
624 return Err("session_secret must not be empty".to_string());
625 }
626 if session_id.is_empty() {
627 return Err("session_id must not be empty".to_string());
628 }
629 if valid_until <= issued_at {
630 return Err("valid_until must be strictly after issued_at".to_string());
631 }
632 let mut scope = result.granted_scope.clone();
633 scope.sort();
634 let mut token = SessionToken {
635 version: 1,
636 session_id: session_id.to_string(),
637 agent_id: result.agent_id.clone(),
638 agent_pub_key: bundle.agent_pub_key.clone(),
639 human_id: result.human_id.clone(),
640 granted_scope: scope,
641 issued_at,
642 valid_until,
643 chain_hash: chain_hash(&bundle.delegations),
644 mac: Vec::new(),
645 };
646 let signable = session_token_sign_bytes(&token);
647 let mut mac =
648 HmacSha256::new_from_slice(session_secret).map_err(|e| format!("init HMAC: {}", e))?;
649 mac.update(&signable);
650 token.mac = mac.finalize().into_bytes().to_vec();
651 Ok(token)
652}
653
654pub fn verify_session_token_e(
657 token: &SessionToken,
658 session_secret: &[u8],
659 now: i64,
660) -> Result<(), String> {
661 if session_secret.is_empty() {
662 return Err("session_secret must not be empty".to_string());
663 }
664 if token.version != 1 {
665 return Err(format!(
666 "version_mismatch: unsupported version {}",
667 token.version
668 ));
669 }
670 if token.chain_hash.len() != 32 {
671 return Err(format!(
672 "chain_hash must be 32 bytes, got {}",
673 token.chain_hash.len()
674 ));
675 }
676 if token.mac.len() != 32 {
677 return Err(format!("mac must be 32 bytes, got {}", token.mac.len()));
678 }
679 let mut mac =
680 HmacSha256::new_from_slice(session_secret).map_err(|e| format!("init HMAC: {}", e))?;
681 mac.update(&session_token_sign_bytes(token));
682 mac.verify_slice(&token.mac)
683 .map_err(|_| "session_token MAC invalid".to_string())?;
684 if now < token.issued_at {
685 return Err("session_token not yet valid".to_string());
686 }
687 if now > token.valid_until {
688 return Err("session_token expired".to_string());
689 }
690 Ok(())
691}
692
693pub fn verify_session_token(token: &SessionToken, session_secret: &[u8], now: i64) -> bool {
694 verify_session_token_e(token, session_secret, now).is_ok()
695}