add crypto/poseidon package (#570)

This commit is contained in:
HAOYUatHZ 2023-11-20 16:48:21 +08:00 committed by GitHub
parent 5ffd345358
commit 932d6e371c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 25354 additions and 0 deletions

View file

@ -0,0 +1,43 @@
package poseidon
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
)
const defaultPoseidonChunk = 3
const nBytesToFieldElement = 31
func CodeHash(code []byte) (h common.Hash) {
nBytes := int64(len(code))
// step 1: pad code with 0x0 (STOP) so that len(code) % nBytesToFieldElement == 0
// step 2: for every nBytesToFieldElement bytes, convert to Fr, so that we get a Fr array
var length = (len(code) + nBytesToFieldElement - 1) / nBytesToFieldElement
Frs := make([]*big.Int, length)
ii := 0
for ii < length-1 {
Frs[ii] = big.NewInt(0)
Frs[ii].SetBytes(code[ii*nBytesToFieldElement : (ii+1)*nBytesToFieldElement])
ii++
}
if length > 0 {
Frs[ii] = big.NewInt(0)
bytes := make([]byte, nBytesToFieldElement)
copy(bytes, code[ii*nBytesToFieldElement:])
Frs[ii].SetBytes(bytes)
}
// step 3: apply the array onto a sponge process with the current poseidon scheme
// (3 Frs permutation and 1 Fr for output, so the throughout is 2 Frs)
// step 4: convert final root Fr to u256 (big-endian representation)
hash, err := HashWithCap(Frs, defaultPoseidonChunk, nBytes)
if err != nil {
return common.Hash{}
}
return common.BigToHash(hash)
}

View file

@ -0,0 +1,42 @@
package poseidon
import (
"fmt"
"testing"
)
func TestPoseidonCodeHash(t *testing.T) {
// nil
got := fmt.Sprintf("%s", CodeHash(nil))
want := "0x2098f5fb9e239eab3ceac3f27b81e481dc3124d55ffed523a839ee8446b64864"
if got != want {
t.Errorf("got %q, wanted %q", got, want)
}
// single byte
got = fmt.Sprintf("%s", CodeHash([]byte{0}))
want = "0x29f94b67ee4e78b2bb08da025f9943c1201a7af025a27600c2dd0a2e71c7cf8b"
if got != want {
t.Errorf("got %q, wanted %q", got, want)
}
got = fmt.Sprintf("%s", CodeHash([]byte{1}))
want = "0x246d3c06960643350a3e2d587fa16315c381635eb5ac1ac4501e195423dbf78e"
if got != want {
t.Errorf("got %q, wanted %q", got, want)
}
// 32 bytes
bytes := make([]byte, 32)
for i := range bytes {
bytes[i] = 1
}
got = fmt.Sprintf("%s", CodeHash(bytes))
want = "0x0b46d156183dffdbed8e6c6b0af139b95c058e735878ca7f4dca334e0ea8bd20"
if got != want {
t.Errorf("got %q, wanted %q", got, want)
}
}

24906
crypto/poseidon/constants.go Normal file

File diff suppressed because it is too large Load diff

185
crypto/poseidon/poseidon.go Normal file
View file

@ -0,0 +1,185 @@
// 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/ethereum/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
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) {
for i := 0; i < len(state); 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) {
for i := 0; i < len(state); 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, t)
for i := 0; i < t; i++ {
newState[i] = zero()
}
for i := 0; i < len(state); i++ {
newState[i].SetUint64(0)
for j := 0; j < len(state); j++ {
mul.Mul(m[j][i], state[j])
newState[i].Add(newState[i], mul)
}
}
return newState
}
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)
for i := 0; i < nRoundsF/2-1; i++ {
exp5state(state)
ark(state, C, (i+1)*t)
state = mix(state, t, M)
}
exp5state(state)
ark(state, C, (nRoundsF/2)*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 < len(state); j++ {
mul.Mul(S[(t*2-1)*i+j], state[j])
newState0.Add(newState0, mul)
}
for k := 1; k < t; k++ {
mul = zero()
state[k] = state[k].Add(state[k], mul.Mul(state[0], S[(t*2-1)*i+t+k-1]))
}
state[0] = newState0
}
for i := 0; i < nRoundsF/2-1; i++ {
exp5state(state)
ark(state, C, (nRoundsF/2+1)*t+nRoundsP+i*t)
state = mix(state, t, M)
}
exp5state(state)
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-2 > len(NROUNDSP) {
return nil, fmt.Errorf("invalid inputs width %d, max %d", width, len(NROUNDSP)+1) //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, width)
state[0] = capflag
for i := 1; i < width; i++ {
state[i] = zero()
}
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 := utils.BigIntArrayToElementArray(inpBI[:])
state := make([]*ff.Element, t)
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))
}

View file

@ -0,0 +1,178 @@
// from github.com/iden3/go-iden3-crypto/ff/poseidon
package poseidon
import (
"math/big"
"testing"
"github.com/iden3/go-iden3-crypto/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPoseidonHashFixed(t *testing.T) {
b0 := big.NewInt(0)
b1 := big.NewInt(1)
b2 := big.NewInt(2)
b3 := big.NewInt(3)
b4 := big.NewInt(4)
b5 := big.NewInt(5)
b6 := big.NewInt(6)
b7 := big.NewInt(7)
b8 := big.NewInt(8)
b9 := big.NewInt(9)
b10 := big.NewInt(10)
b11 := big.NewInt(11)
b12 := big.NewInt(12)
b13 := big.NewInt(13)
b14 := big.NewInt(14)
b15 := big.NewInt(15)
b16 := big.NewInt(16)
h, err := HashFixed([]*big.Int{b1})
assert.Nil(t, err)
assert.Equal(t,
"18586133768512220936620570745912940619677854269274689475585506675881198879027",
h.String())
h, err = HashFixed([]*big.Int{b1, b2})
assert.Nil(t, err)
assert.Equal(t,
"7853200120776062878684798364095072458815029376092732009249414926327459813530",
h.String())
h, err = HashFixed([]*big.Int{b1, b2, b0, b0, b0})
assert.Nil(t, err)
assert.Equal(t,
"1018317224307729531995786483840663576608797660851238720571059489595066344487",
h.String())
h, err = HashFixed([]*big.Int{b1, b2, b0, b0, b0, b0})
assert.Nil(t, err)
assert.Equal(t,
"15336558801450556532856248569924170992202208561737609669134139141992924267169",
h.String())
h, err = HashFixed([]*big.Int{b3, b4, b0, b0, b0})
assert.Nil(t, err)
assert.Equal(t,
"5811595552068139067952687508729883632420015185677766880877743348592482390548",
h.String())
h, err = HashFixed([]*big.Int{b3, b4, b0, b0, b0, b0})
assert.Nil(t, err)
assert.Equal(t,
"12263118664590987767234828103155242843640892839966517009184493198782366909018",
h.String())
h, err = HashFixed([]*big.Int{b1, b2, b3, b4, b5, b6})
assert.Nil(t, err)
assert.Equal(t,
"20400040500897583745843009878988256314335038853985262692600694741116813247201",
h.String())
h, err = HashFixed([]*big.Int{b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14})
assert.Nil(t, err)
assert.Equal(t,
"8354478399926161176778659061636406690034081872658507739535256090879947077494",
h.String())
h, err = HashFixed([]*big.Int{b1, b2, b3, b4, b5, b6, b7, b8, b9, b0, b0, b0, b0, b0})
assert.Nil(t, err)
assert.Equal(t,
"5540388656744764564518487011617040650780060800286365721923524861648744699539",
h.String())
h, err = HashFixed([]*big.Int{b1, b2, b3, b4, b5, b6, b7, b8, b9, b0, b0, b0, b0, b0, b0, b0})
assert.Nil(t, err)
assert.Equal(t,
"11882816200654282475720830292386643970958445617880627439994635298904836126497",
h.String())
h, err = HashFixed([]*big.Int{b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16})
assert.Nil(t, err)
assert.Equal(t,
"9989051620750914585850546081941653841776809718687451684622678807385399211877",
h.String())
h, err = HashFixedWithDomain([]*big.Int{b1, b2}, big.NewInt(256))
assert.Nil(t, err)
assert.Equal(t,
"2362370911616048355006851495576377379220050231129891536935411970097789775493",
h.String())
h_ref, _ := HashFixed([]*big.Int{b1, b2})
assert.NotEqual(t, h_ref, h)
}
func TestErrorInputs(t *testing.T) {
b0 := big.NewInt(0)
b1 := big.NewInt(1)
b2 := big.NewInt(2)
var err error
_, err = HashFixed([]*big.Int{b1, b2, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0})
require.Nil(t, err)
_, err = HashFixed([]*big.Int{b1, b2, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0})
require.NotNil(t, err)
assert.Equal(t, "invalid inputs length 17, max 16", err.Error())
_, err = HashFixed([]*big.Int{b1, b2, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0, b0})
require.NotNil(t, err)
assert.Equal(t, "invalid inputs length 18, max 16", err.Error())
}
func TestInputsNotInField(t *testing.T) {
var err error
// Very big number, should just return error and not go into endless loop
b1 := utils.NewIntFromString("12242166908188651009877250812424843524687801523336557272219921456462821518061999999999999999999999999999999999999999999999999999999999") //nolint:lll
_, err = HashFixed([]*big.Int{b1})
require.Error(t, err, "inputs values not inside Finite Field")
// Finite Field const Q, should return error
b2 := utils.NewIntFromString("21888242871839275222246405745257275088548364400416034343698204186575808495617") //nolint:lll
_, err = HashFixed([]*big.Int{b2})
require.Error(t, err, "inputs values not inside Finite Field")
}
func TestPoseidonHash(t *testing.T) {
ret, err := Hash(nil, 3)
if err != nil {
t.Fatal(err)
}
// Hash nil for width 3 equal to Hash([0, 0])
retRef, err := HashFixed([]*big.Int{big.NewInt(0), big.NewInt(0)})
if err != nil {
t.Fatal(err)
}
assert.Equal(t, ret, retRef)
// hash is different for the cap flag
ret1, err := Hash([]*big.Int{big.NewInt(0)}, 3)
if err != nil {
t.Fatal(err)
}
assert.NotEqual(t, ret1, retRef)
ret2, err := HashWithCap([]*big.Int{big.NewInt(0)}, 3, 0)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, ret2, retRef)
}
func BenchmarkPoseidonHash(b *testing.B) {
b0 := big.NewInt(0)
b1 := utils.NewIntFromString("12242166908188651009877250812424843524687801523336557272219921456462821518061") //nolint:lll
b2 := utils.NewIntFromString("12242166908188651009877250812424843524687801523336557272219921456462821518061") //nolint:lll
bigArray4 := []*big.Int{b1, b2, b0, b0, b0, b0}
for i := 0; i < b.N; i++ {
HashFixed(bigArray4) //nolint:errcheck,gosec
}
}