diff --git a/core/java/src/net/i2p/crypto/Blinding.java b/core/java/src/net/i2p/crypto/Blinding.java
index 22be07a009b8b4eb389279b4c050112eaa7a6da2..66250830523febaf14bcdd549df857cc330f24d7 100644
--- a/core/java/src/net/i2p/crypto/Blinding.java
+++ b/core/java/src/net/i2p/crypto/Blinding.java
@@ -1,10 +1,16 @@
 package net.i2p.crypto;
 
 import java.security.GeneralSecurityException;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+import java.util.TimeZone;
 
+import net.i2p.I2PAppContext;
 import net.i2p.crypto.eddsa.EdDSABlinding;
 import net.i2p.crypto.eddsa.EdDSAPrivateKey;
 import net.i2p.crypto.eddsa.EdDSAPublicKey;
+import net.i2p.data.DataHelper;
+import net.i2p.data.Destination;
 import net.i2p.data.Hash;
 import net.i2p.data.SigningPrivateKey;
 import net.i2p.data.SigningPublicKey;
@@ -20,6 +26,15 @@ public final class Blinding {
 
     private static final SigType TYPE = SigType.EdDSA_SHA512_Ed25519;
     private static final SigType TYPER = SigType.RedDSA_SHA512_Ed25519;
+    private static final String INFO = "i2pblinding1";
+
+    // following copied from RouterKeyGenerator
+    private static final String FORMAT = "yyyyMMdd";
+    private static final int LENGTH = FORMAT.length();
+    private static final SimpleDateFormat _fmt = new SimpleDateFormat(FORMAT, Locale.US);
+    static {
+        _fmt.setTimeZone(TimeZone.getTimeZone("GMT"));
+    }
 
     private Blinding() {}
 
@@ -89,6 +104,40 @@ public final class Blinding {
         }
     }
 
+    /**
+     *  Only for SigType EdDSA_SHA512_Ed25519.
+     *
+     *  @param dest spk must be SigType EdDSA_SHA512_Ed25519
+     *  @param secret may be null or zero-length
+     *  @return SigType RedDSA_SHA512_Ed25519
+     *  @throws UnsupportedOperationException unless supported SigTypes
+     *  @throws IllegalArgumentException on bad inputs
+     *  @since 0.9.39
+     */
+    public static SigningPrivateKey generateAlpha(I2PAppContext ctx, Destination dest, String secret) {
+        long now = ctx.clock().now();
+        String modVal;
+        synchronized(_fmt) {
+            modVal = _fmt.format(now);
+        }
+        if (modVal.length() != LENGTH)
+            throw new IllegalStateException();
+        byte[] mod = DataHelper.getASCII(modVal);
+        byte[] data;
+        if (secret != null && secret.length() > 0) {
+            data = new byte[LENGTH + secret.length()];
+            System.arraycopy(mod, 0, data, 0, LENGTH);
+            System.arraycopy(DataHelper.getASCII(secret), 0, data, LENGTH, secret.length());
+        } else {
+            data = mod;
+        }
+        HKDF hkdf = new HKDF(ctx);
+        byte[] out = new byte[64];
+        hkdf.calculate(dest.getHash().getData(), data, INFO, out, out, 32);
+        byte[] b = EdDSABlinding.reduce(out);
+        return new SigningPrivateKey(TYPER, b);
+    }
+
 /******
     public static void main(String args[]) throws Exception {
         net.i2p.data.SimpleDataStructure[] keys = KeyGenerator.getInstance().generateSigningKeys(TYPE);
diff --git a/core/java/src/net/i2p/crypto/DSAEngine.java b/core/java/src/net/i2p/crypto/DSAEngine.java
index ce505c82196be56d9283d1b2c7773f3f81a0e164..805750edaed0f7e566ea255d13aaa4265e219fa2 100644
--- a/core/java/src/net/i2p/crypto/DSAEngine.java
+++ b/core/java/src/net/i2p/crypto/DSAEngine.java
@@ -44,6 +44,7 @@ import java.security.interfaces.RSAKey;
 import net.i2p.I2PAppContext;
 import net.i2p.crypto.eddsa.EdDSAEngine;
 import net.i2p.crypto.eddsa.EdDSAKey;
+import net.i2p.crypto.eddsa.RedDSAEngine;
 import net.i2p.data.Hash;
 import net.i2p.data.Signature;
 import net.i2p.data.SigningPrivateKey;
@@ -520,7 +521,8 @@ public final class DSAEngine {
         boolean rv;
         if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
             // take advantage of one-shot mode
-            EdDSAEngine jsig = new EdDSAEngine(type.getDigestInstance());
+            MessageDigest md = type.getDigestInstance();
+            EdDSAEngine jsig = (type == SigType.RedDSA_SHA512_Ed25519) ? new RedDSAEngine(md) : new EdDSAEngine(md);
             jsig.initVerify(pubKey);
             rv = jsig.verifyOneShot(data, offset, len, sigbytes);
         } else {
@@ -573,7 +575,7 @@ public final class DSAEngine {
         if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
             // take advantage of one-shot mode
             // Ignore algo, EdDSAKey includes a hash specification.
-            EdDSAEngine jsig = new EdDSAEngine();
+            EdDSAEngine jsig = (type == SigType.RedDSA_SHA512_Ed25519) ? new RedDSAEngine() : new EdDSAEngine();
             jsig.initVerify(pubKey);
             rv = jsig.verifyOneShot(hash.getData(), sigbytes);
         } else {
@@ -621,7 +623,8 @@ public final class DSAEngine {
         byte[] sigbytes;
         if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
             // take advantage of one-shot mode
-            EdDSAEngine jsig = new EdDSAEngine(type.getDigestInstance());
+            MessageDigest md = type.getDigestInstance();
+            EdDSAEngine jsig = (type == SigType.RedDSA_SHA512_Ed25519) ? new RedDSAEngine(md) : new EdDSAEngine(md);
             jsig.initSign(privKey);
             sigbytes = jsig.signOneShot(data, offset, len);
         } else {
@@ -669,7 +672,7 @@ public final class DSAEngine {
         if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
             // take advantage of one-shot mode
             // Ignore algo, EdDSAKey includes a hash specification.
-            EdDSAEngine jsig = new EdDSAEngine();
+            EdDSAEngine jsig = (type == SigType.RedDSA_SHA512_Ed25519) ? new RedDSAEngine() : new EdDSAEngine();
             jsig.initSign(privKey);
             sigbytes = jsig.signOneShot(hash.getData());
         } else {
diff --git a/core/java/src/net/i2p/crypto/eddsa/EdDSAEngine.java b/core/java/src/net/i2p/crypto/eddsa/EdDSAEngine.java
index e6756bad704c4f942477734b4072255021404c2d..d58e9ec902a6dc47314908f316f1e4d6d2112567 100644
--- a/core/java/src/net/i2p/crypto/eddsa/EdDSAEngine.java
+++ b/core/java/src/net/i2p/crypto/eddsa/EdDSAEngine.java
@@ -55,10 +55,10 @@ import net.i2p.crypto.eddsa.math.ScalarOps;
  * @author str4d
  *
  */
-public final class EdDSAEngine extends Signature {
+public class EdDSAEngine extends Signature {
     public static final String SIGNATURE_ALGORITHM = "NONEwithEdDSA";
 
-    private MessageDigest digest;
+    protected MessageDigest digest;
     private ByteArrayOutputStream baos;
     private EdDSAKey key;
     private boolean oneShotMode;
@@ -129,7 +129,7 @@ public final class EdDSAEngine extends Signature {
         }
     }
 
-    private void digestInitSign(EdDSAPrivateKey privKey) {
+    protected void digestInitSign(EdDSAPrivateKey privKey) {
         // Preparing for hash
         // r = H(h_b,...,h_2b-1,M)
         int b = privKey.getParams().getCurve().getField().getb();
diff --git a/core/java/src/net/i2p/crypto/eddsa/RedDSAEngine.java b/core/java/src/net/i2p/crypto/eddsa/RedDSAEngine.java
new file mode 100644
index 0000000000000000000000000000000000000000..6060578b72a5051597f890cc1ab0634656371c84
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/RedDSAEngine.java
@@ -0,0 +1,70 @@
+package net.i2p.crypto.eddsa;
+
+import java.security.MessageDigest;
+
+import net.i2p.util.RandomSource;
+
+/**
+ * Signing and verification for REdDSA using SHA-512 and the Ed25519 curve.
+ * Ref: Zcash Protocol Specification, Version 2018.0-beta-33 [Overwinter+Sapling]
+ * Sections 4.1.6.1, 4.1.6.2, 5.4.6
+ *
+ *<p>
+ * The EdDSA sign and verify algorithms do not interact well with
+ * the Java Signature API, as one or more update() methods must be
+ * called before sign() or verify(). Using the standard API,
+ * this implementation must copy and buffer all data passed in
+ * via update().
+ *</p><p>
+ * This implementation offers two ways to avoid this copying,
+ * but only if all data to be signed or verified is available
+ * in a single byte array.
+ *</p><p>
+ *Option 1:
+ *</p><ol>
+ *<li>Call initSign() or initVerify() as usual.
+ *</li><li>Call setParameter(ONE_SHOT_MODE)
+ *</li><li>Call update(byte[]) or update(byte[], int, int) exactly once
+ *</li><li>Call sign() or verify() as usual.
+ *</li><li>If doing additional one-shot signs or verifies with this object, you must
+ *         call setParameter(ONE_SHOT_MODE) each time
+ *</li></ol>
+ *
+ *<p>
+ *Option 2:
+ *</p><ol>
+ *<li>Call initSign() or initVerify() as usual.
+ *</li><li>Call one of the signOneShot() or verifyOneShot() methods.
+ *</li><li>If doing additional one-shot signs or verifies with this object,
+ *         just call signOneShot() or verifyOneShot() again.
+ *</li></ol>
+ *
+ * @since 0.9.39
+ */
+public final class RedDSAEngine extends EdDSAEngine {
+
+    /**
+     * No specific EdDSA-internal hash requested, allows any EdDSA key.
+     */
+    public RedDSAEngine() {
+        super();
+    }
+
+    /**
+     * Specific EdDSA-internal hash requested, only matching keys will be allowed.
+     * @param digest the hash algorithm that keys must have to sign or verify.
+     */
+    public RedDSAEngine(MessageDigest digest) {
+        super(digest);
+    }
+
+    @Override
+    protected void digestInitSign(EdDSAPrivateKey privKey) {
+        // Preparing for hash
+        // r = H(T, pubkey, M)
+        byte[] t = new byte[digest.getDigestLength() + 16];
+        RandomSource.getInstance().nextBytes(t);
+        digest.update(t);
+        digest.update(privKey.getAbyte());
+    }
+}