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 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
@@ -0,0 +1,7 @@
1
+ {
2
+ "singleQuote": true,
3
+ "trailingComma": "all",
4
+ "printWidth": 130,
5
+ "useTabs": false,
6
+ "bracketSpacing": true
7
+ }
@@ -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
@@ -0,0 +1,7 @@
1
+ {
2
+ "high": true,
3
+ "moderate": true,
4
+ "low": true,
5
+ "report-type": "summary",
6
+ "allowlist": ["GHSA-78xj-cgh5-2h22", "GHSA-c2qf-rxjj-qqgw", "GHSA-pp7h-53gx-mx7r", "GHSA-wrw9-m778-g6mc", "GHSA-x6fg-f45m-jf5q"]
7
+ }
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
+ }
@@ -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
+ });
@@ -0,0 +1,3 @@
1
+ export const SIGN_PREFIX = '\x19Ethereum Signed Message:\n32';
2
+
3
+ export const MIN_ENTROPY_SIZE = 128;
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
+ }
@@ -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
+ }
@@ -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
+ }
@@ -0,0 +1,11 @@
1
+ export type Encrypted = {
2
+ iv: string,
3
+ ephemPublicKey: string,
4
+ ciphertext: string,
5
+ mac: string
6
+ };
7
+
8
+ export type EncryptOptions = {
9
+ iv?: Buffer,
10
+ ephemPrivateKey?: Buffer
11
+ };
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
+ }