Skip to content

Commit

Permalink
feat(core): add ZigZag and VLQ encoding for BigInt
Browse files Browse the repository at this point in the history
  • Loading branch information
capt-nemo429 committed Dec 13, 2022
1 parent 255f116 commit 1271bfd
Show file tree
Hide file tree
Showing 11 changed files with 715 additions and 514 deletions.
1 change: 1 addition & 0 deletions packages/common/src/utils/bigIntLiterals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export const _1n = BigInt(1);
export const _7n = BigInt(7);
export const _63n = BigInt(63);
export const _127n = BigInt(127);
export const _128n = BigInt(128);
14 changes: 7 additions & 7 deletions packages/core/src/serialization/sigma/chainObjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import { Amount, Box, NonMandatoryRegisters, TokenAmount } from "@fleet-sdk/comm
import { ensureBigInt, isDefined, isEmpty } from "@fleet-sdk/common";
import { concatBytes, hexToBytes } from "@noble/hashes/utils";
import { ErgoBox } from "../../models/ergoBox";
import { VLQ } from "../vlq";
import { vlqEncode, vqlEncodeBigInt } from "../vlq";

export function serializeErgoBox(box: Box<Amount> | ErgoBox): Uint8Array {
return concatBytes(
VLQ.encode(ensureBigInt(box.value)),
vqlEncodeBigInt(ensureBigInt(box.value)),
hexToBytes(box.ergoTree),
VLQ.encode(box.creationHeight),
vlqEncode(box.creationHeight),
serializeTokens(box.assets),
serializeRegisters(box.additionalRegisters),
hexToBytes(box.transactionId),
VLQ.encode(box.index)
vlqEncode(box.index)
);
}

Expand All @@ -22,9 +22,9 @@ function serializeTokens(tokens: TokenAmount<Amount>[]): Uint8Array {
}

return concatBytes(
VLQ.encode(tokens.length),
vlqEncode(tokens.length),
...tokens.map((asset) =>
concatBytes(hexToBytes(asset.tokenId), VLQ.encode(ensureBigInt(asset.amount)))
concatBytes(hexToBytes(asset.tokenId), vqlEncodeBigInt(ensureBigInt(asset.amount)))
)
);
}
Expand All @@ -43,5 +43,5 @@ function serializeRegisters(registers: NonMandatoryRegisters): Uint8Array {
}
}

return concatBytes(VLQ.encode(serializedRegisters.length), concatBytes(...serializedRegisters));
return concatBytes(vlqEncode(serializedRegisters.length), concatBytes(...serializedRegisters));
}
40 changes: 40 additions & 0 deletions packages/core/src/serialization/sigma/sigmaReader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { HexString, isEmpty } from "@fleet-sdk/common";
import { hexToBytes } from "@noble/hashes/utils";
import { vlqDecode, vlqDecodeBigInt } from "../vlq";
import { zigZagDecode, zigZagDecodeBigInt } from "../zigZag";
import { SigmaTypeCode } from "./sigmaTypeCode";

export class SigmaReader {
private _bytes!: Uint8Array;
private _cursor!: number;

public get isEmpty(): boolean {
return isEmpty(this._bytes);
}

constructor(bytes: HexString | Uint8Array) {
if (typeof bytes === "string") {
this._bytes = hexToBytes(bytes);
} else {
this._bytes = bytes;
}

this._cursor = 0;
}

public readByte(): number {
return this._bytes[this._cursor++];
}

public readType(): SigmaTypeCode {
return this.readByte();
}

public readNumber(): number {
return Number(zigZagDecode(vlqDecode(this)));
}

public readLong(): bigint {
return zigZagDecodeBigInt(vlqDecodeBigInt(this));
}
}
4 changes: 2 additions & 2 deletions packages/core/src/serialization/sigma/sigmaWriter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ describe("Sigma Writer", () => {

const all = new SigmaWriter(MAX_CONSTANT_LENGTH);
for (const tv of testVectors) {
all.writeInt(tv.int);
expect(new SigmaWriter(tv.hex.length).writeInt(tv.int).toHex()).toBe(tv.hex);
all.writeNumber(tv.int);
expect(new SigmaWriter(tv.hex.length).writeNumber(tv.int).toHex()).toBe(tv.hex);
}

expect(all.toHex()).toEqual(testVectors.map((x) => x.hex).join(""));
Expand Down
18 changes: 12 additions & 6 deletions packages/core/src/serialization/sigma/sigmaWriter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { _0n, ensureBigInt } from "@fleet-sdk/common";
import { _0n } from "@fleet-sdk/common";
import { bytesToHex } from "@noble/hashes/utils";
import { VLQ } from "../vlq";
import { ZigZag } from "../zigZag";
import { vlqEncode, vqlEncodeBigInt } from "../vlq";
import { zigZagEncode, zigZagEncodeBigInt } from "../zigZag";

export class SigmaWriter {
private _bytes!: Uint8Array;
Expand Down Expand Up @@ -30,8 +30,14 @@ export class SigmaWriter {
return this;
}

public writeInt(value: number): SigmaWriter {
this.writeBytes(VLQ.encode(ZigZag.encode(ensureBigInt(value))));
public writeNumber(value: number): SigmaWriter {
this.writeBytes(vlqEncode(zigZagEncode(value)));

return this;
}

public writeLong(value: bigint): SigmaWriter {
this.writeBytes(vqlEncodeBigInt(zigZagEncodeBigInt(value)));

return this;
}
Expand Down Expand Up @@ -104,7 +110,7 @@ export class SigmaWriter {
hex = "00" + hex;
}

this.writeBytes(VLQ.encode(hex.length / 2));
this.writeBytes(vlqEncode(hex.length / 2));
this.writeHex(hex);

return this;
Expand Down
19 changes: 12 additions & 7 deletions packages/core/src/serialization/sigma/utils.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { SigmaTypeCode } from "./sigmaTypeCode";
import { IPrimitiveSigmaType, ISigmaCollection, ISigmaType } from "./sigmaTypes";

export function isEmbeddable(typeCode: number): boolean {
return typeCode >= 0x01 && typeCode <= 0x0b;
}

export function isPrimitiveType<T>(data: ISigmaType): data is IPrimitiveSigmaType<T> {
return !isConstructorType(data);
return !isConstructorTypeCode(data.type);
}

export function isColl<T>(data: ISigmaType): data is ISigmaCollection<T> {
return data.type >= 0x0c && data.type <= 0x23;
}

export function isConstructorType(data: ISigmaType): boolean {
return data.type >= 0x0c && data.type <= 0x60;
export function isEmbeddableTypeCode(typeCode: number): boolean {
return typeCode >= 0x01 && typeCode <= 0x0b;
}

export function isPrimitiveTypeCode(typeCode: SigmaTypeCode): boolean {
return !isConstructorTypeCode(typeCode);
}

export function isConstructorTypeCode(type: SigmaTypeCode): boolean {
return type >= 0x0c && type <= 0x60;
}
65 changes: 56 additions & 9 deletions packages/core/src/serialization/vlq.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,54 @@
import { VLQ } from "./vlq";
import { SigmaReader } from "./sigma/sigmaReader";
import { vlqDecode, vlqDecodeBigInt, vlqEncode, vqlEncodeBigInt } from "./vlq";

describe("[int] VLQ serialization - Variable-length quantity", () => {
describe("32-bit VLQ encoding/decoding", () => {
const testVectors = [
{ uint: 0, bytes: Uint8Array.from([0x00]) },
{ uint: 126, bytes: Uint8Array.from([0x7e]) },
{ uint: 127, bytes: Uint8Array.from([0x7f]) },
{ uint: 128, bytes: Uint8Array.from([0x80, 0x01]) },
{ uint: 129, bytes: Uint8Array.from([0x81, 0x01]) },
{ uint: 16383, bytes: Uint8Array.from([0xff, 0x7f]) },
{ uint: 16384, bytes: Uint8Array.from([0x80, 0x80, 0x01]) },
{ uint: 16385, bytes: Uint8Array.from([0x81, 0x80, 0x01]) },
{ uint: 2097151, bytes: Uint8Array.from([0xff, 0xff, 0x7f]) },
{ uint: 2097152, bytes: Uint8Array.from([0x80, 0x80, 0x80, 0x01]) },
{ uint: 268435455, bytes: Uint8Array.from([0xff, 0xff, 0xff, 0x7f]) },
{ uint: 268435456, bytes: Uint8Array.from([0x80, 0x80, 0x80, 0x80, 0x01]) }
];

it("Should encode", () => {
testVectors.forEach((tv) => {
expect(vlqEncode(tv.uint)).toEqual(tv.bytes);
});
});

it("Should fail trying to encode negative values", () => {
expect(() => {
vlqEncode(-1);
}).toThrow();
});

it("Should decode", () => {
testVectors.forEach((tv) => {
expect(vlqDecode(new SigmaReader(tv.bytes))).toEqual(tv.uint);
});
});

it("Should decode empty Buffer to 0", () => {
expect(vlqDecode(new SigmaReader(Uint8Array.from([])))).toEqual(0);
});

it("Should encode/decode radom numbers", () => {
Array.from(Array(100))
.map(() => Math.ceil(Math.random() * 100000))
.forEach((n) => {
expect(vlqDecode(new SigmaReader(vlqEncode(n)))).toBe(n);
});
});
});

describe("64-bit VLQ encoding/decoding", () => {
const testVectors = [
{ uint: 0n, bytes: Uint8Array.from([0x00]) },
{ uint: 126n, bytes: Uint8Array.from([0x7e]) },
Expand All @@ -18,32 +66,31 @@ describe("[int] VLQ serialization - Variable-length quantity", () => {

it("Should encode", () => {
testVectors.forEach((tv) => {
expect(VLQ.encode(tv.uint)).toEqual(tv.bytes);
expect(VLQ.encode(Number(tv.uint))).toEqual(tv.bytes);
expect(vqlEncodeBigInt(tv.uint)).toEqual(tv.bytes);
});
});

it("Should fail trying to encode negative values", () => {
expect(() => {
VLQ.encode(-1);
vqlEncodeBigInt(-1n);
}).toThrow();
});

it("Should decode", () => {
testVectors.forEach((tv) => {
expect(VLQ.decode(tv.bytes)).toEqual(tv.uint);
expect(vlqDecodeBigInt(new SigmaReader(tv.bytes))).toEqual(tv.uint);
});
});

it("Should decode empty Buffer to 0", () => {
expect(VLQ.decode(Uint8Array.from([]))).toEqual(0n);
expect(vlqDecodeBigInt(new SigmaReader(Uint8Array.from([])))).toEqual(0n);
});

it("Should encode/decode radom numbers", () => {
Array.from(Array(100))
.map(() => Math.ceil(Math.random() * 100000))
.map(() => BigInt(Math.ceil(Math.random() * 100000000000)) * BigInt(Number.MAX_SAFE_INTEGER))
.forEach((n) => {
expect(VLQ.decode(VLQ.encode(n))).toBe(BigInt(n));
expect(vlqDecodeBigInt(new SigmaReader(vqlEncodeBigInt(n)))).toBe(n);
});
});
});
Loading

0 comments on commit 1271bfd

Please sign in to comment.