diff --git a/package-lock.json b/package-lock.json index d8579c65..9371e36d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,9 +27,11 @@ }, "devDependencies": { "@types/absinthe__socket": "^0.2.3", + "@types/ed2curve": "^0.2.4", "@types/chrome": "0.0.266", "@types/elliptic": "^6.4.14", "@types/jest": "^29.5.0", + "@types/sjcl": "^1.0.34", "esbuild": "^0.19.0", "jest": "^29.5.0", "nock": "^13.3.0", @@ -707,14 +709,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.2.tgz", "integrity": "sha512-tM8yLeYVe7pRyAu9VMi/Q7aunpLwD139EY1S99xbQkT4/q2qa6eA4ige/WJQYdJ8GBL1K33pPFhPfPdJ/WzT8Q==", - "cpu": [ - "arm" - ], + "cpu": ["arm"], "dev": true, "optional": true, - "os": [ - "android" - ], + "os": ["android"], "engines": { "node": ">=12" } @@ -723,14 +721,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.2.tgz", "integrity": "sha512-lsB65vAbe90I/Qe10OjkmrdxSX4UJDjosDgb8sZUKcg3oefEuW2OT2Vozz8ef7wrJbMcmhvCC+hciF8jY/uAkw==", - "cpu": [ - "arm64" - ], + "cpu": ["arm64"], "dev": true, "optional": true, - "os": [ - "android" - ], + "os": ["android"], "engines": { "node": ">=12" } @@ -739,14 +733,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.2.tgz", "integrity": "sha512-qK/TpmHt2M/Hg82WXHRc/W/2SGo/l1thtDHZWqFq7oi24AjZ4O/CpPSu6ZuYKFkEgmZlFoa7CooAyYmuvnaG8w==", - "cpu": [ - "x64" - ], + "cpu": ["x64"], "dev": true, "optional": true, - "os": [ - "android" - ], + "os": ["android"], "engines": { "node": ">=12" } @@ -755,14 +745,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.2.tgz", "integrity": "sha512-Ora8JokrvrzEPEpZO18ZYXkH4asCdc1DLdcVy8TGf5eWtPO1Ie4WroEJzwI52ZGtpODy3+m0a2yEX9l+KUn0tA==", - "cpu": [ - "arm64" - ], + "cpu": ["arm64"], "dev": true, "optional": true, - "os": [ - "darwin" - ], + "os": ["darwin"], "engines": { "node": ">=12" } @@ -771,14 +757,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.2.tgz", "integrity": "sha512-tP+B5UuIbbFMj2hQaUr6EALlHOIOmlLM2FK7jeFBobPy2ERdohI4Ka6ZFjZ1ZYsrHE/hZimGuU90jusRE0pwDw==", - "cpu": [ - "x64" - ], + "cpu": ["x64"], "dev": true, "optional": true, - "os": [ - "darwin" - ], + "os": ["darwin"], "engines": { "node": ">=12" } @@ -787,14 +769,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.2.tgz", "integrity": "sha512-YbPY2kc0acfzL1VPVK6EnAlig4f+l8xmq36OZkU0jzBVHcOTyQDhnKQaLzZudNJQyymd9OqQezeaBgkTGdTGeQ==", - "cpu": [ - "arm64" - ], + "cpu": ["arm64"], "dev": true, "optional": true, - "os": [ - "freebsd" - ], + "os": ["freebsd"], "engines": { "node": ">=12" } @@ -803,14 +781,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.2.tgz", "integrity": "sha512-nSO5uZT2clM6hosjWHAsS15hLrwCvIWx+b2e3lZ3MwbYSaXwvfO528OF+dLjas1g3bZonciivI8qKR/Hm7IWGw==", - "cpu": [ - "x64" - ], + "cpu": ["x64"], "dev": true, "optional": true, - "os": [ - "freebsd" - ], + "os": ["freebsd"], "engines": { "node": ">=12" } @@ -819,14 +793,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.2.tgz", "integrity": "sha512-Odalh8hICg7SOD7XCj0YLpYCEc+6mkoq63UnExDCiRA2wXEmGlK5JVrW50vZR9Qz4qkvqnHcpH+OFEggO3PgTg==", - "cpu": [ - "arm" - ], + "cpu": ["arm"], "dev": true, "optional": true, - "os": [ - "linux" - ], + "os": ["linux"], "engines": { "node": ">=12" } @@ -835,14 +805,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.2.tgz", "integrity": "sha512-ig2P7GeG//zWlU0AggA3pV1h5gdix0MA3wgB+NsnBXViwiGgY77fuN9Wr5uoCrs2YzaYfogXgsWZbm+HGr09xg==", - "cpu": [ - "arm64" - ], + "cpu": ["arm64"], "dev": true, "optional": true, - "os": [ - "linux" - ], + "os": ["linux"], "engines": { "node": ">=12" } @@ -851,14 +817,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.2.tgz", "integrity": "sha512-mLfp0ziRPOLSTek0Gd9T5B8AtzKAkoZE70fneiiyPlSnUKKI4lp+mGEnQXcQEHLJAcIYDPSyBvsUbKUG2ri/XQ==", - "cpu": [ - "ia32" - ], + "cpu": ["ia32"], "dev": true, "optional": true, - "os": [ - "linux" - ], + "os": ["linux"], "engines": { "node": ">=12" } @@ -867,14 +829,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.2.tgz", "integrity": "sha512-hn28+JNDTxxCpnYjdDYVMNTR3SKavyLlCHHkufHV91fkewpIyQchS1d8wSbmXhs1fiYDpNww8KTFlJ1dHsxeSw==", - "cpu": [ - "loong64" - ], + "cpu": ["loong64"], "dev": true, "optional": true, - "os": [ - "linux" - ], + "os": ["linux"], "engines": { "node": ">=12" } @@ -883,14 +841,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.2.tgz", "integrity": "sha512-KbXaC0Sejt7vD2fEgPoIKb6nxkfYW9OmFUK9XQE4//PvGIxNIfPk1NmlHmMg6f25x57rpmEFrn1OotASYIAaTg==", - "cpu": [ - "mips64el" - ], + "cpu": ["mips64el"], "dev": true, "optional": true, - "os": [ - "linux" - ], + "os": ["linux"], "engines": { "node": ">=12" } @@ -899,14 +853,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.2.tgz", "integrity": "sha512-dJ0kE8KTqbiHtA3Fc/zn7lCd7pqVr4JcT0JqOnbj4LLzYnp+7h8Qi4yjfq42ZlHfhOCM42rBh0EwHYLL6LEzcw==", - "cpu": [ - "ppc64" - ], + "cpu": ["ppc64"], "dev": true, "optional": true, - "os": [ - "linux" - ], + "os": ["linux"], "engines": { "node": ">=12" } @@ -915,14 +865,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.2.tgz", "integrity": "sha512-7Z/jKNFufZ/bbu4INqqCN6DDlrmOTmdw6D0gH+6Y7auok2r02Ur661qPuXidPOJ+FSgbEeQnnAGgsVynfLuOEw==", - "cpu": [ - "riscv64" - ], + "cpu": ["riscv64"], "dev": true, "optional": true, - "os": [ - "linux" - ], + "os": ["linux"], "engines": { "node": ">=12" } @@ -931,14 +877,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.2.tgz", "integrity": "sha512-U+RinR6aXXABFCcAY4gSlv4CL1oOVvSSCdseQmGO66H+XyuQGZIUdhG56SZaDJQcLmrSfRmx5XZOWyCJPRqS7g==", - "cpu": [ - "s390x" - ], + "cpu": ["s390x"], "dev": true, "optional": true, - "os": [ - "linux" - ], + "os": ["linux"], "engines": { "node": ">=12" } @@ -947,14 +889,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.2.tgz", "integrity": "sha512-oxzHTEv6VPm3XXNaHPyUTTte+3wGv7qVQtqaZCrgstI16gCuhNOtBXLEBkBREP57YTd68P0VgDgG73jSD8bwXQ==", - "cpu": [ - "x64" - ], + "cpu": ["x64"], "dev": true, "optional": true, - "os": [ - "linux" - ], + "os": ["linux"], "engines": { "node": ">=12" } @@ -963,14 +901,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.2.tgz", "integrity": "sha512-WNa5zZk1XpTTwMDompZmvQLHszDDDN7lYjEHCUmAGB83Bgs20EMs7ICD+oKeT6xt4phV4NDdSi/8OfjPbSbZfQ==", - "cpu": [ - "x64" - ], + "cpu": ["x64"], "dev": true, "optional": true, - "os": [ - "netbsd" - ], + "os": ["netbsd"], "engines": { "node": ">=12" } @@ -979,14 +913,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.2.tgz", "integrity": "sha512-S6kI1aT3S++Dedb7vxIuUOb3oAxqxk2Rh5rOXOTYnzN8JzW1VzBd+IqPiSpgitu45042SYD3HCoEyhLKQcDFDw==", - "cpu": [ - "x64" - ], + "cpu": ["x64"], "dev": true, "optional": true, - "os": [ - "openbsd" - ], + "os": ["openbsd"], "engines": { "node": ">=12" } @@ -995,14 +925,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.2.tgz", "integrity": "sha512-VXSSMsmb+Z8LbsQGcBMiM+fYObDNRm8p7tkUDMPG/g4fhFX5DEFmjxIEa3N8Zr96SjsJ1woAhF0DUnS3MF3ARw==", - "cpu": [ - "x64" - ], + "cpu": ["x64"], "dev": true, "optional": true, - "os": [ - "sunos" - ], + "os": ["sunos"], "engines": { "node": ">=12" } @@ -1011,14 +937,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.2.tgz", "integrity": "sha512-5NayUlSAyb5PQYFAU9x3bHdsqB88RC3aM9lKDAz4X1mo/EchMIT1Q+pSeBXNgkfNmRecLXA0O8xP+x8V+g/LKg==", - "cpu": [ - "arm64" - ], + "cpu": ["arm64"], "dev": true, "optional": true, - "os": [ - "win32" - ], + "os": ["win32"], "engines": { "node": ">=12" } @@ -1027,14 +949,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.2.tgz", "integrity": "sha512-47gL/ek1v36iN0wL9L4Q2MFdujR0poLZMJwhO2/N3gA89jgHp4MR8DKCmwYtGNksbfJb9JoTtbkoe6sDhg2QTA==", - "cpu": [ - "ia32" - ], + "cpu": ["ia32"], "dev": true, "optional": true, - "os": [ - "win32" - ], + "os": ["win32"], "engines": { "node": ">=12" } @@ -1043,14 +961,10 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.2.tgz", "integrity": "sha512-tcuhV7ncXBqbt/Ybf0IyrMcwVOAPDckMK9rXNHtF17UTK18OKLpg08glminN06pt2WCoALhXdLfSPbVvK/6fxw==", - "cpu": [ - "x64" - ], + "cpu": ["x64"], "dev": true, "optional": true, - "os": [ - "win32" - ], + "os": ["win32"], "engines": { "node": ">=12" } @@ -1526,6 +1440,15 @@ "@types/node": "*" } }, + "node_modules/@types/ed2curve": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@types/ed2curve/-/ed2curve-0.2.4.tgz", + "integrity": "sha512-1m9IX8qypOa0K1NfdMsMxB3gjhKyXbr5Yl9FzzwWQjLSDFGLkvRvSfla1NYqzIt72ocIALMXsF+twRlzr1ov/g==", + "dev": true, + "dependencies": { + "tweetnacl": "^1.0.0" + } + }, "node_modules/@types/chrome": { "version": "0.0.266", "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.266.tgz", @@ -1621,6 +1544,12 @@ "integrity": "sha512-g2/8Ogi2zfiS25jdGT5iDSo5yjruhhXaOuOJCkOxMW28w16VxFvjtAXjBNRo7WlRS4+UXAMj3mK46UwieNM/5g==", "dev": true }, + "node_modules/@types/sjcl": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/@types/sjcl/-/sjcl-1.0.34.tgz", + "integrity": "sha512-bQHEeK5DTQRunIfQeUMgtpPsNNCcZyQ9MJuAfW1I7iN0LDunTc78Fu17STbLMd7KiEY/g2zHVApippa70h6HoQ==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -2156,9 +2085,9 @@ } }, "node_modules/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "node_modules/curve25519-js": { "version": "0.0.4", @@ -2457,9 +2386,7 @@ "dev": true, "hasInstallScript": true, "optional": true, - "os": [ - "darwin" - ], + "os": ["darwin"], "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -5699,6 +5626,15 @@ "@types/node": "*" } }, + "@types/ed2curve": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@types/ed2curve/-/ed2curve-0.2.4.tgz", + "integrity": "sha512-1m9IX8qypOa0K1NfdMsMxB3gjhKyXbr5Yl9FzzwWQjLSDFGLkvRvSfla1NYqzIt72ocIALMXsF+twRlzr1ov/g==", + "dev": true, + "requires": { + "tweetnacl": "^1.0.0" + } + }, "@types/chrome": { "version": "0.0.266", "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.266.tgz", @@ -5794,6 +5730,12 @@ "integrity": "sha512-g2/8Ogi2zfiS25jdGT5iDSo5yjruhhXaOuOJCkOxMW28w16VxFvjtAXjBNRo7WlRS4+UXAMj3mK46UwieNM/5g==", "dev": true }, + "@types/sjcl": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/@types/sjcl/-/sjcl-1.0.34.tgz", + "integrity": "sha512-bQHEeK5DTQRunIfQeUMgtpPsNNCcZyQ9MJuAfW1I7iN0LDunTc78Fu17STbLMd7KiEY/g2zHVApippa70h6HoQ==", + "dev": true + }, "@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -6187,9 +6129,9 @@ } }, "crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "curve25519-js": { "version": "0.0.4", diff --git a/package.json b/package.json index d4fdba66..8edac370 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,10 @@ "devDependencies": { "@types/chrome": "0.0.266", "@types/absinthe__socket": "^0.2.3", + "@types/ed2curve": "^0.2.4", "@types/elliptic": "^6.4.14", "@types/jest": "^29.5.0", + "@types/sjcl": "^1.0.34", "esbuild": "^0.19.0", "jest": "^29.5.0", "nock": "^13.3.0", diff --git a/src/crypto.ts b/src/crypto.ts index 368131e3..0ac94076 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -1,10 +1,11 @@ -import { Curve, HashAlgorithm, Keypair } from "./types.js"; +import { AuthorizedKeyUserInput, Curve, HashAlgorithm, Keypair } from "./types.js"; import { concatUint8Arrays, hexToUint8Array, intToUint8Array, maybeHexToUint8Array, maybeStringToUint8Array, + maybeUint8ArrayToHex, uint8ArrayToHex, wordArrayToUint8Array } from "./utils.js"; @@ -15,20 +16,21 @@ import * as curve25519 from "curve25519-js"; import CryptoJS from "crypto-js"; import blake from "blakejs"; import nacl from "tweetnacl"; -import pkg from "elliptic"; -const { ec } = pkg; +import { ec } from "elliptic"; import sha3 from "js-sha3"; -// @ts-ignore import ed2curve from "ed2curve"; -// @ts-ignore import sjcl from "sjcl"; +import Keychain from "./keychain.js"; const { sha3_512, sha3_256 } = sha3; -const EC = ec; -const ec_P256 = new EC("p256"); -const ec_secp256k1 = new EC("secp256k1"); +const ec_P256 = new ec("p256"); +const ec_secp256k1 = new ec("secp256k1"); const SOFTWARE_ID = 1; +/** + * Generate a random secret key of 32 bytes + * @returns {Uint8Array} Random secret key + */ export function randomSecretKey(): Uint8Array { return wordArrayToUint8Array(CryptoJS.lib.WordArray.random(32)); } @@ -44,6 +46,7 @@ const hashAlgoMap = { /** * Get the hash algo name from the hash algorithm ID * @param {number} ID Hash algorithm's ID + * @returns {HashAlgorithm} Hash algorithm's name */ export function IDToHashAlgo(ID: number): HashAlgorithm { const hashAlgo = findHashAlgoById(ID); @@ -59,7 +62,8 @@ function findHashAlgoById(ID: number) { /** * Get the ID of a given hash algorithm - * @params {String} hashAlgo Hash algorithm + * @param {String} hashAlgo Hash algorithm + * @returns {number} Hash algorithm's ID */ export function hashAlgoToID(hashAlgo: HashAlgorithm): number { const ID = hashAlgoMap[hashAlgo]; @@ -73,6 +77,7 @@ export function hashAlgoToID(hashAlgo: HashAlgorithm): number { * Get the hash digest of a given content for a given hash algorithm * @param {string | Uint8Array} content Content to hash * @param {HashAlgorithm} algo Hash algorithm + * @returns {Uint8Array} Hash digest */ export function getHashDigest(content: string | Uint8Array, algo: HashAlgorithm): Uint8Array { switch (algo) { @@ -110,7 +115,7 @@ export function getHashDigest(content: string | Uint8Array, algo: HashAlgorithm) * @param {HashAlgorithm} algo Hash algorithm to use * @returns {Uint8Array} Hash digest */ -export function hash(content: string | Uint8Array, algo: HashAlgorithm = HashAlgorithm.sha256) { +export function hash(content: string | Uint8Array, algo: HashAlgorithm = HashAlgorithm.sha256): Uint8Array { content = maybeStringToUint8Array(content); const algoID = hashAlgoToID(algo); @@ -121,7 +126,8 @@ export function hash(content: string | Uint8Array, algo: HashAlgorithm = HashAlg /** * Get the ID of a given Elliptic curve - * @params {String} curve Elliptic curve + * @param {String} curve Elliptic curve + * @returns {number} Curve's ID */ export function curveToID(curve: Curve): number { switch (curve) { @@ -142,6 +148,7 @@ export function curveToID(curve: Curve): number { /** * Get the curve name from the curve ID * @param {number} ID Curve's ID + * @returns {Curve} Curve's name */ export function IDToCurve(ID: number): Curve { switch (ID) { @@ -156,12 +163,18 @@ export function IDToCurve(ID: number): Curve { } } +/** + * Derive a private key from a seed and an index + * @param {string | Uint8Array} seed Seed to derive the private key + * @param {number} index Index to derive the private key + * @returns {Uint8Array} Derived private key + */ export function derivePrivateKey(seed: string | Uint8Array, index: number = 0): Uint8Array { - if (seed == undefined || seed == null) { + if (seed === undefined || seed === null) { throw new Error("Seed must be defined"); } - if (index == undefined || index == null) { + if (index === undefined || index === null) { throw new Error("Index must be defined"); } @@ -191,23 +204,24 @@ export function derivePrivateKey(seed: string | Uint8Array, index: number = 0): } /** - * Generate a keypair using a derivation function with a seed and an index. Each keys is prepending with a curve identification. + * Generate a keypair using a derivation function with a seed and an index. Each keys is prepending with a curve identification * @param {String} seed Keypair derivation seed * @param {number} index Number to identify the order of keys to generate - * @param {String} curve Elliptic curve to use ("ed25519", "P256", "secp256k1") + * @param {Curve} curve Elliptic curve to use ("ed25519", "P256", "secp256k1") * @param {number} origin_id Origin id of the public key (0, 1, 2) = ("on chain wallet", "software", "tpm") + * @returns {Object} {publicKey: Uint8Array, privateKey: Uint8Array} */ export function deriveKeyPair( seed: string | Uint8Array, index: number = 0, - curve = Curve.ed25519, + curve: Curve = Curve.ed25519, origin_id: number = SOFTWARE_ID ): Keypair { - if (seed == undefined || seed == null) { + if (seed === undefined || seed === null) { throw new Error("Seed must be defined"); } - if (index == undefined || index == null) { + if (index === undefined || index === null) { throw new Error("Index must be defined"); } @@ -220,13 +234,14 @@ export function deriveKeyPair( } /** - * Create an address from a seed, an index, an elliptic curve and an hash algorithm. - * The address is prepended by the curve identification, the hash algorithm and the digest of the address + * Create an address from a seed, an index, an elliptic curve and an hash algorithm * + * The address is prepended by the curve identification, the hash algorithm and the digest of the address * @param {string | Uint8Array} seed Keypair derivation seed * @param {number} index Number to identify the order of keys to generate * @param {Curve} curve Elliptic Curve to use * @param {HashAlgorithm} hashAlgo Hash algorithm to use + * @returns {Uint8Array} Address */ export function deriveAddress( seed: string | Uint8Array, @@ -245,9 +260,10 @@ export function deriveAddress( /** * Generate a new keypair deterministically with a given private key, curve and origin id - * @params {Uint8Array} privateKey Private key - * @params {String} curve Elliptic curve - * @params {Integer} originID Origin identification + * @param {Uint8Array} pvKey Private key + * @param {String} curve Elliptic curve + * @param {Integer} originID Origin identification + * @returns {Object} {publicKey: Uint8Array, privateKey: Uint8Array} */ export function generateDeterministicKeyPair(pvKey: string | Uint8Array, curve: Curve, originID: number): Keypair { if (typeof pvKey === "string") { @@ -317,6 +333,7 @@ function getKeypair(pvKey: string | Uint8Array, curve: Curve): { publicKey: Uint * Sign data with a private key * @param { string | Uint8Array } data Data to sign * @param { string | Uint8Array } privateKey Private key used to sign the data + * @returns { Uint8Array } Signature */ export function sign(data: string | Uint8Array, privateKey: string | Uint8Array): Uint8Array { privateKey = maybeStringToUint8Array(privateKey); @@ -354,6 +371,7 @@ export function sign(data: string | Uint8Array, privateKey: string | Uint8Array) * @param {string | Uint8Array} sig Signature to verify * @param {string | Uint8Array} data Data to verify * @param {string | Uint8Array} publicKey Public key used to verify the signature + * @returns {boolean} True if the signature is valid, false otherwise */ export function verify(sig: string | Uint8Array, data: string | Uint8Array, publicKey: string | Uint8Array): boolean { sig = maybeStringToUint8Array(sig); @@ -389,6 +407,7 @@ export function verify(sig: string | Uint8Array, data: string | Uint8Array, publ * Encrypt a data for a given public key using ECIES algorithm * @param {string | Uint8Array} data Data to encrypt * @param {string | Uint8Array} publicKey Public key for the shared secret encryption + * @returns {Uint8Array} Encrypted data */ export function ecEncrypt(data: string | Uint8Array, publicKey: string | Uint8Array): Uint8Array { publicKey = maybeStringToUint8Array(publicKey); @@ -439,6 +458,12 @@ export function ecEncrypt(data: string | Uint8Array, publicKey: string | Uint8Ar } } +/** + * Decrypt a data for a given private key using ECIES algorithm + * @param {string | Uint8Array} ciphertext Data to decrypt + * @param {string | Uint8Array} privateKey Private key for the shared secret decryption + * @returns {Uint8Array} Decrypted data + */ export function ecDecrypt(ciphertext: string | Uint8Array, privateKey: string | Uint8Array): Uint8Array { ciphertext = maybeStringToUint8Array(ciphertext); privateKey = maybeStringToUint8Array(privateKey); @@ -492,40 +517,42 @@ export function ecDecrypt(ciphertext: string | Uint8Array, privateKey: string | } /** - * Encrypt a data for a given public key using AES algorithm + * Encrypt a data for a given AES key using AES algorithm * @param {string | Uint8Array} data Data to encrypt - * @param {string | Uint8Array} key Symmetric key + * @param {string | Uint8Array} aesKey AES key (Symmetric key) + * @returns {Uint8Array} Encrypted data */ -export function aesEncrypt(data: string | Uint8Array, key: string | Uint8Array): Uint8Array { - key = maybeHexToUint8Array(key); +export function aesEncrypt(data: string | Uint8Array, aesKey: string | Uint8Array): Uint8Array { + aesKey = maybeHexToUint8Array(aesKey); data = maybeStringToUint8Array(data); const iv = wordArrayToUint8Array(CryptoJS.lib.WordArray.random(12)); - - const { tag, encrypted } = aesAuthEncrypt(data, key, iv); + const { tag, encrypted } = aesAuthEncrypt(data, aesKey, iv); return concatUint8Arrays(new Uint8Array(iv), tag, encrypted); } /** - * Decrypt cipherText for a given key using AES algorithm - * @param cipherText Ciphertext to decrypt - * @param key Symmetric key + * Decrypt a data for a given AES key using AES algorithm + * @param {string | Uint8Array} cipherText Data to decrypt + * @param {string | Uint8Array} aesKey AES key (Symmetric key) + * @returns {Uint8Array} Decrypted data */ -export function aesDecrypt(cipherText: string | Uint8Array, key: string | Uint8Array): Uint8Array { +export function aesDecrypt(cipherText: string | Uint8Array, aesKey: string | Uint8Array): Uint8Array { cipherText = maybeHexToUint8Array(cipherText); - key = maybeHexToUint8Array(key); + aesKey = maybeHexToUint8Array(aesKey); const iv = cipherText.slice(0, 12); const tag = cipherText.slice(12, 12 + 16); const encrypted = cipherText.slice(28, cipherText.length); - return aesAuthDecrypt(encrypted, key, iv, tag); + return aesAuthDecrypt(encrypted, aesKey, iv, tag); } /** * Derive a secret from a shared key - * @param sharedKey + * @param {Uint8Array} sharedKey + * @returns {Object} {aesKey: Uint8Array, iv: Uint8Array} */ function deriveSecret(sharedKey: Uint8Array): { aesKey: Uint8Array; iv: Uint8Array } { sharedKey = CryptoJS.lib.WordArray.create(sharedKey); @@ -539,9 +566,10 @@ function deriveSecret(sharedKey: Uint8Array): { aesKey: Uint8Array; iv: Uint8Arr /** * Encrypt data with AES - * @param data Data to encrypt - * @param aesKey AES key - * @param iv Initialization vector + * @param {Uint8Array} data Data to encrypt + * @param {Uint8Array} aesKey AES key + * @param {Uint8Array} iv Initialization vector + * @returns {Object} {tag: Uint8Array, encrypted: Uint8Array} */ function aesAuthEncrypt( data: Uint8Array, @@ -553,6 +581,7 @@ function aesAuthEncrypt( const dataBits = sjcl.codec.hex.toBits(uint8ArrayToHex(data)); const ivBits = sjcl.codec.hex.toBits(uint8ArrayToHex(iv)); + // @ts-expect-error sjcl.mode.gcm.C is not in types const { tag, data: encrypted } = sjcl.mode.gcm.C(true, new sjcl.cipher.aes(keyBits), dataBits, [], ivBits, 128); return { @@ -563,18 +592,20 @@ function aesAuthEncrypt( /** * Decrypt data with AES - * @param encrypted Encrypted data - * @param aesKey AES key - * @param iv Initialization vector - * @param tag Tag + * @param {Uint8Array} encrypted Encrypted data + * @param {Uint8Array} aesKey AES key + * @param {Uint8Array} iv Initialization vector + * @param {Uint8Array} tag Tag + * @returns {Uint8Array} Decrypted data */ -function aesAuthDecrypt(encrypted: Uint8Array, aesKey: Uint8Array, iv: Uint8Array, tag: Uint8Array) { +function aesAuthDecrypt(encrypted: Uint8Array, aesKey: Uint8Array, iv: Uint8Array, tag: Uint8Array): Uint8Array { // Format for SJCL const encryptedBits = sjcl.codec.hex.toBits(uint8ArrayToHex(encrypted)); const aesKeyBits = sjcl.codec.hex.toBits(uint8ArrayToHex(aesKey)); const ivBits = sjcl.codec.hex.toBits(uint8ArrayToHex(iv)); const tagBits = sjcl.codec.hex.toBits(uint8ArrayToHex(tag)); + // @ts-expect-error sjcl.mode.gcm.C is not in types const { tag: actualTag, data: decrypted } = sjcl.mode.gcm.C( false, new sjcl.cipher.aes(aesKeyBits), @@ -645,3 +676,81 @@ function validHash(hashAlgo: HashAlgorithm, digest: Uint8Array) { return digest.length == 64; } } + +/** + * Generates the genesis address (the first address) from a given seed + * @param {string | Uint8Array} seed The seed used to generate the address + * @returns {string} The genesis address in hexadecimal format + */ +export function getGenesisAddress(seed: string | Uint8Array): string { + return uint8ArrayToHex(deriveAddress(seed, 0)); +} + +/** + * Derives the genesis address for a given service from a keychain + * @param {Keychain} keychain The keychain used to derive the address + * @param {string} service The service for which to derive the address + * @param {string} [suffix=""] An optional suffix to append to the service before deriving the address + * @returns {string} The genesis address for the service in hexadecimal format + */ +export function getServiceGenesisAddress(keychain: Keychain, service: string, suffix: string = ""): string { + return uint8ArrayToHex(keychain.deriveAddress(service, 0, suffix)); +} + +/** + * Encrypts a secret using a given public key + * @param {string | Uint8Array} secret The secret to encrypt + * @param {string[] | Uint8Array[]} publicKeys The public keys authorized to decrypt the secret + * @returns {{encryptedSecret: Uint8Array, authorizedKeys: AuthorizedKeyUserInput[]}} + * @example + * const storageNoncePublicKey = await archethic.network.getStorageNoncePublicKey(); + * const { encryptedSecret, authorizedKeys } = encryptSecret("something secret", storageNoncePublicKey); + * const code = "" // The contract code + * const tx = await archethic.transaction + * .new() + * .setType("contract") + * .setCode(code) + * .addOwnership(encryptedSecret, authorizedKeys) + * .build(seed, 0) + * .originSign(originPrivateKey) + * .send(); + */ +export function encryptSecret( + secret: string | Uint8Array, + ...publicKeys: string[] | Uint8Array[] +): { encryptedSecret: Uint8Array; authorizedKeys: AuthorizedKeyUserInput[] } { + const aesKey = randomSecretKey(); + const encryptedSecret = aesEncrypt(secret, aesKey); + + const authorizedKeys: AuthorizedKeyUserInput[] = publicKeys.map((publicKey) => { + const encryptedAesKey = uint8ArrayToHex(ecEncrypt(aesKey, publicKey)); + return { encryptedSecretKey: encryptedAesKey, publicKey: maybeUint8ArrayToHex(publicKey) }; + }); + + return { encryptedSecret, authorizedKeys }; +} + +/** + * Decrypt a secret if authorized + * @param {Uint8Array} encryptedSecret The secret to decrypt + * @param {AuthorizedKeyUserInput[]} authorizedKeys The AES keys of authorized keys + * @param {Keypair} keyPair The keyPair to use for decrypting + * @returns {Uint8Array} The decrypted secret + * @example + * const keypair = deriveKeyPair("seed", 0); + * const { encryptedSecret, authorizedKeys } = encryptSecret("something secret", keypair.publicKey); + * const decryptedSecret = decryptSecret(encryptedSecret, authorizedKeys, keypair); + */ +export function decryptSecret( + encryptedSecret: Uint8Array, + authorizedKeys: AuthorizedKeyUserInput[], + keyPair: Keypair +): Uint8Array { + const publicKeyInHex = uint8ArrayToHex(keyPair.publicKey); + + const authorizedKey = authorizedKeys.find(({ publicKey: currentPubKey }) => currentPubKey == publicKeyInHex); + if (!authorizedKey) throw new Error("This keypair is not authorized to decrypt the secret"); + + const aesKey = ecDecrypt(authorizedKey.encryptedSecretKey, keyPair.privateKey); + return aesDecrypt(encryptedSecret, aesKey); +} diff --git a/tests/crypto.test.ts b/tests/crypto.test.ts index 62ee2dd3..7ae003ec 100644 --- a/tests/crypto.test.ts +++ b/tests/crypto.test.ts @@ -1,4 +1,14 @@ -import { deriveKeyPair, hash, sign, verify, ecDecrypt, ecEncrypt, isValidAddress } from "../src/crypto"; +import { + deriveKeyPair, + ecDecrypt, + ecEncrypt, + isValidAddress, + encryptSecret, + hash, + sign, + verify, + decryptSecret +} from "../src/crypto"; import { uint8ArrayToHex } from "../src/utils"; import { Curve, HashAlgorithm } from "../src/types"; @@ -172,4 +182,95 @@ describe("crypto", () => { ).toBeTruthy(); }); }); + + describe("encryptSecret / decryptSecret", () => { + it("should encrypt a secret (string) using a public key and then decrypt it", () => { + const keypair = deriveKeyPair("seed", 0); + const secret = "mySecret"; + const result = encryptSecret(secret, keypair.publicKey); + + const secretDecrypted = decryptSecret(result.encryptedSecret, result.authorizedKeys, keypair); + expect(new TextDecoder().decode(secretDecrypted)).toStrictEqual(secret); + }); + + it("should encrypt a secret (uint8array) using a public key and then decrypt it", () => { + const keypair = deriveKeyPair("seed", 0); + const secret = new TextEncoder().encode("mySecret"); + const result = encryptSecret(secret, keypair.publicKey); + + const secretDecrypted = decryptSecret(result.encryptedSecret, result.authorizedKeys, keypair); + expect(secretDecrypted).toStrictEqual(secret); + }); + + it("should be able to be decrypted by anyone authorized", () => { + const keypair1 = deriveKeyPair("seed", 0); + const keypair2 = deriveKeyPair("seed2", 0); + const secret = new TextEncoder().encode("mySecret"); + const result = encryptSecret(secret, keypair1.publicKey, keypair2.publicKey); + + const secretDecrypted1 = decryptSecret(result.encryptedSecret, result.authorizedKeys, keypair1); + const secretDecrypted2 = decryptSecret(result.encryptedSecret, result.authorizedKeys, keypair2); + expect(secretDecrypted1).toStrictEqual(secret); + expect(secretDecrypted2).toStrictEqual(secret); + }); + + it("should not be able to be decrypted by non-authorized keys", () => { + const keypair1 = deriveKeyPair("seed", 0); + const keypair2 = deriveKeyPair("seed2", 0); + const secret = "mySecret"; + const publicKey = uint8ArrayToHex(keypair1.publicKey); + const result = encryptSecret(secret, ...[publicKey]); + + expect(() => { + decryptSecret(result.encryptedSecret, result.authorizedKeys, keypair2); + }).toThrow(); + }); + + it("should return an object with encryptedSecret and authorizedKeys", () => { + const keypair = deriveKeyPair("seed", 0); + const secret = "mySecret"; + const publicKey = uint8ArrayToHex(keypair.publicKey); + const result = encryptSecret(secret, publicKey); + + expect(result).toHaveProperty("encryptedSecret"); + expect(result).toHaveProperty("authorizedKeys"); + expect(result.authorizedKeys[0]).toHaveProperty("encryptedSecretKey"); + expect(result.authorizedKeys[0]).toHaveProperty("publicKey"); + }); + + it("should return different results for different secrets", () => { + const keypair = deriveKeyPair("seed", 0); + const secret1 = "mySecret1"; + const secret2 = "mySecret2"; + const publicKey = uint8ArrayToHex(keypair.publicKey); + const result1 = encryptSecret(secret1, publicKey); + const result2 = encryptSecret(secret2, publicKey); + + expect(result1.encryptedSecret).not.toEqual(result2.encryptedSecret); + }); + + it("should return different results for different public keys", () => { + const keypair1 = deriveKeyPair("seed", 0); + const keypair2 = deriveKeyPair("seed2", 0); + const secret = "mySecret"; + const publicKey1 = uint8ArrayToHex(keypair1.publicKey); + const publicKey2 = uint8ArrayToHex(keypair2.publicKey); + const result1 = encryptSecret(secret, publicKey1); + const result2 = encryptSecret(secret, publicKey2); + + expect(result1.authorizedKeys[0].encryptedSecretKey).not.toEqual(result2.authorizedKeys[0].encryptedSecretKey); + }); + + it("should return the diferent result with different curve", () => { + const keypair1 = deriveKeyPair("seed", 0, Curve.ed25519); + const keypair2 = deriveKeyPair("seed", 0, Curve.P256); + const secret = "mySecret"; + const publicKey1 = uint8ArrayToHex(keypair1.publicKey); + const publicKey2 = uint8ArrayToHex(keypair2.publicKey); + const result1 = encryptSecret(secret, publicKey1); + const result2 = encryptSecret(secret, publicKey2); + + expect(result1.authorizedKeys[0].encryptedSecretKey).not.toEqual(result2.authorizedKeys[0].encryptedSecretKey); + }); + }); });