ecdsa-quirks 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/src/index.js +11 -9
- package/package.json +2 -2
- package/src/index.ts +13 -9
package/README.md
CHANGED
|
@@ -16,8 +16,11 @@ To generate the signatures, run:
|
|
|
16
16
|
ecdsa-quirks --m1 "<some message>" --m2 "<some other message>" [--eip191]
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
The tool will log the private key and generated signatures via that private key.
|
|
19
|
+
The tool will log the private key and generated signatures via that private key that are the same for the specified messages.
|
|
20
|
+
|
|
21
|
+
> [!CAUTION]
|
|
22
|
+
> If both messages (or their hashes) with the signature are disclosed, anyone can reconstruct the private key. Be extremely cautious.
|
|
20
23
|
|
|
21
24
|
## Disclaimer
|
|
22
25
|
|
|
23
|
-
The
|
|
26
|
+
The implementation is based on [this research paper](https://www.di.ens.fr/david.pointcheval/Documents/Papers/2002_cryptoA.pdf). Please check it out to understand the math behind.
|
package/dist/src/index.js
CHANGED
|
@@ -7,24 +7,26 @@ import { Command } from "commander";
|
|
|
7
7
|
import { expect } from "chai";
|
|
8
8
|
// based on https://www.di.ens.fr/david.pointcheval/Documents/Papers/2002_cryptoA.pdf
|
|
9
9
|
function quirk(message1, message2, eip191) {
|
|
10
|
+
let message1Hash;
|
|
11
|
+
let message2Hash;
|
|
10
12
|
// EIP-191 hash
|
|
11
13
|
if (eip191) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
message1Hash = ethers.hashMessage(message1);
|
|
15
|
+
message2Hash = ethers.hashMessage(message2);
|
|
14
16
|
}
|
|
15
17
|
else {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
message1Hash = ethers.keccak256(ethers.toUtf8Bytes(message1));
|
|
19
|
+
message2Hash = ethers.keccak256(ethers.toUtf8Bytes(message2));
|
|
18
20
|
}
|
|
19
21
|
const n = secp256k1.Point.CURVE().n;
|
|
20
22
|
const k = ethers.hexlify(ethers.randomBytes(32));
|
|
21
23
|
const r = "0x" + ethers.hexlify(secp256k1.getPublicKey(Buffer.from(k.slice(2), "hex"))).slice(4); // take x coordinate
|
|
22
24
|
// x = -((h1 + h2) / 2r) (mod n)
|
|
23
|
-
const numer = BigInt(
|
|
25
|
+
const numer = BigInt(message1Hash) + BigInt(message2Hash);
|
|
24
26
|
const denom = BigInt(modInv(2n * BigInt(r), BigInt(n)));
|
|
25
27
|
const x = BigInt(n) - ((numer * denom) % BigInt(n));
|
|
26
28
|
// regular ECDSA signature
|
|
27
|
-
let s = (BigInt(modInv(BigInt(k), BigInt(n))) * (BigInt(
|
|
29
|
+
let s = (BigInt(modInv(BigInt(k), BigInt(n))) * (BigInt(message1Hash) + x * BigInt(r))) % BigInt(n);
|
|
28
30
|
// make s be from the lower part of the curve
|
|
29
31
|
if (s > n / 2n) {
|
|
30
32
|
s = n - s;
|
|
@@ -32,14 +34,14 @@ function quirk(message1, message2, eip191) {
|
|
|
32
34
|
const wallet = new ethers.Wallet(ethers.toBeHex(x, 32));
|
|
33
35
|
let sig1 = ethers.toBeHex(r, 32) + ethers.toBeHex(s, 32).slice(2) + "1b";
|
|
34
36
|
let sig2 = ethers.toBeHex(r, 32) + ethers.toBeHex(s, 32).slice(2) + "1c";
|
|
35
|
-
if (ethers.
|
|
37
|
+
if (ethers.recoverAddress(message1Hash, sig1) != wallet.address) {
|
|
36
38
|
const tmp = sig1;
|
|
37
39
|
sig1 = sig2;
|
|
38
40
|
sig2 = tmp;
|
|
39
41
|
}
|
|
40
42
|
// sanity check
|
|
41
|
-
expect(ethers.
|
|
42
|
-
expect(ethers.
|
|
43
|
+
expect(ethers.recoverAddress(message1Hash, sig1)).to.eq(wallet.address);
|
|
44
|
+
expect(ethers.recoverAddress(message2Hash, sig2)).to.eq(wallet.address);
|
|
43
45
|
return {
|
|
44
46
|
privateKey: ethers.toBeHex(x, 32),
|
|
45
47
|
address: wallet.address,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ecdsa-quirks",
|
|
3
3
|
"description": "Generate the same ECDSA signature for two different messages",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
7
7
|
"types": "dist/src/index.d.ts",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"lint": "prettier --check . && node --import tsx/esm ./node_modules/eslint/bin/eslint.js .",
|
|
40
40
|
"test": "node --import ./tests/loader-bootstrap.mjs --no-deprecation ./node_modules/mocha/bin/mocha.js \"tests/**/*.test.ts\" -t 120000",
|
|
41
41
|
"lint-fix": "prettier --write . && node --import tsx/esm ./node_modules/eslint/bin/eslint.js . --fix",
|
|
42
|
-
"publish": "npm run build && npm publish --access public"
|
|
42
|
+
"publish-to-npm": "npm run build && npm publish --access public"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@noble/curves": "2.0.1",
|
package/src/index.ts
CHANGED
|
@@ -18,13 +18,16 @@ type Quirked = {
|
|
|
18
18
|
|
|
19
19
|
// based on https://www.di.ens.fr/david.pointcheval/Documents/Papers/2002_cryptoA.pdf
|
|
20
20
|
function quirk(message1: string, message2: string, eip191: boolean): Quirked {
|
|
21
|
+
let message1Hash;
|
|
22
|
+
let message2Hash;
|
|
23
|
+
|
|
21
24
|
// EIP-191 hash
|
|
22
25
|
if (eip191) {
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
message1Hash = ethers.hashMessage(message1);
|
|
27
|
+
message2Hash = ethers.hashMessage(message2);
|
|
25
28
|
} else {
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
message1Hash = ethers.keccak256(ethers.toUtf8Bytes(message1));
|
|
30
|
+
message2Hash = ethers.keccak256(ethers.toUtf8Bytes(message2));
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
const n = secp256k1.Point.CURVE().n;
|
|
@@ -32,12 +35,13 @@ function quirk(message1: string, message2: string, eip191: boolean): Quirked {
|
|
|
32
35
|
const r = "0x" + ethers.hexlify(secp256k1.getPublicKey(Buffer.from(k.slice(2), "hex"))).slice(4); // take x coordinate
|
|
33
36
|
|
|
34
37
|
// x = -((h1 + h2) / 2r) (mod n)
|
|
35
|
-
const numer = BigInt(
|
|
38
|
+
const numer = BigInt(message1Hash) + BigInt(message2Hash);
|
|
36
39
|
const denom = BigInt(modInv(2n * BigInt(r), BigInt(n)));
|
|
37
40
|
const x = BigInt(n) - ((numer * denom) % BigInt(n));
|
|
38
41
|
|
|
39
42
|
// regular ECDSA signature
|
|
40
|
-
let s =
|
|
43
|
+
let s =
|
|
44
|
+
(BigInt(modInv(BigInt(k), BigInt(n))) * (BigInt(message1Hash) + x * BigInt(r))) % BigInt(n);
|
|
41
45
|
|
|
42
46
|
// make s be from the lower part of the curve
|
|
43
47
|
if (s > n / 2n) {
|
|
@@ -49,15 +53,15 @@ function quirk(message1: string, message2: string, eip191: boolean): Quirked {
|
|
|
49
53
|
let sig1 = ethers.toBeHex(r, 32) + ethers.toBeHex(s, 32).slice(2) + "1b";
|
|
50
54
|
let sig2 = ethers.toBeHex(r, 32) + ethers.toBeHex(s, 32).slice(2) + "1c";
|
|
51
55
|
|
|
52
|
-
if (ethers.
|
|
56
|
+
if (ethers.recoverAddress(message1Hash, sig1) != wallet.address) {
|
|
53
57
|
const tmp = sig1;
|
|
54
58
|
sig1 = sig2;
|
|
55
59
|
sig2 = tmp;
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
// sanity check
|
|
59
|
-
expect(ethers.
|
|
60
|
-
expect(ethers.
|
|
63
|
+
expect(ethers.recoverAddress(message1Hash, sig1)).to.eq(wallet.address);
|
|
64
|
+
expect(ethers.recoverAddress(message2Hash, sig2)).to.eq(wallet.address);
|
|
61
65
|
|
|
62
66
|
return {
|
|
63
67
|
privateKey: ethers.toBeHex(x, 32),
|