@wallaby-cash/cryptography 1.0.0 → 1.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/CHANGELOG.md +6 -0
- package/dist/createIdentity.d.ts +18 -0
- package/dist/createIdentity.d.ts.map +1 -0
- package/dist/createIdentity.js +60 -0
- package/dist/decryptWithPrivateKey.d.ts +3 -0
- package/dist/decryptWithPrivateKey.d.ts.map +1 -0
- package/dist/decryptWithPrivateKey.js +7 -0
- package/dist/encryptWithPublicKey.d.ts +3 -0
- package/dist/encryptWithPublicKey.d.ts.map +1 -0
- package/dist/encryptWithPublicKey.js +9 -0
- package/dist/encryption-utils.d.ts +19 -0
- package/dist/encryption-utils.d.ts.map +1 -0
- package/dist/encryption-utils.js +82 -0
- package/dist/hash.d.ts +2 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +12 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/{src/index.ts → dist/index.js} +10 -22
- package/dist/publicKeyByPrivateKey.d.ts +8 -0
- package/dist/publicKeyByPrivateKey.d.ts.map +1 -0
- package/dist/publicKeyByPrivateKey.js +15 -0
- package/dist/recoverPublicKey.d.ts +8 -0
- package/dist/recoverPublicKey.d.ts.map +1 -0
- package/dist/recoverPublicKey.js +20 -0
- package/dist/sign.d.ts +9 -0
- package/dist/sign.d.ts.map +1 -0
- package/dist/sign.js +24 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/util.d.ts +38 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +98 -0
- package/package.json +15 -5
- package/src/createIdentity.ts +0 -66
- package/src/decryptWithPrivateKey.ts +0 -10
- package/src/encryptWithPublicKey.ts +0 -12
- package/src/encryption-utils.ts +0 -93
- package/src/hash.ts +0 -14
- package/src/publicKeyByPrivateKey.ts +0 -17
- package/src/recoverPublicKey.ts +0 -26
- package/src/sign.ts +0 -27
- package/src/types.ts +0 -10
- package/src/util.ts +0 -110
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const DEFAULT_ENTROPY_BYTES = 32;
|
|
2
|
+
export declare const MINIMUM_SHANNON_ENTROPY = 4;
|
|
3
|
+
/**
|
|
4
|
+
* creates a new private key
|
|
5
|
+
* @param { Uint8Array } entropy - optional entropy to create the private key
|
|
6
|
+
* @returns a new private key
|
|
7
|
+
*/
|
|
8
|
+
export declare const createPrivateKey: (entropy?: Uint8Array) => string;
|
|
9
|
+
/**
|
|
10
|
+
* creates a new identity
|
|
11
|
+
* @param { Uint8Array } entropy - optional entropy to create the private key
|
|
12
|
+
* @returns a new pair of private and public key
|
|
13
|
+
*/
|
|
14
|
+
export declare const createIdentity: (entropy?: Uint8Array) => {
|
|
15
|
+
privateKey: string;
|
|
16
|
+
publicKey: string;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=createIdentity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createIdentity.d.ts","sourceRoot":"","sources":["../src/createIdentity.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,qBAAqB,KAAK,CAAC;AACxC,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,UAAU,UAAU,WAmCpD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,UAAU,UAAU;;;CASlD,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { getRandomBytesSync as randomBytes } from 'ethereum-cryptography/random.js';
|
|
2
|
+
import { addLeading0x, stripHexPrefix, concatUint8Arrays } from './util';
|
|
3
|
+
import { publicKeyByPrivateKey } from './publicKeyByPrivateKey';
|
|
4
|
+
import { bytesToHex } from 'ethereum-cryptography/utils';
|
|
5
|
+
import { keccak256 } from 'ethereum-cryptography/keccak';
|
|
6
|
+
export const DEFAULT_ENTROPY_BYTES = 32;
|
|
7
|
+
export const MINIMUM_SHANNON_ENTROPY = 4;
|
|
8
|
+
/**
|
|
9
|
+
* creates a new private key
|
|
10
|
+
* @param { Uint8Array } entropy - optional entropy to create the private key
|
|
11
|
+
* @returns a new private key
|
|
12
|
+
*/
|
|
13
|
+
export const createPrivateKey = (entropy) => {
|
|
14
|
+
if (entropy) {
|
|
15
|
+
if (!(entropy instanceof Uint8Array) || entropy.length < DEFAULT_ENTROPY_BYTES) {
|
|
16
|
+
throw new Error(`entropy must be a Uint8Array of at least ${DEFAULT_ENTROPY_BYTES} bytes`);
|
|
17
|
+
}
|
|
18
|
+
// Check byte diversity
|
|
19
|
+
const uniqueBytes = new Set(entropy);
|
|
20
|
+
if (uniqueBytes.size < Math.min(8, entropy.length / 4)) {
|
|
21
|
+
throw new Error(`entropy is too repetitive (only ${uniqueBytes.size} unique byte values)`);
|
|
22
|
+
}
|
|
23
|
+
// Estimate Shannon entropy
|
|
24
|
+
const byteCounts = new Uint32Array(256);
|
|
25
|
+
entropy.forEach((b) => byteCounts[b]++);
|
|
26
|
+
const total = entropy.length;
|
|
27
|
+
let shannonEntropy = 0;
|
|
28
|
+
for (let i = 0; i < 256; i++) {
|
|
29
|
+
if (byteCounts[i] > 0) {
|
|
30
|
+
const p = byteCounts[i] / total;
|
|
31
|
+
shannonEntropy -= p * Math.log2(p);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (shannonEntropy < MINIMUM_SHANNON_ENTROPY) {
|
|
35
|
+
throw new Error(`entropy has low Shannon entropy (${shannonEntropy.toFixed(2)} bits/byte)`);
|
|
36
|
+
}
|
|
37
|
+
const outerHex = keccak256(entropy);
|
|
38
|
+
return addLeading0x(bytesToHex(outerHex));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const innerHex = keccak256(concatUint8Arrays([randomBytes(32), randomBytes(32)]));
|
|
42
|
+
const middleHex = concatUint8Arrays([concatUint8Arrays([randomBytes(32), innerHex]), randomBytes(32)]);
|
|
43
|
+
const outerHex = keccak256(middleHex);
|
|
44
|
+
return addLeading0x(bytesToHex(outerHex));
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* creates a new identity
|
|
49
|
+
* @param { Uint8Array } entropy - optional entropy to create the private key
|
|
50
|
+
* @returns a new pair of private and public key
|
|
51
|
+
*/
|
|
52
|
+
export const createIdentity = (entropy) => {
|
|
53
|
+
const privateKey = createPrivateKey(entropy);
|
|
54
|
+
const walletPublicKey = publicKeyByPrivateKey(privateKey);
|
|
55
|
+
const identity = {
|
|
56
|
+
privateKey: privateKey,
|
|
57
|
+
publicKey: stripHexPrefix(walletPublicKey),
|
|
58
|
+
};
|
|
59
|
+
return identity;
|
|
60
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decryptWithPrivateKey.d.ts","sourceRoot":"","sources":["../src/decryptWithPrivateKey.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC,eAAO,MAAM,qBAAqB,GAAI,YAAY,MAAM,EAAE,WAAW,SAAS,WAK7E,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { stripHexPrefix } from './util';
|
|
2
|
+
import { decrypt } from './encryption-utils';
|
|
3
|
+
export const decryptWithPrivateKey = (privateKey, encrypted) => {
|
|
4
|
+
// remove '0x' from privateKey
|
|
5
|
+
const twoStripped = stripHexPrefix(privateKey);
|
|
6
|
+
return decrypt(twoStripped, encrypted);
|
|
7
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryptWithPublicKey.d.ts","sourceRoot":"","sources":["../src/encryptWithPublicKey.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE5C,eAAO,MAAM,oBAAoB,GAAI,WAAW,MAAM,EAAE,SAAS,MAAM,EAAE,UAAU,iBAAiB,gCAOnG,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { decompress } from './util';
|
|
2
|
+
import { encrypt } from './encryption-utils';
|
|
3
|
+
export const encryptWithPublicKey = (publicKey, message, options) => {
|
|
4
|
+
// ensure its an uncompressed publicKey
|
|
5
|
+
const decompressedKey = decompress(publicKey);
|
|
6
|
+
// re-add the compression-flag
|
|
7
|
+
const pubString = '04' + decompressedKey;
|
|
8
|
+
return encrypt(pubString, message, options);
|
|
9
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Encrypted, EncryptionOptions } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* See https://github.com/bitchan/eccrypto for the original implementation that eth-crypto used, it's ancient and not maintained.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Encrypts a message using the recipient's public key.
|
|
7
|
+
* @param {string} publicKeyTo - The recipient's public key.
|
|
8
|
+
* @param {string} msg - The message to encrypt.
|
|
9
|
+
* @returns {Encrypted} The encrypted message.
|
|
10
|
+
*/
|
|
11
|
+
export declare const encrypt: (publicKeyTo: string, msg: string, options?: EncryptionOptions) => Encrypted;
|
|
12
|
+
/**
|
|
13
|
+
* Decrypts an encrypted message using the recipient's private key.
|
|
14
|
+
* @param {string} privateKey - The recipient's private key.
|
|
15
|
+
* @param {Encrypted} opts - The encrypted message.
|
|
16
|
+
* @returns {string} The decrypted message.
|
|
17
|
+
*/
|
|
18
|
+
export declare const decrypt: (privateKey: string, opts: Encrypted) => string;
|
|
19
|
+
//# sourceMappingURL=encryption-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryption-utils.d.ts","sourceRoot":"","sources":["../src/encryption-utils.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAIvD;;GAEG;AAEH;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,GAAI,aAAa,MAAM,EAAE,KAAK,MAAM,EAAE,UAAU,iBAAiB,KAAG,SAsBvF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,OAAO,GAAI,YAAY,MAAM,EAAE,MAAM,SAAS,WA2B1D,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { sha512 } from 'ethereum-cryptography/sha512.js';
|
|
2
|
+
import { secp256k1 } from 'ethereum-cryptography/secp256k1';
|
|
3
|
+
import { getRandomBytesSync as randomBytes } from 'ethereum-cryptography/random.js';
|
|
4
|
+
import { hexToBytes, bytesToHex, bytesToUtf8 } from 'ethereum-cryptography/utils.js';
|
|
5
|
+
import { encrypt as aesEncrypt, decrypt as aesDecrypt } from 'ethereum-cryptography/aes.js';
|
|
6
|
+
import { hmacSha256Sign } from './sign';
|
|
7
|
+
import { concatUint8Arrays, utf8ToBytes } from './util';
|
|
8
|
+
/**
|
|
9
|
+
* See https://github.com/bitchan/eccrypto for the original implementation that eth-crypto used, it's ancient and not maintained.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Encrypts a message using the recipient's public key.
|
|
13
|
+
* @param {string} publicKeyTo - The recipient's public key.
|
|
14
|
+
* @param {string} msg - The message to encrypt.
|
|
15
|
+
* @returns {Encrypted} The encrypted message.
|
|
16
|
+
*/
|
|
17
|
+
export const encrypt = (publicKeyTo, msg, options) => {
|
|
18
|
+
const ephemPrivateKey = options?.ephemPrivateKey ? hexToBytes(options.ephemPrivateKey) : randomBytes(32);
|
|
19
|
+
const iv = randomBytes(16);
|
|
20
|
+
const compressedPub = secp256k1.getPublicKey(ephemPrivateKey); // defaults to compressed format
|
|
21
|
+
const point = secp256k1.ProjectivePoint.fromHex(compressedPub); // decompress the pub into an EC point
|
|
22
|
+
const ephemPublicKey = point.toRawBytes(false); // Get uncompressed SEC1 format pub
|
|
23
|
+
const sharedSecret = secp256k1.getSharedSecret(ephemPrivateKey, hexToBytes(publicKeyTo), true).slice(1);
|
|
24
|
+
const hash = sha512(sharedSecret);
|
|
25
|
+
const encryptionKey = hash.subarray(0, 32);
|
|
26
|
+
const macKey = hash.subarray(32);
|
|
27
|
+
const message = utf8ToBytes(msg);
|
|
28
|
+
const data = aesEncrypt(message, encryptionKey, iv, 'aes-256-cbc');
|
|
29
|
+
const dataToMac = concatUint8Arrays([iv, ephemPublicKey, data]);
|
|
30
|
+
const mac = hmacSha256Sign(macKey, dataToMac);
|
|
31
|
+
return {
|
|
32
|
+
iv: bytesToHex(iv),
|
|
33
|
+
ephemPublicKey: bytesToHex(ephemPublicKey),
|
|
34
|
+
ciphertext: bytesToHex(data),
|
|
35
|
+
mac: bytesToHex(mac),
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Decrypts an encrypted message using the recipient's private key.
|
|
40
|
+
* @param {string} privateKey - The recipient's private key.
|
|
41
|
+
* @param {Encrypted} opts - The encrypted message.
|
|
42
|
+
* @returns {string} The decrypted message.
|
|
43
|
+
*/
|
|
44
|
+
export const decrypt = (privateKey, opts) => {
|
|
45
|
+
let sharedSecret;
|
|
46
|
+
try {
|
|
47
|
+
sharedSecret = secp256k1.getSharedSecret(hexToBytes(privateKey), opts.ephemPublicKey, true).slice(1);
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
throw new Error(`Invalid MAC: data integrity check failed: ${e}`);
|
|
51
|
+
}
|
|
52
|
+
const hash = sha512(sharedSecret);
|
|
53
|
+
const encryptionKey = hash.subarray(0, 32);
|
|
54
|
+
const macKey = hash.subarray(32);
|
|
55
|
+
const ciphertext = hexToBytes(opts.ciphertext);
|
|
56
|
+
const iv = hexToBytes(opts.iv);
|
|
57
|
+
const ephemPublicKey = hexToBytes(opts.ephemPublicKey);
|
|
58
|
+
const receivedMac = hexToBytes(opts.mac);
|
|
59
|
+
// Recompute MAC
|
|
60
|
+
const dataToMac = concatUint8Arrays([iv, ephemPublicKey, ciphertext]);
|
|
61
|
+
const expectedMac = hmacSha256Sign(macKey, dataToMac);
|
|
62
|
+
if (!constantTimeEqual(expectedMac, receivedMac)) {
|
|
63
|
+
throw new Error('Invalid MAC: data integrity check failed');
|
|
64
|
+
}
|
|
65
|
+
const decrypted = aesDecrypt(ciphertext, encryptionKey, iv, 'aes-256-cbc');
|
|
66
|
+
return bytesToUtf8(decrypted);
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Compares two Uint8Arrays in constant time to prevent timing attacks.
|
|
70
|
+
* @param {Uint8Array} a - The first array.
|
|
71
|
+
* @param {Uint8Array} b - The second array.
|
|
72
|
+
* @returns {boolean} True if the arrays are equal, false otherwise.
|
|
73
|
+
*/
|
|
74
|
+
function constantTimeEqual(a, b) {
|
|
75
|
+
if (a.length !== b.length)
|
|
76
|
+
return false;
|
|
77
|
+
let result = 0;
|
|
78
|
+
for (let i = 0; i < a.length; i++) {
|
|
79
|
+
result |= a[i] ^ b[i];
|
|
80
|
+
}
|
|
81
|
+
return result === 0;
|
|
82
|
+
}
|
package/dist/hash.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,WAEvC,CAAC"}
|
package/dist/hash.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { bytesToHex, hexToBytes } from 'ethereum-cryptography/utils.js';
|
|
2
|
+
import { keccak256 as _keccak256 } from 'ethereum-cryptography/keccak.js';
|
|
3
|
+
import { addLeading0x, utf8ToBytes } from './util';
|
|
4
|
+
const solidityPackedKeccak256 = (value) => {
|
|
5
|
+
const bytes = utf8ToBytes(value);
|
|
6
|
+
const hex = addLeading0x(bytesToHex(bytes));
|
|
7
|
+
const hash = _keccak256(hexToBytes(hex));
|
|
8
|
+
return addLeading0x(bytesToHex(hash));
|
|
9
|
+
};
|
|
10
|
+
export const keccak256 = (params) => {
|
|
11
|
+
return solidityPackedKeccak256(params);
|
|
12
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createIdentity } from './createIdentity';
|
|
2
|
+
import { sign } from './sign';
|
|
3
|
+
import { encryptWithPublicKey } from './encryptWithPublicKey';
|
|
4
|
+
import { decryptWithPrivateKey } from './decryptWithPrivateKey';
|
|
5
|
+
import { keccak256 } from './hash';
|
|
6
|
+
import { publicKeyByPrivateKey } from './publicKeyByPrivateKey';
|
|
7
|
+
import { recoverPublicKey } from './recoverPublicKey';
|
|
8
|
+
declare const hash: {
|
|
9
|
+
keccak256: (params: string) => string;
|
|
10
|
+
};
|
|
11
|
+
export { createIdentity, decryptWithPrivateKey, encryptWithPublicKey, hash, keccak256, publicKeyByPrivateKey, recoverPublicKey, sign, };
|
|
12
|
+
declare const _default: {
|
|
13
|
+
createIdentity: (entropy?: Uint8Array) => {
|
|
14
|
+
privateKey: string;
|
|
15
|
+
publicKey: string;
|
|
16
|
+
};
|
|
17
|
+
decryptWithPrivateKey: (privateKey: string, encrypted: import("./types").Encrypted) => string;
|
|
18
|
+
encryptWithPublicKey: (publicKey: string, message: string, options?: import("./types").EncryptionOptions) => import("./types").Encrypted;
|
|
19
|
+
hash: {
|
|
20
|
+
keccak256: (params: string) => string;
|
|
21
|
+
};
|
|
22
|
+
keccak256: (params: string) => string;
|
|
23
|
+
publicKeyByPrivateKey: (privateKey: string) => string;
|
|
24
|
+
recoverPublicKey: (signature: string, hash: string) => string;
|
|
25
|
+
sign: (privateKey: string, hash: string) => string;
|
|
26
|
+
};
|
|
27
|
+
export default _default;
|
|
28
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,QAAA,MAAM,IAAI;;CAET,CAAC;AAEF,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,IAAI,EACJ,SAAS,EACT,qBAAqB,EACrB,gBAAgB,EAChB,IAAI,GACL,CAAC;;;;;;;;;;;;;;;;AAEF,wBASE"}
|
|
@@ -5,29 +5,17 @@ import { decryptWithPrivateKey } from './decryptWithPrivateKey';
|
|
|
5
5
|
import { keccak256 } from './hash';
|
|
6
6
|
import { publicKeyByPrivateKey } from './publicKeyByPrivateKey';
|
|
7
7
|
import { recoverPublicKey } from './recoverPublicKey';
|
|
8
|
-
|
|
9
8
|
const hash = {
|
|
10
|
-
|
|
9
|
+
keccak256,
|
|
11
10
|
};
|
|
12
|
-
|
|
13
|
-
export {
|
|
14
|
-
createIdentity,
|
|
15
|
-
decryptWithPrivateKey,
|
|
16
|
-
encryptWithPublicKey,
|
|
17
|
-
hash,
|
|
18
|
-
keccak256,
|
|
19
|
-
publicKeyByPrivateKey,
|
|
20
|
-
recoverPublicKey,
|
|
21
|
-
sign,
|
|
22
|
-
};
|
|
23
|
-
|
|
11
|
+
export { createIdentity, decryptWithPrivateKey, encryptWithPublicKey, hash, keccak256, publicKeyByPrivateKey, recoverPublicKey, sign, };
|
|
24
12
|
export default {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
13
|
+
createIdentity,
|
|
14
|
+
decryptWithPrivateKey,
|
|
15
|
+
encryptWithPublicKey,
|
|
16
|
+
hash,
|
|
17
|
+
keccak256,
|
|
18
|
+
publicKeyByPrivateKey,
|
|
19
|
+
recoverPublicKey,
|
|
20
|
+
sign,
|
|
33
21
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate publicKey from the privateKey.
|
|
3
|
+
* This creates the uncompressed publicKey,
|
|
4
|
+
* where 04 has stripped from left
|
|
5
|
+
* @returns {string}
|
|
6
|
+
*/
|
|
7
|
+
export declare const publicKeyByPrivateKey: (privateKey: string) => string;
|
|
8
|
+
//# sourceMappingURL=publicKeyByPrivateKey.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publicKeyByPrivateKey.d.ts","sourceRoot":"","sources":["../src/publicKeyByPrivateKey.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AAEH,eAAO,MAAM,qBAAqB,GAAI,YAAY,MAAM,WAKvD,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { decompress, stripHexPrefix } from './util';
|
|
2
|
+
import { secp256k1 } from 'ethereum-cryptography/secp256k1.js';
|
|
3
|
+
import { bytesToHex } from 'ethereum-cryptography/utils';
|
|
4
|
+
/**
|
|
5
|
+
* Generate publicKey from the privateKey.
|
|
6
|
+
* This creates the uncompressed publicKey,
|
|
7
|
+
* where 04 has stripped from left
|
|
8
|
+
* @returns {string}
|
|
9
|
+
*/
|
|
10
|
+
export const publicKeyByPrivateKey = (privateKey) => {
|
|
11
|
+
const key = stripHexPrefix(privateKey);
|
|
12
|
+
const compressedPub = secp256k1.getPublicKey(key); // defaults to compressed format
|
|
13
|
+
const point = secp256k1.ProjectivePoint.fromHex(compressedPub); // decompress the pub into an EC point
|
|
14
|
+
return decompress(bytesToHex(point.toRawBytes(false))); // Get uncompressed SEC1 format pub
|
|
15
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* returns the publicKey for the privateKey with which the messageHash was signed
|
|
3
|
+
* @param {string} signature
|
|
4
|
+
* @param {string} hash
|
|
5
|
+
* @return {string} publicKey
|
|
6
|
+
*/
|
|
7
|
+
export declare const recoverPublicKey: (signature: string, hash: string) => string;
|
|
8
|
+
//# sourceMappingURL=recoverPublicKey.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recoverPublicKey.d.ts","sourceRoot":"","sources":["../src/recoverPublicKey.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,WAAW,MAAM,EAAE,MAAM,MAAM,WAe/D,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ecdsaRecover } from 'ethereum-cryptography/secp256k1-compat';
|
|
2
|
+
import { stripHexPrefix } from './util';
|
|
3
|
+
import { bytesToHex, hexToBytes } from 'ethereum-cryptography/utils';
|
|
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 const recoverPublicKey = (signature, hash) => {
|
|
11
|
+
const noHex = stripHexPrefix(signature);
|
|
12
|
+
// split into v-value and sig
|
|
13
|
+
const sigOnly = noHex.substring(0, noHex.length - 2); // all but last 2 chars
|
|
14
|
+
const vValue = noHex.slice(-2); // last 2 chars
|
|
15
|
+
const recoveryNumber = vValue === '1c' ? 1 : 0;
|
|
16
|
+
let pubKey = bytesToHex(ecdsaRecover(hexToBytes(sigOnly), recoveryNumber, hexToBytes(stripHexPrefix(hash)), false));
|
|
17
|
+
// remove trailing '04'
|
|
18
|
+
pubKey = pubKey.slice(2);
|
|
19
|
+
return pubKey;
|
|
20
|
+
};
|
package/dist/sign.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* signs the given message
|
|
3
|
+
* @param {string} privateKey
|
|
4
|
+
* @param {string} hash
|
|
5
|
+
* @return {string} hexString
|
|
6
|
+
*/
|
|
7
|
+
export declare const sign: (privateKey: string, hash: string) => string;
|
|
8
|
+
export declare const hmacSha256Sign: (key: Uint8Array, msg: Uint8Array) => Uint8Array<ArrayBufferLike>;
|
|
9
|
+
//# sourceMappingURL=sign.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"AAMA;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,YAAY,MAAM,EAAE,MAAM,MAAM,WASpD,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,KAAK,UAAU,EAAE,KAAK,UAAU,gCAG9D,CAAC"}
|
package/dist/sign.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { secp256k1 } from 'ethereum-cryptography/secp256k1.js';
|
|
2
|
+
import { hmac } from '@noble/hashes/hmac.js';
|
|
3
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
4
|
+
import { hexToBytes } from 'ethereum-cryptography/utils';
|
|
5
|
+
import { addLeading0x, isHexString, stripHexPrefix } from './util';
|
|
6
|
+
/**
|
|
7
|
+
* signs the given message
|
|
8
|
+
* @param {string} privateKey
|
|
9
|
+
* @param {string} hash
|
|
10
|
+
* @return {string} hexString
|
|
11
|
+
*/
|
|
12
|
+
export const sign = (privateKey, hash) => {
|
|
13
|
+
const hashWith0x = addLeading0x(hash);
|
|
14
|
+
if (hashWith0x.length !== 66 || !isHexString(hashWith0x))
|
|
15
|
+
throw new Error('Can only sign hashes, given: ' + hash);
|
|
16
|
+
const sigObj = secp256k1.sign(hexToBytes(stripHexPrefix(hash)), hexToBytes(stripHexPrefix(privateKey)));
|
|
17
|
+
const recoveryId = sigObj.recovery === 1 ? '1c' : '1b';
|
|
18
|
+
const newSignature = '0x' + sigObj.toCompactHex() + recoveryId;
|
|
19
|
+
return newSignature;
|
|
20
|
+
};
|
|
21
|
+
export const hmacSha256Sign = (key, msg) => {
|
|
22
|
+
const result = hmac(sha256, key, msg);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a `Boolean` on whether or not the a `String` starts with '0x'
|
|
3
|
+
* @param str the string input value
|
|
4
|
+
* @return a boolean if it is or is not hex prefixed
|
|
5
|
+
* @throws if the str input is not a string
|
|
6
|
+
*/
|
|
7
|
+
export declare const isHexPrefixed: (str: string) => boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Removes '0x' from a given `String` if present
|
|
10
|
+
* @param str the string value
|
|
11
|
+
* @returns the string without 0x prefix
|
|
12
|
+
*/
|
|
13
|
+
export declare const stripHexPrefix: (str: string) => string;
|
|
14
|
+
/**
|
|
15
|
+
* Is the string a hex string.
|
|
16
|
+
*
|
|
17
|
+
* @param value
|
|
18
|
+
* @param length
|
|
19
|
+
* @returns output the string is a hex string
|
|
20
|
+
*/
|
|
21
|
+
export declare const isHexString: (value: string, length?: number) => boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Adds '0x' to a given `String` if not present
|
|
24
|
+
* @param str the string input value
|
|
25
|
+
* @return the string with a 0x prefix
|
|
26
|
+
*/
|
|
27
|
+
export declare const addLeading0x: (str: string) => string;
|
|
28
|
+
export declare const decompress: (startsWith02Or03: string) => string;
|
|
29
|
+
/** Helper function to concat UInt8Arrays mimicking the behaviour of the
|
|
30
|
+
* Buffer.concat function in Node.js
|
|
31
|
+
*/
|
|
32
|
+
export declare const concatUint8Arrays: (uint8arrays: Uint8Array[]) => Uint8Array<ArrayBuffer>;
|
|
33
|
+
/**
|
|
34
|
+
* Converts a UTF-8 string to a Uint8Array without using TextEncoder, which is not available in mobile
|
|
35
|
+
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
|
|
36
|
+
*/
|
|
37
|
+
export declare const utf8ToBytes: (str: string) => Uint8Array;
|
|
38
|
+
//# sourceMappingURL=util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,KAAK,MAAM,YAMxC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,MAI5C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,EAAE,SAAS,MAAM,KAAG,OAM5D,CAAC;AAEF;;;;GAIG;AAEH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,WAGvC,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,kBAAkB,MAAM,WAOlD,CAAC;AAEF;;GAEG;AAEH,eAAO,MAAM,iBAAiB,GAAI,aAAa,UAAU,EAAE,4BAS1D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,UA4BzC,CAAC"}
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { hexToBytes } from 'ethereum-cryptography/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Returns a `Boolean` on whether or not the a `String` starts with '0x'
|
|
4
|
+
* @param str the string input value
|
|
5
|
+
* @return a boolean if it is or is not hex prefixed
|
|
6
|
+
* @throws if the str input is not a string
|
|
7
|
+
*/
|
|
8
|
+
export const isHexPrefixed = (str) => {
|
|
9
|
+
if (typeof str !== 'string') {
|
|
10
|
+
throw new Error(`[isHexPrefixed] input must be type 'string', received type ${typeof str}`);
|
|
11
|
+
}
|
|
12
|
+
return str[0] === '0' && str[1] === 'x';
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Removes '0x' from a given `String` if present
|
|
16
|
+
* @param str the string value
|
|
17
|
+
* @returns the string without 0x prefix
|
|
18
|
+
*/
|
|
19
|
+
export const stripHexPrefix = (str) => {
|
|
20
|
+
if (typeof str !== 'string')
|
|
21
|
+
throw new Error(`[stripHexPrefix] input must be type 'string', received ${typeof str}`);
|
|
22
|
+
return isHexPrefixed(str) ? str.slice(2) : str;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Is the string a hex string.
|
|
26
|
+
*
|
|
27
|
+
* @param value
|
|
28
|
+
* @param length
|
|
29
|
+
* @returns output the string is a hex string
|
|
30
|
+
*/
|
|
31
|
+
export const isHexString = (value, length) => {
|
|
32
|
+
if (typeof value !== 'string' || !value.match(/^0x[0-9A-Fa-f]+$/))
|
|
33
|
+
return false;
|
|
34
|
+
if (length && value.length !== 2 + 2 * length)
|
|
35
|
+
return false;
|
|
36
|
+
return true;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Adds '0x' to a given `String` if not present
|
|
40
|
+
* @param str the string input value
|
|
41
|
+
* @return the string with a 0x prefix
|
|
42
|
+
*/
|
|
43
|
+
export const addLeading0x = (str) => {
|
|
44
|
+
if (!str.startsWith('0x'))
|
|
45
|
+
return '0x' + str;
|
|
46
|
+
else
|
|
47
|
+
return str;
|
|
48
|
+
};
|
|
49
|
+
export const decompress = (startsWith02Or03) => {
|
|
50
|
+
const testByteArray = hexToBytes(startsWith02Or03);
|
|
51
|
+
let startsWith04 = startsWith02Or03;
|
|
52
|
+
if (testByteArray.length === 64) {
|
|
53
|
+
startsWith04 = '04' + startsWith02Or03;
|
|
54
|
+
}
|
|
55
|
+
return startsWith04.substring(2);
|
|
56
|
+
};
|
|
57
|
+
/** Helper function to concat UInt8Arrays mimicking the behaviour of the
|
|
58
|
+
* Buffer.concat function in Node.js
|
|
59
|
+
*/
|
|
60
|
+
export const concatUint8Arrays = (uint8arrays) => {
|
|
61
|
+
const totalLength = uint8arrays.reduce((total, uint8array) => total + uint8array.byteLength, 0);
|
|
62
|
+
const result = new Uint8Array(totalLength);
|
|
63
|
+
let offset = 0;
|
|
64
|
+
uint8arrays.forEach((uint8array) => {
|
|
65
|
+
result.set(uint8array, offset);
|
|
66
|
+
offset += uint8array.byteLength;
|
|
67
|
+
});
|
|
68
|
+
return result;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Converts a UTF-8 string to a Uint8Array without using TextEncoder, which is not available in mobile
|
|
72
|
+
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
|
|
73
|
+
*/
|
|
74
|
+
export const utf8ToBytes = (str) => {
|
|
75
|
+
if (typeof str !== 'string')
|
|
76
|
+
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
|
|
77
|
+
const bytes = [];
|
|
78
|
+
for (let i = 0; i < str.length; i++) {
|
|
79
|
+
const codePoint = str.codePointAt(i);
|
|
80
|
+
if (!codePoint) {
|
|
81
|
+
throw new Error('Invalid code point');
|
|
82
|
+
}
|
|
83
|
+
if (codePoint < 0x80) {
|
|
84
|
+
bytes.push(codePoint);
|
|
85
|
+
}
|
|
86
|
+
else if (codePoint < 0x800) {
|
|
87
|
+
bytes.push(0xc0 | (codePoint >> 6), 0x80 | (codePoint & 0x3f));
|
|
88
|
+
}
|
|
89
|
+
else if (codePoint < 0x10000) {
|
|
90
|
+
bytes.push(0xe0 | (codePoint >> 12), 0x80 | ((codePoint >> 6) & 0x3f), 0x80 | (codePoint & 0x3f));
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
i++; // skip one iteration since we have a surrogate pair
|
|
94
|
+
bytes.push(0xf0 | (codePoint >> 18), 0x80 | ((codePoint >> 12) & 0x3f), 0x80 | ((codePoint >> 6) & 0x3f), 0x80 | (codePoint & 0x3f));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return new Uint8Array(bytes);
|
|
98
|
+
};
|
package/package.json
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wallaby-cash/cryptography",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Cryptography utilities for Wallaby Cash",
|
|
5
|
-
"main": "./
|
|
6
|
-
"types": "./
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
-
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
10
12
|
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md",
|
|
16
|
+
"CHANGELOG.md"
|
|
17
|
+
],
|
|
11
18
|
"publishConfig": {
|
|
12
19
|
"access": "public"
|
|
13
20
|
},
|
|
@@ -25,6 +32,8 @@
|
|
|
25
32
|
"directory": "packages/cryptography"
|
|
26
33
|
},
|
|
27
34
|
"scripts": {
|
|
35
|
+
"build": "tsc",
|
|
36
|
+
"prepublishOnly": "npm run build",
|
|
28
37
|
"lint": "eslint . --max-warnings 0",
|
|
29
38
|
"generate:component": "turbo gen react-component",
|
|
30
39
|
"check-types": "tsc --noEmit"
|
|
@@ -39,6 +48,7 @@
|
|
|
39
48
|
"typescript": "5.9.2"
|
|
40
49
|
},
|
|
41
50
|
"dependencies": {
|
|
51
|
+
"@noble/hashes": "^2.0.1",
|
|
42
52
|
"ethereum-cryptography": "^3.2.0",
|
|
43
53
|
"react": "^19.2.0",
|
|
44
54
|
"react-dom": "^19.2.0"
|
package/src/createIdentity.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { getRandomBytesSync as randomBytes } from 'ethereum-cryptography/random.js';
|
|
2
|
-
import { addLeading0x, stripHexPrefix, concatUint8Arrays } from './util';
|
|
3
|
-
import { publicKeyByPrivateKey } from './publicKeyByPrivateKey';
|
|
4
|
-
import { bytesToHex } from 'ethereum-cryptography/utils';
|
|
5
|
-
import { keccak256 } from 'ethereum-cryptography/keccak';
|
|
6
|
-
|
|
7
|
-
export const DEFAULT_ENTROPY_BYTES = 32;
|
|
8
|
-
export const MINIMUM_SHANNON_ENTROPY = 4;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* creates a new private key
|
|
12
|
-
* @param { Uint8Array } entropy - optional entropy to create the private key
|
|
13
|
-
* @returns a new private key
|
|
14
|
-
*/
|
|
15
|
-
export const createPrivateKey = (entropy?: Uint8Array) => {
|
|
16
|
-
if (entropy) {
|
|
17
|
-
if (!(entropy instanceof Uint8Array) || entropy.length < DEFAULT_ENTROPY_BYTES) {
|
|
18
|
-
throw new Error(`entropy must be a Uint8Array of at least ${DEFAULT_ENTROPY_BYTES} bytes`);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Check byte diversity
|
|
22
|
-
const uniqueBytes = new Set(entropy);
|
|
23
|
-
if (uniqueBytes.size < Math.min(8, entropy.length / 4)) {
|
|
24
|
-
throw new Error(`entropy is too repetitive (only ${uniqueBytes.size} unique byte values)`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Estimate Shannon entropy
|
|
28
|
-
const byteCounts = new Uint32Array(256);
|
|
29
|
-
entropy.forEach((b) => byteCounts[b]!++);
|
|
30
|
-
const total = entropy.length;
|
|
31
|
-
let shannonEntropy = 0;
|
|
32
|
-
for (let i = 0; i < 256; i++) {
|
|
33
|
-
if (byteCounts[i]! > 0) {
|
|
34
|
-
const p = byteCounts[i]! / total;
|
|
35
|
-
shannonEntropy -= p * Math.log2(p);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
if (shannonEntropy < MINIMUM_SHANNON_ENTROPY) {
|
|
39
|
-
throw new Error(`entropy has low Shannon entropy (${shannonEntropy.toFixed(2)} bits/byte)`);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const outerHex = keccak256(entropy);
|
|
43
|
-
return addLeading0x(bytesToHex(outerHex));
|
|
44
|
-
} else {
|
|
45
|
-
const innerHex = keccak256(concatUint8Arrays([randomBytes(32), randomBytes(32)]));
|
|
46
|
-
const middleHex = concatUint8Arrays([concatUint8Arrays([randomBytes(32), innerHex]), randomBytes(32)]);
|
|
47
|
-
const outerHex = keccak256(middleHex);
|
|
48
|
-
return addLeading0x(bytesToHex(outerHex));
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* creates a new identity
|
|
54
|
-
* @param { Uint8Array } entropy - optional entropy to create the private key
|
|
55
|
-
* @returns a new pair of private and public key
|
|
56
|
-
*/
|
|
57
|
-
export const createIdentity = (entropy?: Uint8Array) => {
|
|
58
|
-
const privateKey = createPrivateKey(entropy);
|
|
59
|
-
|
|
60
|
-
const walletPublicKey = publicKeyByPrivateKey(privateKey);
|
|
61
|
-
const identity = {
|
|
62
|
-
privateKey: privateKey,
|
|
63
|
-
publicKey: stripHexPrefix(walletPublicKey),
|
|
64
|
-
};
|
|
65
|
-
return identity;
|
|
66
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { stripHexPrefix } from './util';
|
|
2
|
-
import { decrypt } from './encryption-utils';
|
|
3
|
-
import { Encrypted } from './types';
|
|
4
|
-
|
|
5
|
-
export const decryptWithPrivateKey = (privateKey: string, encrypted: Encrypted) => {
|
|
6
|
-
// remove '0x' from privateKey
|
|
7
|
-
const twoStripped = stripHexPrefix(privateKey);
|
|
8
|
-
|
|
9
|
-
return decrypt(twoStripped, encrypted);
|
|
10
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { decompress } from './util';
|
|
2
|
-
import { encrypt } from './encryption-utils';
|
|
3
|
-
import { EncryptionOptions } from './types';
|
|
4
|
-
|
|
5
|
-
export const encryptWithPublicKey = (publicKey: string, message: string, options?: EncryptionOptions) => {
|
|
6
|
-
// ensure its an uncompressed publicKey
|
|
7
|
-
const decompressedKey = decompress(publicKey);
|
|
8
|
-
|
|
9
|
-
// re-add the compression-flag
|
|
10
|
-
const pubString = '04' + decompressedKey;
|
|
11
|
-
return encrypt(pubString, message, options);
|
|
12
|
-
};
|
package/src/encryption-utils.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { sha512 } from 'ethereum-cryptography/sha512.js';
|
|
2
|
-
import { secp256k1 } from 'ethereum-cryptography/secp256k1';
|
|
3
|
-
import { getRandomBytesSync as randomBytes } from 'ethereum-cryptography/random.js';
|
|
4
|
-
import { hexToBytes, bytesToHex, bytesToUtf8 } from 'ethereum-cryptography/utils.js';
|
|
5
|
-
import { encrypt as aesEncrypt, decrypt as aesDecrypt } from 'ethereum-cryptography/aes.js';
|
|
6
|
-
import { Encrypted, EncryptionOptions } from './types';
|
|
7
|
-
import { hmacSha256Sign } from './sign';
|
|
8
|
-
import { concatUint8Arrays, utf8ToBytes } from './util';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* See https://github.com/bitchan/eccrypto for the original implementation that eth-crypto used, it's ancient and not maintained.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Encrypts a message using the recipient's public key.
|
|
16
|
-
* @param {string} publicKeyTo - The recipient's public key.
|
|
17
|
-
* @param {string} msg - The message to encrypt.
|
|
18
|
-
* @returns {Encrypted} The encrypted message.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
export const encrypt = (publicKeyTo: string, msg: string, options?: EncryptionOptions): Encrypted => {
|
|
22
|
-
const ephemPrivateKey = options?.ephemPrivateKey ? hexToBytes(options.ephemPrivateKey) : randomBytes(32);
|
|
23
|
-
const iv = randomBytes(16);
|
|
24
|
-
|
|
25
|
-
const compressedPub = secp256k1.getPublicKey(ephemPrivateKey); // defaults to compressed format
|
|
26
|
-
const point = secp256k1.ProjectivePoint.fromHex(compressedPub); // decompress the pub into an EC point
|
|
27
|
-
const ephemPublicKey = point.toRawBytes(false); // Get uncompressed SEC1 format pub
|
|
28
|
-
const sharedSecret = secp256k1.getSharedSecret(ephemPrivateKey, hexToBytes(publicKeyTo), true).slice(1);
|
|
29
|
-
const hash = sha512(sharedSecret);
|
|
30
|
-
const encryptionKey = hash.subarray(0, 32);
|
|
31
|
-
const macKey = hash.subarray(32);
|
|
32
|
-
const message = utf8ToBytes(msg);
|
|
33
|
-
const data = aesEncrypt(message, encryptionKey, iv, 'aes-256-cbc');
|
|
34
|
-
const dataToMac = concatUint8Arrays([iv, ephemPublicKey, data]);
|
|
35
|
-
const mac = hmacSha256Sign(macKey, dataToMac);
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
iv: bytesToHex(iv),
|
|
39
|
-
ephemPublicKey: bytesToHex(ephemPublicKey),
|
|
40
|
-
ciphertext: bytesToHex(data),
|
|
41
|
-
mac: bytesToHex(mac),
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Decrypts an encrypted message using the recipient's private key.
|
|
47
|
-
* @param {string} privateKey - The recipient's private key.
|
|
48
|
-
* @param {Encrypted} opts - The encrypted message.
|
|
49
|
-
* @returns {string} The decrypted message.
|
|
50
|
-
*/
|
|
51
|
-
export const decrypt = (privateKey: string, opts: Encrypted) => {
|
|
52
|
-
let sharedSecret: Uint8Array;
|
|
53
|
-
try {
|
|
54
|
-
sharedSecret = secp256k1.getSharedSecret(hexToBytes(privateKey), opts.ephemPublicKey, true).slice(1);
|
|
55
|
-
} catch (e) {
|
|
56
|
-
throw new Error(`Invalid MAC: data integrity check failed: ${e}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const hash = sha512(sharedSecret);
|
|
60
|
-
const encryptionKey = hash.subarray(0, 32);
|
|
61
|
-
const macKey = hash.subarray(32);
|
|
62
|
-
|
|
63
|
-
const ciphertext = hexToBytes(opts.ciphertext);
|
|
64
|
-
const iv = hexToBytes(opts.iv);
|
|
65
|
-
const ephemPublicKey = hexToBytes(opts.ephemPublicKey);
|
|
66
|
-
const receivedMac = hexToBytes(opts.mac);
|
|
67
|
-
|
|
68
|
-
// Recompute MAC
|
|
69
|
-
const dataToMac = concatUint8Arrays([iv, ephemPublicKey, ciphertext]);
|
|
70
|
-
const expectedMac = hmacSha256Sign(macKey, dataToMac);
|
|
71
|
-
|
|
72
|
-
if (!constantTimeEqual(expectedMac, receivedMac)) {
|
|
73
|
-
throw new Error('Invalid MAC: data integrity check failed');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const decrypted = aesDecrypt(ciphertext, encryptionKey, iv, 'aes-256-cbc');
|
|
77
|
-
return bytesToUtf8(decrypted);
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Compares two Uint8Arrays in constant time to prevent timing attacks.
|
|
82
|
-
* @param {Uint8Array} a - The first array.
|
|
83
|
-
* @param {Uint8Array} b - The second array.
|
|
84
|
-
* @returns {boolean} True if the arrays are equal, false otherwise.
|
|
85
|
-
*/
|
|
86
|
-
function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {
|
|
87
|
-
if (a.length !== b.length) return false;
|
|
88
|
-
let result = 0;
|
|
89
|
-
for (let i = 0; i < a.length; i++) {
|
|
90
|
-
result |= a[i]! ^ b[i]!;
|
|
91
|
-
}
|
|
92
|
-
return result === 0;
|
|
93
|
-
}
|
package/src/hash.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { bytesToHex, hexToBytes } from 'ethereum-cryptography/utils.js';
|
|
2
|
-
import { keccak256 as _keccak256 } from 'ethereum-cryptography/keccak.js';
|
|
3
|
-
import { addLeading0x, utf8ToBytes } from './util';
|
|
4
|
-
|
|
5
|
-
const solidityPackedKeccak256 = (value: string) => {
|
|
6
|
-
const bytes = utf8ToBytes(value);
|
|
7
|
-
const hex = addLeading0x(bytesToHex(bytes));
|
|
8
|
-
const hash = _keccak256(hexToBytes(hex));
|
|
9
|
-
return addLeading0x(bytesToHex(hash));
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export const keccak256 = (params: string) => {
|
|
13
|
-
return solidityPackedKeccak256(params);
|
|
14
|
-
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { decompress, stripHexPrefix } from './util';
|
|
2
|
-
import { secp256k1 } from 'ethereum-cryptography/secp256k1.js';
|
|
3
|
-
import { bytesToHex } from 'ethereum-cryptography/utils';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Generate publicKey from the privateKey.
|
|
7
|
-
* This creates the uncompressed publicKey,
|
|
8
|
-
* where 04 has stripped from left
|
|
9
|
-
* @returns {string}
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export const publicKeyByPrivateKey = (privateKey: string) => {
|
|
13
|
-
const key = stripHexPrefix(privateKey);
|
|
14
|
-
const compressedPub = secp256k1.getPublicKey(key); // defaults to compressed format
|
|
15
|
-
const point = secp256k1.ProjectivePoint.fromHex(compressedPub); // decompress the pub into an EC point
|
|
16
|
-
return decompress(bytesToHex(point.toRawBytes(false))); // Get uncompressed SEC1 format pub
|
|
17
|
-
};
|
package/src/recoverPublicKey.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { ecdsaRecover } from 'ethereum-cryptography/secp256k1-compat';
|
|
2
|
-
import { stripHexPrefix } from './util';
|
|
3
|
-
import { bytesToHex, hexToBytes } from 'ethereum-cryptography/utils';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* returns the publicKey for the privateKey with which the messageHash was signed
|
|
7
|
-
* @param {string} signature
|
|
8
|
-
* @param {string} hash
|
|
9
|
-
* @return {string} publicKey
|
|
10
|
-
*/
|
|
11
|
-
export const recoverPublicKey = (signature: string, hash: string) => {
|
|
12
|
-
const noHex = stripHexPrefix(signature);
|
|
13
|
-
|
|
14
|
-
// split into v-value and sig
|
|
15
|
-
const sigOnly = noHex.substring(0, noHex.length - 2); // all but last 2 chars
|
|
16
|
-
const vValue = noHex.slice(-2); // last 2 chars
|
|
17
|
-
|
|
18
|
-
const recoveryNumber = vValue === '1c' ? 1 : 0;
|
|
19
|
-
|
|
20
|
-
let pubKey = bytesToHex(ecdsaRecover(hexToBytes(sigOnly), recoveryNumber, hexToBytes(stripHexPrefix(hash)), false));
|
|
21
|
-
|
|
22
|
-
// remove trailing '04'
|
|
23
|
-
pubKey = pubKey.slice(2);
|
|
24
|
-
|
|
25
|
-
return pubKey;
|
|
26
|
-
};
|
package/src/sign.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { secp256k1 } from 'ethereum-cryptography/secp256k1.js';
|
|
2
|
-
import { hmac } from '@noble/hashes/hmac';
|
|
3
|
-
import { sha256 } from '@noble/hashes/sha2';
|
|
4
|
-
import { hexToBytes } from 'ethereum-cryptography/utils';
|
|
5
|
-
import { addLeading0x, isHexString, stripHexPrefix } from './util';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* signs the given message
|
|
9
|
-
* @param {string} privateKey
|
|
10
|
-
* @param {string} hash
|
|
11
|
-
* @return {string} hexString
|
|
12
|
-
*/
|
|
13
|
-
export const sign = (privateKey: string, hash: string) => {
|
|
14
|
-
const hashWith0x = addLeading0x(hash);
|
|
15
|
-
if (hashWith0x.length !== 66 || !isHexString(hashWith0x)) throw new Error('Can only sign hashes, given: ' + hash);
|
|
16
|
-
|
|
17
|
-
const sigObj = secp256k1.sign(hexToBytes(stripHexPrefix(hash)), hexToBytes(stripHexPrefix(privateKey)));
|
|
18
|
-
|
|
19
|
-
const recoveryId = sigObj.recovery === 1 ? '1c' : '1b';
|
|
20
|
-
const newSignature = '0x' + sigObj.toCompactHex() + recoveryId;
|
|
21
|
-
return newSignature;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export const hmacSha256Sign = (key: Uint8Array, msg: Uint8Array) => {
|
|
25
|
-
const result = hmac(sha256, key, msg);
|
|
26
|
-
return result;
|
|
27
|
-
};
|
package/src/types.ts
DELETED
package/src/util.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { hexToBytes } from 'ethereum-cryptography/utils';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Returns a `Boolean` on whether or not the a `String` starts with '0x'
|
|
5
|
-
* @param str the string input value
|
|
6
|
-
* @return a boolean if it is or is not hex prefixed
|
|
7
|
-
* @throws if the str input is not a string
|
|
8
|
-
*/
|
|
9
|
-
export const isHexPrefixed = (str: string) => {
|
|
10
|
-
if (typeof str !== 'string') {
|
|
11
|
-
throw new Error(`[isHexPrefixed] input must be type 'string', received type ${typeof str}`);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return str[0] === '0' && str[1] === 'x';
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Removes '0x' from a given `String` if present
|
|
19
|
-
* @param str the string value
|
|
20
|
-
* @returns the string without 0x prefix
|
|
21
|
-
*/
|
|
22
|
-
export const stripHexPrefix = (str: string): string => {
|
|
23
|
-
if (typeof str !== 'string') throw new Error(`[stripHexPrefix] input must be type 'string', received ${typeof str}`);
|
|
24
|
-
|
|
25
|
-
return isHexPrefixed(str) ? str.slice(2) : str;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Is the string a hex string.
|
|
30
|
-
*
|
|
31
|
-
* @param value
|
|
32
|
-
* @param length
|
|
33
|
-
* @returns output the string is a hex string
|
|
34
|
-
*/
|
|
35
|
-
export const isHexString = (value: string, length?: number): boolean => {
|
|
36
|
-
if (typeof value !== 'string' || !value.match(/^0x[0-9A-Fa-f]+$/)) return false;
|
|
37
|
-
|
|
38
|
-
if (length && value.length !== 2 + 2 * length) return false;
|
|
39
|
-
|
|
40
|
-
return true;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Adds '0x' to a given `String` if not present
|
|
45
|
-
* @param str the string input value
|
|
46
|
-
* @return the string with a 0x prefix
|
|
47
|
-
*/
|
|
48
|
-
|
|
49
|
-
export const addLeading0x = (str: string) => {
|
|
50
|
-
if (!str.startsWith('0x')) return '0x' + str;
|
|
51
|
-
else return str;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export const decompress = (startsWith02Or03: string) => {
|
|
55
|
-
const testByteArray = hexToBytes(startsWith02Or03);
|
|
56
|
-
let startsWith04 = startsWith02Or03;
|
|
57
|
-
if (testByteArray.length === 64) {
|
|
58
|
-
startsWith04 = '04' + startsWith02Or03;
|
|
59
|
-
}
|
|
60
|
-
return startsWith04.substring(2);
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
/** Helper function to concat UInt8Arrays mimicking the behaviour of the
|
|
64
|
-
* Buffer.concat function in Node.js
|
|
65
|
-
*/
|
|
66
|
-
|
|
67
|
-
export const concatUint8Arrays = (uint8arrays: Uint8Array[]) => {
|
|
68
|
-
const totalLength = uint8arrays.reduce((total, uint8array) => total + uint8array.byteLength, 0);
|
|
69
|
-
const result = new Uint8Array(totalLength);
|
|
70
|
-
let offset = 0;
|
|
71
|
-
uint8arrays.forEach((uint8array) => {
|
|
72
|
-
result.set(uint8array, offset);
|
|
73
|
-
offset += uint8array.byteLength;
|
|
74
|
-
});
|
|
75
|
-
return result;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Converts a UTF-8 string to a Uint8Array without using TextEncoder, which is not available in mobile
|
|
80
|
-
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
|
|
81
|
-
*/
|
|
82
|
-
export const utf8ToBytes = (str: string): Uint8Array => {
|
|
83
|
-
if (typeof str !== 'string') throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
|
|
84
|
-
const bytes = [];
|
|
85
|
-
|
|
86
|
-
for (let i = 0; i < str.length; i++) {
|
|
87
|
-
const codePoint = str.codePointAt(i);
|
|
88
|
-
|
|
89
|
-
if (!codePoint) {
|
|
90
|
-
throw new Error('Invalid code point');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (codePoint < 0x80) {
|
|
94
|
-
bytes.push(codePoint);
|
|
95
|
-
} else if (codePoint < 0x800) {
|
|
96
|
-
bytes.push(0xc0 | (codePoint >> 6), 0x80 | (codePoint & 0x3f));
|
|
97
|
-
} else if (codePoint < 0x10000) {
|
|
98
|
-
bytes.push(0xe0 | (codePoint >> 12), 0x80 | ((codePoint >> 6) & 0x3f), 0x80 | (codePoint & 0x3f));
|
|
99
|
-
} else {
|
|
100
|
-
i++; // skip one iteration since we have a surrogate pair
|
|
101
|
-
bytes.push(
|
|
102
|
-
0xf0 | (codePoint >> 18),
|
|
103
|
-
0x80 | ((codePoint >> 12) & 0x3f),
|
|
104
|
-
0x80 | ((codePoint >> 6) & 0x3f),
|
|
105
|
-
0x80 | (codePoint & 0x3f),
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return new Uint8Array(bytes);
|
|
110
|
-
};
|