Pull out executeKdf
This commit is contained in:
parent
21202e821d
commit
91aec70650
@ -1,8 +1,8 @@
|
||||
import { Argon2id, Argon2idOptions, Secp256k1, Secp256k1Signature, Sha256 } from "@cosmjs/crypto";
|
||||
import { Secp256k1, Secp256k1Signature, Sha256 } from "@cosmjs/crypto";
|
||||
import { fromBase64, fromHex, toAscii } from "@cosmjs/encoding";
|
||||
|
||||
import { base64Matcher, hexMatcher } from "./testutils.spec";
|
||||
import { extractKdfParams, Secp256k1Wallet, secp256k1WalletSalt } from "./wallet";
|
||||
import { executeKdf, extractKdfConfiguration, KdfConfiguration, Secp256k1Wallet } from "./wallet";
|
||||
|
||||
describe("Secp256k1Wallet", () => {
|
||||
// m/44'/118'/0'/0/0
|
||||
@ -57,18 +57,21 @@ describe("Secp256k1Wallet", () => {
|
||||
let serialized: string;
|
||||
{
|
||||
const original = await Secp256k1Wallet.fromMnemonic(defaultMnemonic);
|
||||
const anyKdfParams: Argon2idOptions = {
|
||||
outputLength: 32,
|
||||
opsLimit: 4,
|
||||
memLimitKib: 3 * 1024,
|
||||
const anyKdfParams: KdfConfiguration = {
|
||||
algorithm: "argon2id",
|
||||
params: {
|
||||
outputLength: 32,
|
||||
opsLimit: 4,
|
||||
memLimitKib: 3 * 1024,
|
||||
},
|
||||
};
|
||||
const encryptionKey = await Argon2id.execute(password, secp256k1WalletSalt, anyKdfParams);
|
||||
const encryptionKey = await executeKdf(password, anyKdfParams);
|
||||
serialized = await original.serializeWithEncryptionKey(encryptionKey, anyKdfParams);
|
||||
}
|
||||
|
||||
{
|
||||
const kdfOptions: any = extractKdfParams(serialized);
|
||||
const encryptionKey = await Argon2id.execute(password, secp256k1WalletSalt, kdfOptions);
|
||||
const kdfConfiguration = extractKdfConfiguration(serialized);
|
||||
const encryptionKey = await executeKdf(password, kdfConfiguration);
|
||||
const deserialized = await Secp256k1Wallet.deserializeWithEncryptionKey(serialized, encryptionKey);
|
||||
expect(deserialized.mnemonic).toEqual(defaultMnemonic);
|
||||
expect(await deserialized.getAccounts()).toEqual([
|
||||
@ -149,19 +152,19 @@ describe("Secp256k1Wallet", () => {
|
||||
const wallet = await Secp256k1Wallet.fromMnemonic(defaultMnemonic);
|
||||
|
||||
const key = fromHex("aabb221100aabb332211aabb33221100aabb221100aabb332211aabb33221100");
|
||||
const customKdfParams: Argon2idOptions = {
|
||||
outputLength: 32,
|
||||
opsLimit: 321,
|
||||
memLimitKib: 11 * 1024,
|
||||
const customKdfConfiguration: KdfConfiguration = {
|
||||
algorithm: "argon2id",
|
||||
params: {
|
||||
outputLength: 32,
|
||||
opsLimit: 321,
|
||||
memLimitKib: 11 * 1024,
|
||||
},
|
||||
};
|
||||
const serialized = await wallet.serializeWithEncryptionKey(key, customKdfParams);
|
||||
const serialized = await wallet.serializeWithEncryptionKey(key, customKdfConfiguration);
|
||||
expect(JSON.parse(serialized)).toEqual(
|
||||
jasmine.objectContaining({
|
||||
type: "secp256k1wallet-v1",
|
||||
kdf: {
|
||||
algorithm: "argon2id",
|
||||
params: customKdfParams,
|
||||
},
|
||||
kdf: customKdfConfiguration,
|
||||
encryption: {
|
||||
algorithm: "xchacha20poly1305-ietf",
|
||||
params: {
|
||||
|
||||
@ -76,19 +76,22 @@ const serializationTypeV1 = "secp256k1wallet-v1";
|
||||
|
||||
/**
|
||||
* A fixed salt is chosen to archive a deterministic password to key derivation.
|
||||
* This reduces the scope of a potential rainbow attack to all Secp256k1Wallet v1 users.
|
||||
* This reduces the scope of a potential rainbow attack to all CosmJS users.
|
||||
* Must be 16 bytes due to implementation limitations.
|
||||
*/
|
||||
export const secp256k1WalletSalt = toAscii("Secp256k1Wallet1");
|
||||
const cosmjsSalt = toAscii("The CosmJS salt.");
|
||||
|
||||
/**
|
||||
* A KDF configuration that is not very strong but can be used on the main thread.
|
||||
* It takes about 1 second in Node.js 12.15 and should have similar runtimes in other modern Wasm hosts.
|
||||
*/
|
||||
const basicPasswordHashingOptions: Argon2idOptions = {
|
||||
outputLength: 32,
|
||||
opsLimit: 20,
|
||||
memLimitKib: 12 * 1024,
|
||||
const basicPasswordHashingOptions: KdfConfiguration = {
|
||||
algorithm: "argon2id",
|
||||
params: {
|
||||
outputLength: 32,
|
||||
opsLimit: 20,
|
||||
memLimitKib: 12 * 1024,
|
||||
},
|
||||
};
|
||||
|
||||
const algorithmIdXchacha20poly1305Ietf = "xchacha20poly1305-ietf";
|
||||
@ -101,14 +104,7 @@ export interface Secp256k1WalletSerialization {
|
||||
/** A format+version identifier for this serialization format */
|
||||
readonly type: string;
|
||||
/** Information about the key derivation function (i.e. password to encrytion key) */
|
||||
readonly kdf: {
|
||||
/**
|
||||
* An algorithm identifier, such as "argon2id" or "scrypt".
|
||||
*/
|
||||
readonly algorithm: string;
|
||||
/** A map of algorithm-specific parameters */
|
||||
readonly params: Record<string, unknown>;
|
||||
};
|
||||
readonly kdf: KdfConfiguration;
|
||||
/** Information about the symmetric encryption */
|
||||
readonly encryption: {
|
||||
/**
|
||||
@ -135,22 +131,46 @@ export interface Secp256k1WalletData {
|
||||
}>;
|
||||
}
|
||||
|
||||
function extractKdfParamsV1(document: any): Record<string, any> {
|
||||
return document.kdf.params;
|
||||
function extractKdfConfigurationV1(document: any): KdfConfiguration {
|
||||
return document.kdf;
|
||||
}
|
||||
|
||||
export function extractKdfParams(serialization: string): Record<string, any> {
|
||||
export function extractKdfConfiguration(serialization: string): KdfConfiguration {
|
||||
const root = JSON.parse(serialization);
|
||||
if (!isNonNullObject(root)) throw new Error("Root document is not an object.");
|
||||
|
||||
switch ((root as any).type) {
|
||||
case serializationTypeV1:
|
||||
return extractKdfParamsV1(root);
|
||||
return extractKdfConfigurationV1(root);
|
||||
default:
|
||||
throw new Error("Unsupported serialization type");
|
||||
}
|
||||
}
|
||||
|
||||
export interface KdfConfiguration {
|
||||
/**
|
||||
* An algorithm identifier, such as "argon2id" or "scrypt".
|
||||
*/
|
||||
readonly algorithm: string;
|
||||
/** A map of algorithm-specific parameters */
|
||||
readonly params: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export async function executeKdf(password: string, configuration: KdfConfiguration): Promise<Uint8Array> {
|
||||
switch (configuration.algorithm) {
|
||||
case "argon2id": {
|
||||
const { outputLength, opsLimit, memLimitKib } = configuration.params;
|
||||
assert(typeof outputLength === "number");
|
||||
assert(typeof opsLimit === "number");
|
||||
assert(typeof memLimitKib === "number");
|
||||
const options: Argon2idOptions = { outputLength, opsLimit, memLimitKib };
|
||||
return Argon2id.execute(password, cosmjsSalt, options);
|
||||
}
|
||||
default:
|
||||
throw new Error("Unsupported KDF algorithm");
|
||||
}
|
||||
}
|
||||
|
||||
export class Secp256k1Wallet implements OfflineSigner {
|
||||
/**
|
||||
* Restores a wallet from the given BIP39 mnemonic.
|
||||
@ -250,7 +270,7 @@ export class Secp256k1Wallet implements OfflineSigner {
|
||||
switch (untypedRoot.kdf.algorithm) {
|
||||
case "argon2id": {
|
||||
const kdfOptions = untypedRoot.kdf.params;
|
||||
encryptionKey = await Argon2id.execute(password, secp256k1WalletSalt, kdfOptions);
|
||||
encryptionKey = await Argon2id.execute(password, cosmjsSalt, kdfOptions);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -333,9 +353,9 @@ export class Secp256k1Wallet implements OfflineSigner {
|
||||
* This is not normalized internally (see "Unicode normalization" to learn more).
|
||||
*/
|
||||
public async serialize(password: string): Promise<string> {
|
||||
const kdfOption = basicPasswordHashingOptions;
|
||||
const encryptionKey = await Argon2id.execute(password, secp256k1WalletSalt, kdfOption);
|
||||
return this.serializeWithEncryptionKey(encryptionKey, kdfOption);
|
||||
const kdfConfiguration = basicPasswordHashingOptions;
|
||||
const encryptionKey = await executeKdf(password, kdfConfiguration);
|
||||
return this.serializeWithEncryptionKey(encryptionKey, kdfConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -349,7 +369,7 @@ export class Secp256k1Wallet implements OfflineSigner {
|
||||
*/
|
||||
public async serializeWithEncryptionKey(
|
||||
encryptionKey: Uint8Array,
|
||||
kdfOptions: Argon2idOptions,
|
||||
kdfConfiguration: KdfConfiguration,
|
||||
): Promise<string> {
|
||||
const encrytedData: Secp256k1WalletData = {
|
||||
mnemonic: this.mnemonic,
|
||||
@ -365,7 +385,7 @@ export class Secp256k1Wallet implements OfflineSigner {
|
||||
|
||||
const out: Secp256k1WalletSerialization = {
|
||||
type: serializationTypeV1,
|
||||
kdf: { algorithm: "argon2id", params: { ...kdfOptions } },
|
||||
kdf: kdfConfiguration,
|
||||
encryption: {
|
||||
algorithm: algorithmIdXchacha20poly1305Ietf,
|
||||
params: {
|
||||
|
||||
30
packages/sdk38/types/wallet.d.ts
vendored
30
packages/sdk38/types/wallet.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
import { Argon2idOptions, Slip10RawIndex } from "@cosmjs/crypto";
|
||||
import { Slip10RawIndex } from "@cosmjs/crypto";
|
||||
import { StdSignature } from "./types";
|
||||
export declare type PrehashType = "sha256" | "sha512" | null;
|
||||
export declare type Algo = "secp256k1" | "ed25519" | "sr25519";
|
||||
@ -22,12 +22,6 @@ export interface OfflineSigner {
|
||||
* with 0-based account index `a`.
|
||||
*/
|
||||
export declare function makeCosmoshubPath(a: number): readonly Slip10RawIndex[];
|
||||
/**
|
||||
* A fixed salt is chosen to archive a deterministic password to key derivation.
|
||||
* This reduces the scope of a potential rainbow attack to all Secp256k1Wallet v1 users.
|
||||
* Must be 16 bytes due to implementation limitations.
|
||||
*/
|
||||
export declare const secp256k1WalletSalt: Uint8Array;
|
||||
/**
|
||||
* This interface describes a JSON object holding the encrypted wallet and the meta data.
|
||||
* All fields in here must be JSON types.
|
||||
@ -36,14 +30,7 @@ export interface Secp256k1WalletSerialization {
|
||||
/** A format+version identifier for this serialization format */
|
||||
readonly type: string;
|
||||
/** Information about the key derivation function (i.e. password to encrytion key) */
|
||||
readonly kdf: {
|
||||
/**
|
||||
* An algorithm identifier, such as "argon2id" or "scrypt".
|
||||
*/
|
||||
readonly algorithm: string;
|
||||
/** A map of algorithm-specific parameters */
|
||||
readonly params: Record<string, unknown>;
|
||||
};
|
||||
readonly kdf: KdfConfiguration;
|
||||
/** Information about the symmetric encryption */
|
||||
readonly encryption: {
|
||||
/**
|
||||
@ -68,7 +55,16 @@ export interface Secp256k1WalletData {
|
||||
readonly prefix: string;
|
||||
}>;
|
||||
}
|
||||
export declare function extractKdfParams(serialization: string): Record<string, any>;
|
||||
export declare function extractKdfConfiguration(serialization: string): KdfConfiguration;
|
||||
export interface KdfConfiguration {
|
||||
/**
|
||||
* An algorithm identifier, such as "argon2id" or "scrypt".
|
||||
*/
|
||||
readonly algorithm: string;
|
||||
/** A map of algorithm-specific parameters */
|
||||
readonly params: Record<string, unknown>;
|
||||
}
|
||||
export declare function executeKdf(password: string, configuration: KdfConfiguration): Promise<Uint8Array>;
|
||||
export declare class Secp256k1Wallet implements OfflineSigner {
|
||||
/**
|
||||
* Restores a wallet from the given BIP39 mnemonic.
|
||||
@ -128,5 +124,5 @@ export declare class Secp256k1Wallet implements OfflineSigner {
|
||||
* The caller is responsible for ensuring the key was derived with the given kdf options. If this
|
||||
* is not the case, the wallet cannot be restored with the original password.
|
||||
*/
|
||||
serializeWithEncryptionKey(encryptionKey: Uint8Array, kdfOptions: Argon2idOptions): Promise<string>;
|
||||
serializeWithEncryptionKey(encryptionKey: Uint8Array, kdfConfiguration: KdfConfiguration): Promise<string>;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user