gnark-plonky2-verifier/poseidon/bn254.go
poseidon/bn254.goRaw
1package poseidon
2
3// This is a customized implementation of the Poseidon hash function inside the BN254 field.
4// This implementation is based on the following implementation:
5//
6// https://github.com/iden3/go-iden3-crypto/blob/master/poseidon/poseidon.go
7//
8// The input and output are modified to ingest Goldilocks field elements.
9
10import (
11 "math/big"
12
13 "github.com/consensys/gnark/frontend"
14 gl "github.com/succinctlabs/gnark-plonky2-verifier/goldilocks"
15)
16
17const BN254_FULL_ROUNDS int = 8
18const BN254_PARTIAL_ROUNDS int = 56
19const BN254_SPONGE_WIDTH int = 4
20const BN254_SPONGE_RATE int = 3
21
22type BN254Chip struct {
23 api frontend.API `gnark:"-"`
24 gl gl.Chip `gnark:"-"`
25}
26
27type BN254State = [BN254_SPONGE_WIDTH]frontend.Variable
28type BN254HashOut = frontend.Variable
29
30func NewBN254Chip(api frontend.API) *BN254Chip {
31 return &BN254Chip{api: api, gl: *gl.New(api)}
32}
33
34func (c *BN254Chip) Poseidon(state BN254State) BN254State {
35 state = c.ark(state, 0)
36 state = c.fullRounds(state, true)
37 state = c.partialRounds(state)
38 state = c.fullRounds(state, false)
39 return state
40}
41
42func (c *BN254Chip) HashNoPad(input []gl.Variable) BN254HashOut {
43 state := BN254State{
44 frontend.Variable(0),
45 frontend.Variable(0),
46 frontend.Variable(0),
47 frontend.Variable(0),
48 }
49
50 two_to_32 := new(big.Int).SetInt64(1 << 32)
51 two_to_64 := new(big.Int).Mul(two_to_32, two_to_32)
52
53 for i := 0; i < len(input); i += BN254_SPONGE_RATE * 3 {
54 endI := c.min(len(input), i+BN254_SPONGE_RATE*3)
55 rateChunk := input[i:endI]
56 for j, stateIdx := 0, 0; j < len(rateChunk); j, stateIdx = j+3, stateIdx+1 {
57 endJ := c.min(len(rateChunk), j+3)
58 bn254Chunk := rateChunk[j:endJ]
59
60 inter := frontend.Variable(0)
61 for k := 0; k < len(bn254Chunk); k++ {
62 inter = c.api.MulAcc(inter, bn254Chunk[k].Limb, new(big.Int).Exp(two_to_64, big.NewInt(int64(k)), nil))
63 }
64
65 state[stateIdx+1] = inter
66 }
67
68 state = c.Poseidon(state)
69 }
70
71 return BN254HashOut(state[0])
72}
73
74func (c *BN254Chip) HashOrNoop(input []gl.Variable) BN254HashOut {
75 if len(input) <= 3 {
76 returnVal := frontend.Variable(0)
77
78 alpha := new(big.Int).SetInt64(1 << 32)
79 for i, inputElement := range input {
80 returnVal = c.api.MulAcc(returnVal, inputElement, alpha.Exp(alpha, big.NewInt(int64(i)), nil))
81 }
82
83 return BN254HashOut(returnVal)
84 } else {
85 return c.HashNoPad(input)
86 }
87}
88
89func (c *BN254Chip) TwoToOne(left BN254HashOut, right BN254HashOut) BN254HashOut {
90 var inputs BN254State
91 inputs[0] = frontend.Variable(0)
92 inputs[1] = frontend.Variable(0)
93 inputs[2] = left
94 inputs[3] = right
95 state := c.Poseidon(inputs)
96 return state[0]
97}
98
99func (c *BN254Chip) ToVec(hash BN254HashOut) []gl.Variable {
100 bits := c.api.ToBinary(hash)
101
102 returnElements := []gl.Variable{}
103
104 // Split into 7 byte chunks, since 8 byte chunks can result in collisions
105 chunkSize := 56
106 for i := 0; i < len(bits); i += chunkSize {
107 maxIdx := c.min(len(bits), i+chunkSize)
108 bitChunk := bits[i:maxIdx]
109 returnElements = append(returnElements, gl.NewVariable(c.api.FromBinary(bitChunk...)))
110 }
111
112 return returnElements
113}
114
115func (c *BN254Chip) min(x, y int) int {
116 if x < y {
117 return x
118 }
119
120 return y
121}
122
123func (c *BN254Chip) fullRounds(state BN254State, isFirst bool) BN254State {
124 for i := 0; i < BN254_FULL_ROUNDS/2-1; i++ {
125 state = c.exp5state(state)
126 if isFirst {
127 state = c.ark(state, (i+1)*BN254_SPONGE_WIDTH)
128 } else {
129 state = c.ark(state, (BN254_FULL_ROUNDS/2+1)*BN254_SPONGE_WIDTH+BN254_PARTIAL_ROUNDS+i*BN254_SPONGE_WIDTH)
130 }
131 state = c.mix(state, mMatrix)
132 }
133
134 state = c.exp5state(state)
135 if isFirst {
136 state = c.ark(state, (BN254_FULL_ROUNDS/2)*BN254_SPONGE_WIDTH)
137 state = c.mix(state, pMatrix)
138 } else {
139 state = c.mix(state, mMatrix)
140 }
141
142 return state
143}
144
145func (c *BN254Chip) partialRounds(state BN254State) BN254State {
146 for i := 0; i < BN254_PARTIAL_ROUNDS; i++ {
147 state[0] = c.exp5(state[0])
148 state[0] = c.api.Add(state[0], cConstants[(BN254_FULL_ROUNDS/2+1)*BN254_SPONGE_WIDTH+i])
149
150 newState0 := frontend.Variable(0)
151 for j := 0; j < BN254_SPONGE_WIDTH; j++ {
152 newState0 = c.api.MulAcc(newState0, sConstants[(BN254_SPONGE_WIDTH*2-1)*i+j], state[j])
153 }
154
155 for k := 1; k < BN254_SPONGE_WIDTH; k++ {
156 state[k] = c.api.MulAcc(state[k], state[0], sConstants[(BN254_SPONGE_WIDTH*2-1)*i+BN254_SPONGE_WIDTH+k-1])
157 }
158 state[0] = newState0
159 }
160
161 return state
162}
163
164func (c *BN254Chip) ark(state BN254State, it int) BN254State {
165 var result BN254State
166
167 for i := 0; i < len(state); i++ {
168 result[i] = c.api.Add(state[i], cConstants[it+i])
169 }
170
171 return result
172}
173
174func (c *BN254Chip) exp5(x frontend.Variable) frontend.Variable {
175 x2 := c.api.Mul(x, x)
176 x4 := c.api.Mul(x2, x2)
177 return c.api.Mul(x4, x)
178}
179
180func (c *BN254Chip) exp5state(state BN254State) BN254State {
181 for i := 0; i < BN254_SPONGE_WIDTH; i++ {
182 state[i] = c.exp5(state[i])
183 }
184 return state
185}
186
187func (c *BN254Chip) mix(state_ BN254State, constantMatrix [][]*big.Int) BN254State {
188 var result BN254State
189
190 for i := 0; i < BN254_SPONGE_WIDTH; i++ {
191 result[i] = frontend.Variable(0)
192 }
193
194 for i := 0; i < BN254_SPONGE_WIDTH; i++ {
195 for j := 0; j < BN254_SPONGE_WIDTH; j++ {
196 result[i] = c.api.MulAcc(result[i], constantMatrix[j][i], state_[j])
197 }
198 }
199
200 return result
201}