feat: 添加 Lighter 适配器及相关功能,支持 trailing stops 和新的交易逻辑

This commit is contained in:
discountry
2025-09-30 22:08:05 +08:00
parent e83bdfccf9
commit c8b0ab1c8e
101 changed files with 90621 additions and 3 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,174 @@
package poseidon2
import g "github.com/elliottech/poseidon_crypto/field/goldilocks"
const (
WIDTH = 12
RATE = 8
OUT = 4
D = 7
// Generated by `poseidon2_round_numbers_128`
ROUNDS_F = 8
ROUNDS_F_HALF = 4
ROUNDS_P = 22
)
var (
// Generated randomly for ROUNDS_F
EXTERNAL_CONSTANTS = [ROUNDS_F][WIDTH]g.Element{
{
g.NewElement(15492826721047263190),
g.NewElement(11728330187201910315),
g.NewElement(8836021247773420868),
g.NewElement(16777404051263952451),
g.NewElement(5510875212538051896),
g.NewElement(6173089941271892285),
g.NewElement(2927757366422211339),
g.NewElement(10340958981325008808),
g.NewElement(8541987352684552425),
g.NewElement(9739599543776434497),
g.NewElement(15073950188101532019),
g.NewElement(12084856431752384512),
},
{
g.NewElement(4584713381960671270),
g.NewElement(8807052963476652830),
g.NewElement(54136601502601741),
g.NewElement(4872702333905478703),
g.NewElement(5551030319979516287),
g.NewElement(12889366755535460989),
g.NewElement(16329242193178844328),
g.NewElement(412018088475211848),
g.NewElement(10505784623379650541),
g.NewElement(9758812378619434837),
g.NewElement(7421979329386275117),
g.NewElement(375240370024755551),
},
{
g.NewElement(3331431125640721931),
g.NewElement(15684937309956309981),
g.NewElement(578521833432107983),
g.NewElement(14379242000670861838),
g.NewElement(17922409828154900976),
g.NewElement(8153494278429192257),
g.NewElement(15904673920630731971),
g.NewElement(11217863998460634216),
g.NewElement(3301540195510742136),
g.NewElement(9937973023749922003),
g.NewElement(3059102938155026419),
g.NewElement(1895288289490976132),
},
{
g.NewElement(5580912693628927540),
g.NewElement(10064804080494788323),
g.NewElement(9582481583369602410),
g.NewElement(10186259561546797986),
g.NewElement(247426333829703916),
g.NewElement(13193193905461376067),
g.NewElement(6386232593701758044),
g.NewElement(17954717245501896472),
g.NewElement(1531720443376282699),
g.NewElement(2455761864255501970),
g.NewElement(11234429217864304495),
g.NewElement(4746959618548874102),
},
{
g.NewElement(13571697342473846203),
g.NewElement(17477857865056504753),
g.NewElement(15963032953523553760),
g.NewElement(16033593225279635898),
g.NewElement(14252634232868282405),
g.NewElement(8219748254835277737),
g.NewElement(7459165569491914711),
g.NewElement(15855939513193752003),
g.NewElement(16788866461340278896),
g.NewElement(7102224659693946577),
g.NewElement(3024718005636976471),
g.NewElement(13695468978618890430),
},
{
g.NewElement(8214202050877825436),
g.NewElement(2670727992739346204),
g.NewElement(16259532062589659211),
g.NewElement(11869922396257088411),
g.NewElement(3179482916972760137),
g.NewElement(13525476046633427808),
g.NewElement(3217337278042947412),
g.NewElement(14494689598654046340),
g.NewElement(15837379330312175383),
g.NewElement(8029037639801151344),
g.NewElement(2153456285263517937),
g.NewElement(8301106462311849241),
},
{
g.NewElement(13294194396455217955),
g.NewElement(17394768489610594315),
g.NewElement(12847609130464867455),
g.NewElement(14015739446356528640),
g.NewElement(5879251655839607853),
g.NewElement(9747000124977436185),
g.NewElement(8950393546890284269),
g.NewElement(10765765936405694368),
g.NewElement(14695323910334139959),
g.NewElement(16366254691123000864),
g.NewElement(15292774414889043182),
g.NewElement(10910394433429313384),
},
{
g.NewElement(17253424460214596184),
g.NewElement(3442854447664030446),
g.NewElement(3005570425335613727),
g.NewElement(10859158614900201063),
g.NewElement(9763230642109343539),
g.NewElement(6647722546511515039),
g.NewElement(909012944955815706),
g.NewElement(18101204076790399111),
g.NewElement(11588128829349125809),
g.NewElement(15863878496612806566),
g.NewElement(5201119062417750399),
g.NewElement(176665553780565743),
},
}
// Generated randomly for ROUNDS_P
INTERNAL_CONSTANTS = [ROUNDS_P]g.Element{
g.NewElement(11921381764981422944),
g.NewElement(10318423381711320787),
g.NewElement(8291411502347000766),
g.NewElement(229948027109387563),
g.NewElement(9152521390190983261),
g.NewElement(7129306032690285515),
g.NewElement(15395989607365232011),
g.NewElement(8641397269074305925),
g.NewElement(17256848792241043600),
g.NewElement(6046475228902245682),
g.NewElement(12041608676381094092),
g.NewElement(12785542378683951657),
g.NewElement(14546032085337914034),
g.NewElement(3304199118235116851),
g.NewElement(16499627707072547655),
g.NewElement(10386478025625759321),
g.NewElement(13475579315436919170),
g.NewElement(16042710511297532028),
g.NewElement(1411266850385657080),
g.NewElement(9024840976168649958),
g.NewElement(14047056970978379368),
g.NewElement(838728605080212101),
}
// Taken from Plonk3 Poseidon2 implementation. https://github.com/Plonky3/Plonky3/blob/eeb4e37b20127c4daa871b2bad0df30a7c7380db/goldilocks/src/poseidon2.rs#L28
MATRIX_DIAG_12_U64 = [WIDTH]g.Element{
g.NewElement(0xc3b6c08e23ba9300),
g.NewElement(0xd84b5de94a324fb6),
g.NewElement(0x0d0c371c5b35b84f),
g.NewElement(0x7964f570e7188037),
g.NewElement(0x5daf18bbd996604b),
g.NewElement(0x6743bc47b9595257),
g.NewElement(0x5528b9362c59bb70),
g.NewElement(0xac45e25b7127b68b),
g.NewElement(0xa2077d7dfbb606b5),
g.NewElement(0xf3faac6faee378ae),
g.NewElement(0x0c6388b51545e883),
g.NewElement(0xd27dbb6944917b60),
}
)
@@ -0,0 +1,237 @@
package poseidon2
import (
"fmt"
"hash"
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
gFp5 "github.com/elliottech/poseidon_crypto/field/goldilocks_quintic_extension"
)
type HashOut [4]g.Element
type NumericalHashOut [4]uint64
func (h HashOut) ToLittleEndianBytes() []byte {
return g.ArrayToLittleEndianBytes([]g.Element{h[0], h[1], h[2], h[3]})
}
func (h HashOut) ToUint64Array() [4]uint64 {
return [4]uint64{h[0].Uint64(), h[1].Uint64(), h[2].Uint64(), h[3].Uint64()}
}
func HashToQuinticExtension(m []g.Element) gFp5.Element {
res := HashNToMNoPad(m, 5)
return gFp5.Element(res[:])
}
func HashOutFromUint64Array(arr [4]uint64) HashOut {
return HashOut{g.FromUint64(arr[0]), g.FromUint64(arr[1]), g.FromUint64(arr[2]), g.FromUint64(arr[3])}
}
func HashOutFromLittleEndianBytes(b []byte) (HashOut, error) {
if len(b) != 4*g.Bytes {
return HashOut{}, fmt.Errorf("input bytes len should be 32 but is %d", len(b))
}
gArr, err := g.ArrayFromCanonicalLittleEndianBytes(b)
if err != nil {
return HashOut{}, fmt.Errorf("failed to convert bytes to field element. bytes: %v, error: %w", b, err)
}
return HashOut{gArr[0], gArr[1], gArr[2], gArr[3]}, nil
}
func EmptyHashOut() HashOut {
return HashOut{g.Zero(), g.Zero(), g.Zero(), g.Zero()}
}
type Poseidon2 struct{}
func HashNoPad(input []g.Element) HashOut {
return HashNToHashNoPad(input)
}
func HashNToOne(input []HashOut) HashOut {
if len(input) == 1 {
return input[0]
}
res := HashTwoToOne(input[0], input[1])
for i := 2; i < len(input); i++ {
res = HashTwoToOne(res, input[i])
}
return res
}
func HashTwoToOne(input1, input2 HashOut) HashOut {
return HashNToHashNoPad([]g.Element{input1[0], input1[1], input1[2], input1[3], input2[0], input2[1], input2[2], input2[3]})
}
func HashNToHashNoPad(input []g.Element) HashOut {
res := HashNToMNoPad(input, 4)
return HashOut{res[0], res[1], res[2], res[3]}
}
func HashNToMNoPad(input []g.Element, numOutputs int) []g.Element {
var perm [WIDTH]g.Element
for i := 0; i < len(input); i += RATE {
for j := 0; j < RATE && i+j < len(input); j++ {
perm[j].Set(&input[i+j])
}
Permute(&perm)
}
outputs := make([]g.Element, 0, numOutputs)
for {
for i := 0; i < RATE; i++ {
outputs = append(outputs, perm[i])
if len(outputs) == numOutputs {
return outputs
}
}
Permute(&perm)
}
}
func Permute(input *[WIDTH]g.Element) {
externalLinearLayer(input)
fullRounds(input, 0)
partialRounds(input)
fullRounds(input, ROUNDS_F_HALF)
}
func fullRounds(state *[WIDTH]g.Element, start int) {
for r := start; r < start+ROUNDS_F_HALF; r++ {
addRC(state, r)
sbox(state)
externalLinearLayer(state)
}
}
func partialRounds(state *[WIDTH]g.Element) {
for r := 0; r < ROUNDS_P; r++ {
addRCI(state, r)
sboxP(0, state)
internalLinearLayer(state)
}
}
func externalLinearLayer(s *[WIDTH]g.Element) {
for i := 0; i < 3; i++ { // 4 size window
var t0, t1, t2, t3, t4, t5, t6 g.Element
t0.Add(&s[4*i], &s[4*i+1]) // s0+s1
t1.Add(&s[4*i+2], &s[4*i+3]) // s2+s3
t2.Add(&t0, &t1) // t0+t1 = s0+s1+s2+s3
t3.Add(&t2, &s[4*i+1]) // t2+s1 = s0+2s1+s2+s3
t4.Add(&t2, &s[4*i+3]) // t2+s3 = s0+s1+s2+2s3
t5.Double(&s[4*i]) // 2s0
t6.Double(&s[4*i+2]) // 2s2
s[4*i].Add(&t3, &t0)
s[4*i+1].Add(&t6, &t3)
s[4*i+2].Add(&t1, &t4)
s[4*i+3].Add(&t5, &t4)
}
sums := [4]g.Element{}
for k := 0; k < 4; k++ {
for j := 0; j < WIDTH; j += 4 {
sums[k].Add(&sums[k], &s[j+k])
}
}
for i := 0; i < WIDTH; i++ {
s[i].Add(&s[i], &sums[i%4])
}
}
func internalLinearLayer(state *[WIDTH]g.Element) {
var sum g.Element
sum.Set(&state[0])
for i := 1; i < WIDTH; i++ {
sum.Add(&sum, &state[i])
}
for i := 0; i < WIDTH; i++ {
state[i].Mul(&state[i], &MATRIX_DIAG_12_U64[i]).
Add(&state[i], &sum)
}
}
func addRC(state *[WIDTH]g.Element, externalRound int) {
for i := 0; i < WIDTH; i++ {
state[i].Add(&state[i], &EXTERNAL_CONSTANTS[externalRound][i])
}
}
func addRCI(state *[WIDTH]g.Element, round int) {
state[0].Add(&state[0], &INTERNAL_CONSTANTS[round])
}
func sbox(state *[WIDTH]g.Element) {
for i := range state {
sboxP(i, state)
}
}
func sboxP(index int, state *[WIDTH]g.Element) {
var tmp g.Element
tmp.Set(&state[index])
var tmpSquare g.Element
tmpSquare.Square(&tmp)
var tmpSixth g.Element
tmpSixth.Mul(&tmpSquare, &tmp)
tmpSixth.Square(&tmpSixth)
state[index].Mul(&tmpSixth, &tmp)
}
const BlockSize = g.Bytes // BlockSize size that poseidon consumes
type digest struct {
data []g.Element
}
func NewPoseidon2() hash.Hash {
d := new(digest)
d.Reset()
return d
}
// Reset resets the Hash to its initial state.
func (d *digest) Reset() {
d.data = nil
}
// Get element by element.
func (d *digest) Write(p []byte) (n int, err error) {
if len(p)%g.Bytes != 0 {
return 0, fmt.Errorf("input bytes len should be multiple of 8 but is %d", len(p))
}
gArr, err := g.ArrayFromCanonicalLittleEndianBytes(p)
if err != nil {
return 0, fmt.Errorf("failed to convert bytes to field element. bytes: %v, error: %w", p, err)
}
d.data = append(d.data, gArr...)
return len(p), nil
}
func (d *digest) Size() int {
return BlockSize
}
// BlockSize returns the number of bytes Sum will return.
func (d *digest) BlockSize() int {
return BlockSize
}
// Sum appends the current hash to b and returns the resulting slice.
// It does not change the underlying hash state.
func (d *digest) Sum(b []byte) []byte {
b = append(b, HashNToHashNoPad(d.data).ToLittleEndianBytes()...)
d.data = nil
return b
}
@@ -0,0 +1,279 @@
package poseidon2
import (
"math"
"testing"
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
)
func TestPermute(t *testing.T) {
inp := [WIDTH]g.Element{
g.FromUint64(5417613058500526590),
g.FromUint64(2481548824842427254),
g.FromUint64(6473243198879784792),
g.FromUint64(1720313757066167274),
g.FromUint64(2806320291675974571),
g.FromUint64(7407976414706455446),
g.FromUint64(1105257841424046885),
g.FromUint64(7613435757403328049),
g.FromUint64(3376066686066811538),
g.FromUint64(5888575799323675710),
g.FromUint64(6689309723188675948),
g.FromUint64(2468250420241012720),
}
Permute(&inp)
expected := [WIDTH]g.Element{
g.FromUint64(5364184781011389007),
g.FromUint64(15309475861242939136),
g.FromUint64(5983386513087443499),
g.FromUint64(886942118604446276),
g.FromUint64(14903657885227062600),
g.FromUint64(7742650891575941298),
g.FromUint64(1962182278500985790),
g.FromUint64(10213480816595178755),
g.FromUint64(3510799061817443836),
g.FromUint64(4610029967627506430),
g.FromUint64(7566382334276534836),
g.FromUint64(2288460879362380348),
}
for i := 0; i < WIDTH; i++ {
if inp[i] != expected[i] {
t.Logf("Expected: %d, got: %d\n", expected[i], inp[i])
t.Fail()
}
}
}
func TestHashNToMNoPad(t *testing.T) {
inp := [WIDTH]g.Element{
g.FromUint64(2963773914414780088),
g.FromUint64(8389525300242074234),
g.FromUint64(3700959901615818008),
g.FromUint64(6116199383751757212),
g.FromUint64(3418607418699599889),
g.FromUint64(8793277256263635044),
g.FromUint64(448623437464918480),
g.FromUint64(1857310021116627925),
g.FromUint64(6145634616307237342),
g.FromUint64(1548353948794474539),
g.FromUint64(2318110128254703527),
g.FromUint64(8347759953730634762),
}
res := HashNToMNoPad(inp[:], 12)
expected := [WIDTH]g.Element{
g.FromUint64(3627923032009111551),
g.FromUint64(1460752551327577353),
g.FromUint64(1084214837491058067),
g.FromUint64(1841622875286057462),
g.FromUint64(3996252440506437984),
g.FromUint64(1276718204392552803),
g.FromUint64(8564515621134952155),
g.FromUint64(9252927025993202701),
g.FromUint64(1147435538714642916),
g.FromUint64(16407277821156164797),
g.FromUint64(11997661877740155273),
g.FromUint64(12485021000320141292),
}
for i := 0; i < 12; i++ {
if res[i] != expected[i] {
t.Fail()
}
}
}
func TestDigest(t *testing.T) {
hFunc := NewPoseidon2()
inputs := make([][]byte, 2)
inputs[0] = make([]byte, 8)
inputs[0][0] = 1
inputs[0][1] = 2
inputs[0][2] = 3
inputs[0][3] = 4
inputs[0][4] = 5
inputs[0][5] = 6
inputs[0][6] = 7
inputs[0][7] = 0
inputs[1] = make([]byte, 8)
inputs[1][0] = 7
inputs[1][1] = 6
inputs[1][2] = 5
inputs[1][3] = 4
inputs[1][4] = 3
inputs[1][5] = 2
inputs[1][6] = 1
inputs[1][7] = 0
g1, _ := g.FromCanonicalLittleEndianBytes(inputs[0]) // 289077004332300282
g2, _ := g.FromCanonicalLittleEndianBytes(inputs[1]) // 289644378102298614
hFunc.Write(inputs[0])
hFunc.Write(inputs[1])
hash := hFunc.Sum(nil)
hash2Elems := HashNoPad([]g.Element{*g1, *g2})
hash2 := hash2Elems.ToLittleEndianBytes()
for i := 0; i < len(hash); i++ {
if hash[i] != hash2[i] {
t.Fail()
}
}
reconstructed, _ := HashOutFromLittleEndianBytes(hash)
for i := 0; i < 4; i++ {
if hash2Elems[i] != reconstructed[i] {
t.Fail()
}
}
}
func TestHashNToHashNoPad(t *testing.T) {
res := HashNToHashNoPad([]g.Element{
g.FromUint64(11295517158488612626),
g.FromUint64(10669470463693797151),
g.FromUint64(17232114065640264171),
g.FromUint64(4175927072186299193),
g.FromUint64(13985285184240204531),
g.FromUint64(7901017084268693144),
g.FromUint64(4326299618263946178),
g.FromUint64(14787024750292535041),
g.FromUint64(894520636503353046),
g.FromUint64(12556655399058578835),
g.FromUint64(3097737892474696200),
g.FromUint64(7515335668060050861),
})
expected := HashOut{
g.FromUint64(15396602476382546759),
g.FromUint64(12422280135166335470),
g.FromUint64(8165681190607828974),
g.FromUint64(3475588160239961712),
}
for i := 0; i < 4; i++ {
if res[i] != expected[i] {
t.Fail()
}
}
}
func TestHashNToHashNoPadLarge(t *testing.T) {
res := HashNToHashNoPad([]g.Element{
g.FromUint64(g.ORDER + 1),
g.FromUint64(g.ORDER + 2),
g.FromUint64(g.ORDER + 3),
g.FromUint64(math.MaxUint64),
g.FromUint64(math.MaxUint64 - 1),
})
expected := HashOut{
g.FromUint64(14216040864787980138),
g.FromUint64(17275303675000904868),
g.FromUint64(11831395338463193314),
g.FromUint64(281267649235863375),
}
for i := 0; i < 4; i++ {
if res[i] != expected[i] {
t.Logf("Expected: [%v], got: [%v]\n", g.ToString(expected[:]...), g.ToString(res[:]...))
t.FailNow()
}
}
}
func TestHashTwoToOne(t *testing.T) {
input1 := HashOut{
g.FromUint64(3777312593917610528),
g.FromUint64(6858608920877200812),
g.FromUint64(5269611035257552853),
g.FromUint64(10607733449481270434),
}
input2 := HashOut{
g.FromUint64(10355703322562521155),
g.FromUint64(1039917189921776884),
g.FromUint64(10844249567941924238),
g.FromUint64(14291130953945924124),
}
expected := HashOut{
g.FromUint64(1453933811752520343),
g.FromUint64(16186418140372484281),
g.FromUint64(9207215809524681813),
g.FromUint64(10182182911172027974),
}
res := HashTwoToOne(input1, input2)
for i := 0; i < 4; i++ {
if res[i] != expected[i] {
t.Fail()
}
}
}
func TestHashNToOne(t *testing.T) {
hashIns := []HashOut{HashNToHashNoPad([]g.Element{
g.FromUint64(18231458557829081414),
g.FromUint64(16449039301999856654),
g.FromUint64(14758090268883299362),
g.FromUint64(10271725147130672875),
g.FromUint64(6253304685402495037),
g.FromUint64(16079709420464120062),
g.FromUint64(10838593640248082543),
g.FromUint64(2974225335734585509),
g.FromUint64(6365466669981419503),
g.FromUint64(12964544245312854826),
g.FromUint64(3161534615047618958),
g.FromUint64(15109271288782125222),
})}
for i := 1; i < 12; i++ {
hashIns = append(hashIns, HashTwoToOne(hashIns[i-1], hashIns[i-1]))
}
res := HashNToOne(hashIns)
expected := HashOut{
g.FromUint64(3346041518891302234),
g.FromUint64(10181430332820953144),
g.FromUint64(14852547783810217847),
g.FromUint64(17043509806476508794),
}
for i := 0; i < 4; i++ {
if res[i] != expected[i] {
t.Fail()
}
}
}
func TestHashToQuinticExtension(t *testing.T) {
result := HashToQuinticExtension([]g.Element{
g.FromUint64(3451004116618606032),
g.FromUint64(11263134342958518251),
g.FromUint64(10957204882857370932),
g.FromUint64(5369763041201481933),
g.FromUint64(7695734348563036858),
g.FromUint64(1393419330378128434),
g.FromUint64(7387917082382606332),
})
expected := [5]uint64{
17992684813643984528,
5243896189906434327,
7705560276311184368,
2785244775876017560,
14449776097783372302,
}
for i := 0; i < 5; i++ {
if result[i] != g.FromUint64(expected[i]) {
t.Logf("Expected limb %d to be %x, but got %x", i, expected[i], result[i])
t.Fail()
}
}
}
@@ -0,0 +1,174 @@
package poseidon2_plonky2
import g "github.com/elliottech/poseidon_crypto/field/goldilocks"
const (
WIDTH = 12
RATE = 8
OUT = 4
D = 7
// Generated by `poseidon2_round_numbers_128`
ROUNDS_F = 8
ROUNDS_F_HALF = 4
ROUNDS_P = 22
)
var (
// Generated randomly for ROUNDS_F
EXTERNAL_CONSTANTS = [ROUNDS_F][WIDTH]g.GoldilocksField{
{
15492826721047263190,
11728330187201910315,
8836021247773420868,
16777404051263952451,
5510875212538051896,
6173089941271892285,
2927757366422211339,
10340958981325008808,
8541987352684552425,
9739599543776434497,
15073950188101532019,
12084856431752384512,
},
{
4584713381960671270,
8807052963476652830,
54136601502601741,
4872702333905478703,
5551030319979516287,
12889366755535460989,
16329242193178844328,
412018088475211848,
10505784623379650541,
9758812378619434837,
7421979329386275117,
375240370024755551,
},
{
3331431125640721931,
15684937309956309981,
578521833432107983,
14379242000670861838,
17922409828154900976,
8153494278429192257,
15904673920630731971,
11217863998460634216,
3301540195510742136,
9937973023749922003,
3059102938155026419,
1895288289490976132,
},
{
5580912693628927540,
10064804080494788323,
9582481583369602410,
10186259561546797986,
247426333829703916,
13193193905461376067,
6386232593701758044,
17954717245501896472,
1531720443376282699,
2455761864255501970,
11234429217864304495,
4746959618548874102,
},
{
13571697342473846203,
17477857865056504753,
15963032953523553760,
16033593225279635898,
14252634232868282405,
8219748254835277737,
7459165569491914711,
15855939513193752003,
16788866461340278896,
7102224659693946577,
3024718005636976471,
13695468978618890430,
},
{
8214202050877825436,
2670727992739346204,
16259532062589659211,
11869922396257088411,
3179482916972760137,
13525476046633427808,
3217337278042947412,
14494689598654046340,
15837379330312175383,
8029037639801151344,
2153456285263517937,
8301106462311849241,
},
{
13294194396455217955,
17394768489610594315,
12847609130464867455,
14015739446356528640,
5879251655839607853,
9747000124977436185,
8950393546890284269,
10765765936405694368,
14695323910334139959,
16366254691123000864,
15292774414889043182,
10910394433429313384,
},
{
17253424460214596184,
3442854447664030446,
3005570425335613727,
10859158614900201063,
9763230642109343539,
6647722546511515039,
909012944955815706,
18101204076790399111,
11588128829349125809,
15863878496612806566,
5201119062417750399,
176665553780565743,
},
}
// Generated randomly for ROUNDS_P
INTERNAL_CONSTANTS = [ROUNDS_P]g.GoldilocksField{
11921381764981422944,
10318423381711320787,
8291411502347000766,
229948027109387563,
9152521390190983261,
7129306032690285515,
15395989607365232011,
8641397269074305925,
17256848792241043600,
6046475228902245682,
12041608676381094092,
12785542378683951657,
14546032085337914034,
3304199118235116851,
16499627707072547655,
10386478025625759321,
13475579315436919170,
16042710511297532028,
1411266850385657080,
9024840976168649958,
14047056970978379368,
838728605080212101,
}
// Taken from Plonk3 Poseidon2 implementation. https://github.com/Plonky3/Plonky3/blob/eeb4e37b20127c4daa871b2bad0df30a7c7380db/goldilocks/src/poseidon2.rs#L28
MATRIX_DIAG_12_U64 = [WIDTH]g.GoldilocksField{
0xc3b6c08e23ba9300,
0xd84b5de94a324fb6,
0x0d0c371c5b35b84f,
0x7964f570e7188037,
0x5daf18bbd996604b,
0x6743bc47b9595257,
0x5528b9362c59bb70,
0xac45e25b7127b68b,
0xa2077d7dfbb606b5,
0xf3faac6faee378ae,
0x0c6388b51545e883,
0xd27dbb6944917b60,
}
)
@@ -0,0 +1,262 @@
package poseidon2_plonky2
import (
"fmt"
"hash"
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
gFp5 "github.com/elliottech/poseidon_crypto/field/goldilocks_quintic_extension"
)
type HashOut [4]g.GoldilocksField
type NumericalHashOut [4]uint64
func EmptyHashOut() HashOut {
return HashOut{g.ZeroF(), g.ZeroF(), g.ZeroF(), g.ZeroF()}
}
func (h HashOut) ToLittleEndianBytes() []byte {
res := make([]byte, 0, 4*g.Bytes)
for _, elem := range h {
res = append(res, g.ToLittleEndianBytesF(elem)...)
}
return res
}
func HashOutFromLittleEndianBytes(b []byte) (HashOut, error) {
if len(b) != 4*g.Bytes {
return HashOut{}, fmt.Errorf("input bytes len should be 32 but is %d", len(b))
}
var res HashOut
for i := 0; i < 4; i++ {
res[i] = g.FromCanonicalLittleEndianBytesF(b[i*g.Bytes : (i+1)*g.Bytes])
}
return res, nil
}
func (h HashOut) ToUint64Array() [4]uint64 {
return [4]uint64{uint64(h[0]), uint64(h[1]), uint64(h[2]), uint64(h[3])}
}
func HashOutFromUint64Array(arr [4]uint64) HashOut {
return HashOut{g.GoldilocksField(arr[0]), g.GoldilocksField(arr[1]), g.GoldilocksField(arr[2]), g.GoldilocksField(arr[3])}
}
func HashToQuinticExtension(m []g.GoldilocksField) gFp5.Element {
res := HashNToMNoPad(m, 5)
return gFp5.FromPlonky2GoldilocksField(res[:])
}
type Poseidon2 struct{}
func HashNoPad(input []g.GoldilocksField) HashOut {
return HashNToHashNoPad(input)
}
func HashNToOne(input []HashOut) HashOut {
if len(input) == 1 {
return input[0]
}
res := HashTwoToOne(input[0], input[1])
for i := 2; i < len(input); i++ {
res = HashTwoToOne(res, input[i])
}
return res
}
func HashTwoToOne(input1, input2 HashOut) HashOut {
return HashNToHashNoPad([]g.GoldilocksField{input1[0], input1[1], input1[2], input1[3], input2[0], input2[1], input2[2], input2[3]})
}
func HashNToHashNoPad(input []g.GoldilocksField) HashOut {
res := HashNToMNoPad(input, 4)
return HashOut{res[0], res[1], res[2], res[3]}
}
func HashNToMNoPad(input []g.GoldilocksField, numOutputs int) []g.GoldilocksField {
var perm [WIDTH]g.GoldilocksField
for i := 0; i < len(input); i += RATE {
for j := 0; j < RATE && i+j < len(input); j++ {
perm[j] = input[i+j]
}
Permute(&perm)
}
outputs := make([]g.GoldilocksField, 0, numOutputs)
for {
for i := 0; i < RATE; i++ {
outputs = append(outputs, perm[i])
if len(outputs) == numOutputs {
return outputs
}
}
Permute(&perm)
}
}
func HashNToMNoPadBytes(input []byte, numOutputs int) []g.GoldilocksField {
if len(input)%g.Bytes != 0 {
panic("input length should be multiple of 8")
}
inputLen := len(input) / g.Bytes
var perm [WIDTH]g.GoldilocksField
for i := 0; i < inputLen; i += RATE {
for j := 0; j < RATE && i+j < inputLen; j++ {
index := (i + j) * g.Bytes
perm[j] = g.FromCanonicalLittleEndianBytesF(input[index : index+g.Bytes])
}
Permute(&perm)
}
outputs := make([]g.GoldilocksField, 0, numOutputs)
for {
for i := 0; i < RATE; i++ {
outputs = append(outputs, perm[i])
if len(outputs) == numOutputs {
return outputs
}
}
Permute(&perm)
}
}
func Permute(input *[WIDTH]g.GoldilocksField) {
externalLinearLayer(input)
fullRounds(input, 0)
partialRounds(input)
fullRounds(input, ROUNDS_F_HALF)
}
func fullRounds(state *[WIDTH]g.GoldilocksField, start int) {
for r := start; r < start+ROUNDS_F_HALF; r++ {
addRC(state, r)
sbox(state)
externalLinearLayer(state)
}
}
func partialRounds(state *[WIDTH]g.GoldilocksField) {
for r := 0; r < ROUNDS_P; r++ {
addRCI(state, r)
sboxP(0, state)
internalLinearLayer(state)
}
}
func externalLinearLayer(s *[WIDTH]g.GoldilocksField) {
for i := 0; i < 3; i++ { // 4 size window
var t0, t1, t2, t3, t4, t5, t6 g.GoldilocksField
t0 = g.AddF(s[4*i], s[4*i+1]) // s0+s1
t1 = g.AddF(s[4*i+2], s[4*i+3]) // s2+s3
t2 = g.AddF(t0, t1) // t0+t1 = s0+s1+s2+s3
t3 = g.AddF(t2, s[4*i+1]) // t2+s1 = s0+2s1+s2+s3
t4 = g.AddF(t2, s[4*i+3]) // t2+s3 = s0+s1+s2+2s3
t5 = g.DoubleF(s[4*i]) // 2s0
t6 = g.DoubleF(s[4*i+2]) // 2s2
s[4*i] = g.AddF(t3, t0)
s[4*i+1] = g.AddF(t6, t3)
s[4*i+2] = g.AddF(t1, t4)
s[4*i+3] = g.AddF(t5, t4)
}
sums := [4]g.GoldilocksField{}
for k := 0; k < 4; k++ {
for j := 0; j < WIDTH; j += 4 {
sums[k] = g.AddF(sums[k], s[j+k])
}
}
for i := 0; i < WIDTH; i++ {
s[i] = g.AddF(s[i], sums[i%4])
}
}
func internalLinearLayer(state *[WIDTH]g.GoldilocksField) {
sum := state[0]
for i := 1; i < WIDTH; i++ {
sum = g.AddF(sum, state[i])
}
for i := 0; i < WIDTH; i++ {
state[i] = g.MulF(state[i], MATRIX_DIAG_12_U64[i])
state[i] = g.AddF(state[i], sum)
}
}
func addRC(state *[WIDTH]g.GoldilocksField, externalRound int) {
for i := 0; i < WIDTH; i++ {
state[i] = g.AddF(state[i], EXTERNAL_CONSTANTS[externalRound][i])
}
}
func addRCI(state *[WIDTH]g.GoldilocksField, round int) {
state[0] = g.AddF(state[0], INTERNAL_CONSTANTS[round])
}
func sbox(state *[WIDTH]g.GoldilocksField) {
for i := range state {
sboxP(i, state)
}
}
func sboxP(index int, state *[WIDTH]g.GoldilocksField) {
tmp := state[index]
tmpSquare := g.SquareF(tmp)
var tmpSixth g.GoldilocksField
tmpSixth = g.MulF(tmpSquare, tmp)
tmpSixth = g.SquareF(tmpSixth)
state[index] = g.MulF(tmpSixth, tmp)
}
const BlockSize = g.Bytes * WIDTH // BlockSize size that poseidon consumes
type digest struct {
data []byte
len int
}
func NewPoseidon2() hash.Hash {
d := new(digest)
return d
}
// Reset resets the Hash to its initial state.
func (d *digest) Reset() {
d.data = d.data[:0]
d.len = 0
}
// Get element by element.
func (d *digest) Write(p []byte) (n int, err error) {
d.data = append(d.data, p...)
d.len += len(p)
return len(p), nil
}
// Sum appends the current hash to b and returns the resulting slice.
// It does not change the underlying hash state.
func (d *digest) Sum(b []byte) []byte {
h := HashNToMNoPadBytes(d.data, 4)
d.Reset()
for _, elem := range h {
b = append(b, g.ToLittleEndianBytesF(elem)...)
}
return b
}
func (d *digest) Size() int {
return BlockSize
}
// BlockSize returns the number of bytes Sum will return.
func (d *digest) BlockSize() int {
return BlockSize
}
@@ -0,0 +1,285 @@
package poseidon2_plonky2
import (
"bytes"
"math"
"testing"
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
)
func TestPermute(t *testing.T) {
inp := [WIDTH]g.GoldilocksField{
5417613058500526590,
2481548824842427254,
6473243198879784792,
1720313757066167274,
2806320291675974571,
7407976414706455446,
1105257841424046885,
7613435757403328049,
3376066686066811538,
5888575799323675710,
6689309723188675948,
2468250420241012720,
}
Permute(&inp)
expected := [WIDTH]g.GoldilocksField{
5364184781011389007,
15309475861242939136,
5983386513087443499,
886942118604446276,
14903657885227062600,
7742650891575941298,
1962182278500985790,
10213480816595178755,
3510799061817443836,
4610029967627506430,
7566382334276534836,
2288460879362380348,
}
for i := 0; i < WIDTH; i++ {
if inp[i] != expected[i] {
t.Logf("Expected: %d, got: %d\n", expected[i], inp[i])
t.Fail()
}
}
}
func TestHashNToMNoPad(t *testing.T) {
inp := [WIDTH]g.GoldilocksField{
2963773914414780088,
8389525300242074234,
3700959901615818008,
6116199383751757212,
3418607418699599889,
8793277256263635044,
448623437464918480,
1857310021116627925,
6145634616307237342,
1548353948794474539,
2318110128254703527,
8347759953730634762,
}
res := HashNToMNoPad(inp[:], 12)
expected := [WIDTH]g.GoldilocksField{
3627923032009111551,
1460752551327577353,
1084214837491058067,
1841622875286057462,
3996252440506437984,
1276718204392552803,
8564515621134952155,
9252927025993202701,
1147435538714642916,
16407277821156164797,
11997661877740155273,
12485021000320141292,
}
for i := 0; i < 12; i++ {
if res[i] != expected[i] {
t.Fail()
}
}
}
func TestDigest(t *testing.T) {
hFunc := NewPoseidon2()
inputs := make([][]byte, 2)
inputs[0] = make([]byte, 8)
inputs[0][0] = 1
inputs[0][1] = 2
inputs[0][2] = 3
inputs[0][3] = 4
inputs[0][4] = 5
inputs[0][5] = 6
inputs[0][6] = 7
inputs[0][7] = 0
inputs[1] = make([]byte, 8)
inputs[1][0] = 7
inputs[1][1] = 6
inputs[1][2] = 5
inputs[1][3] = 4
inputs[1][4] = 3
inputs[1][5] = 2
inputs[1][6] = 1
inputs[1][7] = 0
g1 := g.FromCanonicalLittleEndianBytesF(inputs[0]) // 289077004332300282
g2 := g.FromCanonicalLittleEndianBytesF(inputs[1]) // 289644378102298614
hFunc.Write(inputs[0])
hFunc.Write(inputs[1])
hash := hFunc.Sum(nil)
hash2Elems := HashNoPad([]g.GoldilocksField{g1, g2})
hash2 := hash2Elems.ToLittleEndianBytes()
if !bytes.Equal(hash, hash2) {
t.Logf("Expected: %v, got: %v\n", hash2, hash)
t.Fail()
}
reconstructed, err := HashOutFromLittleEndianBytes(hash)
if err != nil {
t.Logf("Error: %v\n", err)
t.FailNow()
}
for i := 0; i < 4; i++ {
if hash2Elems[i] != reconstructed[i] {
t.Logf("Expected: %d, got: %d\n", hash2Elems[i], reconstructed[i])
t.Fail()
}
}
}
func TestHashNToHashNoPad(t *testing.T) {
res := HashNToHashNoPad([]g.GoldilocksField{
11295517158488612626,
10669470463693797151,
17232114065640264171,
4175927072186299193,
13985285184240204531,
7901017084268693144,
4326299618263946178,
14787024750292535041,
894520636503353046,
12556655399058578835,
3097737892474696200,
7515335668060050861,
})
expected := HashOut{
15396602476382546759,
12422280135166335470,
8165681190607828974,
3475588160239961712,
}
for i := 0; i < 4; i++ {
if res[i] != expected[i] {
t.Fail()
}
}
}
func TestHashNToHashNoPadLarge(t *testing.T) {
res := HashNToHashNoPad([]g.GoldilocksField{
g.GoldilocksField(g.ORDER + 1),
g.GoldilocksField(g.ORDER + 2),
g.GoldilocksField(g.ORDER + 3),
g.GoldilocksField(math.MaxUint64),
g.GoldilocksField(math.MaxUint64 - 1),
})
expected := HashOut{
14216040864787980138,
17275303675000904868,
11831395338463193314,
281267649235863375,
}
for i := 0; i < 4; i++ {
if res[i] != expected[i] {
t.Logf("Expected: %v, got: %v\n", expected, res)
t.FailNow()
}
}
}
func TestHashTwoToOne(t *testing.T) {
input1 := HashOut{
3777312593917610528,
6858608920877200812,
5269611035257552853,
10607733449481270434,
}
input2 := HashOut{
10355703322562521155,
1039917189921776884,
10844249567941924238,
14291130953945924124,
}
expected := HashOut{
1453933811752520343,
16186418140372484281,
9207215809524681813,
10182182911172027974,
}
res := HashTwoToOne(input1, input2)
for i := 0; i < 4; i++ {
if res[i] != expected[i] {
t.Fail()
}
}
}
func TestHashNToOne(t *testing.T) {
hashIns := []HashOut{HashNToHashNoPad([]g.GoldilocksField{
18231458557829081414,
16449039301999856654,
14758090268883299362,
10271725147130672875,
6253304685402495037,
16079709420464120062,
10838593640248082543,
2974225335734585509,
6365466669981419503,
12964544245312854826,
3161534615047618958,
15109271288782125222,
})}
for i := 1; i < 12; i++ {
hashIns = append(hashIns, HashTwoToOne(hashIns[i-1], hashIns[i-1]))
}
res := HashNToOne(hashIns)
expected := HashOut{
3346041518891302234,
10181430332820953144,
14852547783810217847,
17043509806476508794,
}
for i := 0; i < 4; i++ {
if res[i] != expected[i] {
t.Fail()
}
}
}
func TestHashToQuinticExtension(t *testing.T) {
result := HashToQuinticExtension([]g.GoldilocksField{
3451004116618606032,
11263134342958518251,
10957204882857370932,
5369763041201481933,
7695734348563036858,
1393419330378128434,
7387917082382606332,
})
expected := [5]uint64{
17992684813643984528,
5243896189906434327,
7705560276311184368,
2785244775876017560,
14449776097783372302,
}
for i := 0; i < 5; i++ {
if result[i] != g.FromUint64(expected[i]) {
t.Logf("Expected limb %d to be %x, but got %x", i, expected[i], result[i])
t.Fail()
}
}
}
@@ -0,0 +1,280 @@
package hash
import (
"bufio"
"crypto/sha256"
"fmt"
"math/rand/v2"
"os"
"runtime"
"strconv"
"strings"
"testing"
"time"
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
poseidon2_gnark "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks"
poseidon2_plonky2 "github.com/elliottech/poseidon_crypto/hash/poseidon2_goldilocks_plonky2"
)
func TestLongRunningCompare(t *testing.T) {
run := os.Getenv("LONG_RUNNING_TESTS")
if run != "true" {
t.Skip("Skipping long running test")
}
file := os.Getenv("LONG_RUNNING_TESTS_FILE")
// Generate random 12 inputs
for j := 0; j < 1_000_000_000; j++ {
inputs := make([]uint64, 12)
for i := 0; i < 12; i++ {
inputs[i] = rand.Uint64N(g.ORDER)
}
// Convert to GoldilocksField
gInputs := make([]g.GoldilocksField, 0, 12)
for _, input := range inputs {
gInputs = append(gInputs, g.GoldilocksField(input))
}
gOutputs := poseidon2_plonky2.HashNToMNoPad(gInputs, 12)
// Convert to Element
eInputs := make([]g.Element, 0, 12)
for _, input := range inputs {
eInputs = append(eInputs, g.NewElement(input))
}
eOutputs := poseidon2_gnark.HashNToMNoPad(eInputs, 12)
// Compare
for i := 0; i < 12; i++ {
if gOutputs[i].ToCanonicalUint64() != eOutputs[i].Uint64() {
if file != "" {
f, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
t.Logf("Error: %v\n", err)
t.FailNow()
}
defer f.Close()
_, err = f.WriteString("<--- Mismatch --->\n")
if err != nil {
t.Logf("Error: %v\n", err)
t.FailNow()
}
_, err = f.WriteString(fmt.Sprintf("Inputs: %v; %v; [%v]\n", inputs, gInputs, g.ToString(eInputs...)))
if err != nil {
t.Logf("Error: %v\n", err)
t.FailNow()
}
_, err = f.WriteString(fmt.Sprintf("Outputs: %v; [%v]\n\n", gOutputs, g.ToString(eOutputs...)))
if err != nil {
t.Logf("Error: %v\n", err)
t.FailNow()
}
f.Close()
} else {
t.Log("<--- Mismatch --->")
t.Logf("Inputs: %v; %v; [%v]", inputs, gInputs, g.ToString(eInputs...))
t.Logf("Outputs: %v; [%v]", gOutputs, g.ToString(eOutputs...))
t.FailNow()
}
break
}
}
}
t.Log("Completed long-running comparison.")
}
func TestPoseidon2Bench(t *testing.T) {
inputs, err := readBenchInputs("bench_vector")
totalInputs := len(inputs)
if err != nil {
t.Logf("Error: %v\n", err)
t.FailNow()
}
for i := 0; i < 10; i++ {
PrintMemUsage()
results := make([]g.GoldilocksField, 0, 4*len(inputs))
start := time.Now()
for _, input := range inputs {
res := poseidon2_plonky2.HashNToHashNoPad(input)
results = append(results, res[:]...)
}
duration := time.Since(start)
t.Logf("HashNToHashNoPad plonky2 took %s for %d inputs", duration, totalInputs)
sha2 := sha256.New()
for _, res := range results {
sha2.Write(g.ToLittleEndianBytesF(res))
}
t.Logf("Hash: %x\n", sha2.Sum(nil))
}
}
func TestPoseidon2HasherBench(t *testing.T) {
inputs, err := readBenchInputsBytes("bench_vector")
totalInputs := len(inputs)
if err != nil {
t.Logf("Error: %v\n", err)
t.FailNow()
}
hasher := poseidon2_plonky2.NewPoseidon2()
start1 := time.Now()
for i := 0; i < 10; i++ {
PrintMemUsage()
results := make([]byte, 0, 4*8*len(inputs))
start := time.Now()
for _, input := range inputs {
for _, b := range input {
hasher.Write(b)
}
res := hasher.Sum(nil)
hasher.Reset()
results = append(results, res...)
}
duration := time.Since(start)
t.Logf("Hasher plonky2 took %s for %d inputs", duration, totalInputs)
sha2 := sha256.New()
sha2.Write(results)
t.Logf("Hash: %x\n", sha2.Sum(nil))
}
duration := time.Since(start1)
t.Logf("===> Hasher plonky2 took %s", duration)
}
func TestPoseidon2BenchOld(t *testing.T) {
inputs, err := readBenchInputsOld("bench_vector")
totalInputs := len(inputs)
if err != nil {
t.Logf("Error: %v\n", err)
t.FailNow()
}
results := make([]g.Element, 0, 4*len(inputs))
start := time.Now()
for _, input := range inputs {
res := poseidon2_gnark.HashNToHashNoPad(input)
results = append(results, res[:]...)
}
duration := time.Since(start)
t.Logf("HashNToHashNoPadPure gnark took %s for %d inputs", duration, totalInputs)
sha2 := sha256.New()
for _, res := range results {
sha2.Write(g.ToLittleEndianBytes(res))
}
hash := sha2.Sum(nil)
t.Logf("Hash: %x\n", hash)
}
func readBenchInputs(filename string) ([][]g.GoldilocksField, error) {
file, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("failed to open file: %v", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var inputs [][]g.GoldilocksField
for scanner.Scan() {
line := scanner.Text()
strVals := strings.Split(line, ",")
var input []g.GoldilocksField
for _, strVal := range strVals {
val, err := strconv.ParseUint(strVal, 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse uint64: %v", err)
}
input = append(input, g.GoldilocksField(val))
}
inputs = append(inputs, input)
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("failed to read file: %v", err)
}
return inputs, nil
}
func readBenchInputsBytes(filename string) ([][][]byte, error) {
file, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("failed to open file: %v", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var inputs [][][]byte
for scanner.Scan() {
line := scanner.Text()
strVals := strings.Split(line, ",")
var input [][]byte
for _, strVal := range strVals {
val, err := strconv.ParseUint(strVal, 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse uint64: %v", err)
}
input = append(input, g.ToLittleEndianBytesF(g.GoldilocksField(val)))
}
inputs = append(inputs, input)
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("failed to read file: %v", err)
}
return inputs, nil
}
func readBenchInputsOld(filename string) ([][]g.Element, error) {
file, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("failed to open file: %v", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var inputs [][]g.Element
for scanner.Scan() {
line := scanner.Text()
strVals := strings.Split(line, ",")
var input []g.Element
for _, strVal := range strVals {
val, err := strconv.ParseUint(strVal, 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse uint64: %v", err)
}
input = append(input, g.NewElement(val))
}
inputs = append(inputs, input)
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("failed to read file: %v", err)
}
return inputs, nil
}
func PrintMemUsage() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
// For info on each, see: https://golang.org/pkg/runtime/#MemStats
fmt.Printf("Alloc = %v Bytes", m.Alloc)
fmt.Printf("\tTotalAlloc = %v Bytes", m.TotalAlloc)
fmt.Printf("\tSys = %v Bytes", m.Sys)
fmt.Printf("\tNumGC = %v\n", m.NumGC)
}
@@ -0,0 +1,67 @@
package poseidon_bn254
import (
"math/big"
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
"github.com/elliottech/poseidon_crypto/hash/poseidon_bn254/constants"
)
// Number of full rounds
const rf = 8
var alpha = big.NewInt(5)
// Number of partial rounds rounded up to nearest integer that divides by t in [2, 13]
var rp = []int{56, 57, 56, 60, 60, 63, 64, 63, 60, 66, 60, 65, 70, 60, 64, 68}
// Round constants and matrices
var (
c, s [][]*fr.Element
m, p [][][]*fr.Element
)
func toElement(value string) *fr.Element {
n, success := new(big.Int).SetString(value, 16)
if !success {
panic("Error parsing hex number")
}
e := fr.Element{0, 0, 0, 0}
e.SetBigInt(n)
return &e
}
func init() {
var size = len(rp)
c = make([][]*fr.Element, size)
s = make([][]*fr.Element, size)
m = make([][][]*fr.Element, size)
p = make([][][]*fr.Element, size)
for i := 0; i < size; i++ {
// initialize round constants and matrices
c[i] = make([]*fr.Element, len(constants.CStr[i]))
s[i] = make([]*fr.Element, len(constants.SStr[i]))
m[i] = make([][]*fr.Element, len(constants.MStr[i]))
p[i] = make([][]*fr.Element, len(constants.PStr[i]))
for j := 0; j < len(c[i]); j++ {
c[i][j] = toElement(constants.CStr[i][j])
}
for j := 0; j < len(s[i]); j++ {
s[i][j] = toElement(constants.SStr[i][j])
}
for j := 0; j < len(m[i]); j++ {
m[i][j] = make([]*fr.Element, len(constants.MStr[i][j]))
for k := 0; k < len(m[i][j]); k++ {
m[i][j][k] = toElement(constants.MStr[i][j][k])
}
}
for j := 0; j < len(p[i]); j++ {
p[i][j] = make([]*fr.Element, len(constants.PStr[i][j]))
for k := 0; k < len(p[i][j]); k++ {
p[i][j][k] = toElement(constants.PStr[i][j][k])
}
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,208 @@
package poseidon_bn254
import (
"errors"
"hash"
"math/big"
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
)
const (
BlockSize = fr.Bytes // BlockSize size that poseidon consumes
)
func zeroElement() *fr.Element {
return &fr.Element{0, 0, 0, 0}
}
func deepCopy(dst, src []*fr.Element) {
if len(src) > len(dst) {
panic("Cannot copy to a smaller destination")
}
for i := 0; i < len(src); i++ {
v := *src[i]
dst[i] = &v
}
}
// Add round constants
func arc(state []*fr.Element, C []*fr.Element, t, offset int) {
for i := 0; i < t; i++ {
state[i].Add(state[i], C[offset+i])
}
}
// power 5 as s-box for full state
func sbox(state []*fr.Element, t int) {
for i := 0; i < t; i++ {
state[i].Exp(*state[i], alpha)
}
}
// Matrix vector multiplication
func mix(state []*fr.Element, M [][]*fr.Element, t int) []*fr.Element {
newState := make([]*fr.Element, t)
for i := 0; i < t; i++ {
newState[i] = zeroElement()
for j := 0; j < t; j++ {
newState[i].Add(newState[i], zeroElement().Mul(M[j][i], state[j]))
}
}
return newState
}
func permutation(state []*fr.Element) []*fr.Element {
// Minimum length of state = nInput + nOutput = 2
t := len(state)
index := t - 2
RP := rp[index]
C := c[index]
M := m[index]
S := s[index]
P := p[index]
// 1. Pre-step to the first-half of full rounds: add round constant for round=0
arc(state, C, t, 0)
// 2. First-half of full rounds starting at roundNumber = 1 except last round
for i := 0; i < rf/2-1; i++ {
sbox(state, t)
arc(state, C, t, (i+1)*t)
state = mix(state, M, t)
}
// 3. Last round of first-half of full rounds
sbox(state, t)
arc(state, C, t, (rf/2)*t)
state = mix(state, P, t)
// 4. Partial rounds
for i := 0; i < RP; i++ {
state[0].Exp(*state[0], alpha)
state[0].Add(state[0], C[(rf/2+1)*t+i])
// S[i] is a vector of [t*2-1] elements where first t elements are used to compute state[0]
// and the remaining elements starting at [t] are used to compute state[1,..,t-1]
offset := (t*2 - 1) * i
newState0 := zeroElement()
for j := 0; j < len(state); j++ {
newState0.Add(newState0, zeroElement().Mul(state[j], S[offset+j]))
}
offset += t - 1
for k := 1; k < t; k++ {
state[k].Add(state[k], zeroElement().Mul(state[0], S[offset+k]))
}
state[0] = newState0
}
// 5. Second-half of full rounds except last round
for i := 0; i < rf/2-1; i++ {
sbox(state, t)
arc(state, C, t, (rf/2+1)*t+RP+i*t)
state = mix(state, M, t)
}
// 6. Last round of the second-half of full rounds
sbox(state, t)
state = mix(state, M, t)
return state
}
func Poseidon(input ...*fr.Element) *fr.Element {
inputLength := len(input)
if inputLength == 0 {
panic("No support for dummy input")
}
const maxLength = 16
state := make([]*fr.Element, maxLength+1)
state[0] = zeroElement()
startIndex := 0
lastIndex := 0
// Make a hash chain of the input if its length > maxLength
if inputLength > maxLength {
count := inputLength / maxLength
for i := 0; i < count; i++ {
lastIndex = (i + 1) * maxLength
deepCopy(state[1:], input[startIndex:lastIndex])
state = permutation(state)
startIndex = lastIndex
}
}
// For the remaining part of the input OR if 1 <= inputLength <= 16
if lastIndex < inputLength {
lastIndex = inputLength
remainigLength := lastIndex - startIndex
deepCopy(state[1:], input[startIndex:lastIndex])
state = permutation(state[:remainigLength+1])
}
// Return capacity element 1
return state[1]
}
func PoseidonBytes(input ...[]byte) []byte {
inputElements := make([]*fr.Element, len(input))
for i, ele := range input {
num := new(big.Int).SetBytes(ele)
if num.Cmp(fr.Modulus()) >= 0 {
panic("not support bytes bigger than modulus")
}
e := fr.Element{0, 0, 0, 0}
e.SetBigInt(num)
inputElements[i] = &e
}
res := Poseidon(inputElements...).Bytes()
return res[:]
}
type digest struct {
h fr.Element
data [][]byte // data to hash
}
func NewPoseidon() hash.Hash {
d := new(digest)
d.Reset()
return d
}
// Reset resets the Hash to its initial state.
func (d *digest) Reset() {
d.data = nil
d.h = fr.Element{0, 0, 0, 0}
}
// Only receive byte slice less than fr.Modulus()
func (d *digest) Write(p []byte) (n int, err error) {
n = len(p)
num := new(big.Int).SetBytes(p)
if num.Cmp(fr.Modulus()) >= 0 {
return 0, errors.New("not support bytes bigger than modulus")
}
d.data = append(d.data, p)
return n, nil
}
func (d *digest) Size() int {
return BlockSize
}
// BlockSize returns the number of bytes Sum will return.
func (d *digest) BlockSize() int {
return BlockSize
}
// Sum appends the current hash to b and returns the resulting slice.
// It does not change the underlying hash state.
func (d *digest) Sum(b []byte) []byte {
e := fr.Element{0, 0, 0, 0}
e.SetBigInt(new(big.Int).SetBytes(PoseidonBytes(d.data...)))
d.h = e
d.data = nil // flush the data already hashed
hash := d.h.Bytes()
b = append(b, hash[:]...)
return b
}
@@ -0,0 +1,184 @@
package poseidon_bn254_test
import (
"math/big"
"testing"
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
"github.com/elliottech/poseidon_crypto/hash/poseidon_bn254"
"github.com/stretchr/testify/assert"
)
func elementFromString(v string) *fr.Element {
n, success := new(big.Int).SetString(v, 10)
if !success {
panic("Error parsing hex number")
}
var e fr.Element
e.SetBigInt(n)
return &e
}
func elementFromStringHex(v string) *fr.Element {
n, success := new(big.Int).SetString(v, 16)
if !success {
panic("Error parsing hex number")
}
var e fr.Element
e.SetBigInt(n)
return &e
}
func TestPoseidon1(t *testing.T) {
// WARNING: No test vector to compare with
expectedHash := elementFromString("7764075183688725171230668857402392634761334547267776368103645048439717572548")
length := 1
inputs := make([]*fr.Element, length)
for i := 0; i < length; i++ {
e := fr.NewElement((uint64)(i + 1))
inputs[i] = &e
}
actualHash := poseidon_bn254.Poseidon(inputs...)
assert.True(t, actualHash.Equal(expectedHash), "%s != %s", actualHash, expectedHash)
}
func TestPoseidon2(t *testing.T) {
// WARNING: No test vector to compare with
expectedHash := elementFromString("7142104613055408817911962100316808866448378443474503659992478482890339429929")
length := 2
inputs := make([]*fr.Element, length)
for i := 0; i < length; i++ {
e := fr.NewElement((uint64)(i + 1))
inputs[i] = &e
}
actualHash := poseidon_bn254.Poseidon(inputs...)
assert.True(t, actualHash.Equal(expectedHash), "%s != %s", actualHash, expectedHash)
}
func TestPoseidon4(t *testing.T) {
// WARNING: No test vector to compare with
expectedHash := elementFromString("7817711165059374331357136443537800893307845083525445872661165200086166013245")
length := 4
inputs := make([]*fr.Element, length)
for i := 0; i < length; i++ {
e := fr.NewElement((uint64)(i + 1))
inputs[i] = &e
}
actualHash := poseidon_bn254.Poseidon(inputs...)
assert.True(t, actualHash.Equal(expectedHash), "%s != %s", actualHash, expectedHash)
}
func TestPoseidon13(t *testing.T) {
// WARNING: No test vector to compare with
expectedHash := elementFromString("1709610050961943784828399921362905178787999827108026634048665681910636069934")
length := 13
inputs := make([]*fr.Element, length)
for i := 0; i < length; i++ {
e := fr.NewElement((uint64)(i + 1))
inputs[i] = &e
}
actualHash := poseidon_bn254.Poseidon(inputs...)
assert.True(t, actualHash.Equal(expectedHash), "%s != %s", actualHash, expectedHash)
}
func TestPoseidon16(t *testing.T) {
// WARNING: No test vector to compare with
expectedHash := elementFromString("8319791455060392555425392842391403897548969645190976863995973180967774875286")
length := 16
inputs := make([]*fr.Element, length)
for i := 0; i < length; i++ {
e := fr.NewElement((uint64)(i + 1))
inputs[i] = &e
}
actualHash := poseidon_bn254.Poseidon(inputs...)
assert.True(t, actualHash.Equal(expectedHash), "%s != %s", actualHash, expectedHash)
}
func TestPoseidon24(t *testing.T) {
// WARNING: No test vector to compare with
expectedHash := elementFromString("14281896993318141900551144554156181598834585543901557749703302979893059224887")
length := 24
inputs := make([]*fr.Element, length)
for i := 0; i < length; i++ {
e := fr.NewElement((uint64)(i + 1))
inputs[i] = &e
}
actualHash := poseidon_bn254.Poseidon(inputs...)
assert.True(t, actualHash.Equal(expectedHash), "%s != %s", actualHash, expectedHash)
}
func TestPoseidon30(t *testing.T) {
// WARNING: No test vector to compare with
expectedHash := elementFromString("3706864405066113783363062549980271879113588784557216652303342540436728346372")
length := 30
inputs := make([]*fr.Element, length)
for i := 0; i < length; i++ {
e := fr.NewElement((uint64)(i + 1))
inputs[i] = &e
}
actualHash := poseidon_bn254.Poseidon(inputs...)
assert.True(t, actualHash.Equal(expectedHash), "%s != %s", actualHash, expectedHash)
}
func TestPoseidon256(t *testing.T) {
// WARNING: No test vector to compare with
expectedHash := elementFromString("3889232958018785041730045800798978544000060048890444628344970190264245196615")
length := 256
inputs := make([]*fr.Element, length)
for i := 0; i < length; i++ {
e := fr.NewElement((uint64)(i + 1))
inputs[i] = &e
}
actualHash := poseidon_bn254.Poseidon(inputs...)
assert.True(t, actualHash.Equal(expectedHash), "%s != %s", actualHash, expectedHash)
}
func TestConsistency(t *testing.T) {
// Check whether Poseidon returns the same value for the same input
// Test vector https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/test_vectors.txt
inputsStr := []string{"1", "2", "3", "4"}
inputs := make([]*fr.Element, len(inputsStr))
for i := 0; i < len(inputsStr); i++ {
inputs[i] = elementFromString(inputsStr[i])
}
actualHash1 := poseidon_bn254.Poseidon(inputs...)
actualHash2 := poseidon_bn254.Poseidon(inputs...)
assert.True(t, actualHash1.Equal(actualHash2), "%s != %s", actualHash1, actualHash2)
}
func TestPoseidonBytes(t *testing.T) {
// Test vector https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/test_vectors.txt
expectedHash := elementFromStringHex("FCA49B798923AB0239DE1C9E7A4A9A2210312B6A2F616D18B5A87F9B628AE29")
inputs := make([][]byte, 2)
inputs[0] = make([]byte, 1)
inputs[0][0] = 1
inputs[1] = make([]byte, 1)
inputs[1][0] = 2
actualHash := poseidon_bn254.PoseidonBytes(inputs...)
actualHashEle := fr.Element{0, 0, 0, 0}
actualHashEle.SetBytes(actualHash)
assert.True(t, actualHashEle.Equal(expectedHash), "%s != %s", actualHashEle, expectedHash)
}
func TestDigest(t *testing.T) {
expectedHash := elementFromStringHex("FCA49B798923AB0239DE1C9E7A4A9A2210312B6A2F616D18B5A87F9B628AE29")
hFunc := poseidon_bn254.NewPoseidon()
inputs := make([][]byte, 2)
inputs[0] = make([]byte, 1)
inputs[0][0] = 1
inputs[1] = make([]byte, 1)
inputs[1][0] = 2
hFunc.Write(inputs[0])
hFunc.Write(inputs[1])
actualHash := hFunc.Sum(nil)
actualHashEle := fr.Element{0, 0, 0, 0}
actualHashEle.SetBytes(actualHash)
assert.True(t, actualHashEle.Equal(expectedHash), "%s != %s", actualHashEle, expectedHash)
hFunc.Reset()
bigNumber, _ := new(big.Int).SetString("21888242871839275222246405745257275088548364400416034343698204186575808495617", 10)
inputs[0] = bigNumber.Bytes()
n, err := hFunc.Write(inputs[0])
assert.EqualError(t, err, "not support bytes bigger than modulus")
assert.Equal(t, n, 0)
}