Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/tidy-hashes-keep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fleet-sdk/crypto": patch
---

Fix `blake2b()` mutating the caller's options object: the `key`, `salt`, and `personalization` fields were converted from hex strings to `Uint8Array` in place, so a caller's options object came back changed (and a frozen options object threw a `TypeError`). The conversion now happens in a fresh options object, leaving the caller's object untouched; hash outputs are unchanged.
43 changes: 43 additions & 0 deletions packages/crypto/src/hashes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,49 @@ describe("Hashes smoke tests", () => {
expect(sha256(byte)).to.be.deep.equal(sha256(hex));
});

it("Should not mutate the caller's options object", () => {
const msg = utf8.decode("blake2b256");
const options = {
dkLen: 64,
key: "6a9059057f259d733766f6b32081b66c",
salt: "d877f8df03fd687ab7c0052e3ce88372",
personalization: "77616c6c65747320636865636b73756d" // "wallets checksum"
};
const snapshot = { ...options };

const result = blake2b(msg, options);

// the options object must be left exactly as the caller passed it:
// hex-string fields must not be replaced with Uint8Arrays
expect(options).to.be.deep.equal(snapshot);
expect(options.key).to.be.equal(snapshot.key);
expect(options.salt).to.be.equal(snapshot.salt);
expect(options.personalization).to.be.equal(snapshot.personalization);

// and the hash must match hashing with pre-decoded byte options
expect(result).to.be.deep.equal(
blake2b(msg, {
dkLen: 64,
key: hex.decode(snapshot.key),
salt: hex.decode(snapshot.salt),
personalization: hex.decode(snapshot.personalization)
})
);
});

it("Should accept a frozen options object", () => {
const msg = utf8.decode("blake2b256");
const options = Object.freeze({
key: "6a9059057f259d733766f6b32081b66c",
salt: "d877f8df03fd687ab7c0052e3ce88372"
});

expect(() => blake2b(msg, options)).not.to.throw();
expect(blake2b(msg, options)).to.be.deep.equal(
blake2b(msg, { key: hex.decode(options.key), salt: hex.decode(options.salt) })
);
});

it("Should hash message using sha256", () => {
expect(sha256(utf8.decode("sha256"))).to.be.deep.equal(
hex.decode("5d5b09f6dcb2d53a5fffc60c4ac0d55fabdf556069d6631545f42aa6e3500f2e")
Expand Down
6 changes: 2 additions & 4 deletions packages/crypto/src/hashes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ export function ensureBytes(input: ByteInput): Uint8Array {
}

export function blake2b(message: ByteInput, options?: Blake2bOptions): Uint8Array {
if (options?.key) options.key = ensureBytes(options.key);
if (options?.salt) options.salt = ensureBytes(options.salt);
if (options?.personalization) options.personalization = ensureBytes(options.personalization);

// convert hex-string fields into a fresh options object; never mutate the
// caller's options object (it may be frozen or reused across calls)
const opts: Blake2Opts = {
key: options?.key ? ensureBytes(options?.key) : undefined,
salt: options?.salt ? ensureBytes(options?.salt) : undefined,
Expand Down