PHP WebShell

Текущая директория: /usr/lib/node_modules/bitgo/node_modules/@vechain/sdk-core/src/keystore/cryptography/experimental

Просмотр файла: keystore.ts

/**
 * Implements the
 * [JSON Keystore v3 Wallet](https://ethereum.org/en/developers/docs/data-structures-and-encoding/web3-secret-storage)
 * encryption, decryption, and validation functionality.
 */
import * as n_utils from '@noble/curves/abstract/utils';
import { Address, Hex, Keccak256 } from '../../../vcdm';
import { InvalidKeystoreParams, stringifyData } from '@vechain/sdk-errors';
import { Secp256k1 } from '../../../secp256k1';
import { ctr } from '@noble/ciphers/aes';
import { scrypt } from '@noble/hashes/scrypt';
import { type Keystore, type KeystoreAccount } from '../../types';

/**
 * The cryptographic algorithm used to store the private key in the
 * keystore is the
 * [Advanced Encryption Standard](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard)
 * [128 bits Counter Mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation)
 * as defined by
 * [NIST AES Recommendation for Block Cipher Modes of Operation](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf).
 *
 * @constant {string}
 */
const KEYSTORE_CRYPTO_CIPHER = 'aes-128-ctr';

/**
 * The length of the key returned by the
 * [Scrypt](https://en.wikipedia.org/wiki/Scrypt)
 * [Key Derivation Function](https://en.wikipedia.org/wiki/Key_derivation_function)
 * used in the keystore.
 */
const KEYSTORE_CRYPTO_PARAMS_DKLEN = 32;

/**
 * The [Key Derivation Function](https://en.wikipedia.org/wiki/Key_derivation_function)
 * of the keystore is [Scrypt](https://en.wikipedia.org/wiki/Scrypt).
 */
const KEYSTORE_CRYPTO_KDF = 'scrypt';

/**
 * The version number of the
 * [Web3 Secret Storage Definition](https://ethereum.org/en/developers/docs/data-structures-and-encoding/web3-secret-storage)
 * specifications used in keystore.
 */
const KEYSTORE_VERSION = 3;

/**
 * The [Scrypt](https://en.wikipedia.org/wiki/Scrypt) parameters
 * used in the keystore encryption.
 *
 * @property {number} N - The CPU/memory cost parameter = 2^17 = 131072.
 * @property {number} r - The block size parameter = 8.
 * @property {number} p - The parallelization parameter = 1.
 */
const SCRYPT_PARAMS = {
    N: 131072,
    r: 8,
    p: 1
};

/**
 * EncryptOptions interface defines the options of the
 * [Scrypt](https://en.wikipedia.org/wiki/Scrypt) algorithm for the
 * [Key Derivation Function](https://en.wikipedia.org/wiki/Key_derivation_function)
 * used in keystore encryption.
 *
 * @property {Uint8Array} iv - Initialization Vector.
 * @property {Uint8Array} salt - Random bytes to protect against [Rainbow table](https://en.wikipedia.org/wiki/Rainbow_table).
 * @property {number} scrypt.N - CPU/memory cost parameter.
 * @property {number} scrypt.p - Parallelization parameter.
 * @property {number} scrypt.r - Block size parameter.
 *
 * @see {encodeScryptParams}
 */
interface EncryptOptions {
    iv?: Uint8Array;
    salt?: Uint8Array;
    uuid?: Uint8Array;
    scrypt?: {
        N?: number;
        p?: number;
        r?: number;
    };
}

/**
 * ScryptParams interfaces defines the parameters of the
 * [Scrypt](https://en.wikipedia.org/wiki/Scrypt) algorithm for the
 * [Key Derivation Function](https://en.wikipedia.org/wiki/Key_derivation_function)
 * used in keystore encryption.
 *
 * Compatible with
 * [ethers ScryptParams](https://github.com/ethers-io/ethers.js/blob/main/src.ts/wallet/json-keystore.ts).
 *
 * @property {number} N - CPU/memory cost parameter.
 * @property {number} dkLen - Derived key length in bytes.
 * @property {string} name - constant "scrypt".
 * @property {number} p - Parallelization parameter.
 * @property {number} r - Block size parameter.
 * @property {Uint8Array} salt - Random bytes to protect against [Rainbow table](https://en.wikipedia.org/wiki/Rainbow_table).
 */
interface ScryptParams {
    N: number;
    dkLen: number;
    name: string;
    p: number;
    r: number;
    salt: Uint8Array;
}

/**
 * Retrieves the
 * [Key Derivation Function](https://en.wikipedia.org/wiki/Key_derivation_function)
 * parameters from the given keystore.
 *
 * Only [Scrypt](https://en.wikipedia.org/wiki/Scrypt) is supported as key-derivation function.
 * [PBKDF2](https://en.wikipedia.org/wiki/PBKDF2) superseded by Scrypt, hence
 * not implemented.
 *
 * @param {Keystore} keystore - The key store object.
 * @returns {ScryptParams} - The decryption key-derivation function parameters.
 * @throws {InvalidKeystoreParams}
 *
 * @see {decryptKeystore}
 * @see {encodeScryptParams}
 */
function decodeScryptParams(keystore: Keystore): ScryptParams {
    const salt = n_utils.hexToBytes(keystore.crypto.kdfparams.salt);
    const N = keystore.crypto.kdfparams.n;
    const r = keystore.crypto.kdfparams.r;
    const p: number = keystore.crypto.kdfparams.p;
    // Make sure N is a power of 2
    if (N <= 0 || (N & (N - 1)) !== 0)
        throw new InvalidKeystoreParams(
            '(EXPERIMENTAL) keystore.decodeScryptParams()',
            'Decryption failed: invalid  keystore.crypto.kdfparams.n parameter.',
            {
                keystore,
                N
            }
        );

    // Make sure r and p are positive
    if (r <= 0 || p <= 0)
        throw new InvalidKeystoreParams(
            '(EXPERIMENTAL) keystore.decodeScryptParams()',
            'Decryption failed: both keystore.crypto.kdfparams.r or keystore.crypto.kdfparams.p parameter must be > 0.',
            {
                keystore,
                r,
                p
            }
        );
    const dkLen = keystore.crypto.kdfparams.dklen;

    if (dkLen !== KEYSTORE_CRYPTO_PARAMS_DKLEN)
        throw new InvalidKeystoreParams(
            '(EXPERIMENTAL) keystore.decodeScryptParams()',
            `Decryption failed: keystore.crypto.kdfparams.dklen parameter must be ${KEYSTORE_CRYPTO_PARAMS_DKLEN}`,
            {
                keystore,
                dkLen
            }
        );

    return {
        N,
        dkLen: KEYSTORE_CRYPTO_PARAMS_DKLEN,
        name: KEYSTORE_CRYPTO_KDF,
        p,
        r,
        salt
    } satisfies ScryptParams;
}

/**
 * Encodes the parameters of the
 * [Scrypt](https://en.wikipedia.org/wiki/Scrypt) algorithm of the
 * [Key Derivation Function](https://en.wikipedia.org/wiki/Key_derivation_function)
 * used in the keystore encryption.
 *
 * @param {EncryptOptions} options - The encryption options used to override
 * the default Scrypt parameters:
 * - N: CPU/memory cost,
 * - p: Parallelization parameter,
 * - r: Block size parameter.
 * @returns {ScryptParams} - The encoded scrypt parameters.
 * @throws {InvalidKeystoreParams}
 *
 * @see {decodeScryptParams}
 * @see {encryptKeystore}
 */
function encodeScryptParams(options: EncryptOptions): ScryptParams {
    // Use or generate the salt.
    const salt =
        options.salt ?? Secp256k1.randomBytes(KEYSTORE_CRYPTO_PARAMS_DKLEN);
    // Override the scrypt password-based key derivation function parameters,
    let N = SCRYPT_PARAMS.N;
    let r = SCRYPT_PARAMS.r;
    let p = SCRYPT_PARAMS.p;
    if (options.scrypt != null) {
        if (options.scrypt.N != null) {
            N = options.scrypt.N;
        }
        if (options.scrypt.r != null) {
            r = options.scrypt.r;
        }
        if (options.scrypt.p != null) {
            p = options.scrypt.p;
        }
    }

    if (N <= 0 || (BigInt(N) & BigInt(N - 1)) !== BigInt(0))
        throw new InvalidKeystoreParams(
            '(EXPERIMENTAL) keystore.encodeScryptParams()',
            'Encryption failed: invalid options.scrypt.N parameter.',
            {
                options,
                N
            }
        );

    if (r <= 0 || !Number.isSafeInteger(r))
        throw new InvalidKeystoreParams(
            '(EXPERIMENTAL) keystore.encodeScryptParams()',
            'Encryption failed: invalid options.scrypt.r parameter.',
            {
                options,
                r
            }
        );

    if (p <= 0 || !Number.isSafeInteger(p))
        throw new InvalidKeystoreParams(
            '(EXPERIMENTAL) keystore.encodeScryptParams()',
            'Encryption failed: invalid options.scrypt.p parameter.',
            {
                options,
                p
            }
        );

    return {
        name: KEYSTORE_CRYPTO_KDF,
        dkLen: KEYSTORE_CRYPTO_PARAMS_DKLEN,
        N,
        p,
        r,
        salt
    } satisfies ScryptParams;
}

/**
 * Encrypts a private key with a password to returns a keystore object
 * compliant with [Web3 Secret Storage Definition](https://ethereum.org/en/developers/docs/data-structures-and-encoding/web3-secret-storage/)
 * version 3.
 *
 * The private key is encoded using the
 * [Advanced Encryption Standard](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard)
 * [128 bits Counter Mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation)
 * as defined by
 * [NIST AES Recommendation for Block Cipher Modes of Operation](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf).
 *
 * The [Key Derivation Function](https://en.wikipedia.org/wiki/Key_derivation_function)
 * algorithm is [Scrypt](https://en.wikipedia.org/wiki/Scrypt).
 *
 * Secure audit function.
 * - {@link encryptKeystore}.
 * - `password` wiped after use.
 * - `privateKey` wiped after use.
 *
 * @param {Uint8Array} privateKey - The private key to encrypt, the memory location is wiped after use.
 * @param {Uint8Array} password - The password to use for encryption, the memory location is wiped after use.
 * @returns {Keystore} - The encrypted keystore object.
 * @throws {InvalidKeystoreParams}
 *
 * @see {encryptKeystore}
 *
 * @remarks **The private key must not be represented as string to avoid the
 * [Memory Dumping](https://github.com/paulmillr/noble-hashes?tab=readme-ov-file#memory-dumping)
 * attack**.
 */
function encrypt(privateKey: Uint8Array, password: Uint8Array): Keystore {
    return encryptKeystore(privateKey, password, {
        scrypt: {
            N: SCRYPT_PARAMS.N,
            r: SCRYPT_PARAMS.r,
            p: SCRYPT_PARAMS.p
        }
    });
}

/**
 * Encrypts a private key with a password to returns a keystore object compliant with
 * [Web3 Secret Storage Definition](https://ethereum.org/en/developers/docs/data-structures-and-encoding/web3-secret-storage/)
 * version {@link KEYSTORE_VERSION}.
 *
 * The private key is encoded using the
 * [Advanced Encryption Standard](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard)
 * [128 bits Counter Mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation)
 * as defined by
 * [NIST AES Recommendation for Block Cipher Modes of Operation](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf).
 *
 * The [Key Derivation Function](https://en.wikipedia.org/wiki/Key_derivation_function)
 * algorithm is [Scrypt](https://en.wikipedia.org/wiki/Scrypt).
 *
 * Secure audit function.
 * - [ctr](https://github.com/paulmillr/noble-ciphers?tab=readme-ov-file#aes).
 * - {@link Keccak256.of}
 * - `password` wiped after use.
 * - `privateKey` wiped after use.
 * - {@link Secp256k1.derivePublicKey}.
 * - {@link Secp256k1.randomBytes}.
 * - [scrypt](https://github.com/paulmillr/noble-hashes/?tab=readme-ov-file#scrypt).
 *
 * @param privateKey - The private key to encrypt, the memory location is wiped after use.
 * @param password - The password to use for encryption, the memory location is wiped after use.
 * @param options - Parameters used to configure the **AES** encryption of the private key and the **Scrypt** derivation key function.
 * @returns {Keystore} - The encrypted keystore object.
 * @throws {InvalidKeystoreParams}
 *
 * @remarks **The private key must not be represented as string to avoid the
 * [Memory Dumping](https://github.com/paulmillr/noble-hashes?tab=readme-ov-file#memory-dumping)
 * attack**.
 *
 * @see {encrypt}
 * @see {uuidV4}
 */
function encryptKeystore(
    privateKey: Uint8Array,
    password: Uint8Array,
    options: EncryptOptions
): Keystore {
    try {
        const kdf = encodeScryptParams(options);
        const key = scrypt(password, kdf.salt, {
            N: kdf.N,
            r: kdf.r,
            p: kdf.p,
            dkLen: kdf.dkLen
        });
        // Override initialization vector.
        const iv = options.iv ?? Secp256k1.randomBytes(16);
        if (iv.length !== 16)
            throw new InvalidKeystoreParams(
                '(EXPERIMENTAL) keystore.encryptKeystore()',
                'Encryption failed: invalid options.iv length.',
                { iv }
            );

        // Override the uuid.
        const uuidRandom = options.uuid ?? Secp256k1.randomBytes(16);

        if (uuidRandom.length !== 16)
            throw new InvalidKeystoreParams(
                '(EXPERIMENTAL) keystore.encryptKeystore()',
                'Encryption failed: invalid options.uuid length.',
                { uuidRandom }
            );

        // Message Authentication Code prefix.
        const macPrefix = key.slice(16, 32);
        // Encrypt the private key: 32 bytes for the Web3 Secret Storage (derivedKey, macPrefix)
        const ciphertext = ctr(key.slice(0, 16), iv).encrypt(privateKey);
        return {
            address: Address.ofPrivateKey(privateKey).toString(),
            crypto: {
                cipher: KEYSTORE_CRYPTO_CIPHER,
                cipherparams: {
                    iv: Hex.of(iv).digits
                },
                ciphertext: Hex.of(ciphertext).digits,
                kdf: 'scrypt',
                kdfparams: {
                    dklen: KEYSTORE_CRYPTO_PARAMS_DKLEN,
                    n: kdf.N,
                    p: kdf.p,
                    r: kdf.r,
                    salt: Hex.of(kdf.salt).digits
                },
                // Compute the message authentication code, used to check the password.
                mac: Keccak256.of(n_utils.concatBytes(macPrefix, ciphertext))
                    .digits
            },
            id: uuidV4(uuidRandom),
            version: KEYSTORE_VERSION
        } satisfies Keystore;
    } finally {
        privateKey.fill(0); // Clear the private key from memory.
        password.fill(0); // Clear the password from memory.
    }
}

/**
 * Decrypts a keystore compliant with
 * [Web3 Secret Storage Definition](https://ethereum.org/en/developers/docs/data-structures-and-encoding/web3-secret-storage/)
 * version 3, using the given password to obtain the private key and wallet address.
 *
 * **WARNING:** call
 * ```javascript
 * privateKey.fill(0)
 * ```
 * after use to avoid to invalidate any security audit and certification granted to this code.
 *
 * The private key should be encoded using the
 * [Advanced Encryption Standard](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard)
 * [128 bits Counter Mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation)
 * as defined by
 * [NIST AES Recommendation for Block Cipher Modes of Operation](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf),
 * any different encryption not supported.
 *
 * The [Key Derivation Function](https://en.wikipedia.org/wiki/Key_derivation_function)
 * algorithm should be [Scrypt](https://en.wikipedia.org/wiki/Scrypt),
 * any different KDF function not supported.
 *
 * Secure audit function.
 * - {@link decryptKeystore}
 *
 * @param {Keystore} keystore - The keystore object to decrypt.
 * @param {Uint8Array} password - The password used for decryption, wiped after use.
 * @return {KeystoreAccount} - The decrypted keystore account object.
 *
 * @see {decryptKeystore}
 * @see {isValid}
 */
function decrypt(keystore: Keystore, password: Uint8Array): KeystoreAccount {
    return decryptKeystore(keystore, password);
}

/**
 * Decrypts a keystore compliant with
 * [Web3 Secret Storage Definition](https://ethereum.org/en/developers/docs/data-structures-and-encoding/web3-secret-storage/)
 * using the given password to obtain the private key and wallet address.
 *
 * **WARNING:** call
 * ```javascript
 * privateKey.fill(0)
 * ```
 * after use to avoid to invalidate any security audit and certification granted to this code.
 *
 * The private key should be encoded using the
 * [Advanced Encryption Standard](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard)
 * [128 bits Counter Mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation)
 * as defined by
 * [NIST AES Recommendation for Block Cipher Modes of Operation](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf),
 * any different encryption not supported.
 *
 * The [Key Derivation Function](https://en.wikipedia.org/wiki/Key_derivation_function)
 * algorithm should be [Scrypt](https://en.wikipedia.org/wiki/Scrypt),
 * any different KDF function not supported.
 *
 * Secure audit function.
 * - {@link Address.ofPrivateKey}
 * - [ctr](https://github.com/paulmillr/noble-ciphers?tab=readme-ov-file#aes).
 * - `password` wiped after use.
 * - [scrypt](https://github.com/paulmillr/noble-hashes/?tab=readme-ov-file#scrypt).
 *
 * @param {Keystore} keystore - The keystore object to decrypt.
 * @param {Uint8Array} password - The password used for decryption, wiped after use.
 * @return {KeystoreAccount} - The decrypted keystore account object.
 * @throws {InvalidKeystoreParams}
 *
 * @see {decodeScryptParams}
 * @see {decrypt}
 */
function decryptKeystore(
    keystore: Keystore,
    password: Uint8Array
): KeystoreAccount {
    try {
        if (keystore.crypto.cipher.toLowerCase() !== KEYSTORE_CRYPTO_CIPHER)
            throw new InvalidKeystoreParams(
                '(EXPERIMENTAL) keystore.decryptKeystore()',
                'Decryption failed: unsupported crypto cipher algorithm.',
                { cipher: keystore.crypto.cipher.toLowerCase() }
            );

        if (keystore.crypto.kdf.toLowerCase() !== KEYSTORE_CRYPTO_KDF)
            throw new InvalidKeystoreParams(
                '(EXPERIMENTAL) keystore.decryptKeystore()',
                'Decryption failed: unsupported crypto key derivation function.',
                { keyDerivationFunction: keystore.crypto.kdf.toLowerCase() }
            );

        if (keystore.version !== KEYSTORE_VERSION)
            throw new InvalidKeystoreParams(
                '(EXPERIMENTAL) keystore.decryptKeystore()',
                'Decryption failed: unsupported keystore version.',
                { version: keystore.version }
            );

        const kdf = decodeScryptParams(keystore);
        const key = scrypt(password, kdf.salt, {
            N: kdf.N,
            r: kdf.r,
            p: kdf.p,
            dkLen: kdf.dkLen
        });
        const ciphertext = n_utils.hexToBytes(keystore.crypto.ciphertext);
        if (
            keystore.crypto.mac !==
            Keccak256.of(n_utils.concatBytes(key.slice(16, 32), ciphertext))
                .digits
        ) {
            throw new InvalidKeystoreParams(
                '(EXPERIMENTAL) keystore.decryptKeystore()',
                'Decryption failed: Invalid Password for the given keystore.',
                // @NOTE: We are not exposing the password in the error data for security reasons.
                {
                    keystore
                }
            );
        }
        const privateKey = ctr(
            key.slice(0, 16),
            n_utils.hexToBytes(keystore.crypto.cipherparams.iv)
        ).decrypt(ciphertext);
        const address = Address.ofPrivateKey(privateKey).toString();
        if (
            keystore.address !== '' &&
            address !== Address.checksum(Hex.of(keystore.address))
        ) {
            throw new InvalidKeystoreParams(
                '(EXPERIMENTAL) keystore.decryptKeystore()',
                'Decryption failed: address/password mismatch.',
                { keystoreAddress: keystore.address }
            );
        }
        return {
            address,
            // @note: Convert the private key to a string to be compatible with ethers
            privateKey: Hex.of(privateKey).toString()
        } satisfies KeystoreAccount;
    } finally {
        password.fill(0); // Clear the password from memory.
    }
}

/**
 * Checks if a given keystore object is valid parsing its JSON representation
 * to catch any parsing errors, only valid
 * [Web3 Secret Storage Definition](https://ethereum.org/en/developers/docs/data-structures-and-encoding/web3-secret-storage/)
 * version 3 keystore are accepted, using
 * [Advanced Encryption Standard](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard)
 * [128 bits Counter Mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation)
 * to encrypt the private key and using
 * [Scrypt](https://en.wikipedia.org/wiki/Scrypt) as
 * [Key Derivation Function](https://en.wikipedia.org/wiki/Key_derivation_function).
 *
 * @param {Keystore} keystore - The keystore object to validate.
 * @return {boolean} Returns true if the keystore is valid, false otherwise.
 */
function isValid(keystore: Keystore): boolean {
    try {
        const copy = JSON.parse(stringifyData(keystore)) as Keystore;
        if (
            copy.crypto.cipher.toLowerCase() === KEYSTORE_CRYPTO_CIPHER &&
            copy.crypto.kdf.toLowerCase() === KEYSTORE_CRYPTO_KDF &&
            copy.version === KEYSTORE_VERSION
        ) {
            return true;
        }
    } catch {} // Return false if parsing fails.
    return false;
}

/**
 * Generates a version 4
 * [UUID (Universally Unique Identifier)](https://en.wikipedia.org/wiki/Universally_unique_identifier)
 * based on the given bytes.
 *
 * @param {Uint8Array} bytes - The byte array used to generate the UUID.
 * @returns {string} The generated UUID.
 */
function uuidV4(bytes: Uint8Array): string {
    // Section: 4.1.3:
    // - time_hi_and_version[12:16] = 0b0100
    bytes[6] = (bytes[6] & 0x0f) | 0x40;
    // Section 4.4
    // - clock_seq_hi_and_reserved[6] = 0b0
    // - clock_seq_hi_and_reserved[7] = 0b1
    bytes[8] = (bytes[8] & 0x3f) | 0x80;
    const value = Hex.of(bytes).digits;
    return [
        value.substring(0, 8),
        value.substring(8, 12),
        value.substring(12, 16),
        value.substring(16, 20),
        value.substring(20, 32)
    ].join('-');
}

/**
 * Exports the keystore functions for encryption, decryption, and validation.
 */
export const keystore = { decrypt, encrypt, isValid };

Выполнить команду


Для локальной разработки. Не используйте в интернете!