Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 28 additions & 28 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Git LFS file not shown
3 changes: 3 additions & 0 deletions .yarn/cache/hash-wasm-npm-4.12.0-d6bb202626-6cb9505531.zip
Git LFS file not shown

This file was deleted.

This file was deleted.

4 changes: 2 additions & 2 deletions packages/amino/src/secp256k1hdwallet.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe("Secp256k1HdWallet", () => {
pubkey: defaultPubkey,
},
]);
});
}, 90000);

it("can restore multiple accounts", async () => {
const mnemonic =
Expand Down Expand Up @@ -123,7 +123,7 @@ describe("Secp256k1HdWallet", () => {
address: "wasm1hsm76p4ahyhl5yh3ve9ur49r5kemhp2r93f89d",
},
]);
});
}, 90000);
});

describe("deserializeWithEncryptionKey", () => {
Expand Down
8 changes: 7 additions & 1 deletion packages/amino/src/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ 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);

// Emulate a slower implementation. The fast WASM code may get removed.
// This approximates the speed of using a pure JS implementation (@noble/hashes) in Node 22.
const screamTest = new Promise((resolve) => setTimeout(resolve, options.opsLimit * 250));
const result = await Argon2id.execute(password, cosmjsSalt, options);
await screamTest;
return result;
}
default:
throw new Error("Unsupported KDF algorithm");
Expand Down
3 changes: 2 additions & 1 deletion packages/crypto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@
"@cosmjs/encoding": "workspace:^",
"@cosmjs/math": "workspace:^",
"@cosmjs/utils": "workspace:^",
"@noble/ciphers": "^1.3.0",
"@noble/curves": "^1.9.2",
"@noble/hashes": "^1",
"libsodium-wrappers-sumo": "^0.7.11"
"hash-wasm": "^4.12.0"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
Expand Down
18 changes: 9 additions & 9 deletions packages/crypto/src/libsodium.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ describe("Libsodium", () => {
fail("promise must not resolve");
})
.catch((error) => {
expect(error.message).toContain("invalid seed length");
expect(error.message).toContain("key of length 32 expected");
});
}

Expand All @@ -250,7 +250,7 @@ describe("Libsodium", () => {
fail("promise must not resolve");
})
.catch((error) => {
expect(error.message).toContain("invalid seed length");
expect(error.message).toContain("key of length 32 expected");
});
}
});
Expand Down Expand Up @@ -426,7 +426,7 @@ describe("Libsodium", () => {
fail("encryption must not succeed");
})
.catch((error) => {
expect(error).toMatch(/invalid key length/);
expect(error).toMatch(/key, got length=0/);
});
}
{
Expand All @@ -437,7 +437,7 @@ describe("Libsodium", () => {
fail("encryption must not succeed");
})
.catch((error) => {
expect(error).toMatch(/invalid key length/);
expect(error).toMatch(/key, got length=31/);
});
}
{
Expand All @@ -448,7 +448,7 @@ describe("Libsodium", () => {
fail("encryption must not succeed");
})
.catch((error) => {
expect(error).toMatch(/invalid key length/);
expect(error).toMatch(/key, got length=33/);
});
}
{
Expand All @@ -461,7 +461,7 @@ describe("Libsodium", () => {
fail("encryption must not succeed");
})
.catch((error) => {
expect(error).toMatch(/invalid key length/);
expect(error).toMatch(/key, got length=64/);
});
}
});
Expand All @@ -487,7 +487,7 @@ describe("Libsodium", () => {
fail("promise must not resolve");
},
(error) => {
expect(error.message).toMatch(/ciphertext cannot be decrypted using that key/i);
expect(error.message).toMatch(/invalid tag/i);
},
);
}
Expand All @@ -499,7 +499,7 @@ describe("Libsodium", () => {
fail("promise must not resolve");
},
(error) => {
expect(error.message).toMatch(/ciphertext cannot be decrypted using that key/i);
expect(error.message).toMatch(/invalid tag/i);
},
);
}
Expand All @@ -511,7 +511,7 @@ describe("Libsodium", () => {
fail("promise must not resolve");
},
(error) => {
expect(error.message).toMatch(/ciphertext cannot be decrypted using that key/i);
expect(error.message).toMatch(/invalid tag/i);
},
);
}
Expand Down
76 changes: 32 additions & 44 deletions packages/crypto/src/libsodium.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
// Keep all classes requiring libsodium-js in one file as having multiple
// requiring of the libsodium-wrappers module currently crashes browsers
//
// libsodium.js API: https://gist.github.com/webmaster128/b2dbe6d54d36dd168c9fabf441b9b09c

import { isNonNullObject } from "@cosmjs/utils";
// Using crypto_pwhash requires sumo. Once we migrate to a standalone
// Argon2 implementation, we can use the normal libsodium-wrappers
// again: https://github.com/cosmos/cosmjs/issues/1031
import sodium from "libsodium-wrappers-sumo";
import { assert, isNonNullObject } from "@cosmjs/utils";
import { xchacha20poly1305 } from "@noble/ciphers/chacha.js";
import { ed25519 } from "@noble/curves/ed25519.js";
import { type IArgon2Options, argon2id } from "hash-wasm";

export interface Argon2idOptions {
/** Output length in bytes */
Expand Down Expand Up @@ -42,15 +36,24 @@ export class Argon2id {
salt: Uint8Array,
options: Argon2idOptions,
): Promise<Uint8Array> {
await sodium.ready;
return sodium.crypto_pwhash(
options.outputLength,
const opts: IArgon2Options = {
password,
salt, // libsodium only supports 16 byte salts and will throw when you don't respect that
options.opsLimit,
options.memLimitKib * 1024,
sodium.crypto_pwhash_ALG_ARGON2ID13,
);
salt,
outputType: "binary",
iterations: options.opsLimit,
memorySize: options.memLimitKib,
parallelism: 1, // no parallelism allowed, just like libsodium
hashLength: options.outputLength,
};

if (salt.length !== 16) {
throw new Error(`Got invalid salt length ${salt.length}. Must be 16.`);
}

const hash = await argon2id(opts);
// guaranteed by outputType: 'binary'
assert(typeof hash !== "string");
return hash;
}
}

Expand Down Expand Up @@ -85,24 +88,21 @@ export class Ed25519 {
* https://download.libsodium.org/doc/public-key_cryptography/public-key_signatures.html
* and diagram on https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/
*/
public static async makeKeypair(seed: Uint8Array): Promise<Ed25519Keypair> {
await sodium.ready;
const keypair = sodium.crypto_sign_seed_keypair(seed);
return Ed25519Keypair.fromLibsodiumPrivkey(keypair.privateKey);
public static async makeKeypair(privKey: Uint8Array): Promise<Ed25519Keypair> {
const pubKey = ed25519.getPublicKey(privKey);
return new Ed25519Keypair(privKey, pubKey);
}

public static async createSignature(message: Uint8Array, keyPair: Ed25519Keypair): Promise<Uint8Array> {
await sodium.ready;
return sodium.crypto_sign_detached(message, keyPair.toLibsodiumPrivkey());
return ed25519.sign(message, keyPair.privkey);
}

public static async verifySignature(
signature: Uint8Array,
message: Uint8Array,
pubkey: Uint8Array,
): Promise<boolean> {
await sodium.ready;
return sodium.crypto_sign_verify_detached(signature, message, pubkey);
return ed25519.verify(signature, message, pubkey);
}
}

Expand All @@ -115,34 +115,22 @@ export const xchacha20NonceLength = 24;

export class Xchacha20poly1305Ietf {
public static async encrypt(message: Uint8Array, key: Uint8Array, nonce: Uint8Array): Promise<Uint8Array> {
await sodium.ready;
const additionalAuthenticatedData = undefined;

const additionalData = null;
const cipher = xchacha20poly1305(key, nonce, additionalAuthenticatedData);

return sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
message,
additionalData,
null, // secret nonce: unused and should be null (https://download.libsodium.org/doc/secret-key_cryptography/aead/chacha20-poly1305/xchacha20-poly1305_construction)
nonce,
key,
);
return cipher.encrypt(message);
}

public static async decrypt(
ciphertext: Uint8Array,
key: Uint8Array,
nonce: Uint8Array,
): Promise<Uint8Array> {
await sodium.ready;
const additionalAuthenticatedData = undefined;

const additionalData = null;
const cipher = xchacha20poly1305(key, nonce, additionalAuthenticatedData);

return sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
null, // secret nonce: unused and should be null (https://download.libsodium.org/doc/secret-key_cryptography/aead/chacha20-poly1305/xchacha20-poly1305_construction)
ciphertext,
additionalData,
nonce,
key,
);
return cipher.decrypt(ciphertext);
}
}
Loading
Loading