github.com/cryptotooltop/go-ethereum@v0.0.0-20231103184714-151d1922f3e5/crypto/poseidon/poseidon.go (about)

     1  // from github.com/iden3/go-iden3-crypto/ff/poseidon
     2  
     3  package poseidon
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"math/big"
     9  
    10  	"github.com/iden3/go-iden3-crypto/ff"
    11  	"github.com/iden3/go-iden3-crypto/utils"
    12  
    13  	"github.com/scroll-tech/go-ethereum/log"
    14  )
    15  
    16  const NROUNDSF = 8 //nolint:golint
    17  
    18  var NROUNDSP = []int{56, 57, 56, 60, 60, 63, 64, 63, 60, 66, 60, 65, 70, 60, 64, 68} //nolint:golint
    19  
    20  func zero() *ff.Element {
    21  	return ff.NewElement()
    22  }
    23  
    24  // exp5 performs x^5 mod p
    25  // https://eprint.iacr.org/2019/458.pdf page 8
    26  func exp5(a *ff.Element) {
    27  	a.Exp(*a, big.NewInt(5)) //nolint:gomnd
    28  }
    29  
    30  // exp5state perform exp5 for whole state
    31  func exp5state(state []*ff.Element) {
    32  	for i := 0; i < len(state); i++ {
    33  		exp5(state[i])
    34  	}
    35  }
    36  
    37  // ark computes Add-Round Key, from the paper https://eprint.iacr.org/2019/458.pdf
    38  func ark(state []*ff.Element, c []*ff.Element, it int) {
    39  	for i := 0; i < len(state); i++ {
    40  		state[i].Add(state[i], c[it+i])
    41  	}
    42  }
    43  
    44  // mix returns [[matrix]] * [vector]
    45  func mix(state []*ff.Element, t int, m [][]*ff.Element) []*ff.Element {
    46  	mul := zero()
    47  	newState := make([]*ff.Element, t)
    48  	for i := 0; i < t; i++ {
    49  		newState[i] = zero()
    50  	}
    51  	for i := 0; i < len(state); i++ {
    52  		newState[i].SetUint64(0)
    53  		for j := 0; j < len(state); j++ {
    54  			mul.Mul(m[j][i], state[j])
    55  			newState[i].Add(newState[i], mul)
    56  		}
    57  	}
    58  	return newState
    59  }
    60  
    61  func permute(state []*ff.Element, t int) []*ff.Element {
    62  
    63  	nRoundsF := NROUNDSF
    64  	nRoundsP := NROUNDSP[t-2]
    65  	C := c.c[t-2]
    66  	S := c.s[t-2]
    67  	M := c.m[t-2]
    68  	P := c.p[t-2]
    69  
    70  	ark(state, C, 0)
    71  
    72  	for i := 0; i < nRoundsF/2-1; i++ {
    73  		exp5state(state)
    74  		ark(state, C, (i+1)*t)
    75  		state = mix(state, t, M)
    76  	}
    77  	exp5state(state)
    78  	ark(state, C, (nRoundsF/2)*t)
    79  	state = mix(state, t, P)
    80  
    81  	for i := 0; i < nRoundsP; i++ {
    82  		exp5(state[0])
    83  		state[0].Add(state[0], C[(nRoundsF/2+1)*t+i])
    84  
    85  		mul := zero()
    86  		newState0 := zero()
    87  		for j := 0; j < len(state); j++ {
    88  			mul.Mul(S[(t*2-1)*i+j], state[j])
    89  			newState0.Add(newState0, mul)
    90  		}
    91  
    92  		for k := 1; k < t; k++ {
    93  			mul = zero()
    94  			state[k] = state[k].Add(state[k], mul.Mul(state[0], S[(t*2-1)*i+t+k-1]))
    95  		}
    96  		state[0] = newState0
    97  	}
    98  
    99  	for i := 0; i < nRoundsF/2-1; i++ {
   100  		exp5state(state)
   101  		ark(state, C, (nRoundsF/2+1)*t+nRoundsP+i*t)
   102  		state = mix(state, t, M)
   103  	}
   104  	exp5state(state)
   105  	return mix(state, t, M)
   106  }
   107  
   108  // for short, use size of inpBI as cap
   109  func Hash(inpBI []*big.Int, width int) (*big.Int, error) {
   110  	return HashWithCap(inpBI, width, int64(len(inpBI)))
   111  }
   112  
   113  // Hash using possible sponge specs specified by width (rate from 1 to 15), the size of input is applied as capacity
   114  // (notice we do not include width in the capacity )
   115  func HashWithCap(inpBI []*big.Int, width int, nBytes int64) (*big.Int, error) {
   116  	if width < 2 {
   117  		return nil, fmt.Errorf("width must be ranged from 2 to 16")
   118  	}
   119  	if width-2 > len(NROUNDSP) {
   120  		return nil, fmt.Errorf("invalid inputs width %d, max %d", width, len(NROUNDSP)+1) //nolint:gomnd,lll
   121  	}
   122  
   123  	// capflag = nBytes * 2^64
   124  	pow64 := big.NewInt(1)
   125  	pow64.Lsh(pow64, 64)
   126  	capflag := ff.NewElement().SetBigInt(big.NewInt(nBytes))
   127  	capflag.Mul(capflag, ff.NewElement().SetBigInt(pow64))
   128  
   129  	// initialize the state
   130  	state := make([]*ff.Element, width)
   131  	state[0] = capflag
   132  	for i := 1; i < width; i++ {
   133  		state[i] = zero()
   134  	}
   135  
   136  	rate := width - 1
   137  	i := 0
   138  	// always perform one round of permutation even when input is empty
   139  	for {
   140  		// each round absorb at most `rate` elements from `inpBI`
   141  		for j := 0; j < rate && i < len(inpBI); i, j = i+1, j+1 {
   142  			state[j+1].Add(state[j+1], ff.NewElement().SetBigInt(inpBI[i]))
   143  		}
   144  		state = permute(state, width)
   145  		if i == len(inpBI) {
   146  			break
   147  		}
   148  	}
   149  
   150  	// squeeze
   151  	rE := state[0]
   152  	r := big.NewInt(0)
   153  	rE.ToBigIntRegular(r)
   154  	return r, nil
   155  
   156  }
   157  
   158  // Hash computes the Poseidon hash for the given fixed-size inputs, with specified domain field
   159  func HashFixedWithDomain(inpBI []*big.Int, domain *big.Int) (*big.Int, error) {
   160  	t := len(inpBI) + 1
   161  	if len(inpBI) == 0 || len(inpBI) > len(NROUNDSP) {
   162  		return nil, fmt.Errorf("invalid inputs length %d, max %d", len(inpBI), len(NROUNDSP)) //nolint:gomnd,lll
   163  	}
   164  	if !utils.CheckBigIntArrayInField(inpBI[:]) {
   165  		return nil, errors.New("inputs values not inside Finite Field")
   166  	}
   167  	inp := utils.BigIntArrayToElementArray(inpBI[:])
   168  
   169  	state := make([]*ff.Element, t)
   170  	state[0] = ff.NewElement().SetBigInt(domain)
   171  	copy(state[1:], inp[:])
   172  
   173  	state = permute(state, t)
   174  
   175  	rE := state[0]
   176  	r := big.NewInt(0)
   177  	rE.ToBigIntRegular(r)
   178  	return r, nil
   179  }
   180  
   181  // Deprecated HashFixed entry, with domain field is 0
   182  func HashFixed(inpBI []*big.Int) (*big.Int, error) {
   183  	log.Warn("called a deprecated method for poseidon fixed hash", "inputs", inpBI)
   184  	return HashFixedWithDomain(inpBI, big.NewInt(0))
   185  }