-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add some code allowing other languages, via JNI, to interact with ed25519-zebra. The initial commit: - Allows users to obtain a random 32 byte signing key seed. - Allows users to obtain a 32 byte verification key from a signing key seed. - Allows users to sign arbitrary data. - Allows users to verify an Ed25519 signature. - Includes a Java file that can be used. - Includes some Scala-based JNI tests.
- Loading branch information
Douglas Roark
committed
Jan 27, 2021
1 parent
014d823
commit 137c57b
Showing
20 changed files
with
702 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
/target | ||
Cargo.lock | ||
natives/ | ||
target/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
lazy val root = project | ||
.in(file(".")) | ||
.aggregate( | ||
ed25519jni | ||
) | ||
|
||
lazy val ed25519jni = project | ||
.in(file("ed25519jni")) | ||
.settings( | ||
unmanagedResourceDirectories in Compile += baseDirectory.value / "natives" | ||
) | ||
.enablePlugins() | ||
|
||
publishArtifact in root := false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[build] | ||
target-dir = "target/rust" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
[package] | ||
name = "ed25519jni" | ||
version = "0.0.1" | ||
authors = ["Douglas Roark <douglas.roark@gemini.com>"] | ||
license = "MIT OR Apache-2.0" | ||
publish = false | ||
edition = "2018" | ||
|
||
[dependencies] | ||
ed25519-zebra = { path = "../" } | ||
failure = "0.1.8" | ||
jni = "0.18.0" | ||
|
||
[lib] | ||
name = "ed25519jni" | ||
path = "src/main/rust/lib.rs" | ||
crate-type = ["staticlib", "cdylib"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# ed25519_zebra JNI code | ||
Code that allows for any JNI-using language to interact with specific `ed25519-zebra` calls. | ||
|
||
## Compilation | ||
`cargo build` | ||
|
||
## Testing | ||
`sbt test` | ||
|
||
## Usage | ||
The code must be able to access the `libed25519jni` dynamic library / shared object generated by Rust. Once compiled and loaded, code can use the accompanying Java file to access specific `ed25519-zebra`. The calls include but are not necessarily limited to: | ||
|
||
* Generating a random 32 byte signing key seed. | ||
* Generating a 32 byte verification key from a signing key seed. | ||
* Signing arbitrary data with a signing key. | ||
* Verifying a signature for arbitrary data with a verification key. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
name := "ed25519jni" | ||
|
||
version := "0.0.1" | ||
|
||
autoScalaLibrary := false | ||
|
||
crossPaths := false | ||
|
||
libraryDependencies ++= Deps.ed25519jni | ||
|
||
publishArtifact := true | ||
|
||
testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "3") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import sbt._ | ||
|
||
object Deps { | ||
|
||
object V { | ||
val nativeLoaderV = "2.3.4" | ||
val scalaTest = "3.0.5" | ||
} | ||
|
||
object Test { | ||
val nativeLoader = "org.scijava" % "native-lib-loader" % V.nativeLoaderV | ||
val scalaTest = "org.scalatest" %% "scalatest" % V.scalaTest % "test" | ||
} | ||
|
||
val coreTest = List( | ||
Test.nativeLoader, | ||
Test.scalaTest, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
sbt.version=1.4.5 |
122 changes: 122 additions & 0 deletions
122
ed25519jni/src/main/java/org/zfnd/ed25519/Ed25519Interface.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package org.zfnd.ed25519; | ||
|
||
import java.math.BigInteger; | ||
import java.security.SecureRandom; | ||
import org.scijava.nativelib.NativeLoader; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
public class Ed25519Interface { | ||
public static final int SEED_LEN = 32; | ||
|
||
private static final Logger logger; | ||
private static final boolean enabled; | ||
|
||
static { | ||
logger = LoggerFactory.getLogger(Ed25519Interface.class); | ||
boolean isEnabled = true; | ||
|
||
try { | ||
NativeLoader.loadLibrary("ed25519jni"); | ||
} catch (java.io.IOException | UnsatisfiedLinkError e) { | ||
logger.error("Could not find ed25519jni - Interface is not enabled - ", e); | ||
isEnabled = false; | ||
} | ||
enabled = isEnabled; | ||
} | ||
|
||
// Helper method to determine whether the Ed25519 Rust backend is loaded and | ||
// available. | ||
// | ||
// @return whether the Ed25519 Rust backend is enabled | ||
public static boolean isEnabled() { | ||
return enabled; | ||
} | ||
|
||
// Generate a new Ed25519 signing key seed and check the results for validity. This | ||
// code is valid but not canonical. If the Rust code ever adds restrictions on which | ||
// values are allowed, this code will have to stay in sync. | ||
// | ||
// @param rng An initialized, secure RNG | ||
// @return sks 32 byte signing key seed | ||
private static byte[] genSigningKeySeedFromJava(SecureRandom rng) { | ||
byte[] seedBytes = new byte[SEED_LEN]; | ||
rng.nextBytes(seedBytes); | ||
BigInteger sb = new BigInteger(seedBytes); | ||
while(sb == BigInteger.ZERO) { | ||
rng.nextBytes(seedBytes); | ||
sb = new BigInteger(seedBytes); | ||
} | ||
|
||
return seedBytes; | ||
} | ||
|
||
// Public frontend to use when generating a signing key seed. | ||
// | ||
// @return sksb Java object containing an EdDSA signing key seed | ||
// @throws RuntimeException if ??? | ||
public static SigningKeySeed genSigningKeySeed(SecureRandom rng) { | ||
return new SigningKeySeed(genSigningKeySeedFromJava(rng)); | ||
} | ||
|
||
// Check if verification key bytes for a verification key are valid. | ||
// | ||
// @return true if valid, false if not | ||
// @param vk_bytes 32 byte verification key bytes to verify | ||
// @throws RuntimeException if ??? | ||
public static native boolean checkVerificationKeyBytes(byte[] vk_bytes); | ||
|
||
// Get verification key bytes from a signing key seed. | ||
// | ||
// @return vkb 32 byte verification key | ||
// @param sk_seed_bytes 32 byte signing key seed | ||
// @throws RuntimeException if ??? | ||
private static native byte[] getVerificationKeyBytes(byte[] sk_seed_bytes); | ||
|
||
// Get verification key bytes from a signing key seed. | ||
// | ||
// @return vkb VerificationKeyBytes object | ||
// @param sk_seed_bytes SigningKeySeed object | ||
// @throws RuntimeException if ??? | ||
public static VerificationKeyBytes getVerificationKeyBytes(SigningKeySeed sksb) { | ||
return new VerificationKeyBytes(getVerificationKeyBytes(sksb.getSigningKeySeed())); | ||
} | ||
|
||
// Creates a signature on msg using the given signing key. | ||
// | ||
// @return sig 64 byte signature | ||
// @param sk_seed_bytes 32 byte signing key seed | ||
// @param msg Message of arbitrary length to be signed | ||
// @throws RuntimeException if ??? | ||
private static native byte[] sign(byte[] sk_seed_bytes, byte[] msg); | ||
|
||
// Creates a signature on msg using the given signing key. | ||
// | ||
// @return sig 64 byte signature | ||
// @param sk_seed_bytes 32 byte signing key seed | ||
// @param msg Message of arbitrary length to be signed | ||
// @throws RuntimeException if ??? | ||
public static byte[] sign(SigningKeySeed sksb, byte[] msg) { | ||
return sign(sksb.getSigningKeySeed(), msg); | ||
} | ||
|
||
/// Verifies a purported `signature` on the given `msg`. | ||
// | ||
// @return true if verified, false if not | ||
// @param vk_bytes 32 byte verification key bytes | ||
// @param sig 64 byte signature to be verified | ||
// @param msg Message of arbitrary length to be signed | ||
// @throws RuntimeException if ??? | ||
private static native boolean verify(byte[] vk_bytes, byte[] sig, byte[] msg); | ||
|
||
/// Verifies a purported `signature` on the given `msg`. | ||
// | ||
// @return true if verified, false if not | ||
// @param vk_bytes 32 byte verification key bytes | ||
// @param sig 64 byte signature to be verified | ||
// @param msg Message of arbitrary length to be signed | ||
// @throws RuntimeException if ??? | ||
public static boolean verify(VerificationKeyBytes vkb, byte[] sig, byte[] msg) { | ||
return verify(vkb.getVerificationKeyBytes(), sig, msg); | ||
} | ||
} |
62 changes: 62 additions & 0 deletions
62
ed25519jni/src/main/java/org/zfnd/ed25519/SigningKeySeed.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package org.zfnd.ed25519; | ||
|
||
import java.util.Arrays; | ||
import java.util.Optional; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
// Java wrapper class for signing key seeds that performs some sanity checking. | ||
public class SigningKeySeed { | ||
public static final int SEED_LENGTH = 32; | ||
private static final Logger logger = LoggerFactory.getLogger(SigningKeySeed.class); | ||
private byte[] seed = new byte[SEED_LENGTH]; | ||
|
||
// Determining if bytes are valid is pretty trivial. Rust code not needed. | ||
private static boolean bytesAreValid(final byte[] seedBytes) { | ||
boolean valid = false; | ||
if(seedBytes.length == SEED_LENGTH) { | ||
for (int b = 0; b < SEED_LENGTH; b++) { | ||
if (seedBytes[b] != 0) { | ||
valid = true; | ||
break; | ||
} | ||
} | ||
} | ||
|
||
return valid; | ||
} | ||
|
||
public SigningKeySeed(final byte[] seedBytes) { | ||
if(bytesAreValid(seedBytes)) { | ||
seed = Arrays.copyOf(seedBytes, SEED_LENGTH); | ||
} | ||
else { | ||
throw new IllegalArgumentException("Attempted to create invalid signing " | ||
+ "key seed - Bytes were invalid"); | ||
} | ||
} | ||
|
||
public byte[] getSigningKeySeed() { | ||
return seed; | ||
} | ||
|
||
public static Optional<SigningKeySeed> fromBytes(final byte[] seedBytes) { | ||
Optional<SigningKeySeed> sks = Optional.empty(); | ||
|
||
try { | ||
sks = Optional.of(new SigningKeySeed(seedBytes)); | ||
} | ||
catch (IllegalArgumentException e) { | ||
logger.error("Attempted to create invalid signing key seed - Illegal " | ||
+ "argument exception has been caught and ignored"); | ||
} | ||
finally { | ||
return sks; | ||
} | ||
} | ||
|
||
public static SigningKeySeed fromBytesOrThrow(final byte[] seedBytes) { | ||
// The constructor already throws, so this method can YOLO the creation. | ||
return new SigningKeySeed(seedBytes); | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
ed25519jni/src/main/java/org/zfnd/ed25519/VerificationKeyBytes.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package org.zfnd.ed25519; | ||
|
||
import java.util.Arrays; | ||
import java.util.Optional; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
// Java wrapper class for verification key bytes that performs some sanity checking. | ||
public class VerificationKeyBytes { | ||
private static final int BYTES_LENGTH = 32; | ||
private static final Logger logger = LoggerFactory.getLogger(VerificationKeyBytes.class); | ||
private byte[] vkb = new byte[BYTES_LENGTH]; | ||
|
||
// Determining if bytes are valid is complicated. Call into Rust. | ||
private static boolean bytesAreValid(final byte[] verificationKeyBytes) { | ||
if(verificationKeyBytes.length == BYTES_LENGTH) { | ||
return Ed25519Interface.checkVerificationKeyBytes(verificationKeyBytes); | ||
} | ||
else { | ||
return false; | ||
} | ||
} | ||
|
||
public VerificationKeyBytes(final byte[] verificationKeyBytes) { | ||
if(bytesAreValid(verificationKeyBytes)) { | ||
vkb = Arrays.copyOf(verificationKeyBytes, BYTES_LENGTH); | ||
} | ||
else { | ||
throw new IllegalArgumentException("Attempted to create invalid " | ||
+ "verification key bytes (input not valid)"); | ||
} | ||
} | ||
|
||
public byte[] getVerificationKeyBytes() { | ||
return vkb; | ||
} | ||
|
||
public static Optional<VerificationKeyBytes> fromBytes(final byte[] verificationKeyBytes) { | ||
Optional<VerificationKeyBytes> vkb = Optional.empty(); | ||
|
||
try { | ||
vkb = Optional.of(new VerificationKeyBytes(verificationKeyBytes)); | ||
} | ||
catch (IllegalArgumentException e) { | ||
logger.error("Attempted to create invalid verification key bytes - Illegal " | ||
+ "argument exception has been caught and ignored"); | ||
} | ||
finally { | ||
return vkb; | ||
} | ||
} | ||
|
||
public static VerificationKeyBytes fromBytesOrThrow(final byte[] verificationKeyBytes) { | ||
// The constructor already throws, so this method can YOLO the creation. | ||
return new VerificationKeyBytes(verificationKeyBytes); | ||
} | ||
} |
Oops, something went wrong.