1use std::sync::Arc;
77
78use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey};
79use tatara_lisp_eval::ffi::Arity;
80use tatara_lisp_eval::{EvalError, Interpreter, Value};
81
82pub const KEY_BYTES: usize = 32;
84pub const SIGNATURE_BYTES: usize = 64;
86
87#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
92pub enum CryptoError {
93 #[error("the {what} must be a hex string; got {got}")]
95 NotHexText {
96 what: &'static str,
97 got: &'static str,
98 },
99 #[error("the {what} is not hex: character {position} is `{found}`")]
101 NotHex {
102 what: &'static str,
103 position: usize,
104 found: char,
105 },
106 #[error("the {what} has an odd number of hex digits ({digits})")]
108 OddHex { what: &'static str, digits: usize },
109 #[error("the {what} is {got} bytes; an Ed25519 {what} is {expected}")]
111 WrongLength {
112 what: &'static str,
113 expected: usize,
114 got: usize,
115 },
116 #[error("a message is a string or a list of bytes; got {got}")]
118 NotAMessage { got: &'static str },
119 #[error("element {index} of the byte list is not an integer; got {got}")]
121 NotAByte { index: usize, got: &'static str },
122 #[error("element {index} of the byte list is {value}, outside 0..=255")]
124 ByteOutOfRange { index: usize, value: i64 },
125 #[error("the public key is not a point on the Ed25519 curve")]
127 NotACurvePoint,
128 #[error("the public key has small order and cannot vouch for any message")]
130 WeakPublicKey,
131}
132
133pub fn message_bytes(v: &Value) -> Result<Vec<u8>, CryptoError> {
140 match v {
141 Value::Str(s) => Ok(s.as_bytes().to_vec()),
142 Value::List(items) => items
143 .iter()
144 .enumerate()
145 .map(|(index, item)| match item {
146 Value::Int(n) => {
147 u8::try_from(*n).map_err(|_| CryptoError::ByteOutOfRange { index, value: *n })
148 }
149 other => Err(CryptoError::NotAByte {
150 index,
151 got: other.type_name(),
152 }),
153 })
154 .collect(),
155 other => Err(CryptoError::NotAMessage {
156 got: other.type_name(),
157 }),
158 }
159}
160
161pub fn hex_array<const N: usize>(v: &Value, what: &'static str) -> Result<[u8; N], CryptoError> {
163 let text = match v {
164 Value::Str(s) => s,
165 other => {
166 return Err(CryptoError::NotHexText {
167 what,
168 got: other.type_name(),
169 })
170 }
171 };
172 let bytes = decode_hex(text, what)?;
173 <[u8; N]>::try_from(bytes.as_slice()).map_err(|_| CryptoError::WrongLength {
174 what,
175 expected: N,
176 got: bytes.len(),
177 })
178}
179
180fn decode_hex(text: &str, what: &'static str) -> Result<Vec<u8>, CryptoError> {
182 let digits: Vec<u8> = text
183 .chars()
184 .enumerate()
185 .map(|(position, found)| {
186 found
187 .to_digit(16)
188 .map(|d| d as u8)
190 .ok_or(CryptoError::NotHex {
191 what,
192 position,
193 found,
194 })
195 })
196 .collect::<Result<_, _>>()?;
197 if !digits.len().is_multiple_of(2) {
198 return Err(CryptoError::OddHex {
199 what,
200 digits: digits.len(),
201 });
202 }
203 Ok(digits
204 .as_chunks::<2>()
205 .0
206 .iter()
207 .map(|[hi, lo]| (hi << 4) | lo)
208 .collect())
209}
210
211pub fn encode_hex(bytes: &[u8]) -> String {
213 const DIGITS: &[u8; 16] = b"0123456789abcdef";
214 let mut out = String::with_capacity(bytes.len() * 2);
215 for b in bytes {
216 out.push(char::from(DIGITS[usize::from(b >> 4)]));
217 out.push(char::from(DIGITS[usize::from(b & 0x0f)]));
218 }
219 out
220}
221
222pub fn blake3_hex(data: &[u8]) -> String {
224 blake3::hash(data).to_hex().to_string()
225}
226
227pub fn ed25519_public(secret: &[u8; KEY_BYTES]) -> [u8; KEY_BYTES] {
229 SigningKey::from_bytes(secret).verifying_key().to_bytes()
230}
231
232pub fn ed25519_sign(secret: &[u8; KEY_BYTES], message: &[u8]) -> [u8; SIGNATURE_BYTES] {
235 SigningKey::from_bytes(secret).sign(message).to_bytes()
236}
237
238pub fn ed25519_verify(
241 public: &[u8; KEY_BYTES],
242 message: &[u8],
243 signature: &[u8; SIGNATURE_BYTES],
244) -> Result<bool, CryptoError> {
245 let key = VerifyingKey::from_bytes(public).map_err(|_| CryptoError::NotACurvePoint)?;
246 if key.is_weak() {
247 return Err(CryptoError::WeakPublicKey);
248 }
249 let signature = Signature::from_bytes(signature);
250 Ok(key.verify_strict(message, &signature).is_ok())
251}
252
253fn refuse(name: &'static str, span: tatara_lisp::Span) -> impl Fn(CryptoError) -> EvalError {
255 move |e| EvalError::native_fn(name, e.to_string(), span)
256}
257
258fn hex_value(bytes: &[u8]) -> Value {
259 Value::Str(Arc::from(encode_hex(bytes)))
260}
261
262pub fn install_crypto_stdlib<H: 'static>(interp: &mut Interpreter<H>) {
264 interp.register_fn(
265 "blake3_hex",
266 Arity::Exact(1),
267 |a: &[Value], _h: &mut H, span| {
268 let data = message_bytes(&a[0]).map_err(refuse("blake3_hex", span))?;
269 Ok(Value::Str(Arc::from(blake3_hex(&data))))
270 },
271 );
272
273 interp.register_fn(
274 "ed25519_keypair",
275 Arity::Exact(1),
276 |a: &[Value], _h: &mut H, span| {
277 let seed: [u8; KEY_BYTES] =
278 hex_array(&a[0], "seed").map_err(refuse("ed25519_keypair", span))?;
279 Ok(Value::List(Arc::new(vec![
280 hex_value(&seed),
281 hex_value(&ed25519_public(&seed)),
282 ])))
283 },
284 );
285
286 interp.register_fn(
287 "ed25519_sign",
288 Arity::Exact(2),
289 |a: &[Value], _h: &mut H, span| {
290 let secret: [u8; KEY_BYTES] =
291 hex_array(&a[0], "secret key").map_err(refuse("ed25519_sign", span))?;
292 let message = message_bytes(&a[1]).map_err(refuse("ed25519_sign", span))?;
293 Ok(hex_value(&ed25519_sign(&secret, &message)))
294 },
295 );
296
297 interp.register_fn(
298 "ed25519_verify",
299 Arity::Exact(3),
300 |a: &[Value], _h: &mut H, span| {
301 let public: [u8; KEY_BYTES] =
302 hex_array(&a[0], "public key").map_err(refuse("ed25519_verify", span))?;
303 let message = message_bytes(&a[1]).map_err(refuse("ed25519_verify", span))?;
304 let signature: [u8; SIGNATURE_BYTES] =
305 hex_array(&a[2], "signature").map_err(refuse("ed25519_verify", span))?;
306 let verdict = ed25519_verify(&public, &message, &signature)
307 .map_err(refuse("ed25519_verify", span))?;
308 Ok(Value::Bool(verdict))
309 },
310 );
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
343
344 fn eval(src: &str) -> Value {
345 crate::run(src)
346 .unwrap_or_else(|e| panic!("{src:?}: {e}"))
347 .value
348 }
349
350 fn s(src: &str) -> String {
351 match eval(src) {
352 Value::Str(v) => v.to_string(),
353 other => panic!("{src:?} produced {other:?}"),
354 }
355 }
356
357 fn b(src: &str) -> bool {
358 match eval(src) {
359 Value::Bool(v) => v,
360 other => panic!("{src:?} produced {other:?}"),
361 }
362 }
363
364 fn err(src: &str) -> String {
365 crate::run(src)
366 .map(|r| r.value)
367 .expect_err("must raise")
368 .to_string()
369 }
370
371 struct Vector {
373 secret: &'static str,
374 public: &'static str,
375 message: &'static str,
377 signature: &'static str,
378 }
379
380 const TEST_1: Vector = Vector {
381 secret: "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
382 public: "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
383 message: "\"\"",
384 signature: "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155\
385 5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
386 };
387
388 const TEST_2: Vector = Vector {
390 secret: "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
391 public: "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
392 message: "\"r\"",
393 signature: "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da\
394 085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
395 };
396
397 const TEST_3: Vector = Vector {
400 secret: "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
401 public: "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
402 message: "[175, 130]",
403 signature: "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac\
404 18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
405 };
406
407 fn check_vector(v: &Vector) {
408 let pair = eval(&["ed25519_keypair(\"", v.secret, "\")"].concat());
409 let Value::List(pair) = pair else {
410 panic!("keypair must be a list, got {pair:?}")
411 };
412 assert!(matches!(&pair[0], Value::Str(x) if &**x == v.secret));
413 assert!(
414 matches!(&pair[1], Value::Str(x) if &**x == v.public),
415 "public key mismatch: {:?}",
416 pair[1]
417 );
418 let sig = s(&["ed25519_sign(\"", v.secret, "\", ", v.message, ")"].concat());
419 assert_eq!(sig, v.signature, "signature mismatch");
420 assert!(b(&[
421 "ed25519_verify(\"",
422 v.public,
423 "\", ",
424 v.message,
425 ", \"",
426 v.signature,
427 "\")"
428 ]
429 .concat()));
430 }
431
432 #[test]
433 fn rfc8032_test_1() {
434 check_vector(&TEST_1);
435 }
436
437 #[test]
438 fn rfc8032_test_2() {
439 check_vector(&TEST_2);
440 }
441
442 #[test]
443 fn rfc8032_test_3() {
444 check_vector(&TEST_3);
445 }
446
447 fn official_input(n: usize) -> Vec<u8> {
449 (0..n).map(|i| (i % 251) as u8).collect()
450 }
451
452 #[test]
453 fn blake3_the_empty_input() {
454 assert_eq!(
455 s("blake3_hex(\"\")"),
456 "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262"
457 );
458 assert_eq!(
460 s("blake3_hex([])"),
461 "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262"
462 );
463 }
464
465 #[test]
466 fn blake3_one_byte_through_blue() {
467 assert_eq!(
468 s("blake3_hex([0])"),
469 "2d3adedff11b61f14c886e35afa036736dcd87a74d27b5c1510225d0f592e213"
470 );
471 }
472
473 #[test]
476 fn blake3_across_the_chunk_boundary() {
477 for (n, want) in [
478 (
479 3,
480 "e1be4d7a8ab5560aa4199eea339849ba8e293d55ca0a81006726d184519e647f",
481 ),
482 (
483 1023,
484 "10108970eeda3eb932baac1428c7a2163b0e924c9a9e25b35bba72b28f70bd11",
485 ),
486 (
487 1024,
488 "42214739f095a406f3fc83deb889744ac00df831c10daa55189b5d121c855af7",
489 ),
490 (
491 1025,
492 "d00278ae47eb27b34faecf67b4fe263f82d5412916c1ffd97c8cb7fb814b8444",
493 ),
494 (
495 8193,
496 "bab6c09cb8ce8cf459261398d2e7aef35700bf488116ceb94a36d0f5f1b7bc3b",
497 ),
498 ] {
499 assert_eq!(blake3_hex(&official_input(n)), want, "input_len {n}");
500 }
501 }
502
503 #[test]
505 fn text_and_bytes_are_one_message() {
506 assert_eq!(s("blake3_hex(\"abc\")"), s("blake3_hex([97, 98, 99])"));
507 assert_eq!(s("blake3_hex(\"é\")"), s("blake3_hex([195, 169])"));
509 }
510
511 #[test]
512 fn a_signature_round_trips_through_blue() {
513 let sig = s(&["ed25519_sign(\"", TEST_2.secret, "\", \"a permit\")"].concat());
514 assert!(b(&[
515 "ed25519_verify(\"",
516 TEST_2.public,
517 "\", \"a permit\", \"",
518 &sig,
519 "\")"
520 ]
521 .concat()));
522 }
523
524 #[test]
527 fn a_tampered_message_does_not_verify() {
528 assert!(!b(&[
530 "ed25519_verify(\"",
531 TEST_2.public,
532 "\", \"s\", \"",
533 TEST_2.signature,
534 "\")"
535 ]
536 .concat()));
537 }
538
539 #[test]
540 fn a_wrong_key_does_not_verify() {
541 assert!(!b(&[
543 "ed25519_verify(\"",
544 TEST_1.public,
545 "\", \"r\", \"",
546 TEST_2.signature,
547 "\")"
548 ]
549 .concat()));
550 }
551
552 #[test]
553 fn a_tampered_signature_does_not_verify() {
554 let mut forged = TEST_1.signature.to_string();
556 forged.pop();
557 forged.push('a');
558 assert_ne!(forged, TEST_1.signature);
559 assert!(!b(&[
560 "ed25519_verify(\"",
561 TEST_1.public,
562 "\", \"\", \"",
563 &forged,
564 "\")"
565 ]
566 .concat()));
567 }
568
569 #[test]
572 fn a_short_key_is_a_named_error_not_false() {
573 let e = err("ed25519_verify(\"abcd\", \"m\", \"00\")");
574 assert!(e.contains("ed25519_verify"), "{e}");
575 assert!(e.contains("public key is 2 bytes"), "{e}");
576 }
577
578 #[test]
579 fn non_hex_is_a_named_error() {
580 let bad = ["\"zz", &TEST_1.secret[2..], "\""].concat();
581 let e = err(&["ed25519_sign(", &bad, ", \"m\")"].concat());
582 assert!(e.contains("not hex: character 0 is `z`"), "{e}");
583 }
584
585 #[test]
586 fn odd_hex_is_a_named_error() {
587 let e = err("ed25519_keypair(\"abc\")");
588 assert!(e.contains("odd number of hex digits (3)"), "{e}");
589 }
590
591 #[test]
592 fn a_key_that_is_not_a_string_is_a_named_error() {
593 let e = err("ed25519_keypair(42)");
594 assert!(e.contains("seed must be a hex string; got int"), "{e}");
595 }
596
597 #[test]
598 fn a_non_message_is_a_named_error() {
599 let e = err("blake3_hex(nil)");
600 assert!(
601 e.contains("a message is a string or a list of bytes"),
602 "{e}"
603 );
604 let e = err("blake3_hex(42)");
605 assert!(e.contains("; got int"), "{e}");
606 }
607
608 #[test]
609 fn a_byte_out_of_range_is_a_named_error() {
610 let e = err("blake3_hex([1, 256])");
611 assert!(e.contains("element 1 of the byte list is 256"), "{e}");
612 let e = err("blake3_hex([1, \"x\"])");
613 assert!(
614 e.contains("element 1 of the byte list is not an integer; got string"),
615 "{e}"
616 );
617 }
618
619 #[test]
621 fn hex_input_is_case_insensitive_and_output_is_lowercase() {
622 let upper = TEST_1.secret.to_uppercase();
623 let pair = eval(&["ed25519_keypair(\"", &upper, "\")"].concat());
624 let Value::List(pair) = pair else { panic!() };
625 assert!(matches!(&pair[0], Value::Str(x) if &**x == TEST_1.secret));
626 assert!(matches!(&pair[1], Value::Str(x) if &**x == TEST_1.public));
627 }
628
629 #[test]
633 fn a_public_key_off_the_curve_is_a_named_error() {
634 let mut off = [0u8; KEY_BYTES];
635 off[0] = 2;
636 assert!(
637 VerifyingKey::from_bytes(&off).is_err(),
638 "control: y=2 is off-curve"
639 );
640 let e = err(&[
641 "ed25519_verify(\"",
642 &encode_hex(&off),
643 "\", \"m\", \"",
644 TEST_1.signature,
645 "\")",
646 ]
647 .concat());
648 assert!(e.contains("not a point on the Ed25519 curve"), "{e}");
649 }
650
651 #[test]
654 fn a_small_order_public_key_is_a_named_error() {
655 let mut identity = [0u8; KEY_BYTES];
656 identity[0] = 1;
657 let key = VerifyingKey::from_bytes(&identity).expect("the identity decompresses");
658 assert!(key.is_weak(), "control: the identity is small-order");
659 let e = err(&[
660 "ed25519_verify(\"",
661 &encode_hex(&identity),
662 "\", \"m\", \"",
663 TEST_1.signature,
664 "\")",
665 ]
666 .concat());
667 assert!(e.contains("small order"), "{e}");
668 }
669
670 #[test]
671 fn hex_round_trips() {
672 let all: Vec<u8> = (0..=255).collect();
673 let text = encode_hex(&all);
674 assert_eq!(decode_hex(&text, "t").expect("round trip"), all);
675 assert_eq!(decode_hex("", "t").expect("empty"), Vec::<u8>::new());
676 }
677}