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 b1963e5
Show file tree
Hide file tree
Showing 25 changed files with 966 additions and 2,961 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Given a version number MAJOR.MINOR.PATCH, increment:


## [Unreleased]
### Changed
- internal structure to suit starkbank's pattern

### Fixed
- groupId in pom.xml
Expand Down
149 changes: 105 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,20 @@ mvn clean install

### Curves

We currently support `secp256k1`, but it's super easy to add more curves to the project. Just add them on `Curve.java`
We currently support `secp256k1`, but you can add more curves to the project. You just need to use the `Curve.add()` function.

### Speed

We ran a test on JDK 13.0.1 on a MAC Pro i5 2019. The libraries ran 100 times and showed the average times displayed bellow:
We ran a test on JDK 13.0.1 on a MAC Air M1 2020. The libraries ran 100 times and showed the average times displayed bellow:

| Library | sign | verify |
| ------------------ |:-------------:| -------:|
| [java.security] | 0.9ms | 2.4ms |
| starkbank-ecdsa | 4.3ms | 9.9ms |
| starkbank-ecdsa | 2.5ms | 3.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,100 @@ 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 +165,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 +197,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
73 changes: 54 additions & 19 deletions src/main/java/com/starkbank/ellipticcurve/Curve.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
import java.math.BigInteger;
import java.util.*;


/**
* Elliptic Curve Equation.
* y^2 = x^3 + A*x + B (mod P)
*
*/

public class Curve {

public BigInteger A;
Expand All @@ -16,6 +16,7 @@ public class Curve {
public BigInteger N;
public Point G;
public String name;
public String nistName;
public long[] oid;

/**
Expand All @@ -27,18 +28,30 @@ public class Curve {
* @param Gx Gx
* @param Gy Gy
* @param name name
* @param nistName nistName
* @param oid oid
*/
public Curve(BigInteger A, BigInteger B, BigInteger P, BigInteger N, BigInteger Gx, BigInteger Gy, String name, long[] oid) {
public Curve(
BigInteger A, BigInteger B, BigInteger P, BigInteger N, BigInteger Gx,
BigInteger Gy, String name, String nistName, long[] oid
) {
this.A = A;
this.B = B;
this.P = P;
this.N = N;
this.G = new Point(Gx, Gy);
this.name = name;
this.nistName = nistName;
this.oid = oid;
}

public Curve(
BigInteger A, BigInteger B, BigInteger P, BigInteger N, BigInteger Gx,
BigInteger Gy, String name, long[] oid
) {
this(A, B, P, N, Gx, Gy, name, null, oid);
}

/**
* Verify if the point `p` is on the curve
*
Expand Down Expand Up @@ -69,9 +82,32 @@ 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.valueOf(2)).equals(BigInteger.ZERO)) {
return y = this.P.subtract(y);
}
return y;
}

public static final Map<Integer, Curve> curvesByOid = new HashMap<Integer, Curve>();

public static void add(Curve curve) {
curvesByOid.put(Arrays.hashCode(curve.oid), curve);
}

public static Curve getByOid(long[] oid) {
String[] names = new String[curvesByOid.size()];
for (int i = 0; i < names.length; i++) {
names[i] = ((Curve) curvesByOid.values().toArray()[i]).name;
}
if(!curvesByOid.containsKey(Arrays.hashCode(oid))) {
throw new Error("Unknown curve with oid " + Arrays.toString(oid) + "; The following are registered: " + Arrays.toString(names));
}
return (Curve) curvesByOid.get(Arrays.hashCode(oid));
}

public static final Curve secp256k1 = new Curve(
BigInteger.ZERO,
BigInteger.valueOf(7),
Expand All @@ -83,22 +119,21 @@ 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();
public static final Curve p256 = prime256v1;

static {
supportedCurves.add(secp256k1);

for (Object c : supportedCurves) {
Curve curve = (Curve) c;
curvesByOid.put(Arrays.hashCode(curve.oid), curve);
}
add(secp256k1);
add(prime256v1);
}
}
34 changes: 22 additions & 12 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 @@ -15,16 +15,26 @@ public class Ecdsa {
* @param hashfunc hashfunc
* @return Signature
*/

public static Signature sign(String message, PrivateKey privateKey, MessageDigest hashfunc) {
byte[] hashMessage = hashfunc.digest(message.getBytes());
BigInteger numberMessage = BinaryAscii.numberFromString(hashMessage);
BigInteger numberMessage = Binary.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) > 0){
recoveryId = recoveryId.add(BigInteger.valueOf(2));
}

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

/**
Expand All @@ -51,7 +61,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 = Binary.numberFromString(hashMessage);
Curve curve = publicKey.curve;
BigInteger r = signature.r;
BigInteger s = signature.s;
Expand All @@ -69,9 +79,9 @@ public static boolean verify(String message, Signature signature, PublicKey publ
return false;
}

BigInteger w = Math.inv(s, curve.N);
Point u1 =Math.multiply(curve.G, numberMessage.multiply(w).mod(curve.N), curve.N, curve.A, curve.P);
Point u2 = Math.multiply(publicKey.point, r.multiply(w).mod(curve.N), curve.N, curve.A, curve.P);
BigInteger inv = Math.inv(s, curve.N);
Point u1 = Math.multiply(curve.G, numberMessage.multiply(inv).mod(curve.N), curve.N, curve.A, curve.P);
Point u2 = Math.multiply(publicKey.point, r.multiply(inv).mod(curve.N), curve.N, curve.A, curve.P);
Point v = Math.add(u1, u2, curve.A, curve.P);
if (v.isAtInfinity()) {
return false;
Expand Down
Loading

0 comments on commit b1963e5

Please sign in to comment.