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()); + } +}