@@ -127,43 +127,106 @@ export function calculateMerkleRoot(quads, chunkSizeBytes = 32) {
127127
128128export 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
169232export 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