Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor ecdsa structure to suit starkbank's pattern #21

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}
Loading