Skip to content

Commit e232235

Browse files
authored
Merge pull request #1934 from cosmos/spliteria
Split libsodium module in argon2, ed25519 and xchacha20poly1305
2 parents 02592ac + 3cc8083 commit e232235

File tree

9 files changed

+973
-972
lines changed

9 files changed

+973
-972
lines changed

packages/crypto/src/argon2.spec.ts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { fromHex, toAscii } from "@cosmjs/encoding";
2+
3+
import { Argon2id, Argon2idOptions } from "./argon2";
4+
5+
describe("Argon2id", () => {
6+
// we use relatively week values here to avoid slowing down test execution
7+
8+
it("works for 1 MiB memory and opsLimit = 5", async () => {
9+
const options: Argon2idOptions = {
10+
outputLength: 32,
11+
opsLimit: 5,
12+
memLimitKib: 1024,
13+
};
14+
const salt = toAscii("ABCDEFGHIJKLMNOP");
15+
16+
// echo -n "123" | ./argon2 ABCDEFGHIJKLMNOP -id -v 13 -k 1024 -t 5
17+
await Argon2id.execute("123", salt, options).then((result) => {
18+
expect(result).toEqual(fromHex("3c5d010180ba0cf5b6b858cba23b318e42d33088983c404598599c3b029ecac6"));
19+
});
20+
await Argon2id.execute("!'§$%&/()", salt, options).then((result) => {
21+
expect(result).toEqual(fromHex("b0268bd63015c3d8f866f9be385507b466a9bfc75f271c2c1e97c00bf53224ba"));
22+
});
23+
await Argon2id.execute("ö", salt, options).then((result) => {
24+
expect(result).toEqual(fromHex("b113fc7863dbc87b7d1366c3b468d3864a2473ce46e90ed3641fff87ada561f7"));
25+
});
26+
await Argon2id.execute("😎", salt, options).then((result) => {
27+
expect(result).toEqual(fromHex("dc92db2a69a5607a75472e1581ac0851292ed9a2606f1000f62fa2efc97964e0"));
28+
});
29+
});
30+
31+
it("works for 8 MiB memory and opsLimit = 2", async () => {
32+
const options: Argon2idOptions = {
33+
outputLength: 32,
34+
opsLimit: 2,
35+
memLimitKib: 8 * 1024,
36+
};
37+
const salt = toAscii("ABCDEFGHIJKLMNOP");
38+
39+
// echo -n "123" | ./argon2 ABCDEFGHIJKLMNOP -id -v 13 -k 8192 -t 2
40+
await Argon2id.execute("123", salt, options).then((result) => {
41+
expect(result).toEqual(fromHex("3ee950488d26ce691657b1d753f562139857b61a58f234d6cb0ce84c4cc27328"));
42+
});
43+
await Argon2id.execute("!'§$%&/()", salt, options).then((result) => {
44+
expect(result).toEqual(fromHex("ab410498b44942a28f9d0dde72f0398edf104021ee41bb80412464975817a8a1"));
45+
});
46+
await Argon2id.execute("ö", salt, options).then((result) => {
47+
expect(result).toEqual(fromHex("f80c502bc3fe7b191f6e7e06359955d5dbd23f532548b7058ecbcf77a58e683d"));
48+
});
49+
await Argon2id.execute("😎", salt, options).then((result) => {
50+
expect(result).toEqual(fromHex("474d9445596d2600ba3dc9bbe87d21ed4879e2445cafb10fcb69c5c3ab8ecbc7"));
51+
});
52+
});
53+
54+
it("works for 10 MiB memory and opsLimit = 1", async () => {
55+
const options: Argon2idOptions = {
56+
outputLength: 32,
57+
opsLimit: 1,
58+
memLimitKib: 10 * 1024,
59+
};
60+
const salt = toAscii("ABCDEFGHIJKLMNOP");
61+
62+
// echo -n "123" | ./argon2 ABCDEFGHIJKLMNOP -id -v 13 -k 10240 -t 1
63+
await Argon2id.execute("123", salt, options).then((result) => {
64+
expect(result).toEqual(fromHex("f1832edbd41c209546eafd01f3aae28390de39bc13ff38981c4fc0c1ceaa05e3"));
65+
});
66+
await Argon2id.execute("!'§$%&/()", salt, options).then((result) => {
67+
expect(result).toEqual(fromHex("30c74f405d148fd5c882a0f4238aad9ed85ef255adc102411d22736d68f76f76"));
68+
});
69+
await Argon2id.execute("ö", salt, options).then((result) => {
70+
expect(result).toEqual(fromHex("b80a62f11e7a058194a8ddd80d341c47e0f3b6c41c72ee15b7926788e9963e8f"));
71+
});
72+
await Argon2id.execute("😎", salt, options).then((result) => {
73+
expect(result).toEqual(fromHex("b868aa1875de2edc57bc22de1fc75f9d19f451067c529565f73c61958088b5e9"));
74+
});
75+
});
76+
77+
it("works for different output lengths", async () => {
78+
// echo -n "123" | ./argon2 ABCDEFGHIJKLMNOP -id -v 13 -k 1024 -t 5 -l 16
79+
const salt = toAscii("ABCDEFGHIJKLMNOP");
80+
81+
// libsodium does not support output length < 16
82+
const data = new Map<number, string>();
83+
data.set(16, "01a5ea70c68132b474bdb7f996f55a5a");
84+
data.set(24, "14cf66110e167ebdbea968328bba3f40113077bc359acbe8");
85+
data.set(32, "3c5d010180ba0cf5b6b858cba23b318e42d33088983c404598599c3b029ecac6");
86+
data.set(
87+
48,
88+
"1141dc209086803b06fe0835be055ed592289c9baf9a9db6cd584cd63c2712ca0efc989017a73d6dafb7211a9d09413f",
89+
);
90+
data.set(
91+
96,
92+
"cdb42cddd7190d0ab2453571f644ebf1214177886a51639f23518e1c92d73a196cadddf927bbc8fac59ab3615325642920d7dd73171c7b63f17e1ae173a7b6372bac7525a3230ab1edf6e3ed5c971321186c00a544c79d96bc65263eb5a85d50",
93+
);
94+
data.set(
95+
128,
96+
"c0108a962f59c30b6af298025ad8b8027791cc91f74b96a01a92993e41871e391516e831210bdd3ae20fe501b9c2279d59d42ebc777286088d56a87f30eea04829b9903cb05a468f320e4aced531c7b10631463141a9cbd903dbad4c9b43b2ca0c56ff5a0093179924685e061979e49a593719bb3373152856df922b0007bd9f",
97+
);
98+
data.set(
99+
192,
100+
"092aeca103de921794a97abfd4f0dd1e51de29c62f372e2a984f72d280c12067316db192e47d37ccfd07243bb1ea9a14f7a361a1ab3f5c4be70fb33fea868d9047bdf9ccc52cde1f1cefbb77923b236a690f30f03ebd2ebf72cd47e2acf28627d64b6bd0fe1f8fb2e598017c4892413b83df2ab4c210b51bd730644fa042fee64653a33fbc81dc715c1e05ed4592b71dee1b3fa080b3d332bd96b50c9a1b1c71b7b4e131517dcb63ab628679d20a386f98948d8b9ecf99f32611c9f747abb2d5",
101+
);
102+
data.set(
103+
256,
104+
"94aaf5677f2c0ad13d504bbbfe9b05bbcb8194c8415c119c9d3c170fbcff0e0a42ffa48c11085c6c61f0942d88d32e0da3408099991148db876e29fc5ca80b8425ac0a09987393d7c67fc62ff21fb9713442f3a67690350a871d99bedaecb7c86c357410631c89eedf04c97e386ecb5c0028d53f2d1d6aacba67d2e7bd23792689367dfd777eb28ff4de1753955dcb5f85f34a03684089590927ebd09c251cec4abb7f717ebed22690116938c5ca8404ae7814e9391c4f3c023bafad92b26899f94b6b3dc13ebc7fa693a9233a73f3b2f06b337af3a848b006e8c53bf24b79ca50df8f638304a8671f6949fde9239e0bfa78b5a7ddf424b808a0bfcd2b4fbb20",
105+
);
106+
107+
for (const length of data.keys()) {
108+
const options: Argon2idOptions = {
109+
outputLength: length,
110+
opsLimit: 5,
111+
memLimitKib: 1024,
112+
};
113+
await Argon2id.execute("123", salt, options).then((result) => {
114+
expect(result).toEqual(fromHex(data.get(length)!));
115+
});
116+
}
117+
});
118+
119+
it("throw for invalid salt lengths", async () => {
120+
const password = "123";
121+
const options: Argon2idOptions = {
122+
outputLength: 32,
123+
opsLimit: 1,
124+
memLimitKib: 10 * 1024,
125+
};
126+
127+
// 8 bytes
128+
await expectAsync(Argon2id.execute(password, fromHex("aabbccddeeff0011"), options)).toBeRejectedWithError(
129+
/invalid salt length/,
130+
);
131+
// 15 bytes
132+
await expectAsync(
133+
Argon2id.execute(password, fromHex("aabbccddeeff001122334455667788"), options),
134+
).toBeRejectedWithError(/invalid salt length/);
135+
// 17 bytes
136+
await expectAsync(
137+
Argon2id.execute(password, fromHex("aabbccddeeff00112233445566778899aa"), options),
138+
).toBeRejectedWithError(/invalid salt length/);
139+
// 32 bytes
140+
await expectAsync(
141+
Argon2id.execute(
142+
password,
143+
fromHex("aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899"),
144+
options,
145+
),
146+
).toBeRejectedWithError(/invalid salt length/);
147+
});
148+
});

packages/crypto/src/argon2.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { assert, isNonNullObject } from "@cosmjs/utils";
2+
import { type IArgon2Options, argon2id } from "hash-wasm";
3+
4+
export interface Argon2idOptions {
5+
/** Output length in bytes */
6+
readonly outputLength: number;
7+
/**
8+
* An integer between 1 and 4294967295 representing the computational difficulty.
9+
*
10+
* @see https://libsodium.gitbook.io/doc/password_hashing/default_phf#key-derivation
11+
*/
12+
readonly opsLimit: number;
13+
/**
14+
* Memory limit measured in KiB (like argon2 command line tool)
15+
*
16+
* Note: only approximately 16 MiB of memory are available using the non-sumo version of libsodium.js
17+
*
18+
* @see https://libsodium.gitbook.io/doc/password_hashing/default_phf#key-derivation
19+
*/
20+
readonly memLimitKib: number;
21+
}
22+
23+
export function isArgon2idOptions(thing: unknown): thing is Argon2idOptions {
24+
if (!isNonNullObject(thing)) return false;
25+
if (typeof (thing as Argon2idOptions).outputLength !== "number") return false;
26+
if (typeof (thing as Argon2idOptions).opsLimit !== "number") return false;
27+
if (typeof (thing as Argon2idOptions).memLimitKib !== "number") return false;
28+
return true;
29+
}
30+
31+
export class Argon2id {
32+
public static async execute(
33+
password: string,
34+
salt: Uint8Array,
35+
options: Argon2idOptions,
36+
): Promise<Uint8Array> {
37+
const opts: IArgon2Options = {
38+
password,
39+
salt,
40+
outputType: "binary",
41+
iterations: options.opsLimit,
42+
memorySize: options.memLimitKib,
43+
parallelism: 1, // no parallelism allowed, just like libsodium
44+
hashLength: options.outputLength,
45+
};
46+
47+
if (salt.length !== 16) {
48+
throw new Error(`Got invalid salt length ${salt.length}. Must be 16.`);
49+
}
50+
51+
const hash = await argon2id(opts);
52+
// guaranteed by outputType: 'binary'
53+
assert(typeof hash !== "string");
54+
return hash;
55+
}
56+
}

0 commit comments

Comments
 (0)