Skip to content

Commit bf0d991

Browse files
complete rewrite with more accurate typings
1 parent 045da61 commit bf0d991

9 files changed

Lines changed: 2181 additions & 352 deletions

File tree

package-lock.json

Lines changed: 1235 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,27 @@
22
"name": "scionic-merkletree",
33
"version": "1.0.0",
44
"description": "",
5-
"main": "lib/index.js",
6-
"types": "lib/index.d.ts",
5+
"main": "dist/index.js",
6+
"types": "dist/index.d.ts",
77
"scripts": {
88
"build": "tsc",
99
"test": "echo \"Error: no test specified\" && exit 1"
1010
},
1111
"author": "",
1212
"license": "",
1313
"devDependencies": {
14+
"@types/lodash": "^4.14.199",
1415
"@types/node": "^20.5.7",
1516
"typescript": "^5.1.6"
1617
},
1718
"dependencies": {
1819
"cbor": "^9.0.1",
1920
"fast-sha256": "^1.3.0",
21+
"gool": "^1.0.11",
22+
"js-sha256": "^0.10.1",
23+
"lodash": "^4.17.21",
2024
"merkle-tools": "^1.4.1",
21-
"multibase": "^4.0.6"
25+
"multibase": "^4.0.6",
26+
"multiformats": "^12.1.2"
2227
}
2328
}

src/index.ts

Lines changed: 4 additions & 321 deletions
Original file line numberDiff line numberDiff line change
@@ -1,321 +1,4 @@
1-
import * as cbor from 'cbor';
2-
import * as crypto from 'crypto';
3-
import * as multibase from 'multibase';
4-
import { BaseNameOrCode } from 'multibase';
5-
6-
export let ChunkSize = 2048 * 1024; // 2 megabytes
7-
8-
export enum LeafType {
9-
FileLeaf = "file",
10-
ChunkLeaf = "chunk",
11-
DirectoryLeaf = "directory"
12-
}
13-
14-
export interface DagBuilder {
15-
Leafs: Record<string, DagLeaf>;
16-
}
17-
18-
export interface DagLeafBuilder {
19-
Name: string;
20-
Label: number;
21-
LeafType: LeafType;
22-
Data: Uint8Array;
23-
Links: Record<string, string>;
24-
}
25-
26-
export interface Dag {
27-
Root: string;
28-
Leafs: Record<string, DagLeaf>;
29-
30-
getDataFromLeaf(leaf: DagLeaf): [Uint8Array, any];
31-
verify(encoder: BaseNameOrCode): Promise<[boolean, any]>;
32-
}
33-
34-
export interface DagLeaf {
35-
Hash: string;
36-
Name: string;
37-
Type: LeafType;
38-
Data: Uint8Array;
39-
MerkleRoot: Uint8Array;
40-
CurrentLinkCount: number;
41-
LatestLabel: string;
42-
LeafCount: number;
43-
Links: Record<string, string>;
44-
ParentHash: string;
45-
46-
hasLink(hash: string): boolean;
47-
addLink(hash: string): void;
48-
clone(): DagLeaf;
49-
setLabel(label: string): void;
50-
verifyLeaf(encoder: BaseNameOrCode): Promise<[boolean, any]>
51-
verifyRootLeaf(encoder: BaseNameOrCode): Promise<[boolean, any]>
52-
}
53-
54-
export function setChunkSize(size: number) {
55-
ChunkSize = size;
56-
}
57-
58-
export function CreateDagLeafBuilder(name: string): DagLeafBuilder {
59-
return new DagLeafBuilder(name);
60-
}
61-
62-
export function hasLabel(hash: string): boolean {
63-
return getLabel(hash) !== "";
64-
}
65-
66-
export function getHash(hash: string): string {
67-
const parts = hash.split(":");
68-
if (parts.length !== 2) return hash;
69-
return parts[1];
70-
}
71-
72-
export function getLabel(hash: string): string {
73-
const parts = hash.split(":");
74-
if (parts.length !== 2) return "";
75-
return parts[0];
76-
}
77-
78-
export class DagLeafBuilder {
79-
Name: string;
80-
Label!: number;
81-
LeafType!: LeafType;
82-
Data!: Uint8Array;
83-
Links: Record<string, string> = {};
84-
85-
constructor(name: string) {
86-
this.Name = name;
87-
}
88-
89-
setType(leafType: LeafType): void {
90-
this.LeafType = leafType;
91-
}
92-
93-
setData(data: Uint8Array): void {
94-
this.Data = data;
95-
}
96-
97-
addLink(label: string, hash: string): void {
98-
this.Links[label] = `${label}:${hash}`;
99-
}
100-
}
101-
102-
export class DagLeaf implements DagLeaf {
103-
Hash!: string;
104-
Name!: string;
105-
Type!: LeafType;
106-
Data!: Uint8Array;
107-
MerkleRoot!: Uint8Array;
108-
CurrentLinkCount!: number;
109-
LatestLabel!: string;
110-
LeafCount!: number;
111-
Links!: Record<string, string>;
112-
ParentHash!: string;
113-
114-
constructor(leaf: DagLeaf) {
115-
Object.assign(this, leaf);
116-
}
117-
118-
hasLink(hash: string): boolean {
119-
for (const label of Object.keys(this.Links)) {
120-
const link = this.Links[label]
121-
122-
if (hasLabel(hash)) {
123-
if (hasLabel(link)) {
124-
if (link === hash) return true;
125-
} else {
126-
if (link === getHash(hash)) return true;
127-
}
128-
} else {
129-
if (hasLabel(link)) {
130-
if (getHash(link) === hash) return true;
131-
} else {
132-
if (getHash(link) === getHash(hash)) return true;
133-
}
134-
}
135-
}
136-
137-
return false;
138-
}
139-
140-
addLink(hash: string): void {
141-
const label = getLabel(hash);
142-
143-
if (label === "") {
144-
console.log("This hash does not have a label");
145-
}
146-
147-
this.Links[label] = hash;
148-
}
149-
150-
clone(): DagLeaf {
151-
return new DagLeaf(this);
152-
}
153-
154-
setLabel(label: string): void {
155-
this.Hash = `${label}:${this.Hash}`;
156-
}
157-
158-
async verifyLeaf(encoder: BaseNameOrCode): Promise<[boolean, any]> {
159-
const leafData = {
160-
Name: this.Name,
161-
Type: this.Type,
162-
MerkleRoot: this.MerkleRoot,
163-
CurrentLinkCount: this.CurrentLinkCount,
164-
Data: this.Data,
165-
};
166-
167-
try {
168-
const serializedLeafData = cbor.encode(leafData);
169-
const hash = crypto.createHash('sha256').update(serializedLeafData).digest();
170-
171-
let result = false;
172-
if (hasLabel(this.Hash)) {
173-
result = multibase.encode(encoder, hash).toString() === getHash(this.Hash);
174-
} else {
175-
result = multibase.encode(encoder, hash).toString() === this.Hash;
176-
}
177-
178-
return [result, null];
179-
} catch (err) {
180-
return [false, err];
181-
}
182-
}
183-
184-
async verifyRootLeaf(encoder: BaseNameOrCode): Promise<[boolean, any]> {
185-
const leafData = {
186-
Name: this.Name,
187-
Type: this.Type,
188-
MerkleRoot: this.MerkleRoot,
189-
CurrentLinkCount: this.CurrentLinkCount,
190-
LatestLabel: this.LatestLabel,
191-
LeafCount: this.LeafCount,
192-
Data: this.Data,
193-
};
194-
195-
try {
196-
const serializedLeafData = cbor.encode(leafData);
197-
const hash = crypto.createHash('sha256').update(serializedLeafData).digest();
198-
199-
let result = false;
200-
if (hasLabel(this.Hash)) {
201-
result = multibase.encode(encoder, hash).toString() === getHash(this.Hash);
202-
} else {
203-
result = multibase.encode(encoder, hash).toString() === this.Hash;
204-
}
205-
206-
return [result, null];
207-
} catch (err) {
208-
return [false, err];
209-
}
210-
}
211-
}
212-
213-
export class DagBuilder {
214-
Leafs: Record<string, DagLeaf> = {};
215-
216-
static CreateDagBuilder(): DagBuilder {
217-
return new DagBuilder();
218-
}
219-
220-
getLatestLabel(): string {
221-
let latestLabel: number = 1;
222-
223-
for (const hash in this.Leafs) {
224-
const label = getLabel(hash);
225-
226-
if (label === "") {
227-
console.log("Failed to find label in hash");
228-
}
229-
230-
const parsed = parseInt(label, 10);
231-
232-
if (parsed > latestLabel) {
233-
latestLabel = parsed;
234-
}
235-
}
236-
237-
return latestLabel.toString();
238-
}
239-
240-
getNextAvailableLabel(): string {
241-
const latestLabel = this.getLatestLabel();
242-
const number = parseInt(latestLabel, 10);
243-
244-
const nextLabel = (number + 1).toString();
245-
246-
return nextLabel;
247-
}
248-
249-
addLeaf(leaf: DagLeaf, parentLeaf: DagLeaf | null): void {
250-
if (parentLeaf !== null) {
251-
const label = getLabel(leaf.Hash);
252-
if (!(label in parentLeaf.Links)) {
253-
parentLeaf.addLink(leaf.Hash);
254-
}
255-
}
256-
257-
this.Leafs[leaf.Hash] = leaf;
258-
}
259-
260-
buildDag(root: string): Dag {
261-
return new Dag(this.Leafs, root);
262-
}
263-
}
264-
265-
export class Dag implements Dag {
266-
Leafs: Record<string, DagLeaf> = {};
267-
Root: string;
268-
269-
constructor(leafs: Record<string, DagLeaf>, root: string) {
270-
this.Leafs = leafs;
271-
this.Root = root;
272-
}
273-
274-
async verify(encoder: any): Promise<[boolean, any]> {
275-
let result = true;
276-
277-
for (const leafHash in this.Leafs) {
278-
const leaf = this.Leafs[leafHash];
279-
let leafResult: boolean, err: Error | null;
280-
if (leaf.Hash === this.Root) {
281-
[leafResult, err] = await leaf.verifyRootLeaf(encoder); // Assuming you have a method verifyRootLeaf in DagLeaf class
282-
} else {
283-
[leafResult, err] = await leaf.verifyLeaf(encoder); // Assuming you have a method verifyLeaf in DagLeaf class
284-
}
285-
286-
if (err) {
287-
return [false, err];
288-
}
289-
290-
if (!leafResult) {
291-
result = false;
292-
}
293-
}
294-
295-
return [result, null];
296-
}
297-
298-
getDataFromLeaf(leaf: DagLeaf): [Uint8Array, any] {
299-
if (leaf.Data.length <= 0) {
300-
return [new Uint8Array(0), null];
301-
}
302-
303-
let content = new Uint8Array(0);
304-
305-
if (Object.keys(leaf.Links).length > 0) {
306-
for (const label of Object.keys(leaf.Links)) {
307-
const linkHash = leaf.Links[label]
308-
const childLeaf = this.Leafs[linkHash];
309-
if (!childLeaf) {
310-
return [new Uint8Array(0), new Error(`Invalid link: ${linkHash}`)];
311-
}
312-
313-
content = new Uint8Array([...content, ...childLeaf.Data]); // Assuming Data is Uint8Array or similar
314-
}
315-
} else {
316-
content = leaf.Data;
317-
}
318-
319-
return [content, null];
320-
}
321-
}
1+
export * from './leaves';
2+
export * from './types';
3+
export * from './serialize';
4+
export * from './tree/tree';

0 commit comments

Comments
 (0)