Skip to content

Commit

Permalink
Refactor ecdsa structure to suit starkbank's pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
xavier-stark committed Dec 6, 2022
1 parent 1a13370 commit 6ccee2e
Show file tree
Hide file tree
Showing 23 changed files with 1,006 additions and 2,951 deletions.
143 changes: 101 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ We ran a test on JDK 13.0.1 on a MAC Pro i5 2019. The libraries ran 100 times an
| Library | sign | verify |
| ------------------ |:-------------:| -------:|
| [java.security] | 0.9ms | 2.4ms |
| starkbank-ecdsa | 4.3ms | 9.9ms |
| starkbank-ecdsa | 250.6ms | 30.7ms |

### Sample Code

How to use it:
How to sign a json message for [Stark Bank]:

```java
import com.starkbank.ellipticcurve.PrivateKey;
Expand All @@ -46,26 +46,98 @@ import com.starkbank.ellipticcurve.Signature;
import com.starkbank.ellipticcurve.Ecdsa;


public class GenerateKeys{
// Generate privateKey from PEM string
PrivateKey privateKey = PrivateKey.fromPem("-----BEGIN EC PARAMETERS-----\nBgUrgQQACg==\n-----END EC PARAMETERS-----\n-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIODvZuS34wFbt0X53+P5EnSj6tMjfVK01dD1dgDH02RzoAcGBSuBBAAK\noUQDQgAE/nvHu/SQQaos9TUljQsUuKI15Zr5SabPrbwtbfT/408rkVVzq8vAisbB\nRmpeRREXj5aog/Mq8RrdYy75W9q/Ig==\n-----END EC PRIVATE KEY-----");

public static void main(String[] args){
// Generate Keys
PrivateKey privateKey = new PrivateKey();
PublicKey publicKey = privateKey.publicKey();
// Create message from json
String message = "{'transfers':[{'amount':100000000,'taxId':'594.739.480-42','name':'Daenerys Targaryen Stormborn','bankCode':'341','branchCode':'2201','accountNumber':'76543-8','tags':['daenerys','targaryen','transfer-1-external-id']}]}'";

String message = "Testing message";
// Generate Signature
Signature signature = Ecdsa.sign(message, privateKey);
Signature signature = Ecdsa.sign(message, privateKey);

// Verify if signature is valid
boolean verified = Ecdsa.verify(message, signature, publicKey) ;
// Generate Signature in base64. This result can be sent to Stark Bank in the request header as the Digital-Signature parameter.
System.out.println(signature.toBase64());

// Return the signature verification status
System.out.println("Verified: " + verified);
// To double check if the message matches the signature, do this:
PublicKey publicKey = privateKey.publicKey();

}
}
System.out.println(Ecdsa.verify(message, signature, publicKey));
```

Simple use:

```java
import com.starkbank.ellipticcurve.PrivateKey;
import com.starkbank.ellipticcurve.Ecdsa;


// Generate new Keys
PrivateKey privateKey = new PrivateKey();
PublicKey publicKey = privateKey.publicKey();

String message = "My test message";

// Generate Signature
Signature signature = Ecdsa.sign(message, privateKey);

// To verify if the signature is valid
System.out.println(Ecdsa.verify(message, signature, publicKey));

```

How to add more curves:

```java
import com.starkbank.ellipticcurve.PrivateKey;
import com.starkbank.ellipticcurve.PublicKey;
import com.starkbank.ellipticcurve.Curve;
import java.math.BigInteger;


Curve newCurve = new Curve(
new BigInteger("f1fd178c0b3ad58f10126de8ce42435b3961adbcabc8ca6de8fcf353d86e9c00", 16),
new BigInteger("ee353fca5428a9300d4aba754a44c00fdfec0c9ae4b1a1803075ed967b7bb73f", 16),
new BigInteger("f1fd178c0b3ad58f10126de8ce42435b3961adbcabc8ca6de8fcf353d86e9c03", 16),
new BigInteger("f1fd178c0b3ad58f10126de8ce42435b53dc67e140d2bf941ffdd459c6d655e1", 16),
new BigInteger("b6b3d4c356c139eb31183d4749d423958c27d2dcaf98b70164c97a2dd98f5cff", 16),
new BigInteger("6142e0f7c8b204911f9271f0f3ecef8c2701c307e8e4c9e183115a1554062cfb", 16),
"frp256v1",
new long[]{1, 2, 250, 1, 223, 101, 256, 1}
);

Curve.add(newCurve);

String publicKeyPem = "-----BEGIN PUBLIC KEY-----\nMFswFQYHKoZIzj0CAQYKKoF6AYFfZYIAAQNCAATeEFFYiQL+HmDYTf+QDmvQmWGD\ndRJPqLj11do8okvkSxq2lwB6Ct4aITMlCyg3f1msafc/ROSN/Vgj69bDhZK6\n-----END PUBLIC KEY-----";

PublicKey publicKey = PublicKey.fromPem(publicKeyPem);

System.out.println(publicKey.toPem());
```

How to generate compressed public key:

```java
import com.starkbank.ellipticcurve.PrivateKey;
import com.starkbank.ellipticcurve.PublicKey;

PrivateKey privateKey = new PrivateKey();
PublicKey publicKey = privateKey.publicKey();
String compressedPublicKey = publicKey.toCompressed();

System.out.println(compressedPublicKey);
```

How to recover a compressed public key:

```java
import com.starkbank.ellipticcurve.PrivateKey;
import com.starkbank.ellipticcurve.PublicKey;

String compressedPublicKey = "0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2";
PublicKey publicKey = PublicKey.fromCompressed(compressedPublicKey);

System.out.println(publicKey.toPem());
```

### OpenSSL

This library is compatible with OpenSSL, so you can use it to generate keys:
Expand All @@ -91,24 +163,16 @@ import com.starkbank.ellipticcurve.utils.ByteString;
import com.starkbank.ellipticcurve.utils.File;


public class VerifyKeys {

public static void main(String[] args){
// Read files
String publicKeyPem = File.read("publicKey.pem");
byte[] signatureBin = File.readBytes("signatureBinary.txt");
String message = File.read("message.txt");
// Read files
String publicKeyPem = Utils.readFileAsString("publicKey.pem");
byte[] signatureBin = Utils.readFileAsBytes("signature.binary");
String message = Utils.readFileAsString("message.txt");

ByteString byteString = new ByteString(signatureBin);
PublicKey publicKey = PublicKey.fromPem(publicKeyPem);
Signature signature = Signature.fromDer(signatureBin);

PublicKey publicKey = PublicKey.fromPem(publicKeyPem);
Signature signature = Signature.fromDer(byteString);

// Get verification status:
boolean verified = Ecdsa.verify(message, signature, publicKey);
System.out.println("Verification status: " + verified);
}
}
// Get verification status:
System.out.println(Ecdsa.verify(message, signature, publicKey));
```

You can also verify it on terminal:
Expand All @@ -131,16 +195,11 @@ import com.starkbank.ellipticcurve.Signature;
import com.starkbank.ellipticcurve.utils.File;


public class GenerateSignature {

public static void main(String[] args) {
// Load signature file
byte[] signatureBin = File.readBytes("signatureBinary.txt");
Signature signature = Signature.fromDer(new ByteString(signatureBin));
// Print signature
System.out.println(signature.toBase64());
}
}
// Load signature file
byte[] signatureBin = File.readBytes("signatureBinary.txt");
Signature signature = Signature.fromDer(signatureBin);
// Print signature
System.out.println(signature.toBase64());
```

[Stark Bank]: https://starkbank.com
Expand Down
53 changes: 37 additions & 16 deletions src/main/java/com/starkbank/ellipticcurve/Curve.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
* y^2 = x^3 + A*x + B (mod P)
*
*/

public class Curve {

public BigInteger A;
Expand Down Expand Up @@ -69,9 +68,15 @@ public int length() {
return (1 + N.toString(16).length()) / 2;
}

/**
*
*/
public BigInteger y(BigInteger x, Boolean isEven) {
BigInteger ySquared = (x.modPow(BigInteger.valueOf(3), this.P).add(this.A.multiply(x)).add(this.B)).mod(this.P);
BigInteger y = Math.modularSquareRoot(ySquared, this.P);
if (isEven != y.mod(BigInteger.TWO).equals(BigInteger.ZERO)) {
return y = this.P.subtract(y);
}
return y;
}

public static final Curve secp256k1 = new Curve(
BigInteger.ZERO,
BigInteger.valueOf(7),
Expand All @@ -83,22 +88,38 @@ public int length() {
new long[]{1, 3, 132, 0, 10}
);

/**
*
*/
public static final List supportedCurves = new ArrayList();
public static final Curve prime256v1 = new Curve(
new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16),
new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16),
new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16),
new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16),
new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16),
new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16),
"prime256v1",
new long[]{1, 2, 840, 10045, 3, 1, 7}
);

/**
*
*/
public static final Map curvesByOid = new HashMap();

static {
supportedCurves.add(secp256k1);

for (Object c : supportedCurves) {
Curve curve = (Curve) c;
curvesByOid.put(Arrays.hashCode(curve.oid), curve);
public static void add(Curve curve) {
curvesByOid.put(Arrays.hashCode(curve.oid), curve);
}

public static Curve getByOid(long[] oid) {
if(!curvesByOid.containsKey(Arrays.hashCode(oid))) {
throw new Error("Unknown curve with oid {oid}; The following are registered: {names}");
}
return (Curve) curvesByOid.get(Arrays.hashCode(oid));
}

public static Curve getByOid(Object oid) {
return getByOid((long[]) oid);
}

static {
add(secp256k1);
add(prime256v1);
}

}
40 changes: 32 additions & 8 deletions src/main/java/com/starkbank/ellipticcurve/Ecdsa.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.starkbank.ellipticcurve;
import com.starkbank.ellipticcurve.utils.BinaryAscii;
import com.starkbank.ellipticcurve.utils.Binary;
import com.starkbank.ellipticcurve.utils.RandomInteger;
import java.math.BigInteger;
import java.security.MessageDigest;
Expand All @@ -18,13 +18,26 @@ public class Ecdsa {

public static Signature sign(String message, PrivateKey privateKey, MessageDigest hashfunc) {
byte[] hashMessage = hashfunc.digest(message.getBytes());
BigInteger numberMessage = BinaryAscii.numberFromString(hashMessage);
BigInteger numberMessage = numberFromString(hashMessage);
Curve curve = privateKey.curve;
BigInteger randNum = RandomInteger.between(BigInteger.ONE, curve.N);
Point randomSignPoint = Math.multiply(curve.G, randNum, curve.N, curve.A, curve.P);
BigInteger r = randomSignPoint.x.mod(curve.N);
BigInteger s = ((numberMessage.add(r.multiply(privateKey.secret))).multiply(Math.inv(randNum, curve.N))).mod(curve.N);
return new Signature(r, s);

BigInteger r = BigInteger.ZERO;
BigInteger s = BigInteger.ZERO;
Point randomSignPoint = null;

while(r.equals(BigInteger.ZERO) || s.equals(BigInteger.ZERO)) {
BigInteger randNum = RandomInteger.between(BigInteger.ONE, curve.N.subtract(BigInteger.ONE));
randomSignPoint = Math.multiply(curve.G, randNum, curve.N, curve.A, curve.P);
r = randomSignPoint.x.mod(curve.N);
s = ((numberMessage.add(r.multiply(privateKey.secret))).multiply(Math.inv(randNum, curve.N))).mod(curve.N);
}
BigInteger recoveryId = randomSignPoint.y.and(BigInteger.ONE);

if(randomSignPoint.y.compareTo(curve.N) == 1){
recoveryId = recoveryId.add(BigInteger.valueOf(2));
}

return new Signature(r, s, recoveryId);
}

/**
Expand All @@ -51,7 +64,7 @@ public static Signature sign(String message, PrivateKey privateKey) {
*/
public static boolean verify(String message, Signature signature, PublicKey publicKey, MessageDigest hashfunc) {
byte[] hashMessage = hashfunc.digest(message.getBytes());
BigInteger numberMessage = BinaryAscii.numberFromString(hashMessage);
BigInteger numberMessage = numberFromString(hashMessage);
Curve curve = publicKey.curve;
BigInteger r = signature.r;
BigInteger s = signature.s;
Expand Down Expand Up @@ -93,4 +106,15 @@ public static boolean verify(String message, Signature signature, PublicKey publ
throw new IllegalStateException("Could not find SHA-256 message digest in provided java environment");
}
}

public static String stringFromNumber(BigInteger number, int length) {
String fmtStr = "%0" + String.valueOf(2 * length) + "x";
String hexString = String.format(fmtStr, number);
return Binary.byteStringFromHex(hexString).toString();
}

public static BigInteger numberFromString(byte[] string) {
return new BigInteger(Binary.hexFromByteString(string), 16);
}

}
20 changes: 11 additions & 9 deletions src/main/java/com/starkbank/ellipticcurve/Math.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

public final class Math {

public static BigInteger modularSquareRoot(BigInteger value, BigInteger prime) {
return value.modPow((prime.add(BigInteger.ONE).divide(BigInteger.valueOf(4))), prime);
}

/**
* Fast way to multiply point and scalar in elliptic curves
*
Expand All @@ -27,7 +31,6 @@ public static Point multiply(Point p, BigInteger n, BigInteger N, BigInteger A,
* @param P Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod P)
* @return Point that represents the sum of First and Second Point
*/

public static Point add(Point p, Point q, BigInteger A, BigInteger P) {
return fromJacobian(jacobianAdd(toJacobian(p), toJacobian(q), A, P), P);
}
Expand Down Expand Up @@ -67,7 +70,6 @@ public static BigInteger inv(BigInteger x, BigInteger n) {
* @return Point in Jacobian coordinates
*/
public static Point toJacobian(Point p) {

return new Point(p.x, p.y, BigInteger.ONE);
}

Expand Down Expand Up @@ -100,9 +102,9 @@ public static Point jacobianDouble(Point p, BigInteger A, BigInteger P) {
BigInteger ysq = p.y.pow(2).mod(P);
BigInteger S = BigInteger.valueOf(4).multiply(p.x).multiply(ysq).mod(P);
BigInteger M = BigInteger.valueOf(3).multiply(p.x.pow(2)).add(A.multiply(p.z.pow(4))).mod(P);
BigInteger nx = M.pow(2).subtract(BigInteger.valueOf(2).multiply(S)).mod(P);
BigInteger nx = M.pow(2).subtract(BigInteger.TWO.multiply(S)).mod(P);
BigInteger ny = M.multiply(S.subtract(nx)).subtract(BigInteger.valueOf(8).multiply(ysq.pow(2))).mod(P);
BigInteger nz = BigInteger.valueOf(2).multiply(p.y).multiply(p.z).mod(P);
BigInteger nz = BigInteger.TWO.multiply(p.y).multiply(p.z).mod(P);
return new Point(nx, ny, nz);
}

Expand Down Expand Up @@ -137,7 +139,7 @@ public static Point jacobianAdd(Point p, Point q, BigInteger A, BigInteger P) {
BigInteger H2 = H.multiply(H).mod(P);
BigInteger H3 = H.multiply(H2).mod(P);
BigInteger U1H2 = U1.multiply(H2).mod(P);
BigInteger nx = R.pow(2).subtract(H3).subtract(BigInteger.valueOf(2).multiply(U1H2)).mod(P);
BigInteger nx = R.pow(2).subtract(H3).subtract(BigInteger.TWO.multiply(U1H2)).mod(P);
BigInteger ny = R.multiply(U1H2.subtract(nx)).subtract(S1.multiply(H3)).mod(P);
BigInteger nz = H.multiply(p.z).multiply(q.z).mod(P);
return new Point(nx, ny, nz);
Expand All @@ -163,11 +165,11 @@ public static Point jacobianMultiply(Point p, BigInteger n, BigInteger N, BigInt
if (n.compareTo(BigInteger.ZERO) < 0 || n.compareTo(N) >= 0) {
return jacobianMultiply(p, n.mod(N), N, A, P);
}
if (n.mod(BigInteger.valueOf(2)).compareTo(BigInteger.ZERO) == 0) {
return jacobianDouble(jacobianMultiply(p, n.divide(BigInteger.valueOf(2)), N, A, P), A, P);
if (n.mod(BigInteger.TWO).compareTo(BigInteger.ZERO) == 0) {
return jacobianDouble(jacobianMultiply(p, n.divide(BigInteger.TWO), N, A, P), A, P);
}
if (n.mod(BigInteger.valueOf(2)).compareTo(BigInteger.ONE) == 0) {
return jacobianAdd(jacobianDouble(jacobianMultiply(p, n.divide(BigInteger.valueOf(2)), N, A, P), A, P), p, A, P);
if (n.mod(BigInteger.TWO).compareTo(BigInteger.ONE) == 0) {
return jacobianAdd(jacobianDouble(jacobianMultiply(p, n.divide(BigInteger.TWO), N, A, P), A, P), p, A, P);
}
return null;
}
Expand Down
Loading

0 comments on commit 6ccee2e

Please sign in to comment.