ethereum-cryptography
Advanced tools
Comparing version 2.2.1 to 3.0.0
@@ -1,2 +0,2 @@ | ||
export declare function encrypt(msg: Uint8Array, key: Uint8Array, iv: Uint8Array, mode?: string, pkcs7PaddingEnabled?: boolean): Promise<Uint8Array>; | ||
export declare function decrypt(cypherText: Uint8Array, key: Uint8Array, iv: Uint8Array, mode?: string, pkcs7PaddingEnabled?: boolean): Promise<Uint8Array>; | ||
export declare function encrypt(msg: Uint8Array, key: Uint8Array, iv: Uint8Array, mode?: string, pkcs7PaddingEnabled?: boolean): Uint8Array; | ||
export declare function decrypt(ciphertext: Uint8Array, key: Uint8Array, iv: Uint8Array, mode?: string, pkcs7PaddingEnabled?: boolean): Uint8Array; |
95
aes.js
@@ -5,94 +5,27 @@ "use strict"; | ||
exports.decrypt = decrypt; | ||
const crypto_1 = require("@noble/hashes/crypto"); | ||
const utils_js_1 = require("./utils.js"); | ||
const crypto = { web: crypto_1.crypto }; | ||
function validateOpt(key, iv, mode) { | ||
const aes_1 = require("@noble/ciphers/aes"); | ||
function getCipher(key, iv, mode, pkcs7PaddingEnabled = true) { | ||
if (!mode.startsWith("aes-")) { | ||
throw new Error(`AES submodule doesn't support mode ${mode}`); | ||
throw new Error("AES: unsupported mode"); | ||
} | ||
const len = key.length; | ||
if ((mode.startsWith("aes-128") && len !== 16) || (mode.startsWith("aes-256") && len !== 32)) { | ||
throw new Error("AES: wrong key length"); | ||
} | ||
if (iv.length !== 16) { | ||
throw new Error("AES: wrong IV length"); | ||
} | ||
if ((mode.startsWith("aes-128") && key.length !== 16) || | ||
(mode.startsWith("aes-256") && key.length !== 32)) { | ||
throw new Error("AES: wrong key length"); | ||
} | ||
} | ||
async function getBrowserKey(mode, key, iv) { | ||
if (!crypto.web) { | ||
throw new Error("Browser crypto not available."); | ||
} | ||
let keyMode; | ||
if (["aes-128-cbc", "aes-256-cbc"].includes(mode)) { | ||
keyMode = "cbc"; | ||
return (0, aes_1.cbc)(key, iv, { disablePadding: !pkcs7PaddingEnabled }); | ||
} | ||
if (["aes-128-ctr", "aes-256-ctr"].includes(mode)) { | ||
keyMode = "ctr"; | ||
return (0, aes_1.ctr)(key, iv); | ||
} | ||
if (!keyMode) { | ||
throw new Error("AES: unsupported mode"); | ||
} | ||
const wKey = await crypto.web.subtle.importKey("raw", key, { name: `AES-${keyMode.toUpperCase()}`, length: key.length * 8 }, true, ["encrypt", "decrypt"]); | ||
// node.js uses whole 128 bit as a counter, without nonce, instead of 64 bit | ||
// recommended by NIST SP800-38A | ||
return [wKey, { name: `aes-${keyMode}`, iv, counter: iv, length: 128 }]; | ||
throw new Error("AES: unsupported mode"); | ||
} | ||
async function encrypt(msg, key, iv, mode = "aes-128-ctr", pkcs7PaddingEnabled = true) { | ||
validateOpt(key, iv, mode); | ||
if (crypto.web) { | ||
const [wKey, wOpt] = await getBrowserKey(mode, key, iv); | ||
const cipher = await crypto.web.subtle.encrypt(wOpt, wKey, msg); | ||
// Remove PKCS7 padding on cbc mode by stripping end of message | ||
let res = new Uint8Array(cipher); | ||
if (!pkcs7PaddingEnabled && wOpt.name === "aes-cbc" && !(msg.length % 16)) { | ||
res = res.slice(0, -16); | ||
} | ||
return res; | ||
} | ||
else if (crypto.node) { | ||
const cipher = crypto.node.createCipheriv(mode, key, iv); | ||
cipher.setAutoPadding(pkcs7PaddingEnabled); | ||
return (0, utils_js_1.concatBytes)(cipher.update(msg), cipher.final()); | ||
} | ||
else { | ||
throw new Error("The environment doesn't have AES module"); | ||
} | ||
function encrypt(msg, key, iv, mode = "aes-128-ctr", pkcs7PaddingEnabled = true) { | ||
return getCipher(key, iv, mode, pkcs7PaddingEnabled).encrypt(msg); | ||
} | ||
async function getPadding(cypherText, key, iv, mode) { | ||
const lastBlock = cypherText.slice(-16); | ||
for (let i = 0; i < 16; i++) { | ||
// Undo xor of iv and fill with lastBlock ^ padding (16) | ||
lastBlock[i] ^= iv[i] ^ 16; | ||
} | ||
const res = await encrypt(lastBlock, key, iv, mode); | ||
return res.slice(0, 16); | ||
function decrypt(ciphertext, key, iv, mode = "aes-128-ctr", pkcs7PaddingEnabled = true) { | ||
return getCipher(key, iv, mode, pkcs7PaddingEnabled).decrypt(ciphertext); | ||
} | ||
async function decrypt(cypherText, key, iv, mode = "aes-128-ctr", pkcs7PaddingEnabled = true) { | ||
validateOpt(key, iv, mode); | ||
if (crypto.web) { | ||
const [wKey, wOpt] = await getBrowserKey(mode, key, iv); | ||
// Add empty padding so Chrome will correctly decrypt message | ||
if (!pkcs7PaddingEnabled && wOpt.name === "aes-cbc") { | ||
const padding = await getPadding(cypherText, key, iv, mode); | ||
cypherText = (0, utils_js_1.concatBytes)(cypherText, padding); | ||
} | ||
const msg = await crypto.web.subtle.decrypt(wOpt, wKey, cypherText); | ||
const msgBytes = new Uint8Array(msg); | ||
// Safari always ignores padding (if no padding -> broken message) | ||
if (wOpt.name === "aes-cbc") { | ||
const encrypted = await encrypt(msgBytes, key, iv, mode); | ||
if (!(0, utils_js_1.equalsBytes)(encrypted, cypherText)) { | ||
throw new Error("AES: wrong padding"); | ||
} | ||
} | ||
return msgBytes; | ||
} | ||
else if (crypto.node) { | ||
const decipher = crypto.node.createDecipheriv(mode, key, iv); | ||
decipher.setAutoPadding(pkcs7PaddingEnabled); | ||
return (0, utils_js_1.concatBytes)(decipher.update(cypherText), decipher.final()); | ||
} | ||
else { | ||
throw new Error("The environment doesn't have AES module"); | ||
} | ||
} |
@@ -1,93 +0,26 @@ | ||
import { crypto as cr } from "@noble/hashes/crypto"; | ||
import { concatBytes, equalsBytes } from "./utils.js"; | ||
const crypto = { web: cr }; | ||
function validateOpt(key, iv, mode) { | ||
import { ctr, cbc } from "@noble/ciphers/aes"; | ||
function getCipher(key, iv, mode, pkcs7PaddingEnabled = true) { | ||
if (!mode.startsWith("aes-")) { | ||
throw new Error(`AES submodule doesn't support mode ${mode}`); | ||
throw new Error("AES: unsupported mode"); | ||
} | ||
const len = key.length; | ||
if ((mode.startsWith("aes-128") && len !== 16) || (mode.startsWith("aes-256") && len !== 32)) { | ||
throw new Error("AES: wrong key length"); | ||
} | ||
if (iv.length !== 16) { | ||
throw new Error("AES: wrong IV length"); | ||
} | ||
if ((mode.startsWith("aes-128") && key.length !== 16) || | ||
(mode.startsWith("aes-256") && key.length !== 32)) { | ||
throw new Error("AES: wrong key length"); | ||
} | ||
} | ||
async function getBrowserKey(mode, key, iv) { | ||
if (!crypto.web) { | ||
throw new Error("Browser crypto not available."); | ||
} | ||
let keyMode; | ||
if (["aes-128-cbc", "aes-256-cbc"].includes(mode)) { | ||
keyMode = "cbc"; | ||
return cbc(key, iv, { disablePadding: !pkcs7PaddingEnabled }); | ||
} | ||
if (["aes-128-ctr", "aes-256-ctr"].includes(mode)) { | ||
keyMode = "ctr"; | ||
return ctr(key, iv); | ||
} | ||
if (!keyMode) { | ||
throw new Error("AES: unsupported mode"); | ||
} | ||
const wKey = await crypto.web.subtle.importKey("raw", key, { name: `AES-${keyMode.toUpperCase()}`, length: key.length * 8 }, true, ["encrypt", "decrypt"]); | ||
// node.js uses whole 128 bit as a counter, without nonce, instead of 64 bit | ||
// recommended by NIST SP800-38A | ||
return [wKey, { name: `aes-${keyMode}`, iv, counter: iv, length: 128 }]; | ||
throw new Error("AES: unsupported mode"); | ||
} | ||
export async function encrypt(msg, key, iv, mode = "aes-128-ctr", pkcs7PaddingEnabled = true) { | ||
validateOpt(key, iv, mode); | ||
if (crypto.web) { | ||
const [wKey, wOpt] = await getBrowserKey(mode, key, iv); | ||
const cipher = await crypto.web.subtle.encrypt(wOpt, wKey, msg); | ||
// Remove PKCS7 padding on cbc mode by stripping end of message | ||
let res = new Uint8Array(cipher); | ||
if (!pkcs7PaddingEnabled && wOpt.name === "aes-cbc" && !(msg.length % 16)) { | ||
res = res.slice(0, -16); | ||
} | ||
return res; | ||
} | ||
else if (crypto.node) { | ||
const cipher = crypto.node.createCipheriv(mode, key, iv); | ||
cipher.setAutoPadding(pkcs7PaddingEnabled); | ||
return concatBytes(cipher.update(msg), cipher.final()); | ||
} | ||
else { | ||
throw new Error("The environment doesn't have AES module"); | ||
} | ||
export function encrypt(msg, key, iv, mode = "aes-128-ctr", pkcs7PaddingEnabled = true) { | ||
return getCipher(key, iv, mode, pkcs7PaddingEnabled).encrypt(msg); | ||
} | ||
async function getPadding(cypherText, key, iv, mode) { | ||
const lastBlock = cypherText.slice(-16); | ||
for (let i = 0; i < 16; i++) { | ||
// Undo xor of iv and fill with lastBlock ^ padding (16) | ||
lastBlock[i] ^= iv[i] ^ 16; | ||
} | ||
const res = await encrypt(lastBlock, key, iv, mode); | ||
return res.slice(0, 16); | ||
export function decrypt(ciphertext, key, iv, mode = "aes-128-ctr", pkcs7PaddingEnabled = true) { | ||
return getCipher(key, iv, mode, pkcs7PaddingEnabled).decrypt(ciphertext); | ||
} | ||
export async function decrypt(cypherText, key, iv, mode = "aes-128-ctr", pkcs7PaddingEnabled = true) { | ||
validateOpt(key, iv, mode); | ||
if (crypto.web) { | ||
const [wKey, wOpt] = await getBrowserKey(mode, key, iv); | ||
// Add empty padding so Chrome will correctly decrypt message | ||
if (!pkcs7PaddingEnabled && wOpt.name === "aes-cbc") { | ||
const padding = await getPadding(cypherText, key, iv, mode); | ||
cypherText = concatBytes(cypherText, padding); | ||
} | ||
const msg = await crypto.web.subtle.decrypt(wOpt, wKey, cypherText); | ||
const msgBytes = new Uint8Array(msg); | ||
// Safari always ignores padding (if no padding -> broken message) | ||
if (wOpt.name === "aes-cbc") { | ||
const encrypted = await encrypt(msgBytes, key, iv, mode); | ||
if (!equalsBytes(encrypted, cypherText)) { | ||
throw new Error("AES: wrong padding"); | ||
} | ||
} | ||
return msgBytes; | ||
} | ||
else if (crypto.node) { | ||
const decipher = crypto.node.createDecipheriv(mode, key, iv); | ||
decipher.setAutoPadding(pkcs7PaddingEnabled); | ||
return concatBytes(decipher.update(cypherText), decipher.final()); | ||
} | ||
else { | ||
throw new Error("The environment doesn't have AES module"); | ||
} | ||
} |
@@ -37,12 +37,1 @@ import assert from "@noble/hashes/_assert"; | ||
} | ||
// TODO(v3): switch away from node crypto, remove this unnecessary variable. | ||
export const crypto = (() => { | ||
const webCrypto = typeof globalThis === "object" && "crypto" in globalThis ? globalThis.crypto : undefined; | ||
const nodeRequire = typeof module !== "undefined" && | ||
typeof module.require === "function" && | ||
module.require.bind(module); | ||
return { | ||
node: nodeRequire && !webCrypto ? nodeRequire("crypto") : undefined, | ||
web: webCrypto | ||
}; | ||
})(); |
214
package.json
{ | ||
"name": "ethereum-cryptography", | ||
"version": "2.2.1", | ||
"version": "3.0.0", | ||
"description": "All the cryptographic primitives used in Ethereum", | ||
@@ -8,2 +8,6 @@ "repository": "https://github.com/ethereum/js-ethereum-cryptography", | ||
"main": "./index.js", | ||
"engines": { | ||
"node": "^14.21.3 || >=16", | ||
"npm": ">=9" | ||
}, | ||
"files": [ | ||
@@ -19,267 +23,240 @@ "bip39/*.js", | ||
"dependencies": { | ||
"@noble/curves": "1.4.2", | ||
"@noble/hashes": "1.4.0", | ||
"@scure/bip32": "1.4.0", | ||
"@scure/bip39": "1.3.0" | ||
"@noble/ciphers": "1.0.0", | ||
"@noble/curves": "1.6.0", | ||
"@noble/hashes": "1.5.0", | ||
"@scure/bip32": "1.5.0", | ||
"@scure/bip39": "1.4.0" | ||
}, | ||
"exports": { | ||
".": { | ||
"types": "./index.d.ts", | ||
"import": "./esm/index.js", | ||
"default": "./index.js" | ||
"require": "./index.js" | ||
}, | ||
"./aes": { | ||
"types": "./aes.d.ts", | ||
"import": "./esm/aes.js", | ||
"default": "./aes.js" | ||
"require": "./aes.js" | ||
}, | ||
"./bip39": { | ||
"types": "./bip39/index.d.ts", | ||
"import": "./esm/bip39/index.js", | ||
"default": "./bip39/index.js" | ||
"require": "./bip39/index.js" | ||
}, | ||
"./blake2b": { | ||
"types": "./blake2b.d.ts", | ||
"import": "./esm/blake2b.js", | ||
"default": "./blake2b.js" | ||
"require": "./blake2b.js" | ||
}, | ||
"./bls": { | ||
"import": "./esm/bls.js", | ||
"require": "./bls.js" | ||
}, | ||
"./bn": { | ||
"import": "./esm/bn.js", | ||
"require": "./bn.js" | ||
}, | ||
"./hdkey": { | ||
"types": "./hdkey.d.ts", | ||
"import": "./esm/hdkey.js", | ||
"default": "./hdkey.js" | ||
"require": "./hdkey.js" | ||
}, | ||
"./index": { | ||
"types": "./index.d.ts", | ||
"import": "./esm/index.js", | ||
"default": "./index.js" | ||
"require": "./index.js" | ||
}, | ||
"./math": { | ||
"import": "./esm/math.js", | ||
"require": "./math.js" | ||
}, | ||
"./keccak": { | ||
"types": "./keccak.d.ts", | ||
"import": "./esm/keccak.js", | ||
"default": "./keccak.js" | ||
"require": "./keccak.js" | ||
}, | ||
"./pbkdf2": { | ||
"types": "./pbkdf2.d.ts", | ||
"import": "./esm/pbkdf2.js", | ||
"default": "./pbkdf2.js" | ||
"require": "./pbkdf2.js" | ||
}, | ||
"./random": { | ||
"types": "./random.d.ts", | ||
"import": "./esm/random.js", | ||
"default": "./random.js" | ||
"require": "./random.js" | ||
}, | ||
"./ripemd160": { | ||
"types": "./ripemd160.d.ts", | ||
"import": "./esm/ripemd160.js", | ||
"default": "./ripemd160.js" | ||
"require": "./ripemd160.js" | ||
}, | ||
"./scrypt": { | ||
"types": "./scrypt.d.ts", | ||
"import": "./esm/scrypt.js", | ||
"default": "./scrypt.js" | ||
"require": "./scrypt.js" | ||
}, | ||
"./secp256k1-compat": { | ||
"types": "./secp256k1-compat.d.ts", | ||
"import": "./esm/secp256k1-compat.js", | ||
"default": "./secp256k1-compat.js" | ||
"require": "./secp256k1-compat.js" | ||
}, | ||
"./secp256k1": { | ||
"types": "./secp256k1.d.ts", | ||
"import": "./esm/secp256k1.js", | ||
"default": "./secp256k1.js" | ||
"require": "./secp256k1.js" | ||
}, | ||
"./sha256": { | ||
"types": "./sha256.d.ts", | ||
"import": "./esm/sha256.js", | ||
"default": "./sha256.js" | ||
"require": "./sha256.js" | ||
}, | ||
"./sha512": { | ||
"types": "./sha512.d.ts", | ||
"import": "./esm/sha512.js", | ||
"default": "./sha512.js" | ||
"require": "./sha512.js" | ||
}, | ||
"./utils": { | ||
"types": "./utils.d.ts", | ||
"import": "./esm/utils.js", | ||
"default": "./utils.js" | ||
"require": "./utils.js" | ||
}, | ||
"./bip39/index": { | ||
"types": "./bip39/index.d.ts", | ||
"import": "./esm/bip39/index.js", | ||
"default": "./bip39/index.js" | ||
"require": "./bip39/index.js" | ||
}, | ||
"./bip39/wordlists/czech": { | ||
"types": "./bip39/wordlists/czech.d.ts", | ||
"import": "./esm/bip39/wordlists/czech.js", | ||
"default": "./bip39/wordlists/czech.js" | ||
"require": "./bip39/wordlists/czech.js" | ||
}, | ||
"./bip39/wordlists/english": { | ||
"types": "./bip39/wordlists/english.d.ts", | ||
"import": "./esm/bip39/wordlists/english.js", | ||
"default": "./bip39/wordlists/english.js" | ||
"require": "./bip39/wordlists/english.js" | ||
}, | ||
"./bip39/wordlists/french": { | ||
"types": "./bip39/wordlists/french.d.ts", | ||
"import": "./esm/bip39/wordlists/french.js", | ||
"default": "./bip39/wordlists/french.js" | ||
"require": "./bip39/wordlists/french.js" | ||
}, | ||
"./bip39/wordlists/italian": { | ||
"types": "./bip39/wordlists/italian.d.ts", | ||
"import": "./esm/bip39/wordlists/italian.js", | ||
"default": "./bip39/wordlists/italian.js" | ||
"require": "./bip39/wordlists/italian.js" | ||
}, | ||
"./bip39/wordlists/japanese": { | ||
"types": "./bip39/wordlists/japanese.d.ts", | ||
"import": "./esm/bip39/wordlists/japanese.js", | ||
"default": "./bip39/wordlists/japanese.js" | ||
"require": "./bip39/wordlists/japanese.js" | ||
}, | ||
"./bip39/wordlists/korean": { | ||
"types": "./bip39/wordlists/korean.d.ts", | ||
"import": "./esm/bip39/wordlists/korean.js", | ||
"default": "./bip39/wordlists/korean.js" | ||
"require": "./bip39/wordlists/korean.js" | ||
}, | ||
"./bip39/wordlists/portuguese": { | ||
"types": "./bip39/wordlists/portuguese.d.ts", | ||
"import": "./esm/bip39/wordlists/portuguese.js", | ||
"default": "./bip39/wordlists/portuguese.js" | ||
"require": "./bip39/wordlists/portuguese.js" | ||
}, | ||
"./bip39/wordlists/simplified-chinese": { | ||
"types": "./bip39/wordlists/simplified-chinese.d.ts", | ||
"import": "./esm/bip39/wordlists/simplified-chinese.js", | ||
"default": "./bip39/wordlists/simplified-chinese.js" | ||
"require": "./bip39/wordlists/simplified-chinese.js" | ||
}, | ||
"./bip39/wordlists/spanish": { | ||
"types": "./bip39/wordlists/spanish.d.ts", | ||
"import": "./esm/bip39/wordlists/spanish.js", | ||
"default": "./bip39/wordlists/spanish.js" | ||
"require": "./bip39/wordlists/spanish.js" | ||
}, | ||
"./bip39/wordlists/traditional-chinese": { | ||
"types": "./bip39/wordlists/traditional-chinese.d.ts", | ||
"import": "./esm/bip39/wordlists/traditional-chinese.js", | ||
"default": "./bip39/wordlists/traditional-chinese.js" | ||
"require": "./bip39/wordlists/traditional-chinese.js" | ||
}, | ||
"./aes.js": { | ||
"types": "./aes.d.ts", | ||
"import": "./esm/aes.js", | ||
"default": "./aes.js" | ||
"require": "./aes.js" | ||
}, | ||
"./bip39.js": { | ||
"types": "./bip39/index.d.ts", | ||
"import": "./esm/bip39/index.js", | ||
"default": "./bip39/index.js" | ||
"require": "./bip39/index.js" | ||
}, | ||
"./blake2b.js": { | ||
"types": "./blake2b.d.ts", | ||
"import": "./esm/blake2b.js", | ||
"default": "./blake2b.js" | ||
"require": "./blake2b.js" | ||
}, | ||
"./bls.js": { | ||
"import": "./esm/bls.js", | ||
"require": "./bls.js" | ||
}, | ||
"./bn.js": { | ||
"import": "./esm/bn.js", | ||
"require": "./bn.js" | ||
}, | ||
"./hdkey.js": { | ||
"types": "./hdkey.d.ts", | ||
"import": "./esm/hdkey.js", | ||
"default": "./hdkey.js" | ||
"require": "./hdkey.js" | ||
}, | ||
"./index.js": { | ||
"types": "./index.d.ts", | ||
"import": "./esm/index.js", | ||
"default": "./index.js" | ||
"require": "./index.js" | ||
}, | ||
"./math.js": { | ||
"import": "./esm/math.js", | ||
"require": "./math.js" | ||
}, | ||
"./keccak.js": { | ||
"types": "./keccak.d.ts", | ||
"import": "./esm/keccak.js", | ||
"default": "./keccak.js" | ||
"require": "./keccak.js" | ||
}, | ||
"./pbkdf2.js": { | ||
"types": "./pbkdf2.d.ts", | ||
"import": "./esm/pbkdf2.js", | ||
"default": "./pbkdf2.js" | ||
"require": "./pbkdf2.js" | ||
}, | ||
"./random.js": { | ||
"types": "./random.d.ts", | ||
"import": "./esm/random.js", | ||
"default": "./random.js" | ||
"require": "./random.js" | ||
}, | ||
"./ripemd160.js": { | ||
"types": "./ripemd160.d.ts", | ||
"import": "./esm/ripemd160.js", | ||
"default": "./ripemd160.js" | ||
"require": "./ripemd160.js" | ||
}, | ||
"./scrypt.js": { | ||
"types": "./scrypt.d.ts", | ||
"import": "./esm/scrypt.js", | ||
"default": "./scrypt.js" | ||
"require": "./scrypt.js" | ||
}, | ||
"./secp256k1-compat.js": { | ||
"types": "./secp256k1-compat.d.ts", | ||
"import": "./esm/secp256k1-compat.js", | ||
"default": "./secp256k1-compat.js" | ||
"require": "./secp256k1-compat.js" | ||
}, | ||
"./secp256k1.js": { | ||
"types": "./secp256k1.d.ts", | ||
"import": "./esm/secp256k1.js", | ||
"default": "./secp256k1.js" | ||
"require": "./secp256k1.js" | ||
}, | ||
"./sha256.js": { | ||
"types": "./sha256.d.ts", | ||
"import": "./esm/sha256.js", | ||
"default": "./sha256.js" | ||
"require": "./sha256.js" | ||
}, | ||
"./sha512.js": { | ||
"types": "./sha512.d.ts", | ||
"import": "./esm/sha512.js", | ||
"default": "./sha512.js" | ||
"require": "./sha512.js" | ||
}, | ||
"./utils.js": { | ||
"types": "./utils.d.ts", | ||
"import": "./esm/utils.js", | ||
"default": "./utils.js" | ||
"require": "./utils.js" | ||
}, | ||
"./bip39/index.js": { | ||
"types": "./bip39/index.d.ts", | ||
"import": "./esm/bip39/index.js", | ||
"default": "./bip39/index.js" | ||
"require": "./bip39/index.js" | ||
}, | ||
"./bip39/wordlists/czech.js": { | ||
"types": "./bip39/wordlists/czech.d.ts", | ||
"import": "./esm/bip39/wordlists/czech.js", | ||
"default": "./bip39/wordlists/czech.js" | ||
"require": "./bip39/wordlists/czech.js" | ||
}, | ||
"./bip39/wordlists/english.js": { | ||
"types": "./bip39/wordlists/english.d.ts", | ||
"import": "./esm/bip39/wordlists/english.js", | ||
"default": "./bip39/wordlists/english.js" | ||
"require": "./bip39/wordlists/english.js" | ||
}, | ||
"./bip39/wordlists/french.js": { | ||
"types": "./bip39/wordlists/french.d.ts", | ||
"import": "./esm/bip39/wordlists/french.js", | ||
"default": "./bip39/wordlists/french.js" | ||
"require": "./bip39/wordlists/french.js" | ||
}, | ||
"./bip39/wordlists/italian.js": { | ||
"types": "./bip39/wordlists/italian.d.ts", | ||
"import": "./esm/bip39/wordlists/italian.js", | ||
"default": "./bip39/wordlists/italian.js" | ||
"require": "./bip39/wordlists/italian.js" | ||
}, | ||
"./bip39/wordlists/japanese.js": { | ||
"types": "./bip39/wordlists/japanese.d.ts", | ||
"import": "./esm/bip39/wordlists/japanese.js", | ||
"default": "./bip39/wordlists/japanese.js" | ||
"require": "./bip39/wordlists/japanese.js" | ||
}, | ||
"./bip39/wordlists/korean.js": { | ||
"types": "./bip39/wordlists/korean.d.ts", | ||
"import": "./esm/bip39/wordlists/korean.js", | ||
"default": "./bip39/wordlists/korean.js" | ||
"require": "./bip39/wordlists/korean.js" | ||
}, | ||
"./bip39/wordlists/simplified-chinese.js": { | ||
"types": "./bip39/wordlists/simplified-chinese.d.ts", | ||
"import": "./esm/bip39/wordlists/simplified-chinese.js", | ||
"default": "./bip39/wordlists/simplified-chinese.js" | ||
"require": "./bip39/wordlists/simplified-chinese.js" | ||
}, | ||
"./bip39/wordlists/spanish.js": { | ||
"types": "./bip39/wordlists/spanish.d.ts", | ||
"import": "./esm/bip39/wordlists/spanish.js", | ||
"default": "./bip39/wordlists/spanish.js" | ||
"require": "./bip39/wordlists/spanish.js" | ||
}, | ||
"./bip39/wordlists/traditional-chinese.js": { | ||
"types": "./bip39/wordlists/traditional-chinese.d.ts", | ||
"import": "./esm/bip39/wordlists/traditional-chinese.js", | ||
"default": "./bip39/wordlists/traditional-chinese.js" | ||
"require": "./bip39/wordlists/traditional-chinese.js" | ||
} | ||
@@ -297,3 +274,3 @@ }, | ||
"test:node": "cd test && npm install && cd .. && mocha", | ||
"clean": "rimraf test/test-builds bip39 '*.js' '*.js.map' '*.d.ts' '*.d.ts.map' 'src/**/*.js'", | ||
"clean": "rm -rf test/test-builds bip39 *.js *.js.map *.d.ts *.d.ts.map src/**/*.js", | ||
"lint": "eslint", | ||
@@ -310,27 +287,20 @@ "lint:fix": "eslint --fix", | ||
"devDependencies": { | ||
"@rollup/plugin-commonjs": "22.0.1", | ||
"@rollup/plugin-node-resolve": "13.3.0", | ||
"@types/estree": "1.0.0", | ||
"@types/mocha": "9.1.1", | ||
"@types/mocha": "10.0.7", | ||
"@types/node": "18.15.11", | ||
"@typescript-eslint/eslint-plugin": "5.30.6", | ||
"@typescript-eslint/parser": "5.30.6", | ||
"browserify": "17.0.0", | ||
"eslint": "8.38.0", | ||
"eslint-plugin-prettier": "4.2.1", | ||
"karma": "6.4.0", | ||
"karma": "6.4.4", | ||
"karma-chrome-launcher": "3.1.1", | ||
"karma-mocha": "2.0.1", | ||
"karma-mocha-reporter": "2.2.5", | ||
"mocha": "10.0.0", | ||
"mocha": "10.7.3", | ||
"npm-run-all": "4.1.5", | ||
"parcel": "2.6.2", | ||
"prettier": "2.7.1", | ||
"rimraf": "~3.0.2", | ||
"rollup": "2.76.0", | ||
"ts-node": "10.9.1", | ||
"typescript": "5.5.2", | ||
"webpack": "5.76.0", | ||
"webpack-cli": "4.10.0" | ||
"typescript": "5.5.4" | ||
}, | ||
"packageManager": "npm@9.9.3", | ||
"keywords": [ | ||
@@ -337,0 +307,0 @@ "ethereum", |
569
README.md
# ethereum-cryptography | ||
[![npm version][1]][2] [![license][3]][4] | ||
[Audited](#security) pure JS library containing all Ethereum-related cryptographic primitives. Implemented with 6 [noble & scure](https://paulmillr.com/noble/) dependencies. | ||
[Audited](#security) pure JS library containing all Ethereum-related cryptographic primitives. | ||
Included algorithms, implemented with just 5 [noble & scure](https://paulmillr.com/noble/) dependencies: | ||
* [Hashes: SHA256, keccak-256, RIPEMD160, BLAKE2b](#hashes-sha256-keccak-256-ripemd160-blake2b) | ||
* [KDFs: PBKDF2, Scrypt](#kdfs-pbkdf2-scrypt) | ||
* [CSPRNG (Cryptographically Secure Pseudorandom Number Generator)](#csprng-cryptographically-strong-pseudorandom-number-generator) | ||
* [secp256k1 elliptic curve](#secp256k1-curve) | ||
* [BIP32 HD Keygen](#bip32-hd-keygen) | ||
* [BIP39 Mnemonic phrases](#bip39-mnemonic-seed-phrase) | ||
* [AES Encryption](#aes-encryption) | ||
**April 2023 update:** v2.0 is out, switching | ||
[noble-secp256k1](https://github.com/paulmillr/noble-secp256k1) to | ||
[noble-curves](https://github.com/paulmillr/noble-curves), | ||
which changes re-exported api of `secp256k1` submodule. | ||
There have been no other changes. | ||
**January 2022 update:** v1.0 has been released. We've rewritten the library from | ||
scratch and [audited](#security) it. It became **6x smaller:** ~5,000 lines of | ||
code instead of ~24,000 (with all deps); 650KB instead of 10.2MB. | ||
5 dependencies by 1 author are now used, instead of 38 by 5 authors. | ||
Check out [Upgrading](#upgrading) section and an article about the library: | ||
Check out [Changelog / Upgrading](#upgrading) and an article about the library: | ||
[A safer, smaller, and faster Ethereum cryptography stack](https://medium.com/nomic-labs-blog/a-safer-smaller-and-faster-ethereum-cryptography-stack-5eeb47f62d79). | ||
@@ -33,70 +10,58 @@ | ||
Use NPM / Yarn in node.js / browser: | ||
> npm install ethereum-cryptography | ||
```bash | ||
# NPM | ||
npm install ethereum-cryptography | ||
We explicitly support major browsers and Node.js on x86 and arm64. Other major runtimes and platforms are supported on a best-effort basis. | ||
Refer to `engines` field of `package.json` for runtime support information for each version. | ||
Tests are being ran with Webpack, Rollup, Parcel and Browserify. | ||
# Yarn | ||
yarn add ethereum-cryptography | ||
``` | ||
See [browser usage](#browser-usage) for information on using the package with major Javascript bundlers. It is | ||
tested with **Webpack, Rollup, Parcel and Browserify**. | ||
This package has no single entry-point, but submodule for each cryptographic | ||
primitive. Read each primitive's section of this document to learn how to use | ||
them. | ||
primitive. The reason for this is that importing everything from a single file will lead to huge bundles when using this package for the web. This could be | ||
avoided through tree-shaking, but the possibility of it not working properly | ||
on one of [the supported bundlers](#browser-usage) is too high. | ||
The reason for this is that importing everything from a single file will lead to | ||
huge bundles when using this package for the web. This could be avoided through | ||
tree-shaking, but the possibility of it not working properly on one of | ||
[the supported bundlers](#browser-usage) is too high. | ||
* [Usage](#usage) | ||
* [Dependencies](#dependencies) | ||
* [hashes: sha256, sha512, keccak, ripemd160, blake2b](#hashes-sha256-sha512-keccak-ripemd160-blake2b) | ||
* [kdfs: pbkdf2, scrypt](#kdfs-pbkdf2-scrypt) | ||
* [random: secure randomness](#random-secure-randomness) | ||
* [secp256k1: curve operations](#secp256k1-curve-operations) | ||
* [bn: pairing-friendly curve](#bn-pairing-friendly-curve) | ||
* [bls: pairing-friendly curve](#bls-pairing-friendly-curve) | ||
* [aes: encryption](#aes-encryption) | ||
* [hdkey: bip32 HD wallets](#hdkey-bip32-hd-wallets) | ||
* [bip39: mnemonic phrases](#bip39-mnemonic-phrases) | ||
* [math: utilities](#math-utilities) | ||
* [utils: generic utilities](#utils-generic-utilities) | ||
* [secp256k1-compat: compatibility layer with other libraries](#secp256k1-compat-compatibility-layer-with-other-libraries) | ||
* [All imports](#all-imports) | ||
* [Caveats](#caveats) | ||
* [Browser usage: Rollup setup](#browser-usage-rollup-setup) | ||
* [AES](#aes) | ||
* [Encrypting with passwords](#encrypting-with-passwords) | ||
* [Operation modes](#operation-modes) | ||
* [Padding plaintext messages](#padding-plaintext-messages) | ||
* [How to use the IV parameter](#how-to-use-the-iv-parameter) | ||
* [How to handle errors with this module](#how-to-handle-errors-with-this-module) | ||
* [Upgrading](#upgrading) | ||
* [Changelog](#changelog) | ||
* [From v2 to v3](#from-v2-to-v3) | ||
* [From v1 to v2](#from-v1-to-v2) | ||
* [From v0.1 to v1](#from-v01-to-v1) | ||
* [Security](#security) | ||
* [License](#license) | ||
```js | ||
// Hashes | ||
import { sha256 } from "ethereum-cryptography/sha256.js"; | ||
import { keccak256 } from "ethereum-cryptography/keccak.js"; | ||
import { ripemd160 } from "ethereum-cryptography/ripemd160.js"; | ||
import { blake2b } from "ethereum-cryptography/blake2b.js"; | ||
### Dependencies | ||
// KDFs | ||
import { pbkdf2Sync } from "ethereum-cryptography/pbkdf2.js"; | ||
import { scryptSync } from "ethereum-cryptography/scrypt.js"; | ||
All functionality of the module is simple | ||
re-export of 6 audited [noble & scure libraries](https://paulmillr.com/noble/): | ||
// Random | ||
import { getRandomBytesSync } from "ethereum-cryptography/random.js"; | ||
- noble-curves, noble-ciphers, noble-hashes | ||
- scure-base, scure-bip32, scure-bip39 | ||
// AES encryption | ||
import { encrypt } from "ethereum-cryptography/aes.js"; | ||
ethereum-cryptography pins versions of the libraries to ensure good | ||
protection against supply chain attacks. Ideally, your app would also | ||
pin version of ethereum-cryptography. That means, no `^3.0.0` - use `3.0.0` instead. | ||
// secp256k1 elliptic curve operations | ||
import { secp256k1 } from "ethereum-cryptography/secp256k1.js"; | ||
### hashes: sha256, sha512, keccak, ripemd160, blake2b | ||
// BIP32 HD Keygen, BIP39 Mnemonic Phrases | ||
import { HDKey } from "ethereum-cryptography/hdkey.js"; | ||
import { generateMnemonic } from "ethereum-cryptography/bip39/index.js"; | ||
import { wordlist } from "ethereum-cryptography/bip39/wordlists/english.js"; | ||
// utilities | ||
import { hexToBytes, toHex, utf8ToBytes } from "ethereum-cryptography/utils.js"; | ||
``` | ||
## Hashes: SHA256, keccak-256, RIPEMD160, BLAKE2b | ||
```typescript | ||
function sha256(msg: Uint8Array): Uint8Array; | ||
function sha512(msg: Uint8Array): Uint8Array; | ||
function keccak256(msg: Uint8Array): Uint8Array; | ||
function ripemd160(msg: Uint8Array): Uint8Array; | ||
function blake2b(msg: Uint8Array, outputLength = 64): Uint8Array; | ||
``` | ||
Exposes following cryptographic hash functions: | ||
- SHA2 (SHA256, SHA512) | ||
- keccak-256 variant of SHA3 (also `keccak224`, `keccak384`, | ||
and `keccak512`) | ||
- RIPEMD160 | ||
- BLAKE2b | ||
```js | ||
@@ -108,21 +73,30 @@ import { sha256 } from "ethereum-cryptography/sha256.js"; | ||
import { blake2b } from "ethereum-cryptography/blake2b.js"; | ||
sha256(Uint8Array.from([1, 2, 3])) // A: buffers | ||
sha256(Uint8Array.from([1, 2, 3])) | ||
// Can be used with strings | ||
import { utf8ToBytes } from "ethereum-cryptography/utils.js"; | ||
sha256(utf8ToBytes("abc")) | ||
sha256(utf8ToBytes("abc")) // B: strings | ||
// If you need hex | ||
import { bytesToHex as toHex } from "ethereum-cryptography/utils.js"; | ||
toHex(sha256(utf8ToBytes("abc"))) | ||
toHex(sha256(utf8ToBytes("abc"))) // C: hex | ||
``` | ||
## KDFs: PBKDF2, Scrypt | ||
### kdfs: pbkdf2, scrypt | ||
```ts | ||
function pbkdf2(password: Uint8Array, salt: Uint8Array, iterations: number, keylen: number, digest: string): Promise<Uint8Array>; | ||
function pbkdf2Sync(password: Uint8Array, salt: Uint8Array, iterations: number, keylen: number, digest: string): Uint8Array; | ||
function scrypt(password: Uint8Array, salt: Uint8Array, N: number, p: number, r: number, dkLen: number, onProgress?: (progress: number) => void): Promise<Uint8Array>; | ||
function scryptSync(password: Uint8Array, salt: Uint8Array, N: number, p: number, r: number, dkLen: number, onProgress?: (progress: number) => void)): Uint8Array; | ||
```js | ||
import { pbkdf2, pbkdf2Sync } from "ethereum-cryptography/pbkdf2.js"; | ||
import { scrypt, scryptSync } from "ethereum-cryptography/scrypt.js"; | ||
import { utf8ToBytes } from "ethereum-cryptography/utils.js"; | ||
// Pass Uint8Array, or convert strings to Uint8Array | ||
const pass = utf8ToBytes("password") | ||
const salt = utf8ToBytes("salt") | ||
const iters = 131072; | ||
const outLength = 32; | ||
console.log(await pbkdf2(pass, salt, iters, outLength, "sha256")); | ||
const N = 262144; | ||
const r = 8; | ||
const p = 1; | ||
const outLengths = 32; | ||
console.log(await scrypt(pass, salt, N, r, p, outLengths)); | ||
``` | ||
@@ -141,76 +115,108 @@ | ||
Encoding passwords is a frequent source of errors. Please read | ||
[these notes](https://github.com/ricmoo/scrypt-js/tree/0eb70873ddf3d24e34b53e0d9a99a0cef06a79c0#encoding-notes) | ||
[notes](https://github.com/ricmoo/scrypt-js/tree/0eb70873ddf3d24e34b53e0d9a99a0cef06a79c0#encoding-notes) | ||
before using these submodules. | ||
### random: secure randomness | ||
```js | ||
import { pbkdf2 } from "ethereum-cryptography/pbkdf2.js"; | ||
import { utf8ToBytes } from "ethereum-cryptography/utils.js"; | ||
// Pass Uint8Array, or convert strings to Uint8Array | ||
console.log(await pbkdf2(utf8ToBytes("password"), utf8ToBytes("salt"), 131072, 32, "sha256")); | ||
import { getRandomBytesSync } from "ethereum-cryptography/random.js"; | ||
console.log(getRandomBytesSync(32)); | ||
``` | ||
The `random` submodule has functions to generate cryptographically strong | ||
pseudo-random data in synchronous and asynchronous ways. Backed by [`crypto.getRandomValues`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) in browser and by [`crypto.randomBytes`](https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback) in node.js. If backends are somehow not available, the module would throw an error and won't work, as keeping them working would be insecure. | ||
### secp256k1: curve operations | ||
```js | ||
import { scrypt } from "ethereum-cryptography/scrypt.js"; | ||
import { utf8ToBytes } from "ethereum-cryptography/utils.js"; | ||
console.log(await scrypt(utf8ToBytes("password"), utf8ToBytes("salt"), 262144, 8, 1, 32)); | ||
import { secp256k1 } from "ethereum-cryptography/secp256k1.js"; | ||
// You pass either a hex string, or Uint8Array | ||
const privateKey = "6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e"; | ||
const messageHash = "a33321f98e4ff1c283c76998f14f57447545d339b3db534c6d886decb4209f28"; | ||
const publicKey = secp256k1.getPublicKey(privateKey); | ||
const signature = secp256k1.sign(messageHash, privateKey); | ||
const isSigned = secp256k1.verify(signature, messageHash, publicKey); | ||
``` | ||
## CSPRNG (Cryptographically strong pseudorandom number generator) | ||
Elliptic curve operations on the curve secp256k1. Check out [noble-curves docs](https://github.com/paulmillr/noble-curves) for more info. | ||
```ts | ||
function getRandomBytes(bytes: number): Promise<Uint8Array>; | ||
function getRandomBytesSync(bytes: number): Uint8Array; | ||
secp256k1 private keys need to be cryptographically secure random numbers with | ||
certain characteristics. If this is not the case, the security of secp256k1 is | ||
compromised. | ||
### bn: pairing-friendly curve | ||
```js | ||
import { bn } from "ethereum-cryptography/bls.js"; | ||
console.log( | ||
bn254.G1, | ||
bn254.G2, | ||
bn254.pairing | ||
) | ||
``` | ||
The `random` submodule has functions to generate cryptographically strong | ||
pseudo-random data in synchronous and asynchronous ways. | ||
For example usage, check out [the implementation of bn254 EVM precompiles](https://github.com/paulmillr/noble-curves/blob/3ed792f8ad9932765b84d1064afea8663a255457/test/bn254.test.js#L697). | ||
Backed by [`crypto.getRandomValues`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) in browser and by [`crypto.randomBytes`](https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback) in node.js. If backends are somehow not available, the module would throw an error and won't work, as keeping them working would be insecure. | ||
### bls: pairing-friendly curve | ||
```js | ||
import { getRandomBytesSync } from "ethereum-cryptography/random.js"; | ||
console.log(getRandomBytesSync(32)); | ||
``` | ||
import { bls12_381 as bls } from "ethereum-cryptography/bls.js"; | ||
## secp256k1 curve | ||
// G1 keys, G2 signatures | ||
const privateKey = '67d53f170b908cabb9eb326c3c337762d59289a8fec79f7bc9254b584b73265c'; | ||
const message = '64726e3da8'; | ||
const publicKey = bls.getPublicKey(privateKey); | ||
const signature = bls.sign(message, privateKey); | ||
const isValid = bls.verify(signature, message, publicKey); | ||
console.log({ publicKey, signature, isValid }); | ||
```ts | ||
function getPublicKey(privateKey: Uint8Array, isCompressed = true): Uint8Array; | ||
function sign(msgHash: Uint8Array, privateKey: Uint8Array): { r: bigint; s: bigint; recovery: number }; | ||
function verify(signature: Uint8Array, msgHash: Uint8Array, publicKey: Uint8Array): boolean | ||
function getSharedSecret(privateKeyA: Uint8Array, publicKeyB: Uint8Array): Uint8Array; | ||
function utils.randomPrivateKey(): Uint8Array; | ||
``` | ||
// G2 signatures, G1 keys | ||
// getPublicKeyForShortSignatures(privateKey) | ||
// signShortSignature(message, privateKey) | ||
// verifyShortSignature(signature, message, publicKey) | ||
// aggregateShortSignatures(signatures) | ||
The `secp256k1` submodule provides a library for elliptic curve operations on | ||
the curve secp256k1. For detailed documentation, follow [README of `noble-curves`](https://github.com/paulmillr/noble-curves), which the module uses as a backend. | ||
// Custom DST | ||
const htfEthereum = { DST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_' }; | ||
const signatureEth = bls.sign(message, privateKey, htfEthereum); | ||
const isValidEth = bls.verify(signature, message, publicKey, htfEthereum); | ||
secp256k1 private keys need to be cryptographically secure random numbers with | ||
certain characteristics. If this is not the case, the security of secp256k1 is | ||
compromised. We strongly recommend using `utils.randomPrivateKey()` to generate them. | ||
// Aggregation | ||
const aggregatedKey = bls.aggregatePublicKeys([bls.utils.randomPrivateKey(), bls.utils.randomPrivateKey()]) | ||
// const aggregatedSig = bls.aggregateSignatures(sigs) | ||
```js | ||
import { secp256k1 } from "ethereum-cryptography/secp256k1.js"; | ||
(async () => { | ||
// You pass either a hex string, or Uint8Array | ||
const privateKey = "6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e"; | ||
const messageHash = "a33321f98e4ff1c283c76998f14f57447545d339b3db534c6d886decb4209f28"; | ||
const publicKey = secp256k1.getPublicKey(privateKey); | ||
const signature = secp256k1.sign(messageHash, privateKey); | ||
const isSigned = secp256k1.verify(signature, messageHash, publicKey); | ||
})(); | ||
// Pairings, with and without final exponentiation | ||
// bls.pairing(PointG1, PointG2); | ||
// bls.pairing(PointG1, PointG2, false); | ||
// bls.fields.Fp12.finalExponentiate(bls.fields.Fp12.mul(PointG1, PointG2)); | ||
// Others | ||
// bls.G1.ProjectivePoint.BASE, bls.G2.ProjectivePoint.BASE; | ||
// bls.fields.Fp, bls.fields.Fp2, bls.fields.Fp12, bls.fields.Fr; | ||
``` | ||
We're also providing a compatibility layer for users who want to upgrade | ||
from `tiny-secp256k1` or `secp256k1` modules without hassle. | ||
Check out [secp256k1 compatibility layer](#legacy-secp256k1-compatibility-layer). | ||
For example usage, check out [the implementation of BLS EVM precompiles](https://github.com/ethereumjs/ethereumjs-monorepo/blob/361f4edbc239e795a411ac2da7e5567298b9e7e5/packages/evm/src/precompiles/bls12_381/noble.ts). | ||
## BIP32 HD Keygen | ||
### aes: encryption | ||
Hierarchical deterministic (HD) wallets that conform to BIP32 standard. | ||
Also available as standalone package [scure-bip32](https://github.com/paulmillr/scure-bip32). | ||
```js | ||
import * as aes from "ethereum-cryptography/aes.js"; | ||
import { hexToBytes, utf8ToBytes } from "ethereum-cryptography/utils.js"; | ||
This module exports a single class `HDKey`, which should be used like this: | ||
console.log( | ||
aes.encrypt( | ||
utf8ToBytes("message"), | ||
hexToBytes("2b7e151628aed2a6abf7158809cf4f3c"), | ||
hexToBytes("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff") | ||
) | ||
); | ||
// const mode = "aes-128-ctr"; // "aes-128-cbc", "aes-256-ctr", "aes-256-cbc" | ||
// function encrypt(msg: Uint8Array, key: Uint8Array, iv: Uint8Array, mode = "aes-128-ctr", pkcs7PaddingEnabled = true): Uint8Array; | ||
// function decrypt(cipherText: Uint8Array, key: Uint8Array, iv: Uint8Array, mode = "aes-128-ctr", pkcs7PaddingEnabled = true): Uint8Array; | ||
``` | ||
```ts | ||
### hdkey: bip32 HD wallets | ||
```js | ||
import { HDKey } from "ethereum-cryptography/hdkey.js"; | ||
@@ -229,102 +235,130 @@ const hdkey1 = HDKey.fromMasterSeed(seed); | ||
Note: `chainCode` property is essentially a private part | ||
of a secret "master" key, it should be guarded from unauthorized access. | ||
Hierarchical deterministic (HD) wallets that conform to | ||
[BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki). | ||
The full API is: | ||
### bip39: mnemonic phrases | ||
```ts | ||
class HDKey { | ||
public static HARDENED_OFFSET: number; | ||
public static fromMasterSeed(seed: Uint8Array, versions: Versions): HDKey; | ||
public static fromExtendedKey(base58key: string, versions: Versions): HDKey; | ||
public static fromJSON(json: { xpriv: string }): HDKey; | ||
```js | ||
import * as bip39 from "ethereum-cryptography/bip39/index.js"; | ||
import { wordlist } from "ethereum-cryptography/bip39/wordlists/english.js"; | ||
readonly versions: Versions; | ||
readonly depth: number = 0; | ||
readonly index: number = 0; | ||
readonly chainCode: Uint8Array | null = null; | ||
readonly parentFingerprint: number = 0; | ||
// import { wordlist } from "ethereum-cryptography/bip39/wordlists/czech.js"; | ||
// import { wordlist } from "ethereum-cryptography/bip39/wordlists/english.js"; | ||
// import { wordlist } from "ethereum-cryptography/bip39/wordlists/french.js"; | ||
// import { wordlist } from "ethereum-cryptography/bip39/wordlists/italian.js"; | ||
// import { wordlist } from "ethereum-cryptography/bip39/wordlists/japanese.js"; | ||
// import { wordlist } from "ethereum-cryptography/bip39/wordlists/korean.js"; | ||
// import { wordlist } from "ethereum-cryptography/bip39/wordlists/portuguese.js"; | ||
// import { wordlist } from "ethereum-cryptography/bip39/wordlists/simplified-chinese.js"; | ||
// import { wordlist } from "ethereum-cryptography/bip39/wordlists/spanish.js"; | ||
// import { wordlist } from "ethereum-cryptography/bip39/wordlists/traditional-chinese.js"; | ||
get fingerprint(): number; | ||
get identifier(): Uint8Array | undefined; | ||
get pubKeyHash(): Uint8Array | undefined; | ||
get privateKey(): Uint8Array | null; | ||
get publicKey(): Uint8Array | null; | ||
get privateExtendedKey(): string; | ||
get publicExtendedKey(): string; | ||
// Generate x random words. Uses Cryptographically-Secure Random Number Generator. | ||
const mn = bip39.generateMnemonic(wordlist); | ||
console.log(mn); | ||
derive(path: string): HDKey; | ||
deriveChild(index: number): HDKey; | ||
sign(hash: Uint8Array): Uint8Array; | ||
verify(hash: Uint8Array, signature: Uint8Array): boolean; | ||
wipePrivateData(): this; | ||
} | ||
// Reversible: Converts mnemonic string to raw entropy in form of byte array. | ||
const ent = bip39.mnemonicToEntropy(mn, wordlist) | ||
interface Versions { | ||
private: number; | ||
public: number; | ||
} | ||
// Reversible: Converts raw entropy in form of byte array to mnemonic string. | ||
bip39.entropyToMnemonic(ent, wordlist); | ||
// Validates mnemonic for being 12-24 words contained in `wordlist`. | ||
bip39.validateMnemonic(mn, wordlist); | ||
// Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password. | ||
await bip39.mnemonicToSeed(mn, 'password'); | ||
bip39.mnemonicToSeedSync(mn, 'password'); | ||
``` | ||
The `hdkey` submodule provides a library for keys derivation according to | ||
[BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki). | ||
The `bip39` submodule provides functions to generate, validate and use seed | ||
recovery phrases according to [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki). | ||
It has almost the exact same API than the version `1.x` of | ||
[`hdkey` from cryptocoinjs](https://github.com/cryptocoinjs/hdkey), | ||
but it's backed by this package's primitives, and has built-in TypeScript types. | ||
Its only difference is that it has to be used with a named import. | ||
The implementation is [loosely based on hdkey, which has MIT License](#LICENSE). | ||
Wordlists for different languages are not imported by default, | ||
as that would increase bundle sizes too much. Instead, you should import and use them explicitly. | ||
## BIP39 Mnemonic Seed Phrase | ||
### math: utilities | ||
```ts | ||
function generateMnemonic(wordlist: string[], strength: number = 128): string; | ||
function mnemonicToEntropy(mnemonic: string, wordlist: string[]): Uint8Array; | ||
function entropyToMnemonic(entropy: Uint8Array, wordlist: string[]): string; | ||
function validateMnemonic(mnemonic: string, wordlist: string[]): boolean; | ||
async function mnemonicToSeed(mnemonic: string, passphrase: string = ""): Promise<Uint8Array>; | ||
function mnemonicToSeedSync(mnemonic: string, passphrase: string = ""): Uint8Array; | ||
```js | ||
import { modPow, modInvert } from "ethereum-cryptography/math.js"; | ||
modPow(123n, 456n, 789n); | ||
modInvert(22n, 5n); | ||
``` | ||
The `bip39` submodule provides functions to generate, validate and use seed | ||
recovery phrases according to [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki). | ||
### utils: generic utilities | ||
Also available as standalone package [scure-bip39](https://github.com/paulmillr/scure-bip39). | ||
```js | ||
import { hexToBytes, toHex, utf8ToBytes } from "ethereum-cryptography/utils.js"; | ||
``` | ||
### secp256k1-compat: compatibility layer with other libraries | ||
```js | ||
import { createPrivateKeySync, ecdsaSign } from "ethereum-cryptography/secp256k1-compat"; | ||
const msgHash = Uint8Array.from( | ||
"82ff40c0a986c6a5cfad4ddf4c3aa6996f1a7837f9c398e17e5de5cbd5a12b28", | ||
"hex" | ||
); | ||
const privateKey = createPrivateKeySync(); | ||
console.log(Uint8Array.from(ecdsaSign(msgHash, privateKey).signature)); | ||
``` | ||
**Warning:** use `secp256k1` instead. This module is only for users who upgraded | ||
from ethereum-cryptography v0.1. It could be removed in the future. | ||
The API of `secp256k1-compat` is the same as [secp256k1-node](https://github.com/cryptocoinjs/secp256k1-node): | ||
### All imports | ||
```js | ||
import { sha256 } from "ethereum-cryptography/sha256.js"; | ||
import { sha512 } from "ethereum-cryptography/sha512.js"; | ||
import { keccak256, keccak224, keccak384, keccak512 } from "ethereum-cryptography/keccak.js"; | ||
import { ripemd160 } from "ethereum-cryptography/ripemd160.js"; | ||
import { blake2b } from "ethereum-cryptography/blake2b.js"; | ||
import { pbkdf2Sync } from "ethereum-cryptography/pbkdf2.js"; | ||
import { scryptSync } from "ethereum-cryptography/scrypt.js"; | ||
import { getRandomBytesSync } from "ethereum-cryptography/random.js"; | ||
import { encrypt } from "ethereum-cryptography/aes.js"; | ||
import { modPow, modInvert } from "ethereum-cryptography/math.js"; | ||
import { secp256k1 } from "ethereum-cryptography/secp256k1.js"; | ||
import { bls12_381 } from "ethereum-cryptography/bls.js"; | ||
import { bn254 } from "ethereum-cryptography/bn.js"; | ||
import { HDKey } from "ethereum-cryptography/hdkey.js"; | ||
import { generateMnemonic } from "ethereum-cryptography/bip39/index.js"; | ||
import { wordlist } from "ethereum-cryptography/bip39/wordlists/english.js"; | ||
console.log(generateMnemonic(wordlist)); | ||
import { modPow, modInvert } from "ethereum-cryptography/math.js"; | ||
import { hexToBytes, toHex, utf8ToBytes } from "ethereum-cryptography/utils.js"; | ||
``` | ||
This submodule also contains the word lists defined by BIP39 for Czech, English, | ||
French, Italian, Japanese, Korean, Simplified and Traditional Chinese, and | ||
Spanish. These are not imported by default, as that would increase bundle sizes | ||
too much. Instead, you should import and use them explicitly. | ||
## Caveats | ||
The word lists are exported as a `wordlist` variable in each of these submodules: | ||
### Browser usage: Rollup setup | ||
* `ethereum-cryptography/bip39/wordlists/czech.js` | ||
* `ethereum-cryptography/bip39/wordlists/english.js` | ||
* `ethereum-cryptography/bip39/wordlists/french.js` | ||
* `ethereum-cryptography/bip39/wordlists/italian.js` | ||
* `ethereum-cryptography/bip39/wordlists/japanese.js` | ||
* `ethereum-cryptography/bip39/wordlists/korean.js` | ||
* `ethereum-cryptography/bip39/wordlists/portuguese.js` | ||
* `ethereum-cryptography/bip39/wordlists/simplified-chinese.js` | ||
* `ethereum-cryptography/bip39/wordlists/spanish.js` | ||
* `ethereum-cryptography/bip39/wordlists/traditional-chinese.js` | ||
Using this library with Rollup requires the following plugins: | ||
## AES Encryption | ||
* [`@rollup/plugin-commonjs`](https://www.npmjs.com/package/@rollup/plugin-commonjs) | ||
* [`@rollup/plugin-node-resolve`](https://www.npmjs.com/package/@rollup/plugin-node-resolve) | ||
```ts | ||
function encrypt(msg: Uint8Array, key: Uint8Array, iv: Uint8Array, mode = "aes-128-ctr", pkcs7PaddingEnabled = true): Promise<Uint8Array>; | ||
function decrypt(cypherText: Uint8Array, key: Uint8Array, iv: Uint8Array, mode = "aes-128-ctr", pkcs7PaddingEnabled = true): Promise<Uint8Array>; | ||
These can be used by setting your `plugins` array like this: | ||
```js | ||
plugins: [ | ||
commonjs(), | ||
resolve({ | ||
browser: true, | ||
preferBuiltins: false, | ||
}), | ||
] | ||
``` | ||
The `aes` submodule contains encryption and decryption functions implementing | ||
the [Advanced Encryption Standard](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) | ||
algorithm. | ||
### AES | ||
### Encrypting with passwords | ||
#### Encrypting with passwords | ||
@@ -339,3 +373,3 @@ AES is not supposed to be used directly with a password. Doing that will | ||
### Operation modes | ||
#### Operation modes | ||
@@ -352,3 +386,3 @@ This submodule works with different [block cipher modes of operation](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation). If you are using this module in a new | ||
### Padding plaintext messages | ||
#### Padding plaintext messages | ||
@@ -372,3 +406,3 @@ Some operation modes require the plaintext message to be a multiple of `16`. If | ||
### How to use the IV parameter | ||
#### How to use the IV parameter | ||
@@ -383,3 +417,3 @@ The `iv` parameter of the `encrypt` function must be unique, or the security | ||
### How to handle errors with this module | ||
#### How to handle errors with this module | ||
@@ -395,72 +429,25 @@ Sensitive information can be leaked via error messages when using this module. | ||
### Example usage | ||
## Upgrading | ||
```js | ||
import { encrypt } from "ethereum-cryptography/aes.js"; | ||
import { hexToBytes, utf8ToBytes } from "ethereum-cryptography/utils.js"; | ||
### Changelog | ||
console.log( | ||
encrypt( | ||
utf8ToBytes("message"), | ||
hexToBytes("2b7e151628aed2a6abf7158809cf4f3c"), | ||
hexToBytes("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff") | ||
) | ||
); | ||
``` | ||
* v3.0 (Sep 2024): new modules `bls`, `bn`, `math` | ||
change async AES to non-native sync, | ||
improve typescript compatibility, new dependency [noble-ciphers](https://github.com/paulmillr/noble-ciphers) | ||
* v2.0 (Apr 2023): switched | ||
[noble-secp256k1](https://github.com/paulmillr/noble-secp256k1) to | ||
[noble-curves](https://github.com/paulmillr/noble-curves), | ||
which changes re-exported api of `secp256k1` submodule. | ||
* v1.0 (Jan 2022): rewritten the library from | ||
scratch and [audited](#security) it. It became **6x smaller:** ~5,000 lines of | ||
code instead of ~24,000 (with all deps); 650KB instead of 10.2MB. | ||
5 dependencies by 1 author are now used, instead of 38 by 5 authors. | ||
## Browser usage | ||
### From v2 to v3 | ||
### Rollup setup | ||
1. utils: `crypto` var had been removed | ||
2. aes: async methods became sync | ||
Using this library with Rollup requires the following plugins: | ||
### From v1 to v2 | ||
* [`@rollup/plugin-commonjs`](https://www.npmjs.com/package/@rollup/plugin-commonjs) | ||
* [`@rollup/plugin-node-resolve`](https://www.npmjs.com/package/@rollup/plugin-node-resolve) | ||
These can be used by setting your `plugins` array like this: | ||
```js | ||
plugins: [ | ||
commonjs(), | ||
resolve({ | ||
browser: true, | ||
preferBuiltins: false, | ||
}), | ||
] | ||
``` | ||
## Legacy secp256k1 compatibility layer | ||
**Warning:** use `secp256k1` instead. This module is only for users who upgraded | ||
from ethereum-cryptography v0.1. It could be removed in the future. | ||
The API of `secp256k1-compat` is the same as [secp256k1-node](https://github.com/cryptocoinjs/secp256k1-node): | ||
```js | ||
import { createPrivateKeySync, ecdsaSign } from "ethereum-cryptography/secp256k1-compat"; | ||
const msgHash = Uint8Array.from( | ||
"82ff40c0a986c6a5cfad4ddf4c3aa6996f1a7837f9c398e17e5de5cbd5a12b28", | ||
"hex" | ||
); | ||
const privateKey = createPrivateKeySync(); | ||
console.log(Uint8Array.from(ecdsaSign(msgHash, privateKey).signature)); | ||
``` | ||
## Missing cryptographic primitives | ||
This package intentionally excludes the cryptographic primitives necessary | ||
to implement the following EIPs: | ||
* [EIP 196: Precompiled contracts for addition and scalar multiplication on the elliptic curve alt_bn128](https://eips.ethereum.org/EIPS/eip-196) | ||
* [EIP 197: Precompiled contracts for optimal ate pairing check on the elliptic curve alt_bn128](https://eips.ethereum.org/EIPS/eip-197) | ||
* [EIP 198: Big integer modular exponentiation](https://eips.ethereum.org/EIPS/eip-198) | ||
* [EIP 152: Add Blake2 compression function `F` precompile](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md) | ||
Feel free to open an issue if you want this decision to be reconsidered, or if | ||
you found another primitive that is missing. | ||
## Upgrading | ||
Upgrading from 1.0 to 2.0: | ||
1. `secp256k1` module was changed massively: | ||
@@ -474,4 +461,6 @@ before, it was using [noble-secp256k1 1.7](https://github.com/paulmillr/noble-secp256k1); | ||
Upgrading from 0.1 to 1.0: **Same functionality**, all old APIs remain the same except for the breaking changes: | ||
### From v0.1 to v1 | ||
All old APIs remain the same except for the breaking changes: | ||
1. We return `Uint8Array` from all methods that worked with `Buffer` before. | ||
@@ -503,2 +492,4 @@ `Buffer` has never been supported in browsers, while `Uint8Array`s are supported natively in both | ||
Dependencies are having separate regular audits: check out their documentation for more info. | ||
## License | ||
@@ -517,5 +508,3 @@ | ||
[1]: https://img.shields.io/npm/v/ethereum-cryptography.svg | ||
[2]: https://www.npmjs.com/package/ethereum-cryptography | ||
[3]: https://img.shields.io/npm/l/ethereum-cryptography | ||
[4]: https://github.com/ethereum/js-ethereum-cryptography/blob/master/packages/ethereum-cryptography/LICENSE | ||
[1]: https://www.npmjs.com/package/ethereum-cryptography | ||
[2]: https://github.com/ethereum/js-ethereum-cryptography/blob/master/packages/ethereum-cryptography/LICENSE |
@@ -9,5 +9,1 @@ declare const assertBool: typeof import("@noble/hashes/_assert").bool; | ||
export declare function wrapHash(hash: (msg: Uint8Array) => Uint8Array): (msg: Uint8Array) => Uint8Array; | ||
export declare const crypto: { | ||
node?: any; | ||
web?: any; | ||
}; |
13
utils.js
@@ -6,3 +6,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.crypto = exports.utf8ToBytes = exports.createView = exports.concatBytes = exports.toHex = exports.bytesToHex = exports.assertBytes = exports.assertBool = void 0; | ||
exports.utf8ToBytes = exports.createView = exports.concatBytes = exports.toHex = exports.bytesToHex = exports.assertBytes = exports.assertBool = void 0; | ||
exports.bytesToUtf8 = bytesToUtf8; | ||
@@ -54,12 +54,1 @@ exports.hexToBytes = hexToBytes; | ||
} | ||
// TODO(v3): switch away from node crypto, remove this unnecessary variable. | ||
exports.crypto = (() => { | ||
const webCrypto = typeof globalThis === "object" && "crypto" in globalThis ? globalThis.crypto : undefined; | ||
const nodeRequire = typeof module !== "undefined" && | ||
typeof module.require === "function" && | ||
module.require.bind(module); | ||
return { | ||
node: nodeRequire && !webCrypto ? nodeRequire("crypto") : undefined, | ||
web: webCrypto | ||
}; | ||
})(); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
16
112
82130
5
1122
499
+ Added@noble/ciphers@1.0.0
+ Added@noble/ciphers@1.0.0(transitive)
+ Added@noble/curves@1.6.0(transitive)
+ Added@noble/hashes@1.5.0(transitive)
+ Added@scure/bip32@1.5.0(transitive)
+ Added@scure/bip39@1.4.0(transitive)
- Removed@noble/curves@1.4.2(transitive)
- Removed@noble/hashes@1.4.0(transitive)
- Removed@scure/bip32@1.4.0(transitive)
- Removed@scure/bip39@1.3.0(transitive)
Updated@noble/curves@1.6.0
Updated@noble/hashes@1.5.0
Updated@scure/bip32@1.5.0
Updated@scure/bip39@1.4.0