Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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";
10 changes: 10 additions & 0 deletions packages/serializer/src/types/monomorphics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,13 @@ export class SBoxType extends SMonomorphicType<SConstant<Box<bigint>>> {
return "SBox";
}
}

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

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