From 1fc6566cc6008fb764f9dec7441f0597d8810b32 Mon Sep 17 00:00:00 2001 From: Marcus Pousette Date: Tue, 17 Jan 2023 10:54:52 +0100 Subject: [PATCH] prevent u256 and u512 deserialization modifying the original ser array when passed as Buffer --- package.json | 2 +- src/__tests__/index.test.ts | 100 +++++++++++++++++++++++++++++++++--- src/bigint.ts | 10 ++-- 3 files changed, 99 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 7ba4bf7c..dc2a8a66 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dao-xyz/borsh", - "version": "5.1.2", + "version": "5.1.3", "readme": "README.md", "homepage": "https://github.com/dao-xyz/borsh-ts#README", "description": "Binary Object Representation Serializer for Hashing simplified with decorators", diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index e4b84aee..e6ea14d2 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -251,6 +251,46 @@ describe("arrays", () => { expect(new Uint8Array(deserialized.a)).toEqual(new Uint8Array([1, 2, 3])); }); + test("byte array should deserialize zero-copy from Uint8array", () => { + class TestStruct { + @field({ type: fixedArray("u8", 3) }) + public a: Uint8Array | number[]; + + constructor(properties?: { a: number[] }) { + if (properties) { + this.a = properties.a; + } + } + } + + validate(TestStruct); + const buf = new Uint8Array(serialize(new TestStruct({ a: [1, 2, 3] }))); + expect(new Uint8Array(buf)).toEqual(new Uint8Array([1, 2, 3])); + const deserialized = deserialize(buf, TestStruct); + deserialized.a[0] = 123; + expect(buf[0]).toEqual(123); + }); + + test("byte array should deserialize zero-copy from Buffer", () => { + class TestStruct { + @field({ type: fixedArray("u8", 3) }) + public a: Uint8Array | number[]; + + constructor(properties?: { a: number[] }) { + if (properties) { + this.a = properties.a; + } + } + } + + validate(TestStruct); + const buf = serialize(new TestStruct({ a: [1, 2, 3] })); + expect(new Uint8Array(buf)).toEqual(new Uint8Array([1, 2, 3])); + const deserialized = deserialize(buf, TestStruct); + deserialized.a[0] = 123; + expect(buf[0]).toEqual(123); + }); + test("fixed array wrong length serialize", () => { class TestStruct { @field({ type: fixedArray("u8", 3) }) @@ -540,11 +580,37 @@ describe("number", () => { const n = BigInt(123); const instance = new Struct(n); const buf = serialize(instance); - expect(new Uint8Array(buf)).toEqual( - new Uint8Array([123, ...new Array(31).fill(0)]) - ); + const serializedExpected = new Uint8Array([123, ...new Array(31).fill(0)]); + + expect(new Uint8Array(buf)).toEqual(serializedExpected); const deserialized = deserialize(buf, Struct); expect(deserialized.a).toEqual(n); + + // check that the original array has not been modified + expect(new Uint8Array(buf)).toEqual(serializedExpected); + }); + + test("u256 with Uin8array", () => { + class Struct { + @field({ type: "u256" }) + public a: bigint; + + constructor(a: bigint) { + this.a = a; + } + } + const n = BigInt(123); + const instance = new Struct(n); + const buf = new Uint8Array(serialize(instance)); + + const serializedExpected = new Uint8Array([123, ...new Array(31).fill(0)]); + + expect(new Uint8Array(buf)).toEqual(serializedExpected); + const deserialized = deserialize(buf, Struct); + expect(deserialized.a).toEqual(n); + + // check that the original array has not been modified + expect(new Uint8Array(buf)).toEqual(serializedExpected); }); test("u512 is le", () => { @@ -558,11 +624,33 @@ describe("number", () => { } const instance = new Struct(BigInt(3)); const buf = serialize(instance); - expect(new Uint8Array(buf)).toEqual( - new Uint8Array([3, ...new Array(63).fill(0)]) - ); + const serializedExpected = new Uint8Array([3, ...new Array(63).fill(0)]); + expect(new Uint8Array(buf)).toEqual(serializedExpected); const deserialized = deserialize(buf, Struct); expect(deserialized.a).toEqual(BigInt(3)); + + // check that the original array has not been modified + expect(new Uint8Array(buf)).toEqual(serializedExpected); + }); + + test("u512 with 8int8array", () => { + class Struct { + @field({ type: "u512" }) + public a: bigint; + + constructor(a: bigint) { + this.a = a; + } + } + const instance = new Struct(BigInt(3)); + const buf = new Uint8Array(serialize(instance)); + const serializedExpected = new Uint8Array([3, ...new Array(63).fill(0)]); + expect(new Uint8Array(buf)).toEqual(serializedExpected); + const deserialized = deserialize(buf, Struct); + expect(deserialized.a).toEqual(BigInt(3)); + + // check that the original array has not been modified + expect(new Uint8Array(buf)).toEqual(serializedExpected); }); test("u512 max", () => { diff --git a/src/bigint.ts b/src/bigint.ts index 9f200df3..41a971ef 100644 --- a/src/bigint.ts +++ b/src/bigint.ts @@ -1,12 +1,11 @@ -function arrayToHex(arr: Uint8Array): string { - return [...new Uint8Array(arr)] +function arrayToHex(arr: Uint8Array, reverse: boolean = false): string { + return [...(reverse ? new Uint8Array(arr).reverse() : new Uint8Array(arr))] .map(b => b.toString(16).padStart(2, "0")) .join(""); } export function toBigIntLE(buf: Uint8Array): bigint { - const reversed = buf.reverse(); - const hex = arrayToHex(reversed); + const hex = arrayToHex(buf, true); if (hex.length === 0) { return BigInt(0); } @@ -91,8 +90,7 @@ export const readBigUInt64LE = (buf: Uint8Array, offset: number) => { } export function readUIntLE(buf: Uint8Array, offset: number, width: number): bigint { - const reversed = buf.slice(offset, offset + width).reverse(); - const hex = arrayToHex(reversed); + const hex = arrayToHex(buf.subarray(offset, offset + width), true); if (hex.length === 0) { return BigInt(0); }