@tootallnate/ivfc 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+
2
+ > @tootallnate/ivfc@0.0.0 build /Users/nrajlich/Code/TooTallNate/switch-tools/packages/ivfc
3
+ > tsc
4
+
@@ -0,0 +1,41 @@
1
+ /**
2
+ * IVFC (Integrity Verification File Collection) hash tree builder.
3
+ *
4
+ * Used in Nintendo Switch NCA RomFS sections. Builds a 6-level SHA-256
5
+ * hash tree where each level is the hash table of the level below it.
6
+ *
7
+ * Level 6 = actual data (RomFS)
8
+ * Level 5 = SHA-256 hashes of level 6 blocks
9
+ * Level 4 = SHA-256 hashes of level 5 blocks
10
+ * ...
11
+ * Level 1 = SHA-256 hashes of level 2 blocks
12
+ * Master hash = SHA-256 of level 1
13
+ *
14
+ * Block size is 0x4000 (16KB) for all levels.
15
+ *
16
+ * Reference: hacbrewpack/ivfc.c, hacbrewpack/ivfc.h
17
+ */
18
+ /** IVFC hash block size: 2^14 = 0x4000 = 16384 bytes */
19
+ export declare const IVFC_HASH_BLOCK_SIZE = 16384;
20
+ /** IVFC header size: 0xE0 bytes */
21
+ export declare const IVFC_HEADER_SIZE = 224;
22
+ /**
23
+ * Result of building an IVFC hash tree.
24
+ */
25
+ export interface IvfcResult {
26
+ /** The IVFC header (0xE0 bytes) including the master hash */
27
+ header: ArrayBuffer;
28
+ /** Level data arrays, from level 1 (top) to level 5 (bottom).
29
+ * Level 6 is the original data and is not included here. */
30
+ levels: Uint8Array[];
31
+ /** Total size of all level data (levels 1-5) */
32
+ totalLevelSize: number;
33
+ }
34
+ /**
35
+ * Build an IVFC hash tree from source data (typically a RomFS image).
36
+ *
37
+ * @param data - The source data (level 6). Must already be padded to IVFC_HASH_BLOCK_SIZE.
38
+ * @param crypto - Optional Crypto implementation
39
+ * @returns IVFC header and all intermediate level data
40
+ */
41
+ export declare function build(data: Uint8Array, crypto?: Crypto): Promise<IvfcResult>;
package/dist/index.js ADDED
@@ -0,0 +1,138 @@
1
+ /**
2
+ * IVFC (Integrity Verification File Collection) hash tree builder.
3
+ *
4
+ * Used in Nintendo Switch NCA RomFS sections. Builds a 6-level SHA-256
5
+ * hash tree where each level is the hash table of the level below it.
6
+ *
7
+ * Level 6 = actual data (RomFS)
8
+ * Level 5 = SHA-256 hashes of level 6 blocks
9
+ * Level 4 = SHA-256 hashes of level 5 blocks
10
+ * ...
11
+ * Level 1 = SHA-256 hashes of level 2 blocks
12
+ * Master hash = SHA-256 of level 1
13
+ *
14
+ * Block size is 0x4000 (16KB) for all levels.
15
+ *
16
+ * Reference: hacbrewpack/ivfc.c, hacbrewpack/ivfc.h
17
+ */
18
+ /** IVFC hash block size: 2^14 = 0x4000 = 16384 bytes */
19
+ export const IVFC_HASH_BLOCK_SIZE = 0x4000;
20
+ /** Number of IVFC levels (excluding the data level) */
21
+ const IVFC_NUM_LEVELS = 6;
22
+ /** IVFC magic: "IVFC" */
23
+ const IVFC_MAGIC = 0x43465649;
24
+ /** IVFC version identifier */
25
+ const IVFC_ID = 0x20000;
26
+ /** SHA-256 hash size */
27
+ const HASH_SIZE = 0x20;
28
+ /** IVFC header size: 0xE0 bytes */
29
+ export const IVFC_HEADER_SIZE = 0xe0;
30
+ /**
31
+ * Align a value up to the given alignment.
32
+ */
33
+ function align(value, alignment) {
34
+ const mask = alignment - 1;
35
+ return (value + mask) & ~mask;
36
+ }
37
+ /**
38
+ * SHA-256 hash a Uint8Array using Web Crypto.
39
+ */
40
+ async function sha256(data, crypto = globalThis.crypto) {
41
+ const hash = await crypto.subtle.digest('SHA-256', data);
42
+ return new Uint8Array(hash);
43
+ }
44
+ /**
45
+ * Create an IVFC level: hash each block of the source data with SHA-256.
46
+ * Returns the hash data padded to the IVFC block size boundary.
47
+ */
48
+ async function createLevel(sourceData, crypto = globalThis.crypto) {
49
+ const blockSize = IVFC_HASH_BLOCK_SIZE;
50
+ const sourceSize = sourceData.length;
51
+ // Calculate number of blocks and hash table size
52
+ const numBlocks = Math.ceil(sourceSize / blockSize);
53
+ const hashDataSize = numBlocks * HASH_SIZE;
54
+ // Pad to block size boundary
55
+ const paddedSize = align(hashDataSize, blockSize);
56
+ const hashes = new Uint8Array(paddedSize);
57
+ // Hash each block
58
+ const block = new Uint8Array(blockSize);
59
+ for (let i = 0; i < numBlocks; i++) {
60
+ const offset = i * blockSize;
61
+ const remaining = sourceSize - offset;
62
+ const readSize = Math.min(remaining, blockSize);
63
+ // Zero-fill the block buffer (important for the last partial block)
64
+ block.fill(0);
65
+ block.set(sourceData.subarray(offset, offset + readSize));
66
+ const hash = await sha256(block.subarray(0, readSize), crypto);
67
+ hashes.set(hash, i * HASH_SIZE);
68
+ }
69
+ return { hashes, hashDataSize };
70
+ }
71
+ /**
72
+ * Build an IVFC hash tree from source data (typically a RomFS image).
73
+ *
74
+ * @param data - The source data (level 6). Must already be padded to IVFC_HASH_BLOCK_SIZE.
75
+ * @param crypto - Optional Crypto implementation
76
+ * @returns IVFC header and all intermediate level data
77
+ */
78
+ export async function build(data, crypto = globalThis.crypto) {
79
+ // Build levels from bottom (6) to top (1)
80
+ // Level 6 = data, Level 5 = hashes of level 6, ..., Level 1 = hashes of level 2
81
+ const levelData = [];
82
+ const levelHashDataSizes = [];
83
+ const levelPaddedSizes = [];
84
+ let currentSource = data;
85
+ for (let level = 0; level < IVFC_NUM_LEVELS - 1; level++) {
86
+ const { hashes, hashDataSize } = await createLevel(currentSource, crypto);
87
+ levelData.unshift(hashes); // Prepend (we're building bottom-up but storing top-down)
88
+ levelHashDataSizes.unshift(hashDataSize);
89
+ levelPaddedSizes.unshift(hashes.length);
90
+ currentSource = hashes;
91
+ }
92
+ // Calculate master hash = SHA-256 of level 1 (the top level, which is levelData[0])
93
+ const masterHash = await sha256(levelData[0], crypto);
94
+ // Build the IVFC header (0xE0 bytes)
95
+ const header = new ArrayBuffer(IVFC_HEADER_SIZE);
96
+ const view = new DataView(header);
97
+ // IVFC header fields
98
+ view.setUint32(0x00, IVFC_MAGIC, true); // magic = "IVFC"
99
+ view.setUint32(0x04, IVFC_ID, true); // id = 0x20000
100
+ view.setUint32(0x08, HASH_SIZE, true); // master_hash_size = 0x20
101
+ view.setUint32(0x0c, IVFC_NUM_LEVELS + 1, true); // num_levels = 7 (6 hash levels + 1 data level)
102
+ // Level headers (6 entries, each 0x18 bytes, starting at offset 0x10)
103
+ // These describe levels 1-6 (level 6 = data itself)
104
+ // The logical_offset for each level is the cumulative offset of all previous levels
105
+ let logicalOffset = 0;
106
+ for (let i = 0; i < IVFC_NUM_LEVELS - 1; i++) {
107
+ const entryOffset = 0x10 + i * 0x18;
108
+ const dataSize = levelPaddedSizes[i];
109
+ // logical_offset (8 bytes)
110
+ view.setBigUint64(entryOffset + 0x00, BigInt(logicalOffset), true);
111
+ // hash_data_size (8 bytes)
112
+ view.setBigUint64(entryOffset + 0x08, BigInt(dataSize), true);
113
+ // block_size (4 bytes) — log2 of the block size
114
+ view.setUint32(entryOffset + 0x10, 0x0e, true); // 2^14 = 0x4000
115
+ // reserved (4 bytes) — already zero
116
+ logicalOffset += dataSize;
117
+ }
118
+ // Level 6 entry (the actual data)
119
+ {
120
+ const entryOffset = 0x10 + (IVFC_NUM_LEVELS - 1) * 0x18;
121
+ view.setBigUint64(entryOffset + 0x00, BigInt(logicalOffset), true);
122
+ view.setBigUint64(entryOffset + 0x08, BigInt(data.length), true);
123
+ view.setUint32(entryOffset + 0x10, 0x0e, true);
124
+ }
125
+ // Master hash at offset 0xC0 (0x20 bytes)
126
+ new Uint8Array(header, 0xc0, HASH_SIZE).set(masterHash);
127
+ // Calculate total level size
128
+ let totalLevelSize = 0;
129
+ for (const level of levelData) {
130
+ totalLevelSize += level.length;
131
+ }
132
+ return {
133
+ header,
134
+ levels: levelData,
135
+ totalLevelSize,
136
+ };
137
+ }
138
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,wDAAwD;AACxD,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAE3C,uDAAuD;AACvD,MAAM,eAAe,GAAG,CAAC,CAAC;AAE1B,yBAAyB;AACzB,MAAM,UAAU,GAAG,UAAU,CAAC;AAE9B,8BAA8B;AAC9B,MAAM,OAAO,GAAG,OAAO,CAAC;AAExB,wBAAwB;AACxB,MAAM,SAAS,GAAG,IAAI,CAAC;AAEvB,mCAAmC;AACnC,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAErC;;GAEG;AACH,SAAS,KAAK,CAAC,KAAa,EAAE,SAAiB;IAC9C,MAAM,IAAI,GAAG,SAAS,GAAG,CAAC,CAAC;IAC3B,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;AAC/B,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,MAAM,CACpB,IAAgB,EAChB,SAAiB,UAAU,CAAC,MAAM;IAElC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACzD,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,WAAW,CACzB,UAAsB,EACtB,SAAiB,UAAU,CAAC,MAAM;IAElC,MAAM,SAAS,GAAG,oBAAoB,CAAC;IACvC,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC;IAErC,iDAAiD;IACjD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC;IACpD,MAAM,YAAY,GAAG,SAAS,GAAG,SAAS,CAAC;IAE3C,6BAA6B;IAC7B,MAAM,UAAU,GAAG,KAAK,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC;IAE1C,kBAAkB;IAClB,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,CAAC;IACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;QAC7B,MAAM,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAEhD,oEAAoE;QACpE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACd,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC;QAE1D,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC;QAC/D,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;AACjC,CAAC;AAiBD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAC1B,IAAgB,EAChB,SAAiB,UAAU,CAAC,MAAM;IAElC,0CAA0C;IAC1C,gFAAgF;IAChF,MAAM,SAAS,GAAiB,EAAE,CAAC;IACnC,MAAM,kBAAkB,GAAa,EAAE,CAAC;IACxC,MAAM,gBAAgB,GAAa,EAAE,CAAC;IAEtC,IAAI,aAAa,GAAG,IAAI,CAAC;IAEzB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,eAAe,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;QAC1D,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,WAAW,CACjD,aAAa,EACb,MAAM,CACN,CAAC;QACF,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,0DAA0D;QACrF,kBAAkB,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACzC,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxC,aAAa,GAAG,MAAM,CAAC;IACxB,CAAC;IAED,oFAAoF;IACpF,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAEtD,qCAAqC;IACrC,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,gBAAgB,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;IAElC,qBAAqB;IACrB,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,iBAAiB;IACzD,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,eAAe;IACpD,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,0BAA0B;IACjE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,eAAe,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,gDAAgD;IAEjG,sEAAsE;IACtE,oDAAoD;IACpD,oFAAoF;IACpF,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,eAAe,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9C,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC;QACpC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC;QAErC,2BAA2B;QAC3B,IAAI,CAAC,YAAY,CAAC,WAAW,GAAG,IAAI,EAAE,MAAM,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC,CAAC;QACnE,2BAA2B;QAC3B,IAAI,CAAC,YAAY,CAAC,WAAW,GAAG,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;QAC9D,gDAAgD;QAChD,IAAI,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,gBAAgB;QAChE,oCAAoC;QAEpC,aAAa,IAAI,QAAQ,CAAC;IAC3B,CAAC;IAED,kCAAkC;IAClC,CAAC;QACA,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,eAAe,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC;QACxD,IAAI,CAAC,YAAY,CAAC,WAAW,GAAG,IAAI,EAAE,MAAM,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC,CAAC;QACnE,IAAI,CAAC,YAAY,CAAC,WAAW,GAAG,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;QACjE,IAAI,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAChD,CAAC;IAED,0CAA0C;IAC1C,IAAI,UAAU,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAExD,6BAA6B;IAC7B,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;QAC/B,cAAc,IAAI,KAAK,CAAC,MAAM,CAAC;IAChC,CAAC;IAED,OAAO;QACN,MAAM;QACN,MAAM,EAAE,SAAS;QACjB,cAAc;KACd,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@tootallnate/ivfc",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "description": "IVFC (Integrity Verification File Collection) hash tree builder for Nintendo Switch",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "test": "vitest run"
10
+ },
11
+ "keywords": [],
12
+ "author": "Nathan Rajlich <n@n8.io>",
13
+ "license": "MIT",
14
+ "devDependencies": {
15
+ "typescript": "^5.3.3"
16
+ }
17
+ }
package/src/index.ts ADDED
@@ -0,0 +1,192 @@
1
+ /**
2
+ * IVFC (Integrity Verification File Collection) hash tree builder.
3
+ *
4
+ * Used in Nintendo Switch NCA RomFS sections. Builds a 6-level SHA-256
5
+ * hash tree where each level is the hash table of the level below it.
6
+ *
7
+ * Level 6 = actual data (RomFS)
8
+ * Level 5 = SHA-256 hashes of level 6 blocks
9
+ * Level 4 = SHA-256 hashes of level 5 blocks
10
+ * ...
11
+ * Level 1 = SHA-256 hashes of level 2 blocks
12
+ * Master hash = SHA-256 of level 1
13
+ *
14
+ * Block size is 0x4000 (16KB) for all levels.
15
+ *
16
+ * Reference: hacbrewpack/ivfc.c, hacbrewpack/ivfc.h
17
+ */
18
+
19
+ /** IVFC hash block size: 2^14 = 0x4000 = 16384 bytes */
20
+ export const IVFC_HASH_BLOCK_SIZE = 0x4000;
21
+
22
+ /** Number of IVFC levels (excluding the data level) */
23
+ const IVFC_NUM_LEVELS = 6;
24
+
25
+ /** IVFC magic: "IVFC" */
26
+ const IVFC_MAGIC = 0x43465649;
27
+
28
+ /** IVFC version identifier */
29
+ const IVFC_ID = 0x20000;
30
+
31
+ /** SHA-256 hash size */
32
+ const HASH_SIZE = 0x20;
33
+
34
+ /** IVFC header size: 0xE0 bytes */
35
+ export const IVFC_HEADER_SIZE = 0xe0;
36
+
37
+ /**
38
+ * Align a value up to the given alignment.
39
+ */
40
+ function align(value: number, alignment: number): number {
41
+ const mask = alignment - 1;
42
+ return (value + mask) & ~mask;
43
+ }
44
+
45
+ /**
46
+ * SHA-256 hash a Uint8Array using Web Crypto.
47
+ */
48
+ async function sha256(
49
+ data: Uint8Array,
50
+ crypto: Crypto = globalThis.crypto
51
+ ): Promise<Uint8Array> {
52
+ const hash = await crypto.subtle.digest('SHA-256', data);
53
+ return new Uint8Array(hash);
54
+ }
55
+
56
+ /**
57
+ * Create an IVFC level: hash each block of the source data with SHA-256.
58
+ * Returns the hash data padded to the IVFC block size boundary.
59
+ */
60
+ async function createLevel(
61
+ sourceData: Uint8Array,
62
+ crypto: Crypto = globalThis.crypto
63
+ ): Promise<{ hashes: Uint8Array; hashDataSize: number }> {
64
+ const blockSize = IVFC_HASH_BLOCK_SIZE;
65
+ const sourceSize = sourceData.length;
66
+
67
+ // Calculate number of blocks and hash table size
68
+ const numBlocks = Math.ceil(sourceSize / blockSize);
69
+ const hashDataSize = numBlocks * HASH_SIZE;
70
+
71
+ // Pad to block size boundary
72
+ const paddedSize = align(hashDataSize, blockSize);
73
+ const hashes = new Uint8Array(paddedSize);
74
+
75
+ // Hash each block
76
+ const block = new Uint8Array(blockSize);
77
+ for (let i = 0; i < numBlocks; i++) {
78
+ const offset = i * blockSize;
79
+ const remaining = sourceSize - offset;
80
+ const readSize = Math.min(remaining, blockSize);
81
+
82
+ // Zero-fill the block buffer (important for the last partial block)
83
+ block.fill(0);
84
+ block.set(sourceData.subarray(offset, offset + readSize));
85
+
86
+ const hash = await sha256(block.subarray(0, readSize), crypto);
87
+ hashes.set(hash, i * HASH_SIZE);
88
+ }
89
+
90
+ return { hashes, hashDataSize };
91
+ }
92
+
93
+ /**
94
+ * Result of building an IVFC hash tree.
95
+ */
96
+ export interface IvfcResult {
97
+ /** The IVFC header (0xE0 bytes) including the master hash */
98
+ header: ArrayBuffer;
99
+
100
+ /** Level data arrays, from level 1 (top) to level 5 (bottom).
101
+ * Level 6 is the original data and is not included here. */
102
+ levels: Uint8Array[];
103
+
104
+ /** Total size of all level data (levels 1-5) */
105
+ totalLevelSize: number;
106
+ }
107
+
108
+ /**
109
+ * Build an IVFC hash tree from source data (typically a RomFS image).
110
+ *
111
+ * @param data - The source data (level 6). Must already be padded to IVFC_HASH_BLOCK_SIZE.
112
+ * @param crypto - Optional Crypto implementation
113
+ * @returns IVFC header and all intermediate level data
114
+ */
115
+ export async function build(
116
+ data: Uint8Array,
117
+ crypto: Crypto = globalThis.crypto
118
+ ): Promise<IvfcResult> {
119
+ // Build levels from bottom (6) to top (1)
120
+ // Level 6 = data, Level 5 = hashes of level 6, ..., Level 1 = hashes of level 2
121
+ const levelData: Uint8Array[] = [];
122
+ const levelHashDataSizes: number[] = [];
123
+ const levelPaddedSizes: number[] = [];
124
+
125
+ let currentSource = data;
126
+
127
+ for (let level = 0; level < IVFC_NUM_LEVELS - 1; level++) {
128
+ const { hashes, hashDataSize } = await createLevel(
129
+ currentSource,
130
+ crypto
131
+ );
132
+ levelData.unshift(hashes); // Prepend (we're building bottom-up but storing top-down)
133
+ levelHashDataSizes.unshift(hashDataSize);
134
+ levelPaddedSizes.unshift(hashes.length);
135
+ currentSource = hashes;
136
+ }
137
+
138
+ // Calculate master hash = SHA-256 of level 1 (the top level, which is levelData[0])
139
+ const masterHash = await sha256(levelData[0], crypto);
140
+
141
+ // Build the IVFC header (0xE0 bytes)
142
+ const header = new ArrayBuffer(IVFC_HEADER_SIZE);
143
+ const view = new DataView(header);
144
+
145
+ // IVFC header fields
146
+ view.setUint32(0x00, IVFC_MAGIC, true); // magic = "IVFC"
147
+ view.setUint32(0x04, IVFC_ID, true); // id = 0x20000
148
+ view.setUint32(0x08, HASH_SIZE, true); // master_hash_size = 0x20
149
+ view.setUint32(0x0c, IVFC_NUM_LEVELS + 1, true); // num_levels = 7 (6 hash levels + 1 data level)
150
+
151
+ // Level headers (6 entries, each 0x18 bytes, starting at offset 0x10)
152
+ // These describe levels 1-6 (level 6 = data itself)
153
+ // The logical_offset for each level is the cumulative offset of all previous levels
154
+ let logicalOffset = 0;
155
+ for (let i = 0; i < IVFC_NUM_LEVELS - 1; i++) {
156
+ const entryOffset = 0x10 + i * 0x18;
157
+ const dataSize = levelPaddedSizes[i];
158
+
159
+ // logical_offset (8 bytes)
160
+ view.setBigUint64(entryOffset + 0x00, BigInt(logicalOffset), true);
161
+ // hash_data_size (8 bytes)
162
+ view.setBigUint64(entryOffset + 0x08, BigInt(dataSize), true);
163
+ // block_size (4 bytes) — log2 of the block size
164
+ view.setUint32(entryOffset + 0x10, 0x0e, true); // 2^14 = 0x4000
165
+ // reserved (4 bytes) — already zero
166
+
167
+ logicalOffset += dataSize;
168
+ }
169
+
170
+ // Level 6 entry (the actual data)
171
+ {
172
+ const entryOffset = 0x10 + (IVFC_NUM_LEVELS - 1) * 0x18;
173
+ view.setBigUint64(entryOffset + 0x00, BigInt(logicalOffset), true);
174
+ view.setBigUint64(entryOffset + 0x08, BigInt(data.length), true);
175
+ view.setUint32(entryOffset + 0x10, 0x0e, true);
176
+ }
177
+
178
+ // Master hash at offset 0xC0 (0x20 bytes)
179
+ new Uint8Array(header, 0xc0, HASH_SIZE).set(masterHash);
180
+
181
+ // Calculate total level size
182
+ let totalLevelSize = 0;
183
+ for (const level of levelData) {
184
+ totalLevelSize += level.length;
185
+ }
186
+
187
+ return {
188
+ header,
189
+ levels: levelData,
190
+ totalLevelSize,
191
+ };
192
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "outDir": "./dist",
7
+ "declaration": true,
8
+ "sourceMap": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "strict": true,
11
+ "skipLibCheck": true
12
+ },
13
+ "include": ["src/**/*.ts"]
14
+ }