mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
add crypto/poseidon package (#570)
This commit is contained in:
parent
5ffd345358
commit
932d6e371c
5 changed files with 25354 additions and 0 deletions
43
crypto/poseidon/codehash.go
Normal file
43
crypto/poseidon/codehash.go
Normal 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)
|
||||
}
|
||||
42
crypto/poseidon/codehash_test.go
Normal file
42
crypto/poseidon/codehash_test.go
Normal 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
24906
crypto/poseidon/constants.go
Normal file
File diff suppressed because it is too large
Load diff
185
crypto/poseidon/poseidon.go
Normal file
185
crypto/poseidon/poseidon.go
Normal 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))
|
||||
}
|
||||
178
crypto/poseidon/poseidon_test.go
Normal file
178
crypto/poseidon/poseidon_test.go
Normal 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
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue