Skip to content

Commit a9455af

Browse files
Rewrite proofing tools
1 parent 4031a2c commit a9455af

File tree

1 file changed

+79
-16
lines changed

1 file changed

+79
-16
lines changed

src/knowledge-collection-tools.js

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -127,43 +127,106 @@ export function calculateMerkleRoot(quads, chunkSizeBytes = 32) {
127127

128128
export function calculateMerkleProof(quads, chunkSizeBytes, challenge) {
129129
const chunks = splitIntoChunks(quads, chunkSizeBytes);
130+
131+
// Step 1: Generate leaf hashes using Solidity-compatible hashing
130132
const leaves = chunks.map((chunk, index) =>
131133
Buffer.from(
132134
ethers.utils
133135
.solidityKeccak256(["string", "uint256"], [chunk, index])
134-
.replace("0x", ""),
136+
.slice(2), // strip "0x"
135137
"hex"
136138
)
137139
);
138140

139-
const tree = new MerkleTree(leaves, arraifyKeccak256, { sortPairs: true });
141+
const proof = [];
142+
let index = challenge;
143+
let currentLevel = leaves;
144+
145+
// Step 2: Traverse tree upward and build proof path
146+
while (currentLevel.length > 1) {
147+
const nextLevel = [];
148+
149+
for (let i = 0; i < currentLevel.length; i += 2) {
150+
const left = currentLevel[i];
151+
const right = i + 1 < currentLevel.length ? currentLevel[i + 1] : null;
152+
153+
if (right) {
154+
// Sort pair like Solidity (<)
155+
const [first, second] =
156+
Buffer.compare(left, right) < 0 ? [left, right] : [right, left];
157+
158+
const combined = Buffer.concat([first, second]);
159+
const parent = Buffer.from(
160+
ethers.utils.keccak256(combined).slice(2),
161+
"hex"
162+
);
163+
164+
nextLevel.push(parent);
165+
166+
// Collect sibling if current index is part of this pair
167+
if (i === index || i + 1 === index) {
168+
const sibling = i === index ? right : left;
169+
proof.push(`0x${sibling.toString("hex")}`);
170+
index = Math.floor(i / 2);
171+
}
172+
} else {
173+
// Odd number of nodes – carry unpaired node up
174+
nextLevel.push(left);
175+
176+
if (i === index) {
177+
index = Math.floor(i / 2);
178+
}
179+
}
180+
}
181+
182+
currentLevel = nextLevel;
183+
}
184+
185+
const leaf = leaves[challenge];
186+
const root = currentLevel[0];
140187

141188
return {
142-
leaf: arraifyKeccak256(chunks[challenge]),
143-
proof: tree.getHexProof(leaves[challenge]),
189+
root: `0x${root.toString("hex")}`,
190+
proof,
191+
leaf: `0x${leaf.toString("hex")}`,
192+
chunk: chunks[challenge],
193+
chunkId: challenge,
144194
};
145195
}
146196

147-
export function calculateMerkleRootFromProof(chunks, challenge, proof) {
148-
let currentHashBuffer = Buffer.from(
197+
export function computeMerkleRootFromProof(chunks, chunkId, proof) {
198+
// Get the specific chunk we're proving
199+
const challengeChunk = chunks[chunkId];
200+
if (!challengeChunk) {
201+
throw new Error(`Chunk ${chunkId} not found in chunks array`);
202+
}
203+
204+
// Calculate initial hash from the chunk and its ID
205+
let currentHash = Buffer.from(
149206
ethers.utils
150-
.solidityKeccak256(["string", "uint256"], [chunks[challenge], challenge])
151-
.replace("0x", ""),
207+
.solidityKeccak256(["string", "uint256"], [challengeChunk, chunkId])
208+
.slice(2),
152209
"hex"
153210
);
154211

155-
for (const proofElement of proof) {
156-
const proofBuffer = Buffer.from(proofElement.replace("0x", ""), "hex");
212+
// Process each proof element
213+
for (const siblingHex of proof) {
214+
const sibling = Buffer.from(siblingHex.slice(2), "hex");
157215

158-
const combined = [currentHashBuffer, proofBuffer].sort(Buffer.compare);
216+
// Ensure deterministic ordering of hashes
217+
const [first, second] =
218+
Buffer.compare(currentHash, sibling) < 0
219+
? [currentHash, sibling]
220+
: [sibling, currentHash];
159221

160-
currentHashBuffer = Buffer.from(
161-
ethers.utils.keccak256(Buffer.concat(combined)).replace("0x", ""),
222+
// Compute parent hash
223+
currentHash = Buffer.from(
224+
ethers.utils.keccak256(Buffer.concat([first, second])).slice(2),
162225
"hex"
163226
);
164227
}
165228

166-
return `0x${currentHashBuffer.toString("hex")}`;
229+
return `0x${currentHash.toString("hex")}`;
167230
}
168231

169232
export function groupNquadsBySubject(nquadsArray, sort = false) {
@@ -179,7 +242,7 @@ export function groupNquadsBySubject(nquadsArray, sort = false) {
179242
const nestedPredicate = subject.predicate.value;
180243
const nestedObject =
181244
subject.object.termType === "Literal"
182-
? `"""${subject.object.value}"""`
245+
? `"${subject.object.value}"`
183246
: `<${subject.object.value}>`;
184247
subjectKey = `<<<${nestedSubject}> <${nestedPredicate}> ${nestedObject}>>`;
185248
} else {
@@ -191,7 +254,7 @@ export function groupNquadsBySubject(nquadsArray, sort = false) {
191254
}
192255

193256
const objectValue =
194-
object.termType === "Literal" ? `"""${object.value}"""` : `<${object.value}>`;
257+
object.termType === "Literal" ? `"${object.value}"` : `<${object.value}>`;
195258

196259
const quadString = `${subjectKey} <${predicate.value}> ${objectValue} .`;
197260
grouped[subjectKey].push(quadString);

0 commit comments

Comments
 (0)