From 9a244b5c9cb1d12e22512c4483871e5c69834769 Mon Sep 17 00:00:00 2001 From: arobsn <87387688+arobsn@users.noreply.github.com> Date: Wed, 25 Dec 2024 11:14:13 -0300 Subject: [PATCH 1/2] add `blake2b` function --- .changeset/few-chefs-sleep.md | 5 +++++ packages/crypto/src/hashes.spec.ts | 21 +++++++++++++++++-- packages/crypto/src/hashes.ts | 20 +++++++++++++++--- .../src/serializers/transactionSerializer.ts | 17 +++++++-------- 4 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 .changeset/few-chefs-sleep.md diff --git a/.changeset/few-chefs-sleep.md b/.changeset/few-chefs-sleep.md new file mode 100644 index 00000000..f2a8ade9 --- /dev/null +++ b/.changeset/few-chefs-sleep.md @@ -0,0 +1,5 @@ +--- +"@fleet-sdk/crypto": patch +--- + +Add `blake2b` function diff --git a/packages/crypto/src/hashes.spec.ts b/packages/crypto/src/hashes.spec.ts index e5f5f005..b2b47e47 100644 --- a/packages/crypto/src/hashes.spec.ts +++ b/packages/crypto/src/hashes.spec.ts @@ -1,14 +1,31 @@ import { describe, expect, it } from "vitest"; import { hex, utf8 } from "./coders"; -import { blake2b256, sha256 } from "./hashes"; +import { blake2b, blake2b256, sha256 } from "./hashes"; describe("Hashes smoke tests", () => { it("Should hash message using BLAKE2b256", () => { - expect(blake2b256(utf8.decode("blake2b256"))).to.be.deep.equal( + const msg = utf8.decode("blake2b256"); + expect(blake2b256(msg)).to.be.deep.equal( hex.decode("eb95e6932cedac15db722fcdb0cfd21437f94690339a716251fad2f89842ea8b") ); }); + it("Should hash message using BLAKE2b with parameters", () => { + const xpk = + "0488b21e000000000000000000b345a673afdeb85091c35d02083035f6e0ca284b1846223b23b566c4070a0cec02a3ad1969b60e85426791b75eccf038e6105c3afab8167af7eb6b73e709b81882"; + + expect( + hex.encode( + blake2b(utf8.decode(xpk), { + dkLen: 64, + personalization: "wallets checksum" + }) + ) + ).to.be.equal( + "5d33031ea3bbba9d3332559b1dafd8612683092f535273a4c15ffa103ffa3fc11f7b6992f5a034b3c8dd30f6f103b24e500c44ba4cff2e5c7f6e3e2eb124cd32" + ); + }); + it("Should have the same result regardless input format", () => { const byte = Uint8Array.from([0xde, 0xad, 0xbe, 0xef]); const hex = "deadbeef"; diff --git a/packages/crypto/src/hashes.ts b/packages/crypto/src/hashes.ts index cbe752c8..b96c7f94 100644 --- a/packages/crypto/src/hashes.ts +++ b/packages/crypto/src/hashes.ts @@ -1,14 +1,28 @@ -import { blake2b } from "@noble/hashes/blake2b"; +import { blake2b as _blake2b } from "@noble/hashes/blake2b"; import { sha256 as _sha256 } from "@noble/hashes/sha256"; import { hex } from "./coders"; import type { ByteInput } from "./types"; +export type Blake2b256Options = { + key?: ByteInput; + salt?: ByteInput; + personalization?: ByteInput; +}; + +export type Blake2bOptions = Blake2b256Options & { + dkLen?: number; +}; + export function ensureBytes(input: ByteInput): Uint8Array { return typeof input === "string" ? hex.decode(input) : input; } -export function blake2b256(message: ByteInput): Uint8Array { - return blake2b(ensureBytes(message), { dkLen: 32 }); +export function blake2b(message: ByteInput, options?: Blake2bOptions): Uint8Array { + return _blake2b(ensureBytes(message), options); +} + +export function blake2b256(message: ByteInput, options?: Blake2b256Options): Uint8Array { + return blake2b(ensureBytes(message), { dkLen: 32, ...options }); } export function sha256(message: ByteInput): Uint8Array { diff --git a/packages/serializer/src/serializers/transactionSerializer.ts b/packages/serializer/src/serializers/transactionSerializer.ts index 515ce555..41eb49e9 100644 --- a/packages/serializer/src/serializers/transactionSerializer.ts +++ b/packages/serializer/src/serializers/transactionSerializer.ts @@ -1,7 +1,6 @@ import { type Amount, type BoxCandidate, - type ContextExtension, type DataInput, isDefined, type UnsignedInput @@ -46,24 +45,24 @@ function writeInput(writer: SigmaByteWriter, input: UnsignedInput): void { writeExtension(writer, input.extension); } -function writeExtension(writer: SigmaByteWriter, extension: ContextExtension): void { +function writeExtension( + writer: SigmaByteWriter, + extension: Record +): void { const keys = Object.keys(extension); let length = 0; for (const key of keys) { - const ext = extension[key as unknown as keyof ContextExtension]; - if (isDefined(ext)) { - length++; - } + if (isDefined(extension[key])) length++; } writer.writeVLQ(length); if (length === 0) return; for (const key of keys) { - const ext = extension[key as unknown as keyof ContextExtension]; - if (isDefined(ext)) { - writer.writeVLQ(Number(key)).writeHex(ext); + const val = extension[key]; + if (isDefined(val)) { + writer.writeVLQ(Number(key)).writeHex(val); } } } From 78a3f5354ad4b25e43d84ed7c8f593b1c3398073 Mon Sep 17 00:00:00 2001 From: arobsn <87387688+arobsn@users.noreply.github.com> Date: Wed, 25 Dec 2024 18:18:57 -0300 Subject: [PATCH 2/2] handle parameters properly --- packages/crypto/src/hashes.spec.ts | 50 +++++++++++++++++++++++++++--- packages/crypto/src/hashes.ts | 5 +++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/packages/crypto/src/hashes.spec.ts b/packages/crypto/src/hashes.spec.ts index b2b47e47..b0ca8336 100644 --- a/packages/crypto/src/hashes.spec.ts +++ b/packages/crypto/src/hashes.spec.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import { hex, utf8 } from "./coders"; import { blake2b, blake2b256, sha256 } from "./hashes"; +import { randomBytes } from "@noble/hashes/utils"; describe("Hashes smoke tests", () => { it("Should hash message using BLAKE2b256", () => { @@ -11,19 +12,60 @@ describe("Hashes smoke tests", () => { }); it("Should hash message using BLAKE2b with parameters", () => { - const xpk = - "0488b21e000000000000000000b345a673afdeb85091c35d02083035f6e0ca284b1846223b23b566c4070a0cec02a3ad1969b60e85426791b75eccf038e6105c3afab8167af7eb6b73e709b81882"; + const xpk = utf8.decode( + "0488b21e000000000000000000b345a673afdeb85091c35d02083035f6e0ca284b1846223b23b566c4070a0cec02a3ad1969b60e85426791b75eccf038e6105c3afab8167af7eb6b73e709b81882" + ); expect( hex.encode( - blake2b(utf8.decode(xpk), { + blake2b(xpk, { dkLen: 64, - personalization: "wallets checksum" + personalization: utf8.decode("wallets checksum") }) ) ).to.be.equal( "5d33031ea3bbba9d3332559b1dafd8612683092f535273a4c15ffa103ffa3fc11f7b6992f5a034b3c8dd30f6f103b24e500c44ba4cff2e5c7f6e3e2eb124cd32" ); + + expect( + hex.encode( + blake2b(xpk, { + dkLen: 64, + personalization: hex.encode(utf8.decode("wallets checksum")), + salt: hex.decode("d877f8df03fd687ab7c0052e3ce88372") + }) + ) + ).to.be.equal( + "f2afda2f44c2f5dd85d0e10e6d42b7c9220a1c8cfb0b25b8d7e554a0be570c39d8d299553fa8b2ecd56e4dc6eb240df93d67640558761df339c04638f2513d75" + ); + + expect( + hex.encode( + blake2b(xpk, { + dkLen: 64, + personalization: hex.encode(utf8.decode("wallets checksum")), + key: "6a9059057f259d733766f6b32081b66c", + salt: "d877f8df03fd687ab7c0052e3ce88372" + }) + ) + ).to.be.equal( + "48cc44fc205ce4932349ad81156b65477029392cb9fd4d6b05519a9a4c4f2485b9902a59ace75bc9215430f226f411ca90e02a34761980ee557ac2b55cc01282" + ); + }); + + it("Should handle strings as hex", () => { + const msg = randomBytes(32); + const key = randomBytes(32); + const salt = randomBytes(16); + const personalization = randomBytes(16); + + expect(blake2b(msg, { key, salt, personalization })).to.be.deep.equal( + blake2b(msg, { + key: hex.encode(key), + salt: hex.encode(salt), + personalization: hex.encode(personalization) + }) + ); }); it("Should have the same result regardless input format", () => { diff --git a/packages/crypto/src/hashes.ts b/packages/crypto/src/hashes.ts index b96c7f94..0ad15422 100644 --- a/packages/crypto/src/hashes.ts +++ b/packages/crypto/src/hashes.ts @@ -18,6 +18,11 @@ 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); + return _blake2b(ensureBytes(message), options); }