1
+ const path = require ( "path" )
2
+ const { expect } = require ( "chai" )
3
+ const { buildEddsa } = require ( 'circomlibjs' )
4
+ const { utils } = require ( 'ffjavascript' )
5
+ const wasm_tester = require ( "circom_tester" ) . wasm
6
+ const { MerkleTree } = require ( 'fixed-merkle-tree' )
7
+ const { DocumentTree, toBuffer } = require ( 'document-tree' )
8
+
9
+ const Poseidon = require ( '../../src/poseidon' )
10
+ const {
11
+ ZERO_VALUE ,
12
+ randomBN,
13
+ toFixedHex,
14
+ prepareCertreeProofInputs,
15
+ bufferToBigIntField
16
+ } = require ( "../../src/utils" )
17
+
18
+ CERT_TREE_HEIGHT = 8
19
+ CRED_TREE_HEIGHT = 3
20
+
21
+ describe ( "Score circuit" , function ( ) {
22
+ this . timeout ( 25000 )
23
+ let circuit , poseidon
24
+
25
+ // TODO: move to utils
26
+ function poseidonHash2 ( a , b ) {
27
+ return poseidon . hash ( [ a , b ] )
28
+ }
29
+
30
+ function poseidonHash ( items ) {
31
+ return poseidon . hash ( items )
32
+ }
33
+
34
+ function getNewCertree ( leaves = [ ] , tree_height = CERT_TREE_HEIGHT , zero = ZERO_VALUE ) {
35
+ return new MerkleTree ( tree_height , leaves , { hashFunction : poseidonHash2 , zeroElement : zero } )
36
+ }
37
+
38
+ function prepareCredInputs ( n , proofFieldKeys , credtreeHeight , doctrees ) {
39
+ const m = 1 << credtreeHeight
40
+
41
+ let fields = [ ]
42
+ let pathElements = new Array ( )
43
+ let leafIndices = new Array ( )
44
+ // TODO: validate schema. Fields must be sorted by the schema
45
+ proofFieldKeys . sort ( )
46
+ const emptyEntry = Array ( m - proofFieldKeys . length ) . fill ( [ 0n , 0n , 0n ] )
47
+ for ( let i = 0 ; i < n ; i ++ ) {
48
+ fields [ i ] = new Array ( )
49
+ for ( let j = 0 ; j < proofFieldKeys . length ; j ++ ) {
50
+ fields [ i ] [ j ] = new Array ( )
51
+ const leaf = doctrees [ i ] . findLeaf ( proofFieldKeys [ j ] )
52
+ fields [ i ] [ j ] [ 0 ] = bufferToBigIntField ( toBuffer ( leaf . key ( ) ) )
53
+ fields [ i ] [ j ] [ 1 ] = bufferToBigIntField ( toBuffer ( leaf . value ) )
54
+ fields [ i ] [ j ] [ 2 ] = bufferToBigIntField ( toBuffer ( leaf . salt ) )
55
+ }
56
+ // TODO: multi proof by key or name
57
+ const multiProof = doctrees [ i ] . multiProof ( proofFieldKeys )
58
+
59
+ expect ( MerkleTree . verifyMultiProof (
60
+ doctrees [ i ] . root ( ) ,
61
+ doctrees [ i ] . levels ( ) ,
62
+ poseidonHash2 ,
63
+ doctrees [ i ] . leafHashes ( proofFieldKeys ) ,
64
+ multiProof . pathElements ,
65
+ multiProof . leafIndices
66
+ ) ) . to . be . true
67
+
68
+ const pe = multiProof . pathElements
69
+ const li = multiProof . leafIndices
70
+ pathElements . push ( pe . concat ( Array ( m - pe . length ) . fill ( 0 ) ) )
71
+ leafIndices . push ( li . concat ( Array ( m - li . length ) . fill ( 0 ) ) )
72
+ fields [ i ] = fields [ i ] . concat ( emptyEntry )
73
+ }
74
+
75
+ return utils . stringifyBigInts ( {
76
+ fields : fields ,
77
+ pathFieldElements : pathElements ,
78
+ fieldIndices : leafIndices ,
79
+ } )
80
+ }
81
+
82
+ function createDocumentTree ( document ) {
83
+ let doctree = new DocumentTree ( {
84
+ zero : ZERO_VALUE ,
85
+ hashFunction : poseidonHash2 ,
86
+ leafHashFunction : poseidonHash
87
+ } )
88
+ doctree . addLeavesFromDocument ( document )
89
+ return doctree
90
+ }
91
+
92
+ function randomDoc ( issuer , subject ) {
93
+ return {
94
+ grade : Math . floor ( Math . random ( ) * 100 ) ,
95
+ tag : Math . random ( ) . toString ( 16 ) . substring ( 2 , 8 ) ,
96
+ subject : subject ,
97
+ issuer : issuer ,
98
+ reference : toFixedHex ( randomBN ( ) ) , // storage chunk reference
99
+ timestamp : Math . floor ( new Date ( ) . getTime ( ) / 1000 )
100
+ }
101
+ }
102
+
103
+ function generateDocuments ( n , issuer , subject ) {
104
+ let docs = [ ]
105
+ for ( let i = 0 ; i < n ; i ++ ) {
106
+ docs . push ( createDocumentTree ( randomDoc ( issuer , subject ) ) )
107
+ }
108
+ return docs
109
+ }
110
+
111
+ // FIXME: dry
112
+ function createCredential ( secret , publicKey , root ) {
113
+ let credential = { secret, root }
114
+ credential . subject = poseidonHash2 ( eddsa . F . toObject ( publicKey [ 0 ] ) , eddsa . F . toObject ( publicKey [ 1 ] ) )
115
+ credential . commitment = poseidonHash ( [ credential . root , credential . subject , credential . secret ] )
116
+ credential . nullifierHash = poseidonHash ( [ credential . root ] )
117
+ return credential
118
+ }
119
+
120
+ function weightedSum ( elements , weights ) {
121
+ return elements . reduce ( ( sum , e , i ) => {
122
+ sum += e * weights [ i ]
123
+ return sum
124
+ } , 0 )
125
+ }
126
+
127
+ before ( async ( ) => {
128
+ circuit = await wasm_tester ( path . join ( __dirname , "circuits" , "score_test.circom" ) )
129
+ poseidon = await Poseidon . initialize ( )
130
+ eddsa = await buildEddsa ( )
131
+ } )
132
+
133
+ it ( "should compute the correct score of multiple credential's fields" , async ( ) => {
134
+ const nCerts = 5
135
+ const issuer = toFixedHex ( randomBN ( ) )
136
+ const subject = toFixedHex ( randomBN ( ) )
137
+ const privateKey = toFixedHex ( randomBN ( ) )
138
+ const publicKey = eddsa . prv2pub ( privateKey )
139
+
140
+ const docs = generateDocuments ( nCerts , issuer , subject )
141
+ const leaves = docs . map ( d => d . root ( ) )
142
+ const certree = getNewCertree ( leaves , CERT_TREE_HEIGHT , ZERO_VALUE )
143
+
144
+ const credentials = [ ]
145
+ for ( let i = 0 ; i < nCerts ; i ++ ) {
146
+ credentials [ i ] = createCredential ( randomBN ( ) . toString ( ) , publicKey , docs [ i ] . root ( ) )
147
+ certree . insert ( credentials [ i ] . commitment )
148
+ }
149
+
150
+ const certProofs = prepareCertreeProofInputs ( certree , credentials )
151
+ const credProofInputs = prepareCredInputs ( nCerts , [ "tag" , "grade" ] , CRED_TREE_HEIGHT , docs )
152
+ const tags = docs . map ( d => bufferToBigIntField ( toBuffer ( d . findLeaf ( "tag" ) . value ) ) )
153
+ const weights = [ ...Array ( nCerts ) ] . map ( ( _ , i ) => ( i % 2 ) + 1 )
154
+ const grades = docs . map ( d => d . findLeaf ( "grade" ) . value )
155
+ const result = weightedSum ( grades , weights )
156
+
157
+ const inputs = utils . stringifyBigInts ( {
158
+ certreeRoot : certree . root ,
159
+ requiredTags : tags ,
160
+ weights : weights ,
161
+ result : result ,
162
+ nullifierHashes : credentials . map ( c => c . nullifierHash ) ,
163
+ credentialRoots : credentials . map ( c => c . root ) ,
164
+ subjects : credentials . map ( c => c . subject ) ,
165
+ secrets : credentials . map ( ( c ) => c . secret ) ,
166
+ pathCertreeElements : certProofs . map ( p => p . pathCertreeElements ) ,
167
+ pathCertreeIndices : certProofs . map ( p => p . pathCertreeIndices ) ,
168
+ ...credProofInputs
169
+ } )
170
+
171
+ const w = await circuit . calculateWitness ( inputs , true )
172
+ await circuit . checkConstraints ( w )
173
+ } ) ;
174
+ } )
0 commit comments