-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsnarkmerkle.js
207 lines (174 loc) · 6.22 KB
/
snarkmerkle.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// https://github.com/raiden-network/raiden/blob/master/raiden/mtree.py
// Create a merkle root from a list of elements
// Elements are assumed to be 32 bytes hashes (Buffers)
// (but may be expressed as 0x prefixed hex strings of length 66)
// The bottom layer of the tree (leaf nodes) are the elements
// All layers above are combined hashes of the element pairs
// Two strategies for creating tree and checking proofs (preserveOrder flag)
// 1. raiden - sort the leaves of the tree, and also sort each pair of
// pre-images, which allows you to verify the proof without the index
// 2. storj - preserve the order of the leaves and pairs of pre-images, and use
// the index to verify the proof
// The MerkleTree is a 2d array of layers
// [ elements, combinedHashes1, combinedHashes2, ... root]
// root is a length 1 array
//import { sha3 } from 'ethereumjs-util'
const {hash, number} = require('starknet');
// Expects elements to be Buffers of length 32
// Empty string elements will be removed prior to the buffer check
// by default, order is not preserved
function SnarkMerkleTree(elements, preserveOrder) {
if (!(this instanceof SnarkMerkleTree)) {
return new SnarkMerkleTree(elements, preserveOrder)
}
// remove empty strings
this.elements = elements.filter(a => a)
// // check buffers
// if (this.elements.some((e) => !(e.length == 32 && Buffer.isBuffer(e)))) {
// throw new Error('elements must be 32 byte buffers')
// }
// if we are not preserving order, dedup and sort
this.preserveOrder = !!preserveOrder
if (!this.preserveOrder) {
this.elements = bufDedup(this.elements)
this.elements.sort()
}
this.layers = getLayers(this.elements, this.preserveOrder)
}
SnarkMerkleTree.prototype.getRoot = function() {
return this.layers[this.layers.length - 1][0]
}
SnarkMerkleTree.prototype.getProof = function(element, hex) {
const index = getBufIndex(element, this.elements)
if (index == -1) {
throw new Error('element not found in merkle tree')
}
return getProof(index, this.layers, hex)
}
// Expects 1-n index, converts it to 0-n index internally
SnarkMerkleTree.prototype.getProofOrdered = function(element, index, hex) {
if (!(element === this.elements[index - 1])) {
throw new Error('element does not match leaf at index in tree')
}
return getProof(index - 1, this.layers, hex)
}
const checkProofOrdered = function(proof, root, element, index) {
// use the index to determine the node ordering
// index ranges 1 to n
let tempHash = element
for (let i = 0; i < proof.length; i++) {
let remaining = proof.length - i
// we don't assume that the tree is padded to a power of 2
// if the index is odd then the proof will start with a hash at a higher
// layer, so we have to adjust the index to be the index at that layer
while (remaining && index % 2 === 1 && index > Math.pow(2, remaining)) {
index = Math.round(index / 2)
}
if (index % 2 === 0) {
tempHash = combinedHash(proof[i], tempHash, true)
} else {
tempHash = combinedHash(tempHash, proof[i], true)
}
console.log("i:",i.toString(), "tempHash:",tempHash.toString())
index = Math.round(index / 2)
}
console.log("temphash:",tempHash.toString());
return tempHash === root
}
const checkProof = function(proof, root, element) {
return root.equals(proof.reduce((hash, pair) => {
return combinedHash(hash, pair)
}, element))
}
const merkleRoot = function(elements, preserveOrder) {
return (new SnarkMerkleTree(elements, preserveOrder)).getRoot()
}
// converts buffers from MerkleRoot functions into hex strings
// merkleProof is the contract abstraction for MerkleProof.sol
const checkProofSolidityFactory = function(checkProofContractMethod) {
return function(proof, root, hash) {
proof = '0x' + proof.map(e => e.toString('hex')).join('')
root = bufToHex(root)
hash = bufToHex(hash)
return checkProofContractMethod(proof, root, hash)
}
}
const checkProofOrderedSolidityFactory = function(checkProofOrderedContractMethod) {
return function(proof, root, hash, index) {
proof = '0x' + proof.map(e => e.toString('hex')).join('')
root = bufToHex(root)
hash = bufToHex(hash)
return checkProofOrderedContractMethod(proof, root, hash, index)
}
}
function combinedHash(first, second, preserveOrder) {
if (!second) { return first }
if (!first) { return second }
if (preserveOrder) {
return hash.pedersen([first,second]);// sha3(bufJoin(first, second))
} else {
console.err('cannot perform with lexographic sort');
//return sha3(bufSortJoin(first, second))
}
}
function getNextLayer(elements, preserveOrder) {
return elements.reduce((layer, element, index, arr) => {
if (index % 2 == 0) { layer.push(combinedHash(element, arr[index + 1], preserveOrder)) }
return layer
}, [])
}
function getLayers(elements, preserveOrder) {
if (elements.length == 0) {
return [['']]
}
const layers = []
layers.push(elements)
while (layers[layers.length - 1].length > 1) {
layers.push(getNextLayer(layers[layers.length - 1], preserveOrder))
}
return layers
}
function getProof(index, layers, hex) {
const proof = layers.reduce((proof, layer) => {
let pair = getPair(index, layer)
if (pair) { proof.push(pair) }
index = Math.floor(index / 2)
return proof
}, [])
if (hex) {
return '0x' + proof.map(e => e.toString('hex')).join('')
} else {
return proof
}
}
function getPair(index, layer) {
let pairIndex = index % 2 ? index - 1 : index + 1
if (pairIndex < layer.length) {
return layer[pairIndex]
} else {
return null
}
}
function getBufIndex(element, array) {
for (let i = 0; i < array.length; i++) {
if (element.equals(array[i])) { return i }
}
return -1
}
function bufToHex(element) {
return Buffer.isBuffer(element) ? '0x' + element.toString('hex') : element
}
function bufJoin(...args) {
return Buffer.concat([...args])
}
function bufSortJoin(...args) {
return Buffer.concat([...args].sort(Buffer.compare))
}
function bufDedup(buffers) {
return buffers.filter((buffer, i) => {
return getBufIndex(buffer, buffers) == i
})
}
module.exports = { SnarkMerkleTree, checkProof, checkProofOrdered, merkleRoot, checkProofSolidityFactory,
checkProofOrderedSolidityFactory, getProof
}