Skip to content
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
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
!/LICENSE
!/README.md
!/package.json
!/dist/**/*
!/ellipticcurve/**/*
186 changes: 49 additions & 137 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,164 +1,76 @@
## A lightweight and fast ECDSA implementation
# ECDSA Node TypeScript

### Overview
A TypeScript implementation of the Elliptic Curve Digital Signature Algorithm (ECDSA).

This is a pure JS implementation of the Elliptic Curve Digital Signature Algorithm. It is compatible with OpenSSL and uses elegant math such as Jacobian Coordinates to speed up the ECDSA on pure JS.
## Features

### Installation
- Full TypeScript support with type definitions
- OpenSSL-compatible implementation
- Fast and efficient using Jacobian coordinates
- Supports secp256k1 and prime256v1 (P-256) curves
- Comprehensive test suite

To install StarkBank`s ECDSA for Node JS, run:
## Installation

```sh
npm install starkbank-ecdsa
```bash
npm install ecdsa-node-ts
# or
yarn add ecdsa-node-ts
```

### Curves
## Usage

We currently support `secp256k1`, but it's super easy to add more curves to the project. Just add them on `curve.js`
```typescript
import { PrivateKey, Ecdsa } from "ecdsa-node-ts";

### Speed
// Generate new private key
const privateKey = new PrivateKey();

We ran a test on Node 13.1.0 on a MAC Pro i5 2019. The libraries ran 100 times and showed the average times displayed bellow:
// Get public key
const publicKey = privateKey.publicKey();

| Library | sign | verify |
| ------------------ |:-------------:| -------:|
| [crypto] | 0.5ms | 1.0ms |
| starkbank-ecdsa | 6.3ms | 15.0ms |
// Create message
const message = "My message";

// Create signature
const signature = Ecdsa.sign(message, privateKey);

### Sample Code

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

```js
var ellipticcurve = require("starkbank-ecdsa");
var Ecdsa = ellipticcurve.Ecdsa;
var PrivateKey = ellipticcurve.PrivateKey;

// Generate privateKey from PEM string
var 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-----\n");

// Create message from json
let message = JSON.stringify({
"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"]
}
]
});

signature = Ecdsa.sign(message, privateKey);

// Generate Signature in base64. This result can be sent to Stark Bank in header as Digital-Signature parameter
console.log(signature.toBase64());

// To double check if message matches the signature
let publicKey = privateKey.publicKey();

console.log(Ecdsa.verify(message, signature, publicKey));
```

Simple use:

```js
var ellipticcurve = require("starkbank-ecdsa");
var Ecdsa = ellipticcurve.Ecdsa;
var PrivateKey = ellipticcurve.PrivateKey;

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

let message = "My test message";

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

// Verify if signature is valid
console.log(Ecdsa.verify(message, signature, publicKey));
// Verify signature
const verified = Ecdsa.verify(message, signature, publicKey);
console.log(verified); // true
```

### OpenSSL
### Working with PEM files

This library is compatible with OpenSSL, so you can use it to generate keys:
```typescript
// Import keys from PEM
const privateKeyPem = File.read("privateKey.pem");
const privateKey = PrivateKey.fromPem(privateKeyPem);

```
openssl ecparam -name secp256k1 -genkey -out privateKey.pem
openssl ec -in privateKey.pem -pubout -out publicKey.pem
```

Create a message.txt file and sign it:
const publicKeyPem = File.read("publicKey.pem");
const publicKey = PublicKey.fromPem(publicKeyPem);

```
openssl dgst -sha256 -sign privateKey.pem -out signatureDer.txt message.txt
// Export keys to PEM
const pemPrivate = privateKey.toPem();
const pemPublic = publicKey.toPem();
```

To verify, do this:
## Development

```js
var ellipticcurve = require("starkbank-ecdsa");
var Ecdsa = ellipticcurve.Ecdsa;
var Signature = ellipticcurve.Signature;
var PublicKey = ellipticcurve.PublicKey;
var File = ellipticcurve.utils.File;
```bash
# Install dependencies
yarn install

let publicKeyPem = File.read("publicKey.pem");
let signatureDer = File.read("signatureDer.txt", "binary");
let message = File.read("message.txt");
# Build
yarn build

let publicKey = PublicKey.fromPem(publicKeyPem);
let signature = Signature.fromDer(signatureDer);
# Run tests
yarn test

console.log(Ecdsa.verify(message, signature, publicKey));
# Clean build files
yarn clean
```

You can also verify it on terminal:

```
openssl dgst -sha256 -verify publicKey.pem -signature signatureDer.txt message.txt
```

NOTE: If you want to create a Digital Signature to use in the [Stark Bank], you need to convert the binary signature to base64.

```
openssl base64 -in signatureDer.txt -out signatureBase64.txt
```

You can do the same with this library:

```js
var ellipticcurve = require("starkbank-ecdsa");
var Signature = ellipticcurve.Signature;
var File = ellipticcurve.utils.File;

let signatureDer = File.read("signatureDer.txt", "binary");

let signature = Signature.fromDer(signatureDer);

console.log(signature.toBase64());
```

[Stark Bank]: https://starkbank.com

### Run all unit tests
Run tests in [Mocha framework]

```sh
node test
```

or

```sh
./node_modules/mocha/bin/mocha
```
## License

[Mocha framework]: https://mochajs.org/#getting-started
[crypto]: https://nodejs.org/api/crypto.html
[ecdsa]: https://www.npmjs.com/package/ecdsa
MIT License
23 changes: 23 additions & 0 deletions dist/ellipticcurve/curve.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { BigInteger } from "big-integer";
import { Point } from "./point";
export declare class CurveFp {
A: BigInteger;
B: BigInteger;
P: BigInteger;
N: BigInteger;
G: Point;
name: string;
nistName: string | null;
private _oid;
constructor(A: BigInteger, B: BigInteger, P: BigInteger, N: BigInteger, Gx: BigInteger, Gy: BigInteger, name: string, oid: number[], nistName?: string | null);
contains(p: Point): boolean;
length(): number;
get oid(): number[];
}
export declare const secp256k1: CurveFp;
export declare const prime256v1: CurveFp;
export declare const p256: CurveFp;
export declare const supportedCurves: CurveFp[];
export declare const curvesByOid: {
[key: string]: CurveFp;
};
53 changes: 53 additions & 0 deletions dist/ellipticcurve/curve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"use strict";
//
// Elliptic Curve Equation
//
// y^2 = x^3 + A*x + B (mod P)
//
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.curvesByOid = exports.supportedCurves = exports.p256 = exports.prime256v1 = exports.secp256k1 = exports.CurveFp = void 0;
const big_integer_1 = __importDefault(require("big-integer"));
const point_1 = require("./point");
const integer_1 = require("./utils/integer");
class CurveFp {
constructor(A, B, P, N, Gx, Gy, name, oid, nistName = null) {
this.A = A;
this.B = B;
this.P = P;
this.N = N;
this.G = new point_1.Point(Gx, Gy);
this.name = name;
this.nistName = nistName;
this._oid = oid;
}
contains(p) {
if (p.x.lesser(0) || p.x.greater(this.P.minus(1))) {
return false;
}
if (p.y.lesser(0) || p.y.greater(this.P.minus(1))) {
return false;
}
if (!(0, integer_1.modulo)(p.y.pow(2).minus(p.x.pow(3).add(this.A.multiply(p.x)).add(this.B)), this.P).equals(0)) {
return false;
}
return true;
}
length() {
return Math.floor((1 + this.N.toString(16).length) / 2);
}
get oid() {
return [...this._oid];
}
}
exports.CurveFp = CurveFp;
exports.secp256k1 = new CurveFp((0, big_integer_1.default)("0000000000000000000000000000000000000000000000000000000000000000", 16), (0, big_integer_1.default)("0000000000000000000000000000000000000000000000000000000000000007", 16), (0, big_integer_1.default)("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", 16), (0, big_integer_1.default)("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16), (0, big_integer_1.default)("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", 16), (0, big_integer_1.default)("483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", 16), "secp256k1", [1, 3, 132, 0, 10]);
exports.prime256v1 = new CurveFp((0, big_integer_1.default)("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16), (0, big_integer_1.default)("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16), (0, big_integer_1.default)("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16), (0, big_integer_1.default)("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16), (0, big_integer_1.default)("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16), (0, big_integer_1.default)("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16), "prime256v1", [1, 2, 840, 10045, 3, 1, 7], "P-256");
exports.p256 = exports.prime256v1;
exports.supportedCurves = [exports.secp256k1, exports.prime256v1];
exports.curvesByOid = {};
exports.supportedCurves.forEach((curve) => {
exports.curvesByOid[curve.oid.join(".")] = curve;
});
8 changes: 8 additions & 0 deletions dist/ellipticcurve/ecdsa.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { BigInteger } from "big-integer";
import { Signature } from "./signature";
import { PrivateKey } from "./privateKey";
import { PublicKey } from "./publicKey";
type HashFunction = (message: string | number[] | Uint8Array) => string;
export declare function sign(message: string, privateKey: PrivateKey, hashfunc?: HashFunction, randNum?: BigInteger): Signature;
export declare function verify(message: string, signature: Signature, publicKey: PublicKey, hashfunc?: HashFunction): boolean;
export {};
83 changes: 83 additions & 0 deletions dist/ellipticcurve/ecdsa.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.sign = sign;
exports.verify = verify;
const js_sha256_1 = require("js-sha256");
const big_integer_1 = __importDefault(require("big-integer"));
const EcdsaMath = __importStar(require("./math"));
const signature_1 = require("./signature");
const BinaryAscii = __importStar(require("./utils/binary"));
const Integer = __importStar(require("./utils/integer"));
const randomInteger = Integer.between;
const modulo = Integer.modulo;
function sign(message, privateKey, hashfunc = js_sha256_1.sha256, randNum) {
let hashMessage = hashfunc(message);
let numberMessage = BinaryAscii.numberFromHex(hashMessage);
let curve = privateKey.curve;
if (!randNum) {
randNum = randomInteger((0, big_integer_1.default)(1), curve.N.minus(1));
}
let randSignPoint = EcdsaMath.multiply(curve.G, randNum, curve.N, curve.A, curve.P);
let r = modulo(randSignPoint.x, curve.N);
let s = modulo(numberMessage
.add(r.multiply(privateKey.secret))
.multiply(EcdsaMath.inv(randNum, curve.N)), curve.N);
return new signature_1.Signature(r, s);
}
function verify(message, signature, publicKey, hashfunc = js_sha256_1.sha256) {
let hashMessage = hashfunc(message);
let numberMessage = BinaryAscii.numberFromHex(hashMessage);
let curve = publicKey.curve;
let sigR = signature.r;
let sigS = signature.s;
if (sigR.lesser(1) || sigR.greaterOrEquals(curve.N)) {
return false;
}
if (sigS.lesser(1) || sigS.greaterOrEquals(curve.N)) {
return false;
}
let inv = EcdsaMath.inv(sigS, curve.N);
let u1 = EcdsaMath.multiply(curve.G, modulo(numberMessage.multiply(inv), curve.N), curve.N, curve.A, curve.P);
let u2 = EcdsaMath.multiply(publicKey.point, modulo(sigR.multiply(inv), curve.N), curve.N, curve.A, curve.P);
let v = EcdsaMath.add(u1, u2, curve.A, curve.P);
if (v.isAtInfinity()) {
return false;
}
return v.x.mod(curve.N).eq(sigR);
}
5 changes: 5 additions & 0 deletions dist/ellipticcurve/math.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Point } from "./point";
import { BigInteger } from "big-integer";
export declare function multiply(p: Point, n: BigInteger, N: BigInteger, A: BigInteger, P: BigInteger): Point;
export declare function add(p: Point, q: Point, A: BigInteger, P: BigInteger): Point;
export declare function inv(x: BigInteger, n: BigInteger): BigInteger;
Loading