go-ethereum/crypto/poseidon/poseidon.go

186 lines
4.6 KiB
Go

// from github.com/iden3/go-iden3-crypto/ff/poseidon
package poseidon
import (
"errors"
"fmt"
"math/big"
"github.com/iden3/go-iden3-crypto/ff"
"github.com/iden3/go-iden3-crypto/utils"
"github.com/scroll-tech/go-ethereum/log"
)
const NROUNDSF = 8 //nolint:golint
var NROUNDSP = []int{56, 57, 56, 60, 60, 63, 64, 63, 60, 66, 60, 65, 70, 60, 64, 68} //nolint:golint
const MAX_WIDTH = 18 // len(NROUNDSP)+2
func zero() *ff.Element {
return ff.NewElement()
}
// exp5 performs x^5 mod p
// https://eprint.iacr.org/2019/458.pdf page 8
func exp5(a *ff.Element) {
a.Exp(*a, big.NewInt(5)) //nolint:gomnd
}
// exp5state perform exp5 for whole state
func exp5state(state []ff.Element, t int) {
for i := 0; i < t; i++ {
exp5(&state[i])
}
}
// ark computes Add-Round Key, from the paper https://eprint.iacr.org/2019/458.pdf
func ark(state []ff.Element, c []*ff.Element, it int, t int) {
for i := 0; i < t; i++ {
state[i].Add(&state[i], c[it+i])
}
}
// mix returns [[matrix]] * [vector]
func mix(state []ff.Element, t int, m [][]*ff.Element) []ff.Element {
mul := zero()
newState := make([]ff.Element, MAX_WIDTH)
for i := 0; i < t; i++ {
newState[i].SetUint64(0)
for j := 0; j < t; j++ {
mul.Mul(m[j][i], &state[j])
newState[i].Add(&newState[i], mul)
}
}
for i := 0; i < t; i++ {
state[i].Set(&newState[i])
}
return state
}
func permute(state []ff.Element, t int) []ff.Element {
nRoundsF := NROUNDSF
nRoundsP := NROUNDSP[t-2]
C := c.c[t-2]
S := c.s[t-2]
M := c.m[t-2]
P := c.p[t-2]
ark(state, C, 0, t)
for i := 0; i < nRoundsF/2-1; i++ {
exp5state(state, t)
ark(state, C, (i+1)*t, t)
state = mix(state, t, M)
}
exp5state(state, t)
ark(state, C, (nRoundsF/2)*t, t)
state = mix(state, t, P)
for i := 0; i < nRoundsP; i++ {
exp5(&state[0])
state[0].Add(&state[0], C[(nRoundsF/2+1)*t+i])
mul := zero()
newState0 := zero()
for j := 0; j < t; j++ {
mul.Mul(S[(t*2-1)*i+j], &state[j])
newState0.Add(newState0, mul)
}
for k := 1; k < t; k++ {
state[k].Add(&state[k], mul.Mul(&state[0], S[(t*2-1)*i+t+k-1]))
}
state[0].Set(newState0)
}
for i := 0; i < nRoundsF/2-1; i++ {
exp5state(state, t)
ark(state, C, (nRoundsF/2+1)*t+nRoundsP+i*t, t)
state = mix(state, t, M)
}
exp5state(state, t)
return mix(state, t, M)
}
// for short, use size of inpBI as cap
func Hash(inpBI []*big.Int, width int) (*big.Int, error) {
return HashWithCap(inpBI, width, int64(len(inpBI)))
}
// Hash using possible sponge specs specified by width (rate from 1 to 15), the size of input is applied as capacity
// (notice we do not include width in the capacity )
func HashWithCap(inpBI []*big.Int, width int, nBytes int64) (*big.Int, error) {
if width < 2 {
return nil, fmt.Errorf("width must be ranged from 2 to 16")
}
if width > MAX_WIDTH {
return nil, fmt.Errorf("invalid inputs width %d, max %d", width, MAX_WIDTH) //nolint:gomnd,lll
}
// capflag = nBytes * 2^64
pow64 := big.NewInt(1)
pow64.Lsh(pow64, 64)
capflag := ff.NewElement().SetBigInt(big.NewInt(nBytes))
capflag.Mul(capflag, ff.NewElement().SetBigInt(pow64))
// initialize the state
state := make([]ff.Element, MAX_WIDTH)
state[0] = *capflag
rate := width - 1
i := 0
// always perform one round of permutation even when input is empty
for {
// each round absorb at most `rate` elements from `inpBI`
for j := 0; j < rate && i < len(inpBI); i, j = i+1, j+1 {
state[j+1].Add(&state[j+1], ff.NewElement().SetBigInt(inpBI[i]))
}
state = permute(state, width)
if i == len(inpBI) {
break
}
}
// squeeze
rE := state[0]
r := big.NewInt(0)
rE.ToBigIntRegular(r)
return r, nil
}
// Hash computes the Poseidon hash for the given fixed-size inputs, with specified domain field
func HashFixedWithDomain(inpBI []*big.Int, domain *big.Int) (*big.Int, error) {
t := len(inpBI) + 1
if len(inpBI) == 0 || len(inpBI) > len(NROUNDSP) {
return nil, fmt.Errorf("invalid inputs length %d, max %d", len(inpBI), len(NROUNDSP)) //nolint:gomnd,lll
}
if !utils.CheckBigIntArrayInField(inpBI[:]) {
return nil, errors.New("inputs values not inside Finite Field")
}
inp := make([]ff.Element, MAX_WIDTH)
for idx, bi := range inpBI {
inp[idx].SetBigInt(bi)
}
state := make([]ff.Element, MAX_WIDTH)
state[0] = *ff.NewElement().SetBigInt(domain)
copy(state[1:], inp[:])
state = permute(state, t)
rE := state[0]
r := big.NewInt(0)
rE.ToBigIntRegular(r)
return r, nil
}
// Deprecated HashFixed entry, with domain field is 0
func HashFixed(inpBI []*big.Int) (*big.Int, error) {
log.Warn("called a deprecated method for poseidon fixed hash", "inputs", inpBI)
return HashFixedWithDomain(inpBI, big.NewInt(0))
}