Skip to content
Merged
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/thirty-pears-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fleet-sdk/serializer": minor
---

Add `SAvlTree` serialization and deserialization
3 changes: 2 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
"suspicious": {
"recommended": true,
"noConsoleLog": "error",
"noAssignInExpressions": "off"
"noAssignInExpressions": "off",
"noConstEnum": "off"
}
}
}
Expand Down
27 changes: 27 additions & 0 deletions packages/serializer/src/_test-vectors/constantVectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,3 +584,30 @@ export const sBoxVectors = [
hex: "63c0843d0008cd02200a1c1b8fa17ec82de54bcaef96f23d7b34196c0410f6f578abdbf163b14b258abd33010cd8c9f416e5b1ca9f986a7f10a84191dfb85941619e49e53c0dc30ebf83324b0100b66aab1e43874ad8c5583f685a7d6d947238c373f615aee1d04ee604ba2c934000"
}
];

export const avlTreeVectors = [
{
name: "AVL Tree without valueLengthOpt",
hex: "643100d2e101ff01fc047c7f6f00ff80129df69a5090012f01ffca99f5bfff0c803601800100",
data: {
digest: "3100d2e101ff01fc047c7f6f00ff80129df69a5090012f01ffca99f5bfff0c8036",
insertAllowed: true,
keyLength: 128,
removeAllowed: false,
updateAllowed: false,
valueLengthOpt: undefined
}
},
{
name: "AVL Tree with valueLengthOpt",
hex: "643100d2e101ff01fc047c7f6f00ff80129df69a5090012f01ffca99f5bfff0c80360180010115",
data: {
digest: "3100d2e101ff01fc047c7f6f00ff80129df69a5090012f01ffca99f5bfff0c8036",
insertAllowed: true,
keyLength: 128,
removeAllowed: false,
updateAllowed: false,
valueLengthOpt: 21
}
}
];
5 changes: 5 additions & 0 deletions packages/serializer/src/coders/sigmaByteReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ export class SigmaByteReader {
return this.readBytes(this.#bytes.length - this.#cursor);
}

readOption<T>(processor: (r: SigmaByteReader) => T): T | undefined {
if (this.readByte()) return processor(this);
return undefined;
}

/**
* Returns bytes without advancing the cursor.
*/
Expand Down
12 changes: 12 additions & 0 deletions packages/serializer/src/coders/sigmaByteWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@ export class SigmaByteWriter {
return this;
}

writeOption<T>(value: T | undefined, processor: (w: SigmaByteWriter) => void): SigmaByteWriter {
if (value === undefined) {
this.write(0x00); // write 0x00 for None
return this;
}

this.write(0x01); // write 0x01 for Some
processor(this); // call the inner writer to write the Option value

return this;
}

writeChecksum(length = 4, hashFn = blake2b256): SigmaByteWriter {
const hash = hashFn(this.toBytes());
return this.writeBytes(length ? hash.subarray(0, length) : hash);
Expand Down
58 changes: 58 additions & 0 deletions packages/serializer/src/serializers/avlTreeSerializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { hex } from "@fleet-sdk/crypto";
import { type SigmaByteReader, SigmaByteWriter } from "../coders";

export interface AvlTreeFlags {
insertAllowed: boolean;
updateAllowed: boolean;
removeAllowed: boolean;
}

export interface AvlTreeData extends AvlTreeFlags {
digest: string;
keyLength: number;
valueLengthOpt?: number;
}

const DIGEST_SIZE = 33;

const enum AvlTreeFlag {
InsertAllowed = 0x01,
UpdateAllowed = 0x02,
RemoveAllowed = 0x04
}

export function serializeAvlTree(
data: AvlTreeData,
writer: SigmaByteWriter = new SigmaByteWriter(4_096)
): SigmaByteWriter {
return writer // (DIGEST_SIZE + 1 + 4 + 1 + 4 /** flags, key len, opt flag, opt value */)
.writeBytes(hex.decode(data.digest))
.write(serializeFlags(data))
.writeUInt(data.keyLength)
.writeOption(data.valueLengthOpt, (w) => w.writeUInt(data.valueLengthOpt as number));
}

export function deserializeAvlTree(reader: SigmaByteReader): AvlTreeData {
return {
digest: hex.encode(reader.readBytes(DIGEST_SIZE)),
...parseFlags(reader.readByte()),
keyLength: reader.readUInt(),
valueLengthOpt: reader.readOption((r) => r.readUInt())
};
}

function parseFlags(byte: number): AvlTreeFlags {
return {
insertAllowed: (byte & AvlTreeFlag.InsertAllowed) !== 0,
updateAllowed: (byte & AvlTreeFlag.UpdateAllowed) !== 0,
removeAllowed: (byte & AvlTreeFlag.RemoveAllowed) !== 0
};
}

function serializeFlags(flags: AvlTreeFlags): number {
let byte = 0x0;
if (flags.insertAllowed) byte |= AvlTreeFlag.InsertAllowed;
if (flags.updateAllowed) byte |= AvlTreeFlag.UpdateAllowed;
if (flags.removeAllowed) byte |= AvlTreeFlag.RemoveAllowed;
return byte;
}
6 changes: 6 additions & 0 deletions packages/serializer/src/serializers/dataSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { SigmaByteReader, SigmaByteWriter } from "../coders";
import type { SConstant } from "../sigmaConstant";
import { type SCollType, type STupleType, type SType, isColl, isTuple } from "../types";
import { descriptors } from "../types/descriptors";
import { type AvlTreeData, deserializeAvlTree, serializeAvlTree } from "./avlTreeSerializer";
import { deserializeBox, serializeBox } from "./boxSerializer";

const GROUP_ELEMENT_LENGTH = 33;
Expand Down Expand Up @@ -82,6 +83,9 @@ export const dataSerializer = {

if (type.code === descriptors.unit.code) return writer;
if (type.code === descriptors.box.code) return serializeBox(data as Box, writer);
if (type.code === descriptors.avlTree.code) {
return serializeAvlTree(data as AvlTreeData, writer);
}

throw Error(`Serialization error: '0x${type.code.toString(16)}' type not implemented.`);
},
Expand Down Expand Up @@ -139,6 +143,8 @@ export const dataSerializer = {
return undefined;
case descriptors.box.code:
return deserializeBox(reader);
case descriptors.avlTree.code:
return deserializeAvlTree(reader);
}
}

Expand Down
4 changes: 4 additions & 0 deletions packages/serializer/src/serializers/typeSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const typeSerializer = {
writer.write(type.code);
} else if (type.code === descriptors.box.code) {
writer.write(type.code);
} else if (type.code === descriptors.avlTree.code) {
writer.write(type.code);
} else if (isColl(type)) {
if (type.elementsType.embeddable) {
writer.write(descriptors.coll.simpleCollTypeCode + type.elementsType.code);
Expand Down Expand Up @@ -144,6 +146,8 @@ export const typeSerializer = {
return descriptors.unit;
case descriptors.box.code:
return descriptors.box;
case descriptors.avlTree.code:
return descriptors.avlTree;
}

throw new Error("Not implemented.");
Expand Down
41 changes: 38 additions & 3 deletions packages/serializer/src/sigmaConstant.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Value$ } from "sigmastate-js/main";
import { describe, expect, it, test, vitest } from "vitest";
import { SPair } from "../dist";
import {
avlTreeVectors,
bigintVectors,
boolVectors,
byteVectors,
Expand All @@ -31,6 +32,7 @@ import {
MIN_I256
} from "./coders/numRanges";
import { dataSerializer } from "./serializers";
import type { AvlTreeData } from "./serializers/avlTreeSerializer";
import { SConstant, decode, parse, stypeof } from "./sigmaConstant";
import type { SGroupElementType } from "./types";
import {
Expand All @@ -46,7 +48,7 @@ import {
STupleType,
SUnit
} from "./types/";
import { SBox, STuple } from "./types/constructors";
import { SAvlTree, SBox, STuple } from "./types/constructors";

describe("Primitive types serialization and parsing", () => {
it.each(boolVectors)("Should road-trip SBool($value)", (tv) => {
Expand Down Expand Up @@ -135,6 +137,39 @@ describe("Monomorphic types serialization and parsing", () => {
});
});

describe("AVL Tree serialization and parsing", () => {
test.each(avlTreeVectors)("AVL Tree serialization", (tv) => {
const decoded = SConstant.from(tv.hex);
expect(decoded.type.toString()).to.be.equal("SAvlTree");
expect(decoded.data).to.deep.equal(tv.data);
expect(Value$.fromHex(tv.hex).data).to.deep.equal(tv.data); // confirm with sigmastate
});

it.each(avlTreeVectors)("AVL Tree roundtrip", (tv) => {
const avlTree = SConstant.from<AvlTreeData>(tv.hex).data;
expect(SAvlTree(avlTree).toHex()).to.be.equal(tv.hex);
});

it("Should serialize different flags", () => {
const tree = avlTreeVectors[0].data;

let obj = { ...tree, insertAllowed: true };
let hex = SAvlTree(obj).toHex();
expect(SConstant.from(hex).data).to.deep.equal(obj);
expect(Value$.fromHex(hex).data).to.deep.equal(obj); // confirm with sigmastate

obj = { ...tree, insertAllowed: false, updateAllowed: true };
hex = SAvlTree(obj).toHex();
expect(SConstant.from(hex).data).to.deep.equal(obj);
expect(Value$.fromHex(hex).data).to.deep.equal(obj); // confirm with sigmastate

obj = { ...tree, removeAllowed: true };
hex = SAvlTree(obj).toHex();
expect(SConstant.from(hex).data).to.deep.equal(obj);
expect(Value$.fromHex(hex).data).to.deep.equal(obj); // confirm with sigmastate
});
});

describe("SColl serialization and parsing", () => {
it.each(collVectors)("Should serialize $name", (tv) => {
expect(tv.sconst.toHex()).to.be.equal(tv.hex);
Expand Down Expand Up @@ -267,7 +302,7 @@ describe("Data only decoding", () => {
describe("Not implemented types", () => {
it("Should fail while trying to serialize a not implemented type", () => {
const unimplementedType = {
code: 0x64, // AvlTree type code
code: 0x66, // SString type code
embeddable: false,
coerce: (val: unknown) => val
} as unknown as SGroupElementType;
Expand All @@ -284,7 +319,7 @@ describe("Not implemented types", () => {
// not implemented SSigmaProp expression
expect(() => {
dataSerializer.serialize("", unimplementedType, new SigmaByteWriter(1));
}).to.throw("Serialization error: '0x64' type not implemented.");
}).to.throw("Serialization error: '0x66' type not implemented.");
});

it("Should fail when trying to deserialize a not implemented SigmaProp expression", () => {
Expand Down
6 changes: 5 additions & 1 deletion packages/serializer/src/types/constructors.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { type Box, isEmpty } from "@fleet-sdk/common";
import type { AvlTreeData } from "../serializers/avlTreeSerializer";
import { SConstant } from "../sigmaConstant";
import type { SType } from "./base";
import { descriptors } from "./descriptors";
import { SCollType, STupleType } from "./generics";
import { SBoxType, SUnitType } from "./monomorphics";
import { SAvlTreeType, SBoxType, SUnitType } from "./monomorphics";
import {
SBigIntType,
SBoolType,
Expand Down Expand Up @@ -102,6 +103,9 @@ export const SUnit = monoProxy(SUnitType, undefined, true) as unknown as SUnit;
type SBox = (value?: Box) => SConstant<Box<bigint>, SBoxType>;
export const SBox = monoProxy(SBoxType, undefined, true) as unknown as SBox;

type SAvlTree = (value?: AvlTreeData) => SConstant<AvlTreeData, SAvlTreeType>;
export const SAvlTree = monoProxy(SAvlTreeType, undefined, true) as unknown as SAvlTree;

type SColl = {
<D, T extends SByteType>(
type: SConstructor<D, T>,
Expand Down
3 changes: 2 additions & 1 deletion packages/serializer/src/types/descriptors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { SType } from "./base";
import type { SCollType, STupleType } from "./generics";
import { SBoxType, SUnitType } from "./monomorphics";
import { SAvlTreeType, SBoxType, SUnitType } from "./monomorphics";
import {
SBigIntType,
SBoolType,
Expand Down Expand Up @@ -62,6 +62,7 @@ export const descriptors = {
sigmaProp: new SSigmaPropType(),
unit: new SUnitType(),
box: new SBoxType(),
avlTree: new SAvlTreeType(),
coll: collDescriptor,
tuple: tupleDescriptor
} satisfies { [key: string]: Descriptor };
Expand Down
3 changes: 2 additions & 1 deletion packages/serializer/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ export {
SUnit,
SColl,
SPair,
SBox
SBox,
SAvlTree
} from "./constructors";
11 changes: 11 additions & 0 deletions packages/serializer/src/types/monomorphics.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Box } from "@fleet-sdk/common";
import type { AvlTreeData } from "../serializers/avlTreeSerializer";
import type { SConstant } from "../sigmaConstant";
import { SMonomorphicType } from "./base";

Expand All @@ -21,3 +22,13 @@ export class SBoxType extends SMonomorphicType<SConstant<Box<bigint>>> {
return "SBox";
}
}

export class SAvlTreeType extends SMonomorphicType<AvlTreeData> {
get code(): 0x64 {
return 0x64;
}

toString(): string {
return "SAvlTree";
}
}