Skip to content

Commit 39e712a

Browse files
authored
Merge pull request #1722 from dynst/desalinate
migrate off libsodium
2 parents 6bc8077 + a0e779d commit 39e712a

13 files changed

+111
-111
lines changed

.pnp.cjs

Lines changed: 28 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:9ba9e326e29f1111f24f0d91cc6663dca45faf2a8e98a7e631db1a8fa62f11e7
3+
size 774440
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:505d7271f3fa67b1baedc9c0259b5c37dcfdbd8a3334f962266468c4945b9de0
3+
size 1820660

.yarn/cache/libsodium-sumo-npm-0.7.11-aaac6bcc6c-f7518d1781.zip

Lines changed: 0 additions & 3 deletions
This file was deleted.

.yarn/cache/libsodium-wrappers-sumo-npm-0.7.11-08fe1b2cf4-81270738b3.zip

Lines changed: 0 additions & 3 deletions
This file was deleted.

packages/amino/src/secp256k1hdwallet.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ describe("Secp256k1HdWallet", () => {
7777
pubkey: defaultPubkey,
7878
},
7979
]);
80-
});
80+
}, 90000);
8181

8282
it("can restore multiple accounts", async () => {
8383
const mnemonic =
@@ -123,7 +123,7 @@ describe("Secp256k1HdWallet", () => {
123123
address: "wasm1hsm76p4ahyhl5yh3ve9ur49r5kemhp2r93f89d",
124124
},
125125
]);
126-
});
126+
}, 90000);
127127
});
128128

129129
describe("deserializeWithEncryptionKey", () => {

packages/amino/src/wallet.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@ export async function executeKdf(password: string, configuration: KdfConfigurati
3131
case "argon2id": {
3232
const options = configuration.params;
3333
if (!isArgon2idOptions(options)) throw new Error("Invalid format of argon2id params");
34-
return Argon2id.execute(password, cosmjsSalt, options);
34+
35+
// Emulate a slower implementation. The fast WASM code may get removed.
36+
// This approximates the speed of using a pure JS implementation (@noble/hashes) in Node 22.
37+
const screamTest = new Promise((resolve) => setTimeout(resolve, options.opsLimit * 250));
38+
const result = await Argon2id.execute(password, cosmjsSalt, options);
39+
await screamTest;
40+
return result;
3541
}
3642
default:
3743
throw new Error("Unsupported KDF algorithm");

packages/crypto/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@
4040
"@cosmjs/encoding": "workspace:^",
4141
"@cosmjs/math": "workspace:^",
4242
"@cosmjs/utils": "workspace:^",
43+
"@noble/ciphers": "^1.3.0",
4344
"@noble/curves": "^1.9.2",
4445
"@noble/hashes": "^1",
45-
"libsodium-wrappers-sumo": "^0.7.11"
46+
"hash-wasm": "^4.12.0"
4647
},
4748
"devDependencies": {
4849
"@istanbuljs/nyc-config-typescript": "^1.0.1",

packages/crypto/src/libsodium.spec.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ describe("Libsodium", () => {
238238
fail("promise must not resolve");
239239
})
240240
.catch((error) => {
241-
expect(error.message).toContain("invalid seed length");
241+
expect(error.message).toContain("key of length 32 expected");
242242
});
243243
}
244244

@@ -250,7 +250,7 @@ describe("Libsodium", () => {
250250
fail("promise must not resolve");
251251
})
252252
.catch((error) => {
253-
expect(error.message).toContain("invalid seed length");
253+
expect(error.message).toContain("key of length 32 expected");
254254
});
255255
}
256256
});
@@ -426,7 +426,7 @@ describe("Libsodium", () => {
426426
fail("encryption must not succeed");
427427
})
428428
.catch((error) => {
429-
expect(error).toMatch(/invalid key length/);
429+
expect(error).toMatch(/key, got length=0/);
430430
});
431431
}
432432
{
@@ -437,7 +437,7 @@ describe("Libsodium", () => {
437437
fail("encryption must not succeed");
438438
})
439439
.catch((error) => {
440-
expect(error).toMatch(/invalid key length/);
440+
expect(error).toMatch(/key, got length=31/);
441441
});
442442
}
443443
{
@@ -448,7 +448,7 @@ describe("Libsodium", () => {
448448
fail("encryption must not succeed");
449449
})
450450
.catch((error) => {
451-
expect(error).toMatch(/invalid key length/);
451+
expect(error).toMatch(/key, got length=33/);
452452
});
453453
}
454454
{
@@ -461,7 +461,7 @@ describe("Libsodium", () => {
461461
fail("encryption must not succeed");
462462
})
463463
.catch((error) => {
464-
expect(error).toMatch(/invalid key length/);
464+
expect(error).toMatch(/key, got length=64/);
465465
});
466466
}
467467
});
@@ -487,7 +487,7 @@ describe("Libsodium", () => {
487487
fail("promise must not resolve");
488488
},
489489
(error) => {
490-
expect(error.message).toMatch(/ciphertext cannot be decrypted using that key/i);
490+
expect(error.message).toMatch(/invalid tag/i);
491491
},
492492
);
493493
}
@@ -499,7 +499,7 @@ describe("Libsodium", () => {
499499
fail("promise must not resolve");
500500
},
501501
(error) => {
502-
expect(error.message).toMatch(/ciphertext cannot be decrypted using that key/i);
502+
expect(error.message).toMatch(/invalid tag/i);
503503
},
504504
);
505505
}
@@ -511,7 +511,7 @@ describe("Libsodium", () => {
511511
fail("promise must not resolve");
512512
},
513513
(error) => {
514-
expect(error.message).toMatch(/ciphertext cannot be decrypted using that key/i);
514+
expect(error.message).toMatch(/invalid tag/i);
515515
},
516516
);
517517
}

packages/crypto/src/libsodium.ts

Lines changed: 32 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
1-
// Keep all classes requiring libsodium-js in one file as having multiple
2-
// requiring of the libsodium-wrappers module currently crashes browsers
3-
//
4-
// libsodium.js API: https://gist.github.com/webmaster128/b2dbe6d54d36dd168c9fabf441b9b09c
5-
6-
import { isNonNullObject } from "@cosmjs/utils";
7-
// Using crypto_pwhash requires sumo. Once we migrate to a standalone
8-
// Argon2 implementation, we can use the normal libsodium-wrappers
9-
// again: https://github.com/cosmos/cosmjs/issues/1031
10-
import sodium from "libsodium-wrappers-sumo";
1+
import { assert, isNonNullObject } from "@cosmjs/utils";
2+
import { xchacha20poly1305 } from "@noble/ciphers/chacha.js";
3+
import { ed25519 } from "@noble/curves/ed25519.js";
4+
import { type IArgon2Options, argon2id } from "hash-wasm";
115

126
export interface Argon2idOptions {
137
/** Output length in bytes */
@@ -42,15 +36,24 @@ export class Argon2id {
4236
salt: Uint8Array,
4337
options: Argon2idOptions,
4438
): Promise<Uint8Array> {
45-
await sodium.ready;
46-
return sodium.crypto_pwhash(
47-
options.outputLength,
39+
const opts: IArgon2Options = {
4840
password,
49-
salt, // libsodium only supports 16 byte salts and will throw when you don't respect that
50-
options.opsLimit,
51-
options.memLimitKib * 1024,
52-
sodium.crypto_pwhash_ALG_ARGON2ID13,
53-
);
41+
salt,
42+
outputType: "binary",
43+
iterations: options.opsLimit,
44+
memorySize: options.memLimitKib,
45+
parallelism: 1, // no parallelism allowed, just like libsodium
46+
hashLength: options.outputLength,
47+
};
48+
49+
if (salt.length !== 16) {
50+
throw new Error(`Got invalid salt length ${salt.length}. Must be 16.`);
51+
}
52+
53+
const hash = await argon2id(opts);
54+
// guaranteed by outputType: 'binary'
55+
assert(typeof hash !== "string");
56+
return hash;
5457
}
5558
}
5659

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

9496
public static async createSignature(message: Uint8Array, keyPair: Ed25519Keypair): Promise<Uint8Array> {
95-
await sodium.ready;
96-
return sodium.crypto_sign_detached(message, keyPair.toLibsodiumPrivkey());
97+
return ed25519.sign(message, keyPair.privkey);
9798
}
9899

99100
public static async verifySignature(
100101
signature: Uint8Array,
101102
message: Uint8Array,
102103
pubkey: Uint8Array,
103104
): Promise<boolean> {
104-
await sodium.ready;
105-
return sodium.crypto_sign_verify_detached(signature, message, pubkey);
105+
return ed25519.verify(signature, message, pubkey);
106106
}
107107
}
108108

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

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

120-
const additionalData = null;
120+
const cipher = xchacha20poly1305(key, nonce, additionalAuthenticatedData);
121121

122-
return sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
123-
message,
124-
additionalData,
125-
null, // secret nonce: unused and should be null (https://download.libsodium.org/doc/secret-key_cryptography/aead/chacha20-poly1305/xchacha20-poly1305_construction)
126-
nonce,
127-
key,
128-
);
122+
return cipher.encrypt(message);
129123
}
130124

131125
public static async decrypt(
132126
ciphertext: Uint8Array,
133127
key: Uint8Array,
134128
nonce: Uint8Array,
135129
): Promise<Uint8Array> {
136-
await sodium.ready;
130+
const additionalAuthenticatedData = undefined;
137131

138-
const additionalData = null;
132+
const cipher = xchacha20poly1305(key, nonce, additionalAuthenticatedData);
139133

140-
return sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
141-
null, // secret nonce: unused and should be null (https://download.libsodium.org/doc/secret-key_cryptography/aead/chacha20-poly1305/xchacha20-poly1305_construction)
142-
ciphertext,
143-
additionalData,
144-
nonce,
145-
key,
146-
);
134+
return cipher.decrypt(ciphertext);
147135
}
148136
}

0 commit comments

Comments
 (0)