diff --git a/Cargo.lock b/Cargo.lock
index ada644474af3ba24c7ffa0cded41dc7d84de6f3a..f9122e1b24a78435e4c8a3441e11f2149bc25340 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -271,19 +271,6 @@ dependencies = [
  "zeroize",
 ]
 
-[[package]]
-name = "curve25519-dalek"
-version = "4.0.0-pre.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12dc3116fe595d7847c701796ac1b189bd86b81f4f593c6f775f9d80fb2e29f4"
-dependencies = [
- "byteorder",
- "digest 0.10.3",
- "rand_core 0.6.3",
- "subtle",
- "zeroize",
-]
-
 [[package]]
 name = "der"
 version = "0.5.1"
@@ -352,7 +339,6 @@ dependencies = [
  "chacha20",
  "ctr",
  "digest 0.10.3",
- "ed25519-dalek 2.0.0-pre.1",
  "heapless",
  "hmac",
  "log",
@@ -363,13 +349,14 @@ dependencies = [
  "proptest",
  "rand",
  "rand_core 0.6.3",
+ "salty",
  "serde_json",
  "sha2 0.10.2",
+ "signature",
  "simplelog",
  "snafu",
  "ssh-key",
  "sshwire_derive",
- "x25519-dalek",
 ]
 
 [[package]]
@@ -401,23 +388,12 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
 dependencies = [
- "curve25519-dalek 3.2.1",
+ "curve25519-dalek",
  "ed25519",
  "sha2 0.9.9",
  "zeroize",
 ]
 
-[[package]]
-name = "ed25519-dalek"
-version = "2.0.0-pre.1"
-dependencies = [
- "curve25519-dalek 4.0.0-pre.2",
- "ed25519",
- "rand",
- "sha2 0.10.2",
- "zeroize",
-]
-
 [[package]]
 name = "embedded-hal"
 version = "0.2.7"
@@ -930,6 +906,16 @@ version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
 
+[[package]]
+name = "salty"
+version = "0.2.0"
+dependencies = [
+ "digest 0.10.3",
+ "ed25519",
+ "subtle",
+ "zeroize",
+]
+
 [[package]]
 name = "scopeguard"
 version = "1.1.0"
@@ -1102,7 +1088,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9e09d8b0ff2fca365c89ad535055c17adbd9fb0a5daf338299049882e95b6931"
 dependencies = [
  "base64ct",
- "ed25519-dalek 1.0.1",
+ "ed25519-dalek",
  "pem-rfc7468",
  "rand_core 0.6.3",
  "sec1",
@@ -1386,16 +1372,6 @@ version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
 
-[[package]]
-name = "x25519-dalek"
-version = "2.0.0-pre.2"
-source = "git+https://github.com/mobilecoinfoundation/x25519-dalek?branch=mobilecoin#c1966b8743d320cd07a54191475e5c0f94b2ea30"
-dependencies = [
- "curve25519-dalek 4.0.0-pre.2",
- "rand_core 0.6.3",
- "zeroize",
-]
-
 [[package]]
 name = "zeroize"
 version = "1.3.0"
@@ -1416,3 +1392,7 @@ dependencies = [
  "syn",
  "synstructure",
 ]
+
+[[patch.unused]]
+name = "ed25519-dalek"
+version = "2.0.0-pre.1"
diff --git a/smol/examples/con1.rs b/smol/examples/con1.rs
index cd97cc26325e7030f826780679bab9941a1382c2..74f3d30cb1275a2bb705010af69117ba13b4a2f9 100644
--- a/smol/examples/con1.rs
+++ b/smol/examples/con1.rs
@@ -67,12 +67,6 @@ fn parse_args() -> Result<Args> {
 
 fn main() -> Result<()> {
     let args = parse_args()?;
-    use std::panic;
-
-    panic::set_hook(Box::new(|_| {
-        println!("Custom panic hook");
-    }));
-
 
     // time crate won't read TZ if we're threaded, in case someone
     // tries to mutate shared state with setenv.
diff --git a/smol/src/async_client.rs b/smol/src/async_client.rs
index ec2dc9538125c9f36c3997db5a0f5a8345527afc..4c02e1cb2c9e84b8da8b165513b2b028c55be736 100644
--- a/smol/src/async_client.rs
+++ b/smol/src/async_client.rs
@@ -35,7 +35,6 @@ impl SimpleClient {
         self.authkeys.push_back(k)
     }
 
-    fn write_stdout
 }
 
 #[async_trait(?Send)]
diff --git a/sshproto/Cargo.toml b/sshproto/Cargo.toml
index ff92e139b83c9f2777b79d0aeb4c066560da93c4..01b8fb6ab76868984b4c6f2f794bee111bb965ba 100644
--- a/sshproto/Cargo.toml
+++ b/sshproto/Cargo.toml
@@ -22,6 +22,7 @@ aes = "0.8"
 sha2 = { version = "0.10", default-features = false }
 hmac = "0.12"
 digest = "0.10"
+signature = { version = "1.4", default-features = false }
 ssh-key = { version = "0.4", default-features = false, features = ["ed25519", "ecdsa", "sha2"] }
 chacha20 = "0.9"
 poly1305 = "0.7"
@@ -33,27 +34,7 @@ pin-utils = "0.1"
 # tokio = { version = "1.18", features = ["sync"], optional = true }
 async-trait = { version = "0.1", optional = true }
 
-[dependencies.ed25519-dalek]
-version = "2.0.0-pre.1"
-default-features = false
-# TODO: switch u32/u64 based on targets somehow? needs to match x25519's
-# TODO: is "rand" needed here, or only for tests?
-features = [ "u32_backend", "rand" ]
-# pending https://github.com/dalek-cryptography/ed25519-dalek/pull/193
-# and also sign_parts()
-# git = "https://github.com/mkj/ed25519-dalek"
-# branch = "parts"
-path = "/home/matt/3rd/rs/ed25519-dalek"
-
-[dependencies.x25519-dalek]
-version = "2.0.0-pre.2"
-default-features = false
-# TODO: switch based on targets somehow?
-features = [ "u32_backend" ]
-# pending https://github.com/dalek-cryptography/x25519-dalek/pull/87
-# upstream version uses older rand
-git = "https://github.com/mobilecoinfoundation/x25519-dalek"
-branch = "mobilecoin"
+salty = { version = "0.2", path = "/home/matt/3rd/rs/salty" }
 
 [features]
 default = [ "getrandom" ]
diff --git a/sshproto/src/error.rs b/sshproto/src/error.rs
index 04bca5a182495484f31e35305a06837771f8a4e9..b311a9a9b7b02ddc9a73a911673abe0d1a2cd825 100644
--- a/sshproto/src/error.rs
+++ b/sshproto/src/error.rs
@@ -23,10 +23,6 @@ pub enum Error {
     /// Input buffer ran out
     RanOut,
 
-    /// Not implemented (unused in SSH protocol)
-    // internal
-    NoSerializer,
-
     /// Not a UTF8 string
     BadString,
 
@@ -37,13 +33,13 @@ pub enum Error {
     BadDecrypt,
 
     /// Signature is incorrect
-    BadSignature,
+    BadSig,
 
     /// Key exchange incorrect
     BadKex,
 
     // Signature doesn't match key type
-    SignatureMismatch,
+    SigMismatch,
 
     /// Error in received SSH protocol
     SSHProtoError,
diff --git a/sshproto/src/kex.rs b/sshproto/src/kex.rs
index c934b6c7317738ed302bb62ec2b24b187ee033cb..95fd08cee5ca681550a08428bc1874fedbecfba6 100644
--- a/sshproto/src/kex.rs
+++ b/sshproto/src/kex.rs
@@ -442,9 +442,9 @@ impl SharedSecret {
         kex_hash.prefinish(&fake_hostkey, p.q_c.0, algos.kex.pubkey())?;
         let kex_out = match algos.kex {
             SharedSecret::KexCurve25519(ref k) => {
-                let pubkey = (k.ours.as_ref().trap()?).into();
+                let pubkey: salty::agreement::PublicKey = k.ours.as_ref().trap()?.into();
                 let mut kex_out = KexCurve25519::secret(&mut algos, p.q_c.0, kex_hash, sess_id)?;
-                kex_out.pubkey = Some(pubkey);
+                kex_out.pubkey = Some(pubkey.to_bytes());
                 kex_out
             }
         };
@@ -464,8 +464,10 @@ pub(crate) struct KexOutput {
     pub keys: Keys,
 
     // storage for kex packet reply content that outlives Kex
-    // TODO: this should become generic
-    pubkey: Option<x25519_dalek::PublicKey>,
+    // in make_kexdhreply().
+
+    // TODO: this should become generic for other kex methods
+    pubkey: Option<[u8; 32]>,
 }
 
 impl fmt::Debug for KexOutput {
@@ -490,7 +492,7 @@ impl<'a> KexOutput {
 
     // server only
     pub fn make_kexdhreply(&'a self) -> Result<Packet<'a>> {
-        let q_s = BinString(self.pubkey.as_ref().trap()?.as_bytes());
+        let q_s = BinString(self.pubkey.as_ref().trap()?);
         // TODO
         let k_s = Blob(PubKey::Ed25519(packets::Ed25519PubKey{ key: BinString(&[]) }));
         let sig = Blob(Signature::Ed25519(packets::Ed25519Sig{ sig: BinString(&[]) }));
@@ -500,28 +502,32 @@ impl<'a> KexOutput {
 }
 
 pub(crate) struct KexCurve25519 {
-    ours: Option<x25519_dalek::EphemeralSecret>,
-    pubkey: x25519_dalek::PublicKey,
+    ours: Option<salty::agreement::SecretKey>,
+    pubkey: [u8; 32],
 }
 
 impl core::fmt::Debug for KexCurve25519 {
     fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
         f.debug_struct("KexCurve25519")
             .field("ours", &if self.ours.is_some() { "Some" } else { "None" })
-            .field("pubkey", self.pubkey.as_bytes())
+            .field("pubkey", &self.pubkey)
             .finish()
     }
 }
 
 impl KexCurve25519 {
     fn new() -> Result<Self> {
-        let ours = x25519_dalek::EphemeralSecret::new(random::DoorRng::default());
-        let pubkey = (&ours).into();
+        let mut s = [0u8; 32];
+        random::fill_random(s.as_mut_slice())?;
+        // TODO: check that pure random bytes are OK
+        let ours = salty::agreement::SecretKey::from_seed(&s);
+        let pubkey: salty::agreement::PublicKey = (&ours).into();
+        let pubkey = pubkey.to_bytes();
         Ok(KexCurve25519 { ours: Some(ours), pubkey })
     }
 
     fn pubkey<'a>(&'a self) -> &'a [u8] {
-        self.pubkey.as_bytes()
+        self.pubkey.as_slice()
     }
 
     fn secret<'a>(
@@ -536,9 +542,9 @@ impl KexCurve25519 {
         };
         let ours = kex.ours.take().trap()?;
         let theirs: [u8; 32] = theirs.try_into().map_err(|_| Error::BadKex)?;
-        let theirs = theirs.into();
-        let shsec = ours.diffie_hellman(&theirs);
-        KexOutput::make(shsec.as_bytes(), algos, kex_hash, sess_id)
+        let theirs = theirs.try_into().map_err(|_| Error::BadKex)?;
+        let shsec = ours.agree(&theirs);
+        KexOutput::make(&shsec.to_bytes(), algos, kex_hash, sess_id)
     }
 }
 
diff --git a/sshproto/src/sign.rs b/sshproto/src/sign.rs
index 819a701f558d585547018888c0f8ff7bc743509d..638499fd7e107a3d8367e25d0bc41b9a1da73cbc 100644
--- a/sshproto/src/sign.rs
+++ b/sshproto/src/sign.rs
@@ -5,10 +5,11 @@ use {
     log::{debug, error, info, log, trace, warn},
 };
 
-use ed25519_dalek as dalek;
-use ed25519_dalek::{Verifier, Signer};
+use salty::{SecretKey, PublicKey};
+use signature::Verifier;
 
-use crate::{*, packets::ParseContext};
+use crate::*;
+use packets::ParseContext;
 use sshnames::*;
 use packets::{PubKey, Signature, Ed25519PubKey};
 use sshwire::{BinString, SSHEncode};
@@ -56,21 +57,23 @@ impl SigType {
         if discriminant(&sig_type) != discriminant(self) {
             warn!("Received {:?} signature, expecting {}",
                 sig.algorithm_name(), self.algorithm_name());
-            return Err(Error::BadSignature)
+            return Err(Error::BadSig)
         }
 
         match (self, pubkey, sig) {
 
             (SigType::Ed25519, PubKey::Ed25519(k), Signature::Ed25519(s)) => {
-                let k = dalek::PublicKey::from_bytes(k.key.0).map_err(|_| Error::BadSignature)?;
-                let s = dalek::Signature::from_bytes(s.sig.0).map_err(|_| Error::BadSignature)?;
-                k.verify(message, &s).map_err(|_| Error::BadSignature)
+                let k: &[u8; 32] = k.key.0.try_into().map_err(|_| Error::BadKey)?;
+                let k: salty::PublicKey = k.try_into().map_err(|_| Error::BadKey)?;
+                let s: &[u8; 64] = s.sig.0.try_into().map_err(|_| Error::BadSig)?;
+                let s: salty::Signature = s.into();
+                k.verify(message, &s).map_err(|_| Error::BadSig)
             }
 
             (SigType::RSA256, PubKey::RSA(_k), Signature::RSA256(_s)) => {
                 // TODO
                 warn!("RSA256 is not implemented for no_std");
-                Err(Error::BadSignature)
+                Err(Error::BadSig)
                 // // untested
                 // use rsa::{PublicKey, RsaPrivateKey, RsaPublicKey, PaddingScheme};
                 // let k: RsaPublicKey = k.try_into()?;
@@ -80,7 +83,7 @@ impl SigType {
                 //     s.sig.0)
                 // .map_err(|e| {
                 //     trace!("RSA signature failed: {e}");
-                //     Error::BadSignature
+                //     Error::BadSig
                 // })
             }
 
@@ -89,21 +92,21 @@ impl SigType {
                     sig.algorithm_name(),
                     pubkey.algorithm_name(),
                     );
-                Err(Error::SignatureMismatch)
+                Err(Error::SigMismatch)
             }
         }
     }
 }
 
 pub(crate) enum OwnedSig {
-    // dalek::Signature doesn't let us borrow the inner bytes,
+    // Signature doesn't let us borrow the inner bytes,
     // so we just store raw bytes here.
     Ed25519([u8; 64]),
     RSA256, // TODO
 }
 
-impl From<dalek::Signature> for OwnedSig {
-    fn from(s: dalek::Signature) -> Self {
+impl From<salty::Signature> for OwnedSig {
+    fn from(s: salty::Signature) -> Self {
         OwnedSig::Ed25519(s.to_bytes())
     }
 
@@ -113,7 +116,7 @@ impl From<dalek::Signature> for OwnedSig {
 /// or could potentially send the signing requests to a SSH agent
 /// or other entitiy.
 pub enum SignKey {
-    Ed25519(dalek::Keypair),
+    Ed25519(salty::Keypair),
 }
 
 impl SignKey {
@@ -137,10 +140,9 @@ impl SignKey {
     pub(crate) fn sign_encode<'s>(&self, msg: &'s impl SSHEncode, parse_ctx: Option<&ParseContext>) -> Result<OwnedSig> {
         match self {
             SignKey::Ed25519(k) => {
-                let exk: dalek::ExpandedSecretKey = (&k.secret).into();
-                exk.sign_parts(|h| {
-                    sshwire::hash_ser(h, parse_ctx, msg).map_err(|_| dalek::SignatureError::new())
-                }, &k.public)
+                k.sign_parts(|h| {
+                    sshwire::hash_ser(h, parse_ctx, msg).map_err(|_| salty::Error::ContextTooLong)
+                })
                 .trap()
                 .map(|s| s.into())
             }
@@ -154,13 +156,11 @@ impl TryFrom<ssh_key::PrivateKey> for SignKey {
     fn try_from(k: ssh_key::PrivateKey) -> Result<Self> {
         match k.key_data() {
             ssh_key::private::KeypairData::Ed25519(k) => {
-                let edk = dalek::Keypair {
-                    secret: dalek::SecretKey::from_bytes(&k.private.to_bytes())
-                        .map_err(|_| Error::BadKey)?,
-                    public: dalek::PublicKey::from_bytes(&k.public.0)
-                        .map_err(|_| Error::BadKey)?,
+                let key = salty::Keypair {
+                    secret: (&k.private.to_bytes()).into(),
+                    public: (&k.public.0).try_into().map_err(|_| Error::BadKey)?,
                 };
-                Ok(SignKey::Ed25519(edk))
+                Ok(SignKey::Ed25519(key))
             }
             _ => Err(Error::NotAvailable { what: k.algorithm().as_str() })
         }
@@ -169,7 +169,6 @@ impl TryFrom<ssh_key::PrivateKey> for SignKey {
 
 #[cfg(test)]
 pub(crate) mod tests {
-    use ed25519_dalek::Signer;
 
     use crate::*;
     use sshnames::SSH_NAME_ED25519;
@@ -178,8 +177,9 @@ pub(crate) mod tests {
     use doorlog::init_test_log;
 
     pub(crate) fn make_ed25519_signkey() -> SignKey {
-        let mut rng = random::DoorRng::default();
-        let ed = dalek::Keypair::generate(&mut rng);
+        let mut s = [0u8; 32];
+        random::fill_random(s.as_mut_slice()).unwrap();
+        let ed = salty::Keypair::from(&s);
         sign::SignKey::Ed25519(ed)
     }