From 9b484ed4c9a3011f59fe8c1a4909cb74addc6426 Mon Sep 17 00:00:00 2001 From: Yury Shapkarin Date: Tue, 19 Oct 2021 00:23:25 +0300 Subject: [PATCH 1/8] test: salt next to the kdf.params wallet salt tests --- .../proto-signing/src/directsecp256k1hdwallet.spec.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/proto-signing/src/directsecp256k1hdwallet.spec.ts b/packages/proto-signing/src/directsecp256k1hdwallet.spec.ts index 0b9122f33b..fd85010bee 100644 --- a/packages/proto-signing/src/directsecp256k1hdwallet.spec.ts +++ b/packages/proto-signing/src/directsecp256k1hdwallet.spec.ts @@ -299,6 +299,15 @@ describe("DirectSecp256k1HdWallet", () => { data: jasmine.stringMatching(base64Matcher), }); }); + + it("Store a salt next to the serialization options", async () => { + const original = await DirectSecp256k1HdWallet.fromMnemonic(defaultMnemonic); + const password = "123"; + const serialized = await original.serialize(password); + const parsed = JSON.parse(serialized); + + expect(Object.keys(parsed.kdf.params)).toContain('salt'); + }); }); describe("serializeWithEncryptionKey", () => { From 43aad3263bd92ec86569348eda97379276fbb965 Mon Sep 17 00:00:00 2001 From: Yury Shapkarin Date: Tue, 19 Oct 2021 18:48:23 +0300 Subject: [PATCH 2/8] test: backward compatibility wallet deserialize --- packages/proto-signing/package.json | 3 ++- .../src/directsecp256k1hdwallet.spec.ts | 20 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/proto-signing/package.json b/packages/proto-signing/package.json index 477191ac0d..c3c1faf40a 100644 --- a/packages/proto-signing/package.json +++ b/packages/proto-signing/package.json @@ -84,6 +84,7 @@ "typedoc": "^0.21", "typescript": "~4.3", "webpack": "^5.32.0", - "webpack-cli": "^4.6.0" + "webpack-cli": "^4.6.0", + "proto-signing-v0-26-2": "npm:@cosmjs/proto-signing@0.26.2" } } diff --git a/packages/proto-signing/src/directsecp256k1hdwallet.spec.ts b/packages/proto-signing/src/directsecp256k1hdwallet.spec.ts index fd85010bee..c5657cbb82 100644 --- a/packages/proto-signing/src/directsecp256k1hdwallet.spec.ts +++ b/packages/proto-signing/src/directsecp256k1hdwallet.spec.ts @@ -3,6 +3,7 @@ import { Secp256k1, Secp256k1Signature, sha256 } from "@cosmjs/crypto"; import { fromBase64, fromHex } from "@cosmjs/encoding"; import { DirectSecp256k1HdWallet, extractKdfConfiguration } from "./directsecp256k1hdwallet"; +import { DirectSecp256k1HdWallet as DirectSecp256k1HdWallet_v0_26_2 } from "proto-signing-v0-26-2" import { makeAuthInfoBytes, makeSignBytes, makeSignDoc } from "./signing"; import { base64Matcher, faucet, testVectors } from "./testutils.spec"; import { executeKdf, KdfConfiguration } from "./wallet"; @@ -124,6 +125,23 @@ describe("DirectSecp256k1HdWallet", () => { }, ]); }); + + it("can restore: backward compatibility test", async () => { + const original = await DirectSecp256k1HdWallet_v0_26_2.fromMnemonic(defaultMnemonic); + const password = "123"; + const serialized = await original.serialize(password); + const deserialized = await DirectSecp256k1HdWallet.deserialize(serialized, password); + const accounts = await deserialized.getAccounts(); + + expect(deserialized.mnemonic).toEqual(defaultMnemonic); + expect(accounts).toEqual([ + { + algo: "secp256k1", + address: defaultAddress, + pubkey: defaultPubkey, + }, + ]); + }); }); describe("deserializeWithEncryptionKey", () => { @@ -299,7 +317,7 @@ describe("DirectSecp256k1HdWallet", () => { data: jasmine.stringMatching(base64Matcher), }); }); - + it("Store a salt next to the serialization options", async () => { const original = await DirectSecp256k1HdWallet.fromMnemonic(defaultMnemonic); const password = "123"; From 663d39c7bb645a8a420ef8e2c7444ccf68e8d718 Mon Sep 17 00:00:00 2001 From: Yury Shapkarin Date: Sat, 12 Mar 2022 23:10:47 +0300 Subject: [PATCH 3/8] Argon2id generate salt --- packages/crypto/src/libsodium.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/crypto/src/libsodium.ts b/packages/crypto/src/libsodium.ts index 0776a0c68a..1477ed1d43 100644 --- a/packages/crypto/src/libsodium.ts +++ b/packages/crypto/src/libsodium.ts @@ -6,6 +6,8 @@ import { isNonNullObject } from "@cosmjs/utils"; import sodium from "libsodium-wrappers"; +import { Random } from './random' + export interface Argon2idOptions { /** Output length in bytes */ readonly outputLength: number; @@ -36,10 +38,11 @@ export function isArgon2idOptions(thing: unknown): thing is Argon2idOptions { export class Argon2id { public static async execute( password: string, - salt: Uint8Array, + saltInput: Uint8Array, options: Argon2idOptions, ): Promise { await sodium.ready; + const salt: Uint8Array = saltInput || Random.getBytes(16); return sodium.crypto_pwhash( options.outputLength, password, From a195e985d16ffe8df95ca2a410f6accea76f3b89 Mon Sep 17 00:00:00 2001 From: Yury Shapkarin Date: Sun, 13 Mar 2022 06:02:31 +0300 Subject: [PATCH 4/8] use crypto_pwhash_SALTBYTES --- packages/crypto/src/libsodium.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/crypto/src/libsodium.ts b/packages/crypto/src/libsodium.ts index 1477ed1d43..48dcf94d1b 100644 --- a/packages/crypto/src/libsodium.ts +++ b/packages/crypto/src/libsodium.ts @@ -42,7 +42,7 @@ export class Argon2id { options: Argon2idOptions, ): Promise { await sodium.ready; - const salt: Uint8Array = saltInput || Random.getBytes(16); + const salt: Uint8Array = Random.getBytes(sodium.crypto_pwhash_SALTBYTES); return sodium.crypto_pwhash( options.outputLength, password, From 012c40fed3f89f4b6766274c688da1a684f1261c Mon Sep 17 00:00:00 2001 From: Yury Shapkarin Date: Sun, 13 Mar 2022 06:04:02 +0300 Subject: [PATCH 5/8] back saltInput arg --- packages/crypto/src/libsodium.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/crypto/src/libsodium.ts b/packages/crypto/src/libsodium.ts index 48dcf94d1b..36ab94915d 100644 --- a/packages/crypto/src/libsodium.ts +++ b/packages/crypto/src/libsodium.ts @@ -42,7 +42,7 @@ export class Argon2id { options: Argon2idOptions, ): Promise { await sodium.ready; - const salt: Uint8Array = Random.getBytes(sodium.crypto_pwhash_SALTBYTES); + const salt: Uint8Array = saltInput || Random.getBytes(sodium.crypto_pwhash_SALTBYTES); return sodium.crypto_pwhash( options.outputLength, password, From 72ea429e294df6d26792213435c5ba6261d80a4e Mon Sep 17 00:00:00 2001 From: Yury Shapkarin Date: Thu, 24 Mar 2022 11:51:47 +0300 Subject: [PATCH 6/8] directsecp256k1hdwallet random salt --- packages/crypto/src/libsodium.ts | 6 ++++-- packages/proto-signing/src/wallet.ts | 13 ++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/crypto/src/libsodium.ts b/packages/crypto/src/libsodium.ts index 36ab94915d..5e49abd1d1 100644 --- a/packages/crypto/src/libsodium.ts +++ b/packages/crypto/src/libsodium.ts @@ -35,14 +35,16 @@ export function isArgon2idOptions(thing: unknown): thing is Argon2idOptions { return true; } +// Equal to sodium.crypto_pwhash_SALTBYTES (16) +export const sodiumSaltBytes = 16; + export class Argon2id { public static async execute( password: string, - saltInput: Uint8Array, + salt: Uint8Array, options: Argon2idOptions, ): Promise { await sodium.ready; - const salt: Uint8Array = saltInput || Random.getBytes(sodium.crypto_pwhash_SALTBYTES); return sodium.crypto_pwhash( options.outputLength, password, diff --git a/packages/proto-signing/src/wallet.ts b/packages/proto-signing/src/wallet.ts index dc85068a1d..d5dc9ea603 100644 --- a/packages/proto-signing/src/wallet.ts +++ b/packages/proto-signing/src/wallet.ts @@ -4,16 +4,10 @@ import { Random, xchacha20NonceLength, Xchacha20poly1305Ietf, + sodiumSaltBytes, } from "@cosmjs/crypto"; import { toAscii } from "@cosmjs/encoding"; -/** - * A fixed salt is chosen to archive a deterministic password to key derivation. - * This reduces the scope of a potential rainbow attack to all CosmJS users. - * Must be 16 bytes due to implementation limitations. - */ -export const cosmjsSalt = toAscii("The CosmJS salt."); - export interface KdfConfiguration { /** * An algorithm identifier, such as "argon2id" or "scrypt". @@ -28,7 +22,8 @@ export async function executeKdf(password: string, configuration: KdfConfigurati case "argon2id": { const options = configuration.params; if (!isArgon2idOptions(options)) throw new Error("Invalid format of argon2id params"); - return Argon2id.execute(password, cosmjsSalt, options); + const salt: Uint8Array = Random.getBytes(sodiumSaltBytes); + return Argon2id.execute(password, salt, options); } default: throw new Error("Unsupported KDF algorithm"); @@ -59,7 +54,7 @@ export async function encrypt( ): Promise { switch (config.algorithm) { case supportedAlgorithms.xchacha20poly1305Ietf: { - const nonce = Random.getBytes(xchacha20NonceLength); + const nonce: Uint8Array = Random.getBytes(xchacha20NonceLength); // Prepend fixed-length nonce to ciphertext as suggested in the example from https://github.com/jedisct1/libsodium.js#api return new Uint8Array([ ...nonce, From 91fdfc22b0ad7f4d52a195f8570355c330719423 Mon Sep 17 00:00:00 2001 From: Yury Shapkarin Date: Thu, 24 Mar 2022 11:55:34 +0300 Subject: [PATCH 7/8] remove deprecated import --- packages/crypto/src/libsodium.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/crypto/src/libsodium.ts b/packages/crypto/src/libsodium.ts index 5e49abd1d1..947ce7f91d 100644 --- a/packages/crypto/src/libsodium.ts +++ b/packages/crypto/src/libsodium.ts @@ -6,8 +6,6 @@ import { isNonNullObject } from "@cosmjs/utils"; import sodium from "libsodium-wrappers"; -import { Random } from './random' - export interface Argon2idOptions { /** Output length in bytes */ readonly outputLength: number; From 24e8588c9da5a5a16601dce3cc461f20a9bb657a Mon Sep 17 00:00:00 2001 From: Yury Shapkarin Date: Thu, 24 Mar 2022 11:51:47 +0300 Subject: [PATCH 8/8] directsecp256k1hdwallet random salt remove deprecated import --- packages/crypto/src/libsodium.ts | 8 ++++---- packages/proto-signing/src/wallet.ts | 13 ++++--------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/crypto/src/libsodium.ts b/packages/crypto/src/libsodium.ts index 36ab94915d..947ce7f91d 100644 --- a/packages/crypto/src/libsodium.ts +++ b/packages/crypto/src/libsodium.ts @@ -6,8 +6,6 @@ import { isNonNullObject } from "@cosmjs/utils"; import sodium from "libsodium-wrappers"; -import { Random } from './random' - export interface Argon2idOptions { /** Output length in bytes */ readonly outputLength: number; @@ -35,14 +33,16 @@ export function isArgon2idOptions(thing: unknown): thing is Argon2idOptions { return true; } +// Equal to sodium.crypto_pwhash_SALTBYTES (16) +export const sodiumSaltBytes = 16; + export class Argon2id { public static async execute( password: string, - saltInput: Uint8Array, + salt: Uint8Array, options: Argon2idOptions, ): Promise { await sodium.ready; - const salt: Uint8Array = saltInput || Random.getBytes(sodium.crypto_pwhash_SALTBYTES); return sodium.crypto_pwhash( options.outputLength, password, diff --git a/packages/proto-signing/src/wallet.ts b/packages/proto-signing/src/wallet.ts index dc85068a1d..d5dc9ea603 100644 --- a/packages/proto-signing/src/wallet.ts +++ b/packages/proto-signing/src/wallet.ts @@ -4,16 +4,10 @@ import { Random, xchacha20NonceLength, Xchacha20poly1305Ietf, + sodiumSaltBytes, } from "@cosmjs/crypto"; import { toAscii } from "@cosmjs/encoding"; -/** - * A fixed salt is chosen to archive a deterministic password to key derivation. - * This reduces the scope of a potential rainbow attack to all CosmJS users. - * Must be 16 bytes due to implementation limitations. - */ -export const cosmjsSalt = toAscii("The CosmJS salt."); - export interface KdfConfiguration { /** * An algorithm identifier, such as "argon2id" or "scrypt". @@ -28,7 +22,8 @@ export async function executeKdf(password: string, configuration: KdfConfigurati case "argon2id": { const options = configuration.params; if (!isArgon2idOptions(options)) throw new Error("Invalid format of argon2id params"); - return Argon2id.execute(password, cosmjsSalt, options); + const salt: Uint8Array = Random.getBytes(sodiumSaltBytes); + return Argon2id.execute(password, salt, options); } default: throw new Error("Unsupported KDF algorithm"); @@ -59,7 +54,7 @@ export async function encrypt( ): Promise { switch (config.algorithm) { case supportedAlgorithms.xchacha20poly1305Ietf: { - const nonce = Random.getBytes(xchacha20NonceLength); + const nonce: Uint8Array = Random.getBytes(xchacha20NonceLength); // Prepend fixed-length nonce to ciphertext as suggested in the example from https://github.com/jedisct1/libsodium.js#api return new Uint8Array([ ...nonce,