diff --git a/field/mamabear/poseidon2/hash.go b/field/mamabear/poseidon2/hash.go index a7f3f3c62..7689cd25f 100644 --- a/field/mamabear/poseidon2/hash.go +++ b/field/mamabear/poseidon2/hash.go @@ -34,15 +34,15 @@ func NewDefaultPermutation() *Permutation { // // 1. for compression: // - width: 16 -// - nbFullRounds: 6 -// - nbPartialRounds: 21 +// - nbFullRounds: 8 +// - nbPartialRounds: 32 // // 2. for sponge: // - width: 24 -// - nbFullRounds: 6 -// - nbPartialRounds: 21 +// - nbFullRounds: 8 +// - nbPartialRounds: 32 var GetDefaultParameters = sync.OnceValue(func() *Parameters { - return NewParameters(16, 6, 21) + return NewParameters(16, 8, 32) }) var diag16 []fr.Element = make([]fr.Element, 16) diff --git a/field/mamabear/poseidon2/poseidon2_test.go b/field/mamabear/poseidon2/poseidon2_test.go index de9cb2062..f26d59464 100644 --- a/field/mamabear/poseidon2/poseidon2_test.go +++ b/field/mamabear/poseidon2/poseidon2_test.go @@ -19,7 +19,7 @@ func TestMulMulInternalInPlaceWidth16(t *testing.T) { expected = input - h := NewPermutation(16, 6, 21) + h := NewPermutation(16, 8, 32) h.matMulInternalInPlace(expected[:]) var sum fr.Element @@ -44,7 +44,7 @@ func TestMulMulInternalInPlaceWidth24(t *testing.T) { expected = input - h := NewPermutation(24, 6, 21) + h := NewPermutation(24, 8, 32) h.matMulInternalInPlace(expected[:]) var sum fr.Element @@ -81,24 +81,24 @@ func TestPoseidon2Width16(t *testing.T) { input[14].SetUint64(32212254727) input[15].SetUint64(34359738375) - expected[0].SetUint64(379305722682392) - expected[1].SetUint64(434710812510599) - expected[2].SetUint64(550056781736517) - expected[3].SetUint64(80018479061411) - expected[4].SetUint64(197065607198553) - expected[5].SetUint64(184550828188449) - expected[6].SetUint64(169985134836) - expected[7].SetUint64(476980079287507) - expected[8].SetUint64(265028309154312) - expected[9].SetUint64(443033730245920) - expected[10].SetUint64(90181392038270) - expected[11].SetUint64(525395989823088) - expected[12].SetUint64(172442016884945) - expected[13].SetUint64(482775715596317) - expected[14].SetUint64(128007781181334) - expected[15].SetUint64(454435891437885) - - h := NewPermutation(16, 6, 21) + expected[0].SetUint64(50602402429679) + expected[1].SetUint64(205154033184305) + expected[2].SetUint64(171690306064314) + expected[3].SetUint64(290743356206654) + expected[4].SetUint64(52057371620722) + expected[5].SetUint64(357993730760812) + expected[6].SetUint64(30792446669472) + expected[7].SetUint64(560929088275684) + expected[8].SetUint64(351129771614234) + expected[9].SetUint64(167825709741404) + expected[10].SetUint64(435628772411913) + expected[11].SetUint64(263726494550579) + expected[12].SetUint64(253184459815810) + expected[13].SetUint64(470108995398990) + expected[14].SetUint64(245845535172115) + expected[15].SetUint64(339090281054048) + + h := NewPermutation(16, 8, 32) h.Permutation(input[:]) for i := range h.params.Width { if !input[i].Equal(&expected[i]) { @@ -135,32 +135,32 @@ func TestPoseidon2Width24(t *testing.T) { input[22].SetUint64(49392123911) input[23].SetUint64(51539607559) - expected[0].SetUint64(368164608783752) - expected[1].SetUint64(257867004539105) - expected[2].SetUint64(201370080365479) - expected[3].SetUint64(356624278773042) - expected[4].SetUint64(124735134016360) - expected[5].SetUint64(205149928229598) - expected[6].SetUint64(226995027181250) - expected[7].SetUint64(329421188535075) - expected[8].SetUint64(472269735666797) - expected[9].SetUint64(336822262946217) - expected[10].SetUint64(286203220709665) - expected[11].SetUint64(106913007484484) - expected[12].SetUint64(159916680148593) - expected[13].SetUint64(421687845713200) - expected[14].SetUint64(133004491284203) - expected[15].SetUint64(184002038378053) - expected[16].SetUint64(92327348080370) - expected[17].SetUint64(117778158779886) - expected[18].SetUint64(492904809075045) - expected[19].SetUint64(559938928111919) - expected[20].SetUint64(299216349767739) - expected[21].SetUint64(249756944698650) - expected[22].SetUint64(549327931909224) - expected[23].SetUint64(184289500831330) - - h := NewPermutation(24, 6, 21) + expected[0].SetUint64(221949310464874) + expected[1].SetUint64(80986520567350) + expected[2].SetUint64(188405925758112) + expected[3].SetUint64(265381136928946) + expected[4].SetUint64(531029040459252) + expected[5].SetUint64(267299294930657) + expected[6].SetUint64(232053702598666) + expected[7].SetUint64(125866528816944) + expected[8].SetUint64(375645824840808) + expected[9].SetUint64(493982275312219) + expected[10].SetUint64(491313642825887) + expected[11].SetUint64(225430104088042) + expected[12].SetUint64(12298241502257) + expected[13].SetUint64(174077256549267) + expected[14].SetUint64(211511214646258) + expected[15].SetUint64(461473402745453) + expected[16].SetUint64(373037057867494) + expected[17].SetUint64(16199147773576) + expected[18].SetUint64(527007590318455) + expected[19].SetUint64(280256541869745) + expected[20].SetUint64(457055166286236) + expected[21].SetUint64(438914526671414) + expected[22].SetUint64(454144458652198) + expected[23].SetUint64(499223682805339) + + h := NewPermutation(24, 8, 32) h.Permutation(input[:]) for i := range h.params.Width { if !input[i].Equal(&expected[i]) { @@ -170,7 +170,7 @@ func TestPoseidon2Width24(t *testing.T) { } func BenchmarkPoseidon2Width16(b *testing.B) { - h := NewPermutation(16, 6, 21) + h := NewPermutation(16, 8, 32) var tmp [16]fr.Element for i := range tmp { @@ -183,7 +183,7 @@ func BenchmarkPoseidon2Width16(b *testing.B) { } func BenchmarkPoseidon2Width24(b *testing.B) { - h := NewPermutation(24, 6, 21) + h := NewPermutation(24, 8, 32) var tmp [24]fr.Element for i := range tmp { @@ -194,3 +194,37 @@ func BenchmarkPoseidon2Width24(b *testing.B) { h.Permutation(tmp[:]) } } + +// TestDefaultRoundNumbers pins the round numbers to the security analysis they +// come from, so they cannot be changed silently. +// +// Eq. (1) of the Poseidon2 paper (https://eprint.iacr.org/2023/323.pdf), for +// n = ceil(log2(p)) = 49, d = 3 and kappa = 128: +// +// R_F = 8 +// R_P = ceil(1.075 * max(R_interp, R_GB)) +// R_interp = ceil(min{kappa,n}/log2(d)) + ceil(log_d(t)) - 5 = 29 (binding) +// R_GB = 25 +// => R_P = ceil(1.075 * 29) = 32 +// +// The bound scales with min{kappa, log2(p)}, so it is NOT safe to inherit these +// from a narrower field: koalabear saturates at n = 31 and needs only 20 +// partial rounds, while mamabear needs 32. +func TestDefaultRoundNumbers(t *testing.T) { + p := GetDefaultParameters() + if p.Width != 16 { + t.Fatalf("width: got %d, want 16", p.Width) + } + if p.NbFullRounds != 8 { + t.Fatalf("full rounds: got %d, want 8", p.NbFullRounds) + } + if p.NbPartialRounds != 32 { + t.Fatalf("partial rounds: got %d, want 32", p.NbPartialRounds) + } + if DegreeSBox() != 3 { + t.Fatalf("sbox degree: got %d, want 3 (smallest d with gcd(d, p-1) = 1)", DegreeSBox()) + } + if len(p.RoundKeys) != p.NbFullRounds+p.NbPartialRounds { + t.Fatalf("round keys: got %d, want %d", len(p.RoundKeys), p.NbFullRounds+p.NbPartialRounds) + } +} diff --git a/internal/generator/field/generator_poseidon2.go b/internal/generator/field/generator_poseidon2.go index f0d5d7e19..f39a2a90b 100644 --- a/internal/generator/field/generator_poseidon2.go +++ b/internal/generator/field/generator_poseidon2.go @@ -119,14 +119,30 @@ func generatePoseidon2(F *config.Field, outputDir string) error { data.ParamsCompression, } case "mamabear": - // Same round structure and the same semantic diagonal as koalabear; the - // entries below are those values reduced mod the mamabear prime. They - // are canonical residues, not Montgomery-encoded, so they do not depend - // on the radix. + // The diagonal is koalabear's, reduced mod the mamabear prime; the + // entries are canonical residues, not Montgomery-encoded, so they do not + // depend on the radix. + // + // The round numbers are NOT koalabear's. They follow Eq. (1) of the + // Poseidon2 paper (https://eprint.iacr.org/2023/323.pdf) for n = 49, + // t = 16/24, d = 3 at kappa = 128: + // + // R_F = 8 + // R_P = ceil(1.075 * max(R_interp, R_GB)) = ceil(1.075 * 29) = 32 + // + // where R_interp = ceil(min{kappa,n}/log2(d)) + ceil(log_d(t)) - 5 = 29 + // is the binding constraint. The bound grows with min{kappa, log2(p)}, + // so a wider field needs MORE rounds, not fewer: koalabear saturates at + // n = 31 and needs R_P = 20, while mamabear at n = 49 needs 32. Reusing + // koalabear's 21 here would target roughly 33 bits of security. + // + // The same formula reproduces Plonky3's shipped constants for babybear + // (13/21/30 at t = 16/24/32) and koalabear (20/23/31), and Table 1 of + // the paper. data.ParamsCompression = amd64.Poseidon2Parameters{ Width: 16, - FullRounds: 6, - PartialRounds: 21, + FullRounds: 8, + PartialRounds: 32, SBoxDegree: 3, // [-2, 1, 2, 1/2, 3, 4, -1/2, -3, -4, 1/2^8, 1/8, 1/2^24, -1/2^8, -1/8, -1/16, -1/2^24] DiagInternal: []uint64{562932773552127, 1, 2, 281466386776065, 3, 4, 281466386776064, 562932773552126, 562932773552125, 560733817405441, 492566176858113, 562932739998721, 2198956146688, 70366596694016, 35183298347008, 33553408}, @@ -134,8 +150,8 @@ func generatePoseidon2(F *config.Field, outputDir string) error { data.ParamsSponge = amd64.Poseidon2Parameters{ Width: 24, - FullRounds: 6, - PartialRounds: 21, + FullRounds: 8, + PartialRounds: 32, SBoxDegree: 3, // [-2, 1, 2, 1/2, 3, 4, -1/2, -3, -4, 1/2^8, 1/4, 1/8, 1/16, 1/32, 1/64, 1/2^24, -1/2^8, -1/8, -1/16, -1/32, -1/64, -1/2^7, -1/2^9, -1/2^24] DiagInternal: []uint64{562932773552127, 1, 2, 281466386776065, 3, 4, 281466386776064, 562932773552126, 562932773552125, 560733817405441, 422199580164097, 492566176858113, 527749475205121, 545341124378625, 554136948965377, 562932739998721, 2198956146688, 70366596694016, 35183298347008, 17591649173504, 8795824586752, 4397912293376, 1099478073344, 33553408}, diff --git a/internal/generator/field/template/poseidon2/poseidon2_test.go.tmpl b/internal/generator/field/template/poseidon2/poseidon2_test.go.tmpl index 56ac0af96..8000db024 100644 --- a/internal/generator/field/template/poseidon2/poseidon2_test.go.tmpl +++ b/internal/generator/field/template/poseidon2/poseidon2_test.go.tmpl @@ -276,24 +276,24 @@ func TestPoseidon2Width16(t *testing.T) { input[14].SetUint64(32212254727) input[15].SetUint64(34359738375) - expected[0].SetUint64(379305722682392) - expected[1].SetUint64(434710812510599) - expected[2].SetUint64(550056781736517) - expected[3].SetUint64(80018479061411) - expected[4].SetUint64(197065607198553) - expected[5].SetUint64(184550828188449) - expected[6].SetUint64(169985134836) - expected[7].SetUint64(476980079287507) - expected[8].SetUint64(265028309154312) - expected[9].SetUint64(443033730245920) - expected[10].SetUint64(90181392038270) - expected[11].SetUint64(525395989823088) - expected[12].SetUint64(172442016884945) - expected[13].SetUint64(482775715596317) - expected[14].SetUint64(128007781181334) - expected[15].SetUint64(454435891437885) + expected[0].SetUint64(50602402429679) + expected[1].SetUint64(205154033184305) + expected[2].SetUint64(171690306064314) + expected[3].SetUint64(290743356206654) + expected[4].SetUint64(52057371620722) + expected[5].SetUint64(357993730760812) + expected[6].SetUint64(30792446669472) + expected[7].SetUint64(560929088275684) + expected[8].SetUint64(351129771614234) + expected[9].SetUint64(167825709741404) + expected[10].SetUint64(435628772411913) + expected[11].SetUint64(263726494550579) + expected[12].SetUint64(253184459815810) + expected[13].SetUint64(470108995398990) + expected[14].SetUint64(245845535172115) + expected[15].SetUint64(339090281054048) - h := NewPermutation(16, 6, 21) + h := NewPermutation({{- $w0}}, {{- .ParamsCompression.FullRounds}}, {{- .ParamsCompression.PartialRounds}}) {{- else}} input[0].SetUint64(595602690) input[1].SetUint64(847709907) @@ -421,32 +421,32 @@ func TestPoseidon2Width24(t *testing.T) { input[22].SetUint64(49392123911) input[23].SetUint64(51539607559) - expected[0].SetUint64(368164608783752) - expected[1].SetUint64(257867004539105) - expected[2].SetUint64(201370080365479) - expected[3].SetUint64(356624278773042) - expected[4].SetUint64(124735134016360) - expected[5].SetUint64(205149928229598) - expected[6].SetUint64(226995027181250) - expected[7].SetUint64(329421188535075) - expected[8].SetUint64(472269735666797) - expected[9].SetUint64(336822262946217) - expected[10].SetUint64(286203220709665) - expected[11].SetUint64(106913007484484) - expected[12].SetUint64(159916680148593) - expected[13].SetUint64(421687845713200) - expected[14].SetUint64(133004491284203) - expected[15].SetUint64(184002038378053) - expected[16].SetUint64(92327348080370) - expected[17].SetUint64(117778158779886) - expected[18].SetUint64(492904809075045) - expected[19].SetUint64(559938928111919) - expected[20].SetUint64(299216349767739) - expected[21].SetUint64(249756944698650) - expected[22].SetUint64(549327931909224) - expected[23].SetUint64(184289500831330) + expected[0].SetUint64(221949310464874) + expected[1].SetUint64(80986520567350) + expected[2].SetUint64(188405925758112) + expected[3].SetUint64(265381136928946) + expected[4].SetUint64(531029040459252) + expected[5].SetUint64(267299294930657) + expected[6].SetUint64(232053702598666) + expected[7].SetUint64(125866528816944) + expected[8].SetUint64(375645824840808) + expected[9].SetUint64(493982275312219) + expected[10].SetUint64(491313642825887) + expected[11].SetUint64(225430104088042) + expected[12].SetUint64(12298241502257) + expected[13].SetUint64(174077256549267) + expected[14].SetUint64(211511214646258) + expected[15].SetUint64(461473402745453) + expected[16].SetUint64(373037057867494) + expected[17].SetUint64(16199147773576) + expected[18].SetUint64(527007590318455) + expected[19].SetUint64(280256541869745) + expected[20].SetUint64(457055166286236) + expected[21].SetUint64(438914526671414) + expected[22].SetUint64(454144458652198) + expected[23].SetUint64(499223682805339) - h := NewPermutation(24, 6, 21) + h := NewPermutation({{- $w1}}, {{- .ParamsSponge.FullRounds}}, {{- .ParamsSponge.PartialRounds}}) {{- else}} input[0].SetUint64(568554527) input[1].SetUint64(1037389773) @@ -880,3 +880,40 @@ func BenchmarkCompressx16ColumnsWithStateVariableSize(b *testing.B) { } } {{- end}} + +{{- if eq .FF "mamabear"}} + +// TestDefaultRoundNumbers pins the round numbers to the security analysis they +// come from, so they cannot be changed silently. +// +// Eq. (1) of the Poseidon2 paper (https://eprint.iacr.org/2023/323.pdf), for +// n = ceil(log2(p)) = 49, d = 3 and kappa = 128: +// +// R_F = 8 +// R_P = ceil(1.075 * max(R_interp, R_GB)) +// R_interp = ceil(min{kappa,n}/log2(d)) + ceil(log_d(t)) - 5 = 29 (binding) +// R_GB = 25 +// => R_P = ceil(1.075 * 29) = 32 +// +// The bound scales with min{kappa, log2(p)}, so it is NOT safe to inherit these +// from a narrower field: koalabear saturates at n = 31 and needs only 20 +// partial rounds, while mamabear needs 32. +func TestDefaultRoundNumbers(t *testing.T) { + p := GetDefaultParameters() + if p.Width != {{ .ParamsCompression.Width }} { + t.Fatalf("width: got %d, want {{ .ParamsCompression.Width }}", p.Width) + } + if p.NbFullRounds != 8 { + t.Fatalf("full rounds: got %d, want 8", p.NbFullRounds) + } + if p.NbPartialRounds != 32 { + t.Fatalf("partial rounds: got %d, want 32", p.NbPartialRounds) + } + if DegreeSBox() != 3 { + t.Fatalf("sbox degree: got %d, want 3 (smallest d with gcd(d, p-1) = 1)", DegreeSBox()) + } + if len(p.RoundKeys) != p.NbFullRounds+p.NbPartialRounds { + t.Fatalf("round keys: got %d, want %d", len(p.RoundKeys), p.NbFullRounds+p.NbPartialRounds) + } +} +{{- end}}