Skip to content

Commit d3f2a02

Browse files
committed
refactor: address Ivo's review
1 parent aae27cf commit d3f2a02

3 files changed

Lines changed: 134 additions & 30 deletions

File tree

ecc/octobear/multiset-hash/cardano_test.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,130 @@ func TestDepressedCubicRootFindsValidRoot(t *testing.T) {
8181
require.True(t, lhs.IsZero())
8282
}
8383
}
84+
85+
// cardanoDelta returns the dispatch discriminant delta = 108 - 27·c² used by
86+
// cardanoRoots to choose a branch (zero → repeatedRoots, non-square →
87+
// quadratic-extension branch, square → base-field branch).
88+
func cardanoDelta(c *extensions.E8) extensions.E8 {
89+
var c2, delta extensions.E8
90+
c2.Square(c)
91+
delta.Mul(&c2, &e8TwentySeven)
92+
delta.Sub(&e8Neg4A3, &delta)
93+
return delta
94+
}
95+
96+
func checkCubicRoots(t *testing.T, c extensions.E8, roots []extensions.E8) {
97+
t.Helper()
98+
require.NotEmpty(t, roots, "cardanoRoots returned no roots")
99+
for _, x := range roots {
100+
var lhs, x3 extensions.E8
101+
x3.Square(&x).Mul(&x3, &x)
102+
lhs.Set(&x3)
103+
lhs.Sub(&lhs, &x).Sub(&lhs, &x).Sub(&lhs, &x).Add(&lhs, &c)
104+
require.True(t, lhs.IsZero(), "root does not satisfy x^3 - 3x + c = 0")
105+
}
106+
}
107+
108+
// TestCardanoRepeatedRootBranch exercises the delta = 0 branch with c = 2:
109+
// x^3 - 3x + 2 = (x-1)^2 (x+2), so the dispatcher must hit repeatedRoots.
110+
func TestCardanoRepeatedRootBranch(t *testing.T) {
111+
var c extensions.E8
112+
c.C0.B0.A0.SetUint64(2)
113+
114+
delta := cardanoDelta(&c)
115+
require.True(t, delta.IsZero(), "c = 2 must drive delta to zero")
116+
117+
roots := cardanoRoots(c)
118+
checkCubicRoots(t, c, roots)
119+
120+
var one, negTwo extensions.E8
121+
one.SetOne()
122+
negTwo.SetOne().Double(&negTwo).Neg(&negTwo)
123+
var foundOne, foundNegTwo bool
124+
for _, x := range roots {
125+
if x.Equal(&one) {
126+
foundOne = true
127+
}
128+
if x.Equal(&negTwo) {
129+
foundNegTwo = true
130+
}
131+
}
132+
require.True(t, foundOne, "repeated-root branch must produce x = 1")
133+
require.True(t, foundNegTwo, "repeated-root branch must produce x = -2")
134+
}
135+
136+
// TestCardanoBaseFieldBranch exercises the square-delta path. We build c from
137+
// a known root x in the prime subfield: c = 3x - x^3 guarantees that x solves
138+
// x^3 - 3x + c = 0, and any c whose components all lie in Fp produces a delta
139+
// that is a square in E8 (since [E8 : Fp] = 8 is even, every element of Fp is
140+
// a square in E8). Whether Cardano can recover roots through E8 depends on
141+
// whether the cube root needed by the formula lies in E8, so we search across
142+
// x values until the dispatcher returns a non-empty set including x.
143+
func TestCardanoBaseFieldBranch(t *testing.T) {
144+
for n := uint64(3); n < 10_000; n++ {
145+
var x extensions.E8
146+
x.C0.B0.A0.SetUint64(n)
147+
148+
var c, x3 extensions.E8
149+
x3.Square(&x).Mul(&x3, &x)
150+
c.Double(&x).Add(&c, &x).Sub(&c, &x3)
151+
152+
delta := cardanoDelta(&c)
153+
if delta.IsZero() {
154+
continue
155+
}
156+
require.Equal(t, 1, delta.Legendre(), "c in Fp must give a square delta in E8")
157+
158+
roots := cardanoRoots(c)
159+
if len(roots) == 0 {
160+
continue
161+
}
162+
checkCubicRoots(t, c, roots)
163+
var found bool
164+
for _, r := range roots {
165+
if r.Equal(&x) {
166+
found = true
167+
break
168+
}
169+
}
170+
if !found {
171+
continue
172+
}
173+
return
174+
}
175+
t.Fatal("could not find a base-field-branch witness in [3, 10000)")
176+
}
177+
178+
// TestCardanoQuadraticExtensionBranch exercises the non-square-delta path.
179+
// To force delta to be a non-square in E8, c must have a non-trivial
180+
// extension component (any element of the prime subfield is a square in E8).
181+
// We construct x with both a base and an extension component, compute
182+
// c = 3x - x^3, and search for one whose delta is a non-square.
183+
func TestCardanoQuadraticExtensionBranch(t *testing.T) {
184+
for n := uint64(1); n < 4096; n++ {
185+
var x extensions.E8
186+
x.C0.B0.A0.SetUint64(n)
187+
x.C1.B0.A0.SetUint64(1)
188+
189+
var c, x3 extensions.E8
190+
x3.Square(&x).Mul(&x3, &x)
191+
c.Double(&x).Add(&c, &x).Sub(&c, &x3)
192+
193+
delta := cardanoDelta(&c)
194+
if delta.IsZero() || delta.Legendre() != -1 {
195+
continue
196+
}
197+
roots := cardanoRoots(c)
198+
checkCubicRoots(t, c, roots)
199+
var found bool
200+
for _, r := range roots {
201+
if r.Equal(&x) {
202+
found = true
203+
break
204+
}
205+
}
206+
require.True(t, found, "extension branch must recover x at n = %d", n)
207+
return
208+
}
209+
t.Fatal("could not find a quadratic-extension-branch witness in [1, 4096)")
210+
}

ecc/octobear/multiset-hash/multiset_hash.go

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ import (
44
"errors"
55

66
"github.com/consensys/gnark-crypto/ecc/octobear"
7-
"github.com/consensys/gnark-crypto/field/koalabear/extensions"
87
)
98

109
const tweakBound = 256
1110

12-
var errMapFailure = errors.New("octobear multiset hash: failed to map message after 256 y-increments")
11+
var errMapFailure = errors.New("octobear multiset hash: failed to map message in tweak window")
1312

1413
// Accumulator stores an additive multiset hash state in affine coordinates.
1514
type Accumulator struct {
@@ -70,26 +69,5 @@ func Hash(msgs []uint16) (octobear.G1Affine, error) {
7069
// y = msg*256 + k yields a point (x, y) on octobear.
7170
func Map(msg uint16) (octobear.G1Affine, uint8, error) {
7271
_, b := octobear.CurveCoefficients()
73-
baseY := uint64(msg) * tweakBound
74-
75-
for k := uint16(0); k < tweakBound; k++ {
76-
var y, c, ySquared extensions.E8
77-
y.SetZero()
78-
y.C0.B0.A0.SetUint64(baseY + uint64(k))
79-
80-
ySquared.Square(&y)
81-
c.Sub(&b, &ySquared)
82-
83-
x, ok := depressedCubicRoot(c)
84-
if !ok {
85-
continue
86-
}
87-
88-
p := octobear.G1Affine{X: x, Y: y}
89-
if p.IsOnCurve() && p.IsInSubGroup() {
90-
return p, uint8(k), nil
91-
}
92-
}
93-
94-
return octobear.G1Affine{}, 0, errMapFailure
72+
return mapAtBase(uint64(msg)*tweakBound, tweakBound, &b)
9573
}

ecc/octobear/multiset-hash/vector_multiset_hash_linear.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package multisethash
22

33
import (
4-
"errors"
54
"fmt"
65

76
"github.com/consensys/gnark-crypto/ecc/octobear"
@@ -115,11 +114,11 @@ func MapLinear(msg uint32) ([linearN]octobear.G1Affine, [linearN]uint8, error) {
115114
return pts, offsets, nil
116115
}
117116

118-
// mapAtBase scans k in [0, tweakBound) and returns the first curve point
119-
// whose ordinate is y = baseY + k in the base subfield. baseY + tweakBound
117+
// mapAtBase scans k in [0, bound) and returns the first curve point
118+
// whose ordinate is y = baseY + k in the base subfield. baseY + bound
120119
// must remain strictly below p/2 to keep the image inverse-free.
121-
func mapAtBase(baseY uint64, tweakBound uint64, b *extensions.E8) (octobear.G1Affine, uint8, error) {
122-
for k := uint64(0); k < tweakBound; k++ {
120+
func mapAtBase(baseY uint64, bound uint64, b *extensions.E8) (octobear.G1Affine, uint8, error) {
121+
for k := uint64(0); k < bound; k++ {
123122
var y, c, ySquared extensions.E8
124123
y.C0.B0.A0.SetUint64(baseY + k)
125124

@@ -136,5 +135,5 @@ func mapAtBase(baseY uint64, tweakBound uint64, b *extensions.E8) (octobear.G1Af
136135
return p, uint8(k), nil
137136
}
138137
}
139-
return octobear.G1Affine{}, 0, errors.New("octobear vector multiset hash: failed to map message in tweak window")
138+
return octobear.G1Affine{}, 0, errMapFailure
140139
}

0 commit comments

Comments
 (0)