eth-crypto-ts 0.0.1
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/.eslintrc.js +21 -0
- package/.github/workflows/cd.yml +43 -0
- package/.github/workflows/ci.yml +37 -0
- package/.nvmrc +1 -0
- package/.prettierrc +7 -0
- package/.releaserc.json +22 -0
- package/audit-ci.jsonc +7 -0
- package/package.json +46 -0
- package/rollup.config.js +76 -0
- package/src/constants/index.ts +3 -0
- package/src/index.ts +10 -0
- package/src/lib/cipher/index.ts +46 -0
- package/src/lib/compress-public-key.ts +10 -0
- package/src/lib/create-identity.ts +41 -0
- package/src/lib/decompress-public-key.ts +14 -0
- package/src/lib/decrypt-with-private-key.ts +23 -0
- package/src/lib/encrypt-with-private-key.ts +23 -0
- package/src/lib/hash/index.ts +20 -0
- package/src/lib/public-key-by-private-key.ts +18 -0
- package/src/lib/recover-public-key.ts +30 -0
- package/src/lib/sign.ts +24 -0
- package/src/lib/utils.ts +19 -0
- package/src/types/index.ts +11 -0
- package/tsconfig.json +25 -0
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
parser: '@typescript-eslint/parser',
|
|
3
|
+
parserOptions: {
|
|
4
|
+
project: 'tsconfig.json',
|
|
5
|
+
sourceType: 'module',
|
|
6
|
+
},
|
|
7
|
+
plugins: ['@typescript-eslint/eslint-plugin', 'prettier'],
|
|
8
|
+
extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', 'prettier'],
|
|
9
|
+
root: true,
|
|
10
|
+
env: {
|
|
11
|
+
node: true,
|
|
12
|
+
jest: true,
|
|
13
|
+
},
|
|
14
|
+
rules: {
|
|
15
|
+
'@typescript-eslint/interface-name-prefix': 'off',
|
|
16
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
17
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
18
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
19
|
+
'prettier/prettier': ['error', { endOfLine: 'auto' }],
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: CD
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types: [published]
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
release:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
issues: write
|
|
15
|
+
pull-requests: write
|
|
16
|
+
id-token: write
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout
|
|
19
|
+
uses: actions/checkout@v4
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
|
|
23
|
+
- name: Setup Node
|
|
24
|
+
uses: actions/setup-node@v4
|
|
25
|
+
with:
|
|
26
|
+
node-version-file: '.nvmrc'
|
|
27
|
+
|
|
28
|
+
- name: Install dependencies
|
|
29
|
+
run: npm ci
|
|
30
|
+
|
|
31
|
+
- name: Build resources
|
|
32
|
+
run: npm run build:prod
|
|
33
|
+
|
|
34
|
+
- name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
|
|
35
|
+
run: npm audit signatures
|
|
36
|
+
|
|
37
|
+
- name: Configure npm
|
|
38
|
+
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
|
|
39
|
+
|
|
40
|
+
- name: Release
|
|
41
|
+
run: npm run release
|
|
42
|
+
env:
|
|
43
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
branches:
|
|
5
|
+
- master
|
|
6
|
+
jobs:
|
|
7
|
+
build-audit-test:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
steps:
|
|
10
|
+
- name: Get branch name
|
|
11
|
+
id: branch_name
|
|
12
|
+
run: echo "branch=$(echo ${GITHUB_HEAD_REF#refs/heads/})" >>$GITHUB_OUTPUT
|
|
13
|
+
|
|
14
|
+
- name: Checkout ${{ steps.branch_name.outputs.branch }}
|
|
15
|
+
uses: actions/checkout@v4
|
|
16
|
+
with:
|
|
17
|
+
ref: ${{ steps.branch_name.outputs.branch }}
|
|
18
|
+
|
|
19
|
+
- name: Setup Up Node
|
|
20
|
+
uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version-file: '.nvmrc'
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: npm install
|
|
26
|
+
|
|
27
|
+
# - name: Run linter
|
|
28
|
+
# run: npm run lint --no-color
|
|
29
|
+
|
|
30
|
+
# - name: Run tests
|
|
31
|
+
# if: always()
|
|
32
|
+
# run: npm run test
|
|
33
|
+
|
|
34
|
+
- name: Run Audit-CI to check for vulnerabilities
|
|
35
|
+
id: audit
|
|
36
|
+
if: always()
|
|
37
|
+
run: npm run audit-ci
|
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
v20.14.0
|
package/.prettierrc
ADDED
package/.releaserc.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"branches": ["main"],
|
|
3
|
+
"preset": "angular",
|
|
4
|
+
"plugins": [
|
|
5
|
+
["@semantic-release/commit-analyzer", {
|
|
6
|
+
"preset": "angular",
|
|
7
|
+
"releaseRules": [
|
|
8
|
+
{"type": "docs", "scope": "README", "release": "patch"},
|
|
9
|
+
{"type": "refactor", "scope": "core-*", "release": "minor"},
|
|
10
|
+
{"type": "chore", "release": "patch"},
|
|
11
|
+
{"feat": "feat", "release": "minor"},
|
|
12
|
+
{"scope": "no-release", "release": false}
|
|
13
|
+
]
|
|
14
|
+
}],
|
|
15
|
+
"@semantic-release/changelog",
|
|
16
|
+
"@semantic-release/release-notes-generator",
|
|
17
|
+
["@semantic-release/git", {
|
|
18
|
+
"assets": ["dist/**"],
|
|
19
|
+
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n ${nextRelease.notes}"
|
|
20
|
+
}]
|
|
21
|
+
]
|
|
22
|
+
}
|
package/audit-ci.jsonc
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eth-crypto-ts",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "eth-crypto library with typescript",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"prebuild": "rimraf dist",
|
|
8
|
+
"build": "rollup --config --sourcemap --validate --bundleConfigAsCjs",
|
|
9
|
+
"build:prod": "npm run build --compact --environment production",
|
|
10
|
+
"build:dev": "rollup --config --bundleConfigAsCjs --watch",
|
|
11
|
+
"test": "NODE_ENV=test jest",
|
|
12
|
+
"test:watch": "NODE_ENV=test jest --watch",
|
|
13
|
+
"lint": "eslint",
|
|
14
|
+
"audit-ci": "audit-ci --config audit-ci.jsonc",
|
|
15
|
+
"release": "npm publish --access-public"
|
|
16
|
+
},
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"eccrypto": "^1.1.6",
|
|
21
|
+
"ethereumjs-util": "^7.1.5",
|
|
22
|
+
"ethers": "^6.13.1",
|
|
23
|
+
"secp256k1": "^5.0.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@rollup/plugin-commonjs": "^26.0.1",
|
|
27
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
28
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
29
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
30
|
+
"@rollup/plugin-typescript": "^11.1.6",
|
|
31
|
+
"@types/eccrypto": "^1.1.6",
|
|
32
|
+
"@types/secp256k1": "^4.0.6",
|
|
33
|
+
"@types/jest": "^29.5.0",
|
|
34
|
+
"audit-ci": "^7.0.1",
|
|
35
|
+
"ts-jest": "^29.1.0",
|
|
36
|
+
"jest-environment-jsdom": "^29.5.0",
|
|
37
|
+
"eslint": "^9.5.0",
|
|
38
|
+
"jest": "^29.7.0",
|
|
39
|
+
"rimraf": "^5.0.7",
|
|
40
|
+
"rollup": "^4.18.0",
|
|
41
|
+
"rollup-plugin-node-builtins": "^2.0.0",
|
|
42
|
+
"rollup-plugin-node-globals": "^1.4.0",
|
|
43
|
+
"rollup-plugin-polyfill-node": "^0.13.0",
|
|
44
|
+
"typescript": "^5.5.2"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @type {import('rollup').RollupOptions}
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import pkg from './package.json' assert { type: 'json' };
|
|
6
|
+
|
|
7
|
+
import { defineConfig } from 'rollup';
|
|
8
|
+
import builtins from 'rollup-plugin-node-builtins';
|
|
9
|
+
import commonjs from '@rollup/plugin-commonjs';
|
|
10
|
+
import globals from 'rollup-plugin-node-globals';
|
|
11
|
+
import json from '@rollup/plugin-json';
|
|
12
|
+
import polyfillNode from 'rollup-plugin-polyfill-node';
|
|
13
|
+
import resolve from '@rollup/plugin-node-resolve';
|
|
14
|
+
import terser from '@rollup/plugin-terser';
|
|
15
|
+
import typescript from '@rollup/plugin-typescript';
|
|
16
|
+
|
|
17
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
18
|
+
|
|
19
|
+
const defaultOutput = {
|
|
20
|
+
chunkFileNames: 'r/oci-[hash].js',
|
|
21
|
+
esModule: true,
|
|
22
|
+
exports: 'auto',
|
|
23
|
+
generatedCode: 'es5',
|
|
24
|
+
indent: false,
|
|
25
|
+
interop: 'compat',
|
|
26
|
+
sourcemap: 'inline',
|
|
27
|
+
validate: true,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default defineConfig({
|
|
31
|
+
perf: true,
|
|
32
|
+
logLevel: 'info',
|
|
33
|
+
input: 'src/index.ts',
|
|
34
|
+
output: [
|
|
35
|
+
{
|
|
36
|
+
file: 'dist/index.cjs.js',
|
|
37
|
+
format: 'cjs',
|
|
38
|
+
...defaultOutput,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
file: 'dist/index.esm.js',
|
|
42
|
+
format: 'esm',
|
|
43
|
+
...defaultOutput,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
file: 'dist/index.js',
|
|
47
|
+
format: 'module',
|
|
48
|
+
...defaultOutput,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
external: Object.keys(pkg.dependencies),
|
|
52
|
+
plugins: [
|
|
53
|
+
builtins(),
|
|
54
|
+
commonjs({ exclude: ['node_modules/**'] }),
|
|
55
|
+
globals(),
|
|
56
|
+
json(),
|
|
57
|
+
polyfillNode({
|
|
58
|
+
include: [
|
|
59
|
+
'assert',
|
|
60
|
+
'browser',
|
|
61
|
+
'browserify',
|
|
62
|
+
'buffer',
|
|
63
|
+
'Buffer',
|
|
64
|
+
'crypto',
|
|
65
|
+
'crypto-browserify',
|
|
66
|
+
'readable-stream',
|
|
67
|
+
'stream',
|
|
68
|
+
],
|
|
69
|
+
}),
|
|
70
|
+
resolve({
|
|
71
|
+
preferBuiltins: true,
|
|
72
|
+
}),
|
|
73
|
+
isProduction && terser(),
|
|
74
|
+
typescript({ tsconfig: './tsconfig.json' }),
|
|
75
|
+
],
|
|
76
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './lib/cipher';
|
|
2
|
+
export * from './lib/hash';
|
|
3
|
+
export * from './lib/sign';
|
|
4
|
+
export * from './lib/utils'
|
|
5
|
+
export * from './lib/encrypt-with-private-key';
|
|
6
|
+
export * from './lib/decrypt-with-private-key';
|
|
7
|
+
export * from './lib/compress-public-key';
|
|
8
|
+
export * from './lib/decompress-public-key';
|
|
9
|
+
export * from './lib/create-identity';
|
|
10
|
+
export * from './lib/public-key-by-private-key';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Encrypted } from '../../types';
|
|
2
|
+
import { compress } from '../compress-public-key';
|
|
3
|
+
import { decompress } from '../decompress-public-key';
|
|
4
|
+
|
|
5
|
+
interface CipherInterface {
|
|
6
|
+
stringify(encrypted: Encrypted): string;
|
|
7
|
+
parse(string: string): Encrypted;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class Cipher implements CipherInterface {
|
|
11
|
+
constructor() {}
|
|
12
|
+
|
|
13
|
+
stringify(encrypted: Encrypted) {
|
|
14
|
+
if (typeof encrypted === 'string') return encrypted;
|
|
15
|
+
|
|
16
|
+
// use compressed key because it's smaller
|
|
17
|
+
const compressedKey = compress(encrypted.ephemPublicKey);
|
|
18
|
+
|
|
19
|
+
const ret = Buffer.concat([
|
|
20
|
+
Buffer.from(encrypted.iv, 'hex'), // 16bit
|
|
21
|
+
Buffer.from(compressedKey, 'hex'), // 33bit
|
|
22
|
+
Buffer.from(encrypted.mac, 'hex'), // 32bit
|
|
23
|
+
Buffer.from(encrypted.ciphertext, 'hex'), // var bit
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
return ret.toString('hex');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
parse(str: string): Encrypted {
|
|
30
|
+
if (typeof str !== 'string') return str;
|
|
31
|
+
|
|
32
|
+
const buf = Buffer.from(str, 'hex');
|
|
33
|
+
|
|
34
|
+
const ret = {
|
|
35
|
+
iv: buf.toString('hex', 0, 16),
|
|
36
|
+
ephemPublicKey: buf.toString('hex', 16, 49),
|
|
37
|
+
mac: buf.toString('hex', 49, 81),
|
|
38
|
+
ciphertext: buf.toString('hex', 81, buf.length),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// decompress publicKey
|
|
42
|
+
ret.ephemPublicKey = '04' + decompress(ret.ephemPublicKey);
|
|
43
|
+
|
|
44
|
+
return ret;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { publicKeyConvert } from 'secp256k1';
|
|
2
|
+
import { uint8ArrayToHex, hexToUnit8Array } from './utils';
|
|
3
|
+
|
|
4
|
+
export function compress(startsWith04: any) {
|
|
5
|
+
// add trailing 04 if not done before
|
|
6
|
+
const testBuffer = Buffer.from(startsWith04, 'hex');
|
|
7
|
+
if (testBuffer.length === 64) startsWith04 = '04' + startsWith04;
|
|
8
|
+
|
|
9
|
+
return uint8ArrayToHex(publicKeyConvert(hexToUnit8Array(startsWith04), true));
|
|
10
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { keccak256, Wallet, concat, randomBytes } from 'ethers';
|
|
2
|
+
import { publicKeyByPrivateKey } from './public-key-by-private-key';
|
|
3
|
+
import { MIN_ENTROPY_SIZE } from '../constants';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* create a privateKey from the given entropy or a new one
|
|
7
|
+
* @param {Buffer} entropy
|
|
8
|
+
* @return {string}
|
|
9
|
+
*/
|
|
10
|
+
export function createPrivateKey(entropy?: Buffer) {
|
|
11
|
+
if (entropy) {
|
|
12
|
+
if (!Buffer.isBuffer(entropy)) throw new Error('EthCrypto.createPrivateKey(): given entropy is no Buffer');
|
|
13
|
+
if (Buffer.byteLength(entropy, 'utf8') < MIN_ENTROPY_SIZE)
|
|
14
|
+
throw new Error('EthCrypto.createPrivateKey(): Entropy-size must be at least ' + MIN_ENTROPY_SIZE);
|
|
15
|
+
|
|
16
|
+
const outerHex = keccak256(entropy);
|
|
17
|
+
return outerHex;
|
|
18
|
+
} else {
|
|
19
|
+
const innerHex = keccak256(concat([randomBytes(32), randomBytes(32)]));
|
|
20
|
+
const middleHex = concat([concat([randomBytes(32), innerHex]), randomBytes(32)]);
|
|
21
|
+
const outerHex = keccak256(middleHex);
|
|
22
|
+
return outerHex;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* creates a new object with
|
|
28
|
+
* private-, public-Key and address
|
|
29
|
+
* @param {Buffer?} entropy if provided, will use that as single random-source
|
|
30
|
+
*/
|
|
31
|
+
export function createIdentity(entropy?: Buffer) {
|
|
32
|
+
const privateKey = createPrivateKey(entropy);
|
|
33
|
+
const wallet = new Wallet(privateKey);
|
|
34
|
+
const identity = {
|
|
35
|
+
privateKey: privateKey,
|
|
36
|
+
publicKey: publicKeyByPrivateKey(privateKey),
|
|
37
|
+
address: wallet.address,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return identity;
|
|
41
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { publicKeyConvert } from 'secp256k1';
|
|
2
|
+
import { hexToUnit8Array, uint8ArrayToHex } from './utils';
|
|
3
|
+
|
|
4
|
+
export function decompress(startsWith02Or03: any) {
|
|
5
|
+
// if already decompressed an not has trailing 04
|
|
6
|
+
const testBuffer = Buffer.from(startsWith02Or03, 'hex');
|
|
7
|
+
if (testBuffer.length === 64) startsWith02Or03 = '04' + startsWith02Or03;
|
|
8
|
+
|
|
9
|
+
let decompressed = uint8ArrayToHex(publicKeyConvert(hexToUnit8Array(startsWith02Or03), false));
|
|
10
|
+
|
|
11
|
+
// remove trailing 04
|
|
12
|
+
decompressed = decompressed.substring(2);
|
|
13
|
+
return decompressed;
|
|
14
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { decrypt } from 'eccrypto';
|
|
2
|
+
import { Encrypted } from '../types';
|
|
3
|
+
import { removeLeading0x } from './utils';
|
|
4
|
+
import { Cipher } from './cipher';
|
|
5
|
+
|
|
6
|
+
export function decryptWithPrivateKey(privateKey: string, encrypted: Encrypted | any) {
|
|
7
|
+
const cipher = new Cipher();
|
|
8
|
+
|
|
9
|
+
// ensuring the encrypted data is in the correct format.
|
|
10
|
+
encrypted = cipher.parse(encrypted);
|
|
11
|
+
|
|
12
|
+
// remove trailing '0x' from privateKey
|
|
13
|
+
const twoStripped = removeLeading0x(privateKey);
|
|
14
|
+
|
|
15
|
+
const encryptedBuffer = {
|
|
16
|
+
iv: Buffer.from(encrypted.iv, 'hex'),
|
|
17
|
+
ephemPublicKey: Buffer.from(encrypted.ephemPublicKey, 'hex'),
|
|
18
|
+
ciphertext: Buffer.from(encrypted.ciphertext, 'hex'),
|
|
19
|
+
mac: Buffer.from(encrypted.mac, 'hex'),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return decrypt(Buffer.from(twoStripped, 'hex'), encryptedBuffer).then((decryptedBuffer) => decryptedBuffer.toString());
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { encrypt } from 'eccrypto';
|
|
2
|
+
import { Encrypted, EncryptOptions } from '../types';
|
|
3
|
+
import { decompress } from './decompress-public-key';
|
|
4
|
+
|
|
5
|
+
export function encryptWithPublicKey(publicKey: string, message: string, options?: EncryptOptions): Promise<Encrypted> {
|
|
6
|
+
|
|
7
|
+
// ensure its an uncompressed publicKey
|
|
8
|
+
publicKey = decompress(publicKey);
|
|
9
|
+
|
|
10
|
+
// re-add the compression-flag
|
|
11
|
+
const pubString = '04' + publicKey;
|
|
12
|
+
|
|
13
|
+
return encrypt(Buffer.from(pubString, 'hex'), Buffer.from(message), options ? options : {}).then((encryptedBuffers) => {
|
|
14
|
+
const encrypted = {
|
|
15
|
+
iv: encryptedBuffers.iv.toString('hex'),
|
|
16
|
+
ephemPublicKey: encryptedBuffers.ephemPublicKey.toString('hex'),
|
|
17
|
+
ciphertext: encryptedBuffers.ciphertext.toString('hex'),
|
|
18
|
+
mac: encryptedBuffers.mac.toString('hex'),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return encrypted;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { solidityPackedKeccak256 } from "ethers";
|
|
2
|
+
|
|
3
|
+
export class Hash {
|
|
4
|
+
constructor() {}
|
|
5
|
+
|
|
6
|
+
keccak256(params: any | any[]) {
|
|
7
|
+
const types = [];
|
|
8
|
+
const values = [];
|
|
9
|
+
if (!Array.isArray(params)) {
|
|
10
|
+
types.push('string');
|
|
11
|
+
values.push(params);
|
|
12
|
+
} else {
|
|
13
|
+
params.forEach((p) => {
|
|
14
|
+
types.push(p.type);
|
|
15
|
+
values.push(p.value);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return solidityPackedKeccak256(types, values);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { privateToPublic, toBuffer } from "ethereumjs-util";
|
|
2
|
+
import { addLeading0x } from "./utils";
|
|
3
|
+
|
|
4
|
+
type publicKeyByPrivateKeyType = (privateKey: string) => string;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate publicKey from the privateKey.
|
|
8
|
+
* This creates the uncompressed publicKey,
|
|
9
|
+
* where 04 has stripped from left
|
|
10
|
+
* @param {string} privateKey
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
export function publicKeyByPrivateKey(privateKey: string) {
|
|
14
|
+
privateKey = addLeading0x(privateKey);
|
|
15
|
+
const publicKeyBuffer = privateToPublic(toBuffer(privateKey));
|
|
16
|
+
|
|
17
|
+
return publicKeyBuffer.toString('hex');
|
|
18
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ecdsaRecover } from "secp256k1";
|
|
2
|
+
import { hexToUnit8Array, removeLeading0x, uint8ArrayToHex } from "./utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* returns the publicKey for the privateKey with which the messageHash was signed
|
|
6
|
+
* @param {string} signature
|
|
7
|
+
* @param {string} hash
|
|
8
|
+
* @return {string} publicKey
|
|
9
|
+
*/
|
|
10
|
+
export function recoverPublicKey(signature: string, hash: string) {
|
|
11
|
+
signature = removeLeading0x(signature);
|
|
12
|
+
|
|
13
|
+
// split into v-value and sig
|
|
14
|
+
const sigOnly = signature.substring(0, signature.length - 2); // all but last 2 chars
|
|
15
|
+
const vValue = signature.slice(-2); // last 2 chars
|
|
16
|
+
|
|
17
|
+
const recoveryNumber = vValue === '1c' ? 1 : 0;
|
|
18
|
+
|
|
19
|
+
let pubKey = uint8ArrayToHex(ecdsaRecover(
|
|
20
|
+
hexToUnit8Array(sigOnly),
|
|
21
|
+
recoveryNumber,
|
|
22
|
+
hexToUnit8Array(removeLeading0x(hash)),
|
|
23
|
+
false
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// remove trailing '04'
|
|
27
|
+
pubKey = pubKey.slice(2);
|
|
28
|
+
|
|
29
|
+
return pubKey;
|
|
30
|
+
}
|
package/src/lib/sign.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ecdsaSign as secp256k1_sign } from 'secp256k1';
|
|
2
|
+
import { addLeading0x, removeLeading0x } from './utils';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* signs the given message
|
|
6
|
+
* we do not use sign from eth-lib because the pure secp256k1-version is 90% faster
|
|
7
|
+
* @param {string} privateKey
|
|
8
|
+
* @param {string} hash
|
|
9
|
+
* @return {string} hexString
|
|
10
|
+
*/
|
|
11
|
+
export function sign(privateKey: string, hash: string) {
|
|
12
|
+
hash = addLeading0x(hash);
|
|
13
|
+
if (hash.length !== 66) throw new Error('EthCrypto.sign(): Can only sign hashes, given: ' + hash);
|
|
14
|
+
|
|
15
|
+
const sigObj = secp256k1_sign(
|
|
16
|
+
new Uint8Array(Buffer.from(removeLeading0x(hash), 'hex')),
|
|
17
|
+
new Uint8Array(Buffer.from(removeLeading0x(privateKey), 'hex')),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const recoveryId = sigObj.recid === 1 ? '1c' : '1b';
|
|
21
|
+
|
|
22
|
+
const newSignature = '0x' + Buffer.from(sigObj.signature).toString('hex') + recoveryId;
|
|
23
|
+
return newSignature;
|
|
24
|
+
}
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function removeLeading0x(str: string) {
|
|
2
|
+
if (str.startsWith('0x'))
|
|
3
|
+
return str.substring(2);
|
|
4
|
+
else return str;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function addLeading0x(str: string) {
|
|
8
|
+
if (!str.startsWith('0x'))
|
|
9
|
+
return '0x' + str;
|
|
10
|
+
else return str;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function uint8ArrayToHex(arr: any[] | Uint8Array) {
|
|
14
|
+
return Buffer.from(arr).toString('hex');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function hexToUnit8Array(str: string) {
|
|
18
|
+
return new Uint8Array(Buffer.from(str, 'hex'));
|
|
19
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"preventAssignment": true,
|
|
3
|
+
"include": ["src", "src/types"],
|
|
4
|
+
"exclude": ["node_modules", "**/*spec.ts", "**/*test.ts", "/test", "/tests", "test", "**/*__mocks__/*"],
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"target": "ESNext",
|
|
7
|
+
"moduleResolution": "node", // use Node's module resolution algorithm, instead of the legacy TS one
|
|
8
|
+
"module": "esnext",
|
|
9
|
+
"lib": ["dom", "esnext"],
|
|
10
|
+
"rootDir": "./src",
|
|
11
|
+
"baseUrl": "./",
|
|
12
|
+
"outDir": "./dist",
|
|
13
|
+
"sourceMap": true,
|
|
14
|
+
"declaration": true, // output .d.ts declaration files for consumers
|
|
15
|
+
"allowJs": true,
|
|
16
|
+
"importHelpers": true,
|
|
17
|
+
"strict": true, // stricter type-checking for stronger correctness. Recommended by TS
|
|
18
|
+
"noImplicitReturns": true, // linter checks for common issues
|
|
19
|
+
"noFallthroughCasesInSwitch": true,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"esModuleInterop": true, // interop between ESM and CJS modules. Recommended by TS
|
|
22
|
+
"skipLibCheck": true, // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS
|
|
23
|
+
"forceConsistentCasingInFileNames": true // error out if import and file system have a casing mismatch. Recommended by TS
|
|
24
|
+
}
|
|
25
|
+
}
|