mirror of
https://github.com/arnaucube/blindsecp256k1-js.git
synced 2026-02-06 19:06:42 +01:00
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
node_modules
|
node_modules
|
||||||
blindsecp256k1-browser.js
|
blindsecp256k1-browser.js
|
||||||
|
dist
|
||||||
|
|||||||
3781
package-lock.json
generated
3781
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@@ -2,21 +2,34 @@
|
|||||||
"name": "blindsecp256k1",
|
"name": "blindsecp256k1",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "Blind signatures over secp256k1, compatible with https://github.com/arnaucube/go-blindsecp256k1",
|
"description": "Blind signatures over secp256k1, compatible with https://github.com/arnaucube/go-blindsecp256k1",
|
||||||
"main": "src/index.js",
|
"main": "dist/index",
|
||||||
|
"types": "dist/index",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"browserify": "browserify src/index.js --standalone blindsecp256k1 > blindsecp256k1-browser.js",
|
"prepublishOnly": "npm run build",
|
||||||
"test": "mocha"
|
"clean": "rimraf dist",
|
||||||
|
"build": "npm run clean && ./node_modules/.bin/tsc",
|
||||||
|
"watch": "./node_modules/.bin/tsc -w -p .",
|
||||||
|
"ts-node": "./node_modules/.bin/ts-node",
|
||||||
|
"test": "npm run build && ./node_modules/.bin/mocha -r ts-node/register test/**/*.ts"
|
||||||
},
|
},
|
||||||
"author": "arnaucube",
|
"author": "arnaucube",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bigi": "^1.1.0",
|
"@ethersproject/keccak256": "5.0.7",
|
||||||
"bignumber.js": "7.0.2",
|
"bn.js": "^5.1.3",
|
||||||
"ecurve": "1.0.0",
|
"elliptic": "^6.5.4"
|
||||||
"@ethersproject/keccak256": "5.0.7"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"browserify": "^16.5.0",
|
"@types/bn.js": "^5.1.0",
|
||||||
"mocha": "^5.2.0"
|
"@types/chai": "^4.2.14",
|
||||||
|
"@types/elliptic": "^6.4.12",
|
||||||
|
"@types/mocha": "^8.2.0",
|
||||||
|
"@types/node": "^14.14.25",
|
||||||
|
"chai": "^4.2.0",
|
||||||
|
"mocha": "^8.2.1",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"ts-node": "^9.1.1",
|
||||||
|
"tslint": "^6.1.3",
|
||||||
|
"typescript": "^4.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
109
src/index.js
109
src/index.js
@@ -1,109 +0,0 @@
|
|||||||
var crypto = require('crypto');
|
|
||||||
var BigInteger = require('bigi')
|
|
||||||
var ecurve = require('ecurve')
|
|
||||||
const {keccak256} = require("@ethersproject/keccak256");
|
|
||||||
|
|
||||||
const ecparams = ecurve.getCurveByName('secp256k1');
|
|
||||||
const G = ecparams.G;
|
|
||||||
const n = ecparams.n;
|
|
||||||
|
|
||||||
function newBigFromString(s) {
|
|
||||||
var a = new BigInteger()
|
|
||||||
a.fromString(s)
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
function random(bytes){
|
|
||||||
do {
|
|
||||||
var k = BigInteger.fromByteArrayUnsigned(crypto.randomBytes(bytes));
|
|
||||||
} while (k.toString() == "0" && k.gcd(n).toString() != "1")
|
|
||||||
return k;
|
|
||||||
}
|
|
||||||
|
|
||||||
function newKeyPair() {
|
|
||||||
const sk = random(32);
|
|
||||||
return {sk: sk, pk: G.multiply(sk)};
|
|
||||||
}
|
|
||||||
|
|
||||||
function newRequestParameters() {
|
|
||||||
const k = random(32);
|
|
||||||
return {k: k, signerR: G.multiply(k)};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Blinds the message for the signer R.
|
|
||||||
* @param {BigInteger} m
|
|
||||||
* @param {Point} signerR
|
|
||||||
* @returns {struct} {mBlinded: BigInteger, userSecretData: {a: BigInteger, b: BigInteger, f: Point}}
|
|
||||||
*/
|
|
||||||
function blind(m, signerR) {
|
|
||||||
let u = {a: BigInteger.ZERO, b: BigInteger.ZERO, f: G};
|
|
||||||
u.a = random(32);
|
|
||||||
u.b = random(32);
|
|
||||||
|
|
||||||
const aR = signerR.multiply(u.a);
|
|
||||||
const bG = G.multiply(u.b);
|
|
||||||
u.f = aR.add(bG);
|
|
||||||
|
|
||||||
const rx = u.f.affineX.mod(n);
|
|
||||||
|
|
||||||
const ainv = u.a.modInverse(n);
|
|
||||||
const ainvrx = ainv.multiply(rx);
|
|
||||||
|
|
||||||
const mHex = m.toString(16);
|
|
||||||
const hHex = keccak256('0x' + mHex);
|
|
||||||
const h = BigInteger.fromHex(hHex.slice(2));
|
|
||||||
const mBlinded = ainvrx.multiply(h);
|
|
||||||
|
|
||||||
return {mBlinded: mBlinded.mod(n), userSecretData: u};
|
|
||||||
}
|
|
||||||
|
|
||||||
function blindSign(sk, mBlinded, k) {
|
|
||||||
let sBlind = sk.multiply(mBlinded);
|
|
||||||
sBlind = sBlind.add(k);
|
|
||||||
return sBlind.mod(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unblinds the blinded signature.
|
|
||||||
* @param {BigInteger} sBlind - blinded signature
|
|
||||||
* @param {a: BigInteger, b: BigInteger, f: Point} - userSecretData
|
|
||||||
* @returns {s: BigInteger, f: Point} - unblinded signature
|
|
||||||
*/
|
|
||||||
function unblind(sBlind, userSecretData) {
|
|
||||||
const s = userSecretData.a.multiply(sBlind).add(userSecretData.b);
|
|
||||||
return {s: s.mod(n), f: userSecretData.f};
|
|
||||||
}
|
|
||||||
|
|
||||||
function verify(m, s, q) {
|
|
||||||
const sG = G.multiply(s.s);
|
|
||||||
|
|
||||||
const mHex = m.toString(16);
|
|
||||||
const hHex = keccak256('0x' + mHex);
|
|
||||||
const h = BigInteger.fromHex(hHex.slice(2));
|
|
||||||
|
|
||||||
const rx = s.f.affineX.mod(n);
|
|
||||||
const right = s.f.add(
|
|
||||||
q.multiply(
|
|
||||||
rx.multiply(h)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if ((sG.affineX.toString() == right.affineX.toString())
|
|
||||||
&& (sG.affineY.toString() == right.affineY.toString())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
newBigFromString: newBigFromString,
|
|
||||||
ecparams: ecparams,
|
|
||||||
newKeyPair: newKeyPair,
|
|
||||||
newRequestParameters: newRequestParameters,
|
|
||||||
blind: blind,
|
|
||||||
blindSign: blindSign,
|
|
||||||
unblind: unblind,
|
|
||||||
verify: verify
|
|
||||||
}
|
|
||||||
124
src/index.ts
Normal file
124
src/index.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { randomBytes } from 'crypto'
|
||||||
|
import * as BigNumber from 'bn.js'
|
||||||
|
import { ec, curve } from 'elliptic'
|
||||||
|
import { keccak256 } from "@ethersproject/keccak256"
|
||||||
|
|
||||||
|
export type Point = curve.base.BasePoint
|
||||||
|
export { BigNumber }
|
||||||
|
|
||||||
|
const secp256k1 = new ec("secp256k1")
|
||||||
|
const G: Point = secp256k1.g
|
||||||
|
const n = secp256k1.n // as BigNumber
|
||||||
|
|
||||||
|
export const ecParams = { G, n }
|
||||||
|
|
||||||
|
export type UserSecretData = { a: BigNumber, b: BigNumber, f: Point }
|
||||||
|
export type UnblindedSignature = { s: BigNumber, f: Point }
|
||||||
|
|
||||||
|
export function messageToBigNumber(message: string) {
|
||||||
|
const msg = Buffer.from(message, 'utf8')
|
||||||
|
return new BigNumber(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hashBigNumber(m: BigNumber) {
|
||||||
|
const mHex = m.toString(16)
|
||||||
|
|
||||||
|
if (mHex.length % 2 == 0)
|
||||||
|
return keccak256('0x' + mHex).slice(2) // Trim 0x
|
||||||
|
else
|
||||||
|
return keccak256('0x0' + mHex).slice(2) // Trim 0x
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stringToBigNumber(s: string) {
|
||||||
|
return new BigNumber(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodePoint(hexPoint: string): Point {
|
||||||
|
return secp256k1.keyFromPublic(Buffer.from(hexPoint, "hex")).getPublic()
|
||||||
|
}
|
||||||
|
|
||||||
|
function random(bytes: number) {
|
||||||
|
let k: BigNumber
|
||||||
|
do {
|
||||||
|
k = new BigNumber(randomBytes(bytes))
|
||||||
|
} while (k.toString() == "0" && k.gcd(n).toString() != "1")
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
|
export function newKeyPair() {
|
||||||
|
const sk = random(32)
|
||||||
|
return { sk: sk, pk: G.mul(sk) }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function newRequestParameters() {
|
||||||
|
const k = random(32)
|
||||||
|
return { k: k, signerR: G.mul(k) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blinds the message for the signer R.
|
||||||
|
* @param m The message to blind
|
||||||
|
* @param signerR
|
||||||
|
* @returns The blinded signature and the user secret data
|
||||||
|
*/
|
||||||
|
export function blind(m: BigNumber, signerR: Point): { mBlinded: BigNumber, userSecretData: UserSecretData } {
|
||||||
|
const u: UserSecretData = { a: new BigNumber(0), b: new BigNumber(0), f: G }
|
||||||
|
u.a = random(32)
|
||||||
|
u.b = random(32)
|
||||||
|
|
||||||
|
const aR = signerR.mul(u.a)
|
||||||
|
const bG = G.mul(u.b)
|
||||||
|
u.f = aR.add(bG)
|
||||||
|
|
||||||
|
const rx = u.f.getX().mod(n)
|
||||||
|
|
||||||
|
const ainv = u.a.invm(n)
|
||||||
|
const ainvrx = ainv.mul(rx)
|
||||||
|
|
||||||
|
// const mHex = m.toString(16)
|
||||||
|
const hHex = hashBigNumber(m)
|
||||||
|
|
||||||
|
const h = new BigNumber(Buffer.from(hHex, "hex"))
|
||||||
|
const mBlinded = ainvrx.mul(h)
|
||||||
|
|
||||||
|
return { mBlinded: mBlinded.mod(n), userSecretData: u }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Performs a signature on a blinded message */
|
||||||
|
export function blindSign(sk: BigNumber, mBlinded: BigNumber, k: BigNumber): BigNumber {
|
||||||
|
let sBlind = sk.mul(mBlinded)
|
||||||
|
sBlind = sBlind.add(k)
|
||||||
|
return sBlind.mod(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unblinds the blinded signature.
|
||||||
|
* @param blinded signature
|
||||||
|
* @param userSecretData
|
||||||
|
* @returns unblinded signature
|
||||||
|
*/
|
||||||
|
export function unblind(sBlind: BigNumber, userSecretData: UserSecretData): UnblindedSignature {
|
||||||
|
const s = userSecretData.a.mul(sBlind).add(userSecretData.b)
|
||||||
|
return { s: s.mod(n), f: userSecretData.f }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verify(m: BigNumber, s: UnblindedSignature, q: Point) {
|
||||||
|
const sG = G.mul(s.s)
|
||||||
|
|
||||||
|
const hHex = hashBigNumber(m)
|
||||||
|
|
||||||
|
const h = new BigNumber(Buffer.from(hHex, "hex"))
|
||||||
|
|
||||||
|
const rx = s.f.getX().mod(n)
|
||||||
|
const right = s.f.add(
|
||||||
|
q.mul(
|
||||||
|
rx.mul(h)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if ((sG.getX().toString() == right.getX().toString())
|
||||||
|
&& (sG.getY().toString() == right.getY().toString())) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
const assert = require('assert');
|
|
||||||
const BigInteger = require('bigi');
|
|
||||||
var {Point} = require('ecurve')
|
|
||||||
const {keccak256} = require("@ethersproject/keccak256");
|
|
||||||
|
|
||||||
const {newBigFromString, ecparams, newKeyPair, newRequestParameters, blind, blindSign, unblind, verify} = require("../src/index.js");
|
|
||||||
|
|
||||||
describe("keccak256", function () {
|
|
||||||
it("keccak256", async () => {
|
|
||||||
const m = BigInteger.fromBuffer(Buffer.from("test", 'utf8'));
|
|
||||||
const mHex = m.toString(16);
|
|
||||||
const hHex = keccak256('0x' + mHex);
|
|
||||||
assert.equal('0x9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658', hHex);
|
|
||||||
const h = BigInteger.fromHex(hHex.slice(2));
|
|
||||||
assert.equal('70622639689279718371527342103894932928233838121221666359043189029713682937432', h.toString());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("test blind", function () {
|
|
||||||
it("blind", async () => {
|
|
||||||
const {sk, pk} = newKeyPair();
|
|
||||||
|
|
||||||
const {k, signerR} = newRequestParameters();
|
|
||||||
|
|
||||||
const msg = BigInteger.fromBuffer(
|
|
||||||
Buffer.from("test", 'utf8')
|
|
||||||
);
|
|
||||||
assert.equal('1952805748', msg.toString());
|
|
||||||
|
|
||||||
const {mBlinded, userSecretData} = blind(msg, signerR);
|
|
||||||
|
|
||||||
const sBlind = blindSign(sk, mBlinded, k);
|
|
||||||
|
|
||||||
const sig = unblind(sBlind, userSecretData);
|
|
||||||
|
|
||||||
const verified = verify(msg, sig, pk);
|
|
||||||
assert.equal(true, verified);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
55
test/index.test.ts
Normal file
55
test/index.test.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import * as assert from 'assert'
|
||||||
|
|
||||||
|
import {
|
||||||
|
stringToBigNumber,
|
||||||
|
messageToBigNumber,
|
||||||
|
decodePoint,
|
||||||
|
ecParams,
|
||||||
|
newKeyPair,
|
||||||
|
newRequestParameters,
|
||||||
|
blind,
|
||||||
|
blindSign,
|
||||||
|
unblind,
|
||||||
|
verify,
|
||||||
|
hashBigNumber,
|
||||||
|
Point,
|
||||||
|
BigNumber
|
||||||
|
} from "../src/index"
|
||||||
|
|
||||||
|
describe("keccak256", function () {
|
||||||
|
it("should hash strings and big numbers", async () => {
|
||||||
|
const m = messageToBigNumber("test")
|
||||||
|
assert.strictEqual('1952805748', m.toString())
|
||||||
|
|
||||||
|
const hHex = hashBigNumber(m)
|
||||||
|
assert.strictEqual(hHex, '9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658')
|
||||||
|
const h = new BigNumber(Buffer.from(hHex, "hex"))
|
||||||
|
assert.strictEqual(h.toString(), '70622639689279718371527342103894932928233838121221666359043189029713682937432')
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should decode points in the secp256k1 curve", () => {
|
||||||
|
const tokenR = "7cfe4af054e13b4e7231d876d23205fb5f939ac8185271ca6b64c635a365faae259fb8cabdb06dde39d1ebeada3cb75cb9739621a79c61a8cf1e9a38abaf782a"
|
||||||
|
const point = decodePoint("04" + tokenR)
|
||||||
|
assert.strictEqual(point.getX().toString(16), tokenR.substr(0, 64))
|
||||||
|
assert.strictEqual(point.getY().toString(16), tokenR.substr(64))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("blind signatures", function () {
|
||||||
|
it("should blind, unblind and verify", async () => {
|
||||||
|
const { sk, pk } = newKeyPair()
|
||||||
|
|
||||||
|
const { k, signerR } = newRequestParameters()
|
||||||
|
|
||||||
|
const msg = messageToBigNumber("test")
|
||||||
|
|
||||||
|
const { mBlinded, userSecretData } = blind(msg, signerR)
|
||||||
|
|
||||||
|
const sBlind = blindSign(sk, mBlinded, k)
|
||||||
|
|
||||||
|
const sig = unblind(sBlind, userSecretData)
|
||||||
|
|
||||||
|
const verified = verify(msg, sig, pk)
|
||||||
|
assert(verified)
|
||||||
|
})
|
||||||
|
})
|
||||||
19
tsconfig.json
Normal file
19
tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"pretty": true,
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"target": "es2017",
|
||||||
|
"outDir": "dist",
|
||||||
|
"baseUrl": "src"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
24
tslint.json
Normal file
24
tslint.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"defaultSeverity": "error",
|
||||||
|
"extends": [
|
||||||
|
"tslint:recommended"
|
||||||
|
],
|
||||||
|
"jsRules": {},
|
||||||
|
"rules": {
|
||||||
|
"indent": [
|
||||||
|
true,
|
||||||
|
"spaces",
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"semicolon": [
|
||||||
|
false,
|
||||||
|
"always"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"rulesDirectory": [],
|
||||||
|
"linterOptions": {
|
||||||
|
"exclude": [
|
||||||
|
"node_modules/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user