typed→rust: x25519 ECDH crypto-prims (RFC 7748)

ober

396697655fd8880f56a41248d68c3d28405109a8

diff --git a/lib/jerboa/typed/checker.ss b/lib/jerboa/typed/checker.ss
index 1cdd3b5..a60463c 100644
--- a/lib/jerboa/typed/checker.ss
+++ b/lib/jerboa/typed/checker.ss
@@ -397,7 +397,14 @@
               'crypto-prim (list (cons 'prim 'hmac-sha256))))
       (cons 'hkdf-sha256
             (make-typed-call-sig (list 'Bytes 'Bytes 'Bytes 'Nat) 'Bytes '()
-              'crypto-prim (list (cons 'prim 'hkdf-sha256))))))
+              'crypto-prim (list (cons 'prim 'hkdf-sha256))))
+      ;; X25519 ECDH (RFC 7748): scalar*point, and public = scalar*basepoint.
+      (cons 'x25519-dh
+            (make-typed-call-sig (list 'Bytes 'Bytes) 'Bytes '()
+              'crypto-prim (list (cons 'prim 'x25519-dh))))
+      (cons 'x25519-base
+            (make-typed-call-sig (list 'Bytes) 'Bytes '()
+              'crypto-prim (list (cons 'prim 'x25519-base))))))
 
   (def (field-types fields)
     (map typed-field-type fields))
diff --git a/lib/jerboa/typed/rust.ss b/lib/jerboa/typed/rust.ss
index 1a49c6d..a99ab5d 100644
--- a/lib/jerboa/typed/rust.ss
+++ b/lib/jerboa/typed/rust.ss
@@ -1400,11 +1400,35 @@
       (bytes-slice (caddr args))
       ", &mut __okm).expect(\"hkdf expand within output limit\"); __okm }"))
 
+  ;; 32-byte scalar/point operand as a fixed [u8; 32], borrowing the Bytes.
+  (def (x25519-key32 arg)
+    (string-append "(&(" (emit-expression arg)
+                   ")[..]).try_into().expect(\"x25519 value is 32 bytes\")"))
+
+  ;; (x25519-dh scalar point) : (Bytes Bytes) -> Bytes — RFC 7748 X25519 ECDH.
+  (def (emit-x25519-dh args)
+    (unless (= (length args) 2)
+      (error 'typed-rust "x25519-dh expects scalar and point Bytes operands" args))
+    (string-append
+      "{ let __k: [u8; 32] = " (x25519-key32 (car args))
+      "; let __u: [u8; 32] = " (x25519-key32 (cadr args))
+      "; x25519_dalek::x25519(__k, __u).to_vec() }"))
+
+  ;; (x25519-base scalar) : Bytes -> Bytes — public key = scalar * basepoint.
+  (def (emit-x25519-base args)
+    (unless (= (length args) 1)
+      (error 'typed-rust "x25519-base expects one scalar Bytes operand" args))
+    (string-append
+      "{ let __k: [u8; 32] = " (x25519-key32 (car args))
+      "; x25519_dalek::x25519(__k, x25519_dalek::X25519_BASEPOINT_BYTES).to_vec() }"))
+
   (def (emit-crypto-prim prim args)
     (case prim
       [(sha256) (emit-sha256 args)]
       [(hmac-sha256) (emit-hmac-sha256 args)]
       [(hkdf-sha256) (emit-hkdf-sha256 args)]
+      [(x25519-dh) (emit-x25519-dh args)]
+      [(x25519-base) (emit-x25519-base args)]
       [else (error 'typed-rust "unknown crypto primitive" prim)]))
 
   ;; (bytevector-append a b) -> a fresh owned buffer a ++ b. Clone the first so