From 7a37994e5df88154a4397077f26d0d861d79ea23 Mon Sep 17 00:00:00 2001 From: Gautam Botrel Date: Fri, 10 Jan 2025 21:29:52 -0600 Subject: [PATCH] refactor: more changes to accomodate users --- ecc/bls12-377/fr/sis/sis.go | 75 ++++++++++-------- ecc/bls12-377/fr/sis/sis_test.go | 9 ++- field/babybear/sis/sis.go | 75 ++++++++++-------- field/babybear/sis/sis_test.go | 9 ++- .../internal/templates/fft/tests/fft.go.tmpl | 1 + .../internal/templates/sis/sis.go.tmpl | 76 +++++++++++-------- .../internal/templates/sis/sis.test.go.tmpl | 9 ++- field/goldilocks/sis/sis.go | 75 ++++++++++-------- field/goldilocks/sis/sis_test.go | 9 ++- field/koalabear/sis/sis.go | 75 ++++++++++-------- field/koalabear/sis/sis_test.go | 9 ++- 11 files changed, 251 insertions(+), 171 deletions(-) diff --git a/ecc/bls12-377/fr/sis/sis.go b/ecc/bls12-377/fr/sis/sis.go index 54d5b261fc..e15973275e 100644 --- a/ecc/bls12-377/fr/sis/sis.go +++ b/ecc/bls12-377/fr/sis/sis.go @@ -65,7 +65,11 @@ func NewRSis(seed int64, logTwoDegree, logTwoBound, maxNbElementsToHash int) (*R // that is, to fill m, we need [degree * n * logTwoBound] bits of data // First n <- #limbs to represent a single field element - n := fr.Bytes / (logTwoBound / 8) // logTwoBound / 8 --> nbBytes per limb + nbBytesPerLimb := logTwoBound / 8 + if fr.Bytes%nbBytesPerLimb != 0 { + return nil, errors.New("nbBytesPerLimb must divide field size") + } + n := fr.Bytes / nbBytesPerLimb // Then multiply by the number of field elements n *= maxNbElementsToHash @@ -134,7 +138,10 @@ func (r *RSis) Hash(v, res []fr.Element) error { k := make([]fr.Element, r.Degree) // inner hash - r.InnerHash(&VectorIterator{v: v}, res, k) + it := NewLimbIterator(&VectorIterator{v: v}, r.LogTwoBound/8) + for i := 0; i < len(r.Ag); i++ { + r.InnerHash(it, res, k, i) + } // reduces mod Xᵈ+1 r.Domain.FFTInverse(res, fft.DIT, fft.OnCoset(), fft.WithNbTasks(1)) @@ -142,34 +149,30 @@ func (r *RSis) Hash(v, res []fr.Element) error { return nil } -func (r *RSis) InnerHash(it ElementIterator, res, k fr.Vector) { - reader := NewLimbIterator(it, r.LogTwoBound/8) - - for i := 0; i < len(r.Ag); i++ { - zero := uint64(0) - for j := 0; j < r.Degree; j += 2 { - k[j].SetZero() - k[j+1].SetZero() - - // read limbs 2 by 2 since degree is a power of 2 (> 1) - l := reader.NextLimb() - zero |= l - k[j][0] = l - - l2 := reader.NextLimb() - zero |= l2 - k[j+1][0] = l2 - } - if zero == 0 { - // means m[i*r.Degree : (i+1)*r.Degree] == [0...0] - // we can skip this, FFT(0) = 0 - continue +func (r *RSis) InnerHash(it *LimbIterator, res, k fr.Vector, polId int) { + zero := uint64(0) + for j := 0; j < r.Degree; j++ { + l, ok := it.NextLimb() + if !ok { + // we need to pad; note that we should use a deterministic padding + // other than 0, but it is not an issue for the current use cases. + for m := j; m < r.Degree; m++ { + k[m].SetZero() + } + break } - - r.Domain.FFT(k, fft.DIF, fft.OnCoset(), fft.WithNbTasks(1)) - mulModAcc(res, r.Ag[i], k) + zero |= l + k[j].SetZero() + k[j][0] = l + } + if zero == 0 { + // means m[i*r.Degree : (i+1)*r.Degree] == [0...0] + // we can skip this, FFT(0) = 0 + return } + r.Domain.FFT(k, fft.DIF, fft.OnCoset(), fft.WithNbTasks(1)) + mulModAcc(res, r.Ag[polId], k) } // mulModAcc computes p * q in ℤ_{p}[X]/Xᵈ+1. @@ -212,6 +215,10 @@ type VectorIterator struct { i int } +func NewVectorIterator(v fr.Vector) *VectorIterator { + return &VectorIterator{v: v} +} + func (vi *VectorIterator) Next() (fr.Element, bool) { if vi.i == len(vi.v) { return fr.Element{}, false @@ -260,13 +267,21 @@ func NewLimbIterator(it ElementIterator, limbSize int) *LimbIterator { // NextLimb returns the next limb of the vector. // This does not perform any bound check, may trigger an out of bound panic. // If underlying vector is "out of limb" -func (vr *LimbIterator) NextLimb() uint64 { +func (vr *LimbIterator) NextLimb() (uint64, bool) { if vr.j == fr.Bytes { + next, ok := vr.it.Next() + if !ok { + return 0, false + } vr.j = 0 - next, _ := vr.it.Next() fr.LittleEndian.PutElement(&vr.buf, next) } - return vr.next(vr.buf[:], &vr.j) + return vr.next(vr.buf[:], &vr.j), true +} + +func (vr *LimbIterator) Reset(it ElementIterator) { + vr.it = it + vr.j = fr.Bytes } func nextUint8(buf []byte, pos *int) uint64 { diff --git a/ecc/bls12-377/fr/sis/sis_test.go b/ecc/bls12-377/fr/sis/sis_test.go index f90a19bc41..88dc2b7276 100644 --- a/ecc/bls12-377/fr/sis/sis_test.go +++ b/ecc/bls12-377/fr/sis/sis_test.go @@ -92,6 +92,7 @@ func TestReference(t *testing.T) { } func TestLimbDecomposeBytes(t *testing.T) { + assert := require.New(t) var montConstant fr.Element var bMontConstant big.Int @@ -110,8 +111,10 @@ func TestLimbDecomposeBytes(t *testing.T) { for cc := 0; cc < 3; cc++ { vr := NewLimbIterator(&VectorIterator{v: a}, logTwoBound/8) m := make(fr.Vector, nbElmts*fr.Bytes*8/logTwoBound) + var ok bool for i := 0; i < len(m); i++ { - m[i][0] = vr.NextLimb() + m[i][0], ok = vr.NextLimb() + assert.True(ok) } for i := 0; i < len(m); i++ { @@ -124,9 +127,7 @@ func TestLimbDecomposeBytes(t *testing.T) { coeffsPerFieldsElmt := fr.Bytes * 8 / logTwoBound for i := 0; i < nbElmts; i++ { r := eval(m[i*coeffsPerFieldsElmt:(i+1)*coeffsPerFieldsElmt], x) - if !r.Equal(&a[i]) { - t.Fatal("limbDecomposeBytes failed") - } + assert.True(r.Equal(&a[i]), "limbDecomposeBytes failed") } logTwoBound *= 2 } diff --git a/field/babybear/sis/sis.go b/field/babybear/sis/sis.go index 9666998004..2b2c0d931b 100644 --- a/field/babybear/sis/sis.go +++ b/field/babybear/sis/sis.go @@ -65,7 +65,11 @@ func NewRSis(seed int64, logTwoDegree, logTwoBound, maxNbElementsToHash int) (*R // that is, to fill m, we need [degree * n * logTwoBound] bits of data // First n <- #limbs to represent a single field element - n := babybear.Bytes / (logTwoBound / 8) // logTwoBound / 8 --> nbBytes per limb + nbBytesPerLimb := logTwoBound / 8 + if babybear.Bytes%nbBytesPerLimb != 0 { + return nil, errors.New("nbBytesPerLimb must divide field size") + } + n := babybear.Bytes / nbBytesPerLimb // Then multiply by the number of field elements n *= maxNbElementsToHash @@ -134,7 +138,10 @@ func (r *RSis) Hash(v, res []babybear.Element) error { k := make([]babybear.Element, r.Degree) // inner hash - r.InnerHash(&VectorIterator{v: v}, res, k) + it := NewLimbIterator(&VectorIterator{v: v}, r.LogTwoBound/8) + for i := 0; i < len(r.Ag); i++ { + r.InnerHash(it, res, k, i) + } // reduces mod Xᵈ+1 r.Domain.FFTInverse(res, fft.DIT, fft.OnCoset(), fft.WithNbTasks(1)) @@ -142,34 +149,30 @@ func (r *RSis) Hash(v, res []babybear.Element) error { return nil } -func (r *RSis) InnerHash(it ElementIterator, res, k babybear.Vector) { - reader := NewLimbIterator(it, r.LogTwoBound/8) - - for i := 0; i < len(r.Ag); i++ { - zero := uint32(0) - for j := 0; j < r.Degree; j += 2 { - k[j].SetZero() - k[j+1].SetZero() - - // read limbs 2 by 2 since degree is a power of 2 (> 1) - l := reader.NextLimb() - zero |= l - k[j][0] = l - - l2 := reader.NextLimb() - zero |= l2 - k[j+1][0] = l2 - } - if zero == 0 { - // means m[i*r.Degree : (i+1)*r.Degree] == [0...0] - // we can skip this, FFT(0) = 0 - continue +func (r *RSis) InnerHash(it *LimbIterator, res, k babybear.Vector, polId int) { + zero := uint32(0) + for j := 0; j < r.Degree; j++ { + l, ok := it.NextLimb() + if !ok { + // we need to pad; note that we should use a deterministic padding + // other than 0, but it is not an issue for the current use cases. + for m := j; m < r.Degree; m++ { + k[m].SetZero() + } + break } - - r.Domain.FFT(k, fft.DIF, fft.OnCoset(), fft.WithNbTasks(1)) - mulModAcc(res, r.Ag[i], k) + zero |= l + k[j].SetZero() + k[j][0] = l + } + if zero == 0 { + // means m[i*r.Degree : (i+1)*r.Degree] == [0...0] + // we can skip this, FFT(0) = 0 + return } + r.Domain.FFT(k, fft.DIF, fft.OnCoset(), fft.WithNbTasks(1)) + mulModAcc(res, r.Ag[polId], k) } // mulModAcc computes p * q in ℤ_{p}[X]/Xᵈ+1. @@ -212,6 +215,10 @@ type VectorIterator struct { i int } +func NewVectorIterator(v babybear.Vector) *VectorIterator { + return &VectorIterator{v: v} +} + func (vi *VectorIterator) Next() (babybear.Element, bool) { if vi.i == len(vi.v) { return babybear.Element{}, false @@ -256,13 +263,21 @@ func NewLimbIterator(it ElementIterator, limbSize int) *LimbIterator { // NextLimb returns the next limb of the vector. // This does not perform any bound check, may trigger an out of bound panic. // If underlying vector is "out of limb" -func (vr *LimbIterator) NextLimb() uint32 { +func (vr *LimbIterator) NextLimb() (uint32, bool) { if vr.j == babybear.Bytes { + next, ok := vr.it.Next() + if !ok { + return 0, false + } vr.j = 0 - next, _ := vr.it.Next() babybear.LittleEndian.PutElement(&vr.buf, next) } - return vr.next(vr.buf[:], &vr.j) + return vr.next(vr.buf[:], &vr.j), true +} + +func (vr *LimbIterator) Reset(it ElementIterator) { + vr.it = it + vr.j = babybear.Bytes } func nextUint8(buf []byte, pos *int) uint32 { diff --git a/field/babybear/sis/sis_test.go b/field/babybear/sis/sis_test.go index 3b00c0d4e6..6d041515e7 100644 --- a/field/babybear/sis/sis_test.go +++ b/field/babybear/sis/sis_test.go @@ -92,6 +92,7 @@ func TestReference(t *testing.T) { } func TestLimbDecomposeBytes(t *testing.T) { + assert := require.New(t) var montConstant babybear.Element var bMontConstant big.Int @@ -110,8 +111,10 @@ func TestLimbDecomposeBytes(t *testing.T) { for cc := 0; cc < 1; cc++ { vr := NewLimbIterator(&VectorIterator{v: a}, logTwoBound/8) m := make(babybear.Vector, nbElmts*babybear.Bytes*8/logTwoBound) + var ok bool for i := 0; i < len(m); i++ { - m[i][0] = vr.NextLimb() + m[i][0], ok = vr.NextLimb() + assert.True(ok) } for i := 0; i < len(m); i++ { @@ -124,9 +127,7 @@ func TestLimbDecomposeBytes(t *testing.T) { coeffsPerFieldsElmt := babybear.Bytes * 8 / logTwoBound for i := 0; i < nbElmts; i++ { r := eval(m[i*coeffsPerFieldsElmt:(i+1)*coeffsPerFieldsElmt], x) - if !r.Equal(&a[i]) { - t.Fatal("limbDecomposeBytes failed") - } + assert.True(r.Equal(&a[i]), "limbDecomposeBytes failed") } logTwoBound *= 2 } diff --git a/field/generator/internal/templates/fft/tests/fft.go.tmpl b/field/generator/internal/templates/fft/tests/fft.go.tmpl index 4500234956..c926779cde 100644 --- a/field/generator/internal/templates/fft/tests/fft.go.tmpl +++ b/field/generator/internal/templates/fft/tests/fft.go.tmpl @@ -303,6 +303,7 @@ func BenchmarkFFTDIFReference(b *testing.B) { } } + func evaluatePolynomial(pol []{{ .FF }}.Element, val {{ .FF }}.Element) {{ .FF }}.Element { var acc, res, tmp {{ .FF }}.Element res.Set(&pol[0]) diff --git a/field/generator/internal/templates/sis/sis.go.tmpl b/field/generator/internal/templates/sis/sis.go.tmpl index bd1e99c96e..0a2f3c7300 100644 --- a/field/generator/internal/templates/sis/sis.go.tmpl +++ b/field/generator/internal/templates/sis/sis.go.tmpl @@ -65,7 +65,11 @@ func NewRSis(seed int64, logTwoDegree, logTwoBound, maxNbElementsToHash int) (*R // that is, to fill m, we need [degree * n * logTwoBound] bits of data // First n <- #limbs to represent a single field element - n := {{ .FF }}.Bytes / (logTwoBound / 8) // logTwoBound / 8 --> nbBytes per limb + nbBytesPerLimb := logTwoBound / 8 + if {{ .FF }}.Bytes % nbBytesPerLimb != 0 { + return nil, errors.New("nbBytesPerLimb must divide field size") + } + n := {{ .FF }}.Bytes / nbBytesPerLimb // Then multiply by the number of field elements n *= maxNbElementsToHash @@ -134,43 +138,41 @@ func (r *RSis) Hash(v, res []{{ .FF }}.Element) error { k := make([]{{ .FF }}.Element, r.Degree) // inner hash - r.InnerHash(&VectorIterator{v: v}, res, k) + it := NewLimbIterator(&VectorIterator{v: v}, r.LogTwoBound/8) + for i := 0; i < len(r.Ag); i++ { + r.InnerHash(it, res, k, i) + } - // reduces mod Xᵈ+1 r.Domain.FFTInverse(res, fft.DIT, fft.OnCoset(), fft.WithNbTasks(1)) return nil } -func (r *RSis) InnerHash(it ElementIterator, res, k {{ .FF }}.Vector) { - reader := NewLimbIterator(it, r.LogTwoBound/8) - - for i := 0; i < len(r.Ag); i++ { - zero := {{$tReturn}}(0) - for j := 0; j < r.Degree; j+=2 { - k[j].SetZero() - k[j+1].SetZero() - - // read limbs 2 by 2 since degree is a power of 2 (> 1) - l := reader.NextLimb() - zero |= l - k[j][0] = l - - l2 := reader.NextLimb() - zero |= l2 - k[j+1][0] = l2 - } - if zero == 0 { - // means m[i*r.Degree : (i+1)*r.Degree] == [0...0] - // we can skip this, FFT(0) = 0 - continue +func (r *RSis) InnerHash(it *LimbIterator, res, k {{ .FF }}.Vector, polId int) { + zero := {{$tReturn}}(0) + for j := 0; j < r.Degree; j++ { + l, ok := it.NextLimb() + if !ok { + // we need to pad; note that we should use a deterministic padding + // other than 0, but it is not an issue for the current use cases. + for m := j; m < r.Degree; m++ { + k[m].SetZero() + } + break } - - r.Domain.FFT(k, fft.DIF, fft.OnCoset(), fft.WithNbTasks(1)) - mulModAcc(res, r.Ag[i], k) + zero |= l + k[j].SetZero() + k[j][0] = l + } + if zero == 0 { + // means m[i*r.Degree : (i+1)*r.Degree] == [0...0] + // we can skip this, FFT(0) = 0 + return } + r.Domain.FFT(k, fft.DIF, fft.OnCoset(), fft.WithNbTasks(1)) + mulModAcc(res, r.Ag[polId], k) } // mulModAcc computes p * q in ℤ_{p}[X]/Xᵈ+1. @@ -215,6 +217,10 @@ type VectorIterator struct { i int } +func NewVectorIterator(v {{ .FF }}.Vector) *VectorIterator { + return &VectorIterator{v: v} +} + func (vi *VectorIterator) Next() ({{ .FF }}.Element, bool) { if vi.i == len(vi.v) { return {{ .FF }}.Element{}, false @@ -266,13 +272,21 @@ func NewLimbIterator(it ElementIterator, limbSize int) *LimbIterator { // NextLimb returns the next limb of the vector. // This does not perform any bound check, may trigger an out of bound panic. // If underlying vector is "out of limb" -func (vr *LimbIterator) NextLimb() {{$tReturn}} { +func (vr *LimbIterator) NextLimb() ({{$tReturn}}, bool) { if vr.j == {{ .FF }}.Bytes { + next, ok := vr.it.Next() + if !ok { + return 0, false + } vr.j = 0 - next, _ := vr.it.Next() {{.FF}}.LittleEndian.PutElement(&vr.buf, next) } - return vr.next(vr.buf[:], &vr.j) + return vr.next(vr.buf[:], &vr.j), true +} + +func (vr *LimbIterator) Reset(it ElementIterator) { + vr.it = it + vr.j = {{ .FF }}.Bytes } diff --git a/field/generator/internal/templates/sis/sis.test.go.tmpl b/field/generator/internal/templates/sis/sis.test.go.tmpl index 9653c84ffc..9233eaf850 100644 --- a/field/generator/internal/templates/sis/sis.test.go.tmpl +++ b/field/generator/internal/templates/sis/sis.test.go.tmpl @@ -87,6 +87,7 @@ func TestReference(t *testing.T) { } func TestLimbDecomposeBytes(t *testing.T) { + assert := require.New(t) var montConstant {{ .FF }}.Element var bMontConstant big.Int @@ -107,8 +108,10 @@ func TestLimbDecomposeBytes(t *testing.T) { for cc:=0;cc<{{- if $f31 }}1{{- else }}3{{- end}}; cc++ { vr := NewLimbIterator(&VectorIterator{v:a}, logTwoBound/8) m := make({{ .FF }}.Vector, nbElmts*{{ .FF }}.Bytes*8/logTwoBound) + var ok bool for i := 0; i < len(m); i++ { - m[i][0] = vr.NextLimb() + m[i][0], ok = vr.NextLimb() + assert.True(ok) } for i := 0; i < len(m); i++ { @@ -121,9 +124,7 @@ func TestLimbDecomposeBytes(t *testing.T) { coeffsPerFieldsElmt := {{ .FF }}.Bytes * 8 / logTwoBound for i := 0; i < nbElmts; i++ { r := eval(m[i*coeffsPerFieldsElmt:(i+1)*coeffsPerFieldsElmt], x) - if !r.Equal(&a[i]) { - t.Fatal("limbDecomposeBytes failed") - } + assert.True(r.Equal(&a[i]), "limbDecomposeBytes failed") } logTwoBound*=2 } diff --git a/field/goldilocks/sis/sis.go b/field/goldilocks/sis/sis.go index a77983705e..c5d703c1f0 100644 --- a/field/goldilocks/sis/sis.go +++ b/field/goldilocks/sis/sis.go @@ -65,7 +65,11 @@ func NewRSis(seed int64, logTwoDegree, logTwoBound, maxNbElementsToHash int) (*R // that is, to fill m, we need [degree * n * logTwoBound] bits of data // First n <- #limbs to represent a single field element - n := goldilocks.Bytes / (logTwoBound / 8) // logTwoBound / 8 --> nbBytes per limb + nbBytesPerLimb := logTwoBound / 8 + if goldilocks.Bytes%nbBytesPerLimb != 0 { + return nil, errors.New("nbBytesPerLimb must divide field size") + } + n := goldilocks.Bytes / nbBytesPerLimb // Then multiply by the number of field elements n *= maxNbElementsToHash @@ -134,7 +138,10 @@ func (r *RSis) Hash(v, res []goldilocks.Element) error { k := make([]goldilocks.Element, r.Degree) // inner hash - r.InnerHash(&VectorIterator{v: v}, res, k) + it := NewLimbIterator(&VectorIterator{v: v}, r.LogTwoBound/8) + for i := 0; i < len(r.Ag); i++ { + r.InnerHash(it, res, k, i) + } // reduces mod Xᵈ+1 r.Domain.FFTInverse(res, fft.DIT, fft.OnCoset(), fft.WithNbTasks(1)) @@ -142,34 +149,30 @@ func (r *RSis) Hash(v, res []goldilocks.Element) error { return nil } -func (r *RSis) InnerHash(it ElementIterator, res, k goldilocks.Vector) { - reader := NewLimbIterator(it, r.LogTwoBound/8) - - for i := 0; i < len(r.Ag); i++ { - zero := uint64(0) - for j := 0; j < r.Degree; j += 2 { - k[j].SetZero() - k[j+1].SetZero() - - // read limbs 2 by 2 since degree is a power of 2 (> 1) - l := reader.NextLimb() - zero |= l - k[j][0] = l - - l2 := reader.NextLimb() - zero |= l2 - k[j+1][0] = l2 - } - if zero == 0 { - // means m[i*r.Degree : (i+1)*r.Degree] == [0...0] - // we can skip this, FFT(0) = 0 - continue +func (r *RSis) InnerHash(it *LimbIterator, res, k goldilocks.Vector, polId int) { + zero := uint64(0) + for j := 0; j < r.Degree; j++ { + l, ok := it.NextLimb() + if !ok { + // we need to pad; note that we should use a deterministic padding + // other than 0, but it is not an issue for the current use cases. + for m := j; m < r.Degree; m++ { + k[m].SetZero() + } + break } - - r.Domain.FFT(k, fft.DIF, fft.OnCoset(), fft.WithNbTasks(1)) - mulModAcc(res, r.Ag[i], k) + zero |= l + k[j].SetZero() + k[j][0] = l + } + if zero == 0 { + // means m[i*r.Degree : (i+1)*r.Degree] == [0...0] + // we can skip this, FFT(0) = 0 + return } + r.Domain.FFT(k, fft.DIF, fft.OnCoset(), fft.WithNbTasks(1)) + mulModAcc(res, r.Ag[polId], k) } // mulModAcc computes p * q in ℤ_{p}[X]/Xᵈ+1. @@ -212,6 +215,10 @@ type VectorIterator struct { i int } +func NewVectorIterator(v goldilocks.Vector) *VectorIterator { + return &VectorIterator{v: v} +} + func (vi *VectorIterator) Next() (goldilocks.Element, bool) { if vi.i == len(vi.v) { return goldilocks.Element{}, false @@ -260,13 +267,21 @@ func NewLimbIterator(it ElementIterator, limbSize int) *LimbIterator { // NextLimb returns the next limb of the vector. // This does not perform any bound check, may trigger an out of bound panic. // If underlying vector is "out of limb" -func (vr *LimbIterator) NextLimb() uint64 { +func (vr *LimbIterator) NextLimb() (uint64, bool) { if vr.j == goldilocks.Bytes { + next, ok := vr.it.Next() + if !ok { + return 0, false + } vr.j = 0 - next, _ := vr.it.Next() goldilocks.LittleEndian.PutElement(&vr.buf, next) } - return vr.next(vr.buf[:], &vr.j) + return vr.next(vr.buf[:], &vr.j), true +} + +func (vr *LimbIterator) Reset(it ElementIterator) { + vr.it = it + vr.j = goldilocks.Bytes } func nextUint8(buf []byte, pos *int) uint64 { diff --git a/field/goldilocks/sis/sis_test.go b/field/goldilocks/sis/sis_test.go index 28f5e53121..480cbb8c33 100644 --- a/field/goldilocks/sis/sis_test.go +++ b/field/goldilocks/sis/sis_test.go @@ -92,6 +92,7 @@ func TestReference(t *testing.T) { } func TestLimbDecomposeBytes(t *testing.T) { + assert := require.New(t) var montConstant goldilocks.Element var bMontConstant big.Int @@ -110,8 +111,10 @@ func TestLimbDecomposeBytes(t *testing.T) { for cc := 0; cc < 3; cc++ { vr := NewLimbIterator(&VectorIterator{v: a}, logTwoBound/8) m := make(goldilocks.Vector, nbElmts*goldilocks.Bytes*8/logTwoBound) + var ok bool for i := 0; i < len(m); i++ { - m[i][0] = vr.NextLimb() + m[i][0], ok = vr.NextLimb() + assert.True(ok) } for i := 0; i < len(m); i++ { @@ -124,9 +127,7 @@ func TestLimbDecomposeBytes(t *testing.T) { coeffsPerFieldsElmt := goldilocks.Bytes * 8 / logTwoBound for i := 0; i < nbElmts; i++ { r := eval(m[i*coeffsPerFieldsElmt:(i+1)*coeffsPerFieldsElmt], x) - if !r.Equal(&a[i]) { - t.Fatal("limbDecomposeBytes failed") - } + assert.True(r.Equal(&a[i]), "limbDecomposeBytes failed") } logTwoBound *= 2 } diff --git a/field/koalabear/sis/sis.go b/field/koalabear/sis/sis.go index 5c773306bf..21224cceed 100644 --- a/field/koalabear/sis/sis.go +++ b/field/koalabear/sis/sis.go @@ -65,7 +65,11 @@ func NewRSis(seed int64, logTwoDegree, logTwoBound, maxNbElementsToHash int) (*R // that is, to fill m, we need [degree * n * logTwoBound] bits of data // First n <- #limbs to represent a single field element - n := koalabear.Bytes / (logTwoBound / 8) // logTwoBound / 8 --> nbBytes per limb + nbBytesPerLimb := logTwoBound / 8 + if koalabear.Bytes%nbBytesPerLimb != 0 { + return nil, errors.New("nbBytesPerLimb must divide field size") + } + n := koalabear.Bytes / nbBytesPerLimb // Then multiply by the number of field elements n *= maxNbElementsToHash @@ -134,7 +138,10 @@ func (r *RSis) Hash(v, res []koalabear.Element) error { k := make([]koalabear.Element, r.Degree) // inner hash - r.InnerHash(&VectorIterator{v: v}, res, k) + it := NewLimbIterator(&VectorIterator{v: v}, r.LogTwoBound/8) + for i := 0; i < len(r.Ag); i++ { + r.InnerHash(it, res, k, i) + } // reduces mod Xᵈ+1 r.Domain.FFTInverse(res, fft.DIT, fft.OnCoset(), fft.WithNbTasks(1)) @@ -142,34 +149,30 @@ func (r *RSis) Hash(v, res []koalabear.Element) error { return nil } -func (r *RSis) InnerHash(it ElementIterator, res, k koalabear.Vector) { - reader := NewLimbIterator(it, r.LogTwoBound/8) - - for i := 0; i < len(r.Ag); i++ { - zero := uint32(0) - for j := 0; j < r.Degree; j += 2 { - k[j].SetZero() - k[j+1].SetZero() - - // read limbs 2 by 2 since degree is a power of 2 (> 1) - l := reader.NextLimb() - zero |= l - k[j][0] = l - - l2 := reader.NextLimb() - zero |= l2 - k[j+1][0] = l2 - } - if zero == 0 { - // means m[i*r.Degree : (i+1)*r.Degree] == [0...0] - // we can skip this, FFT(0) = 0 - continue +func (r *RSis) InnerHash(it *LimbIterator, res, k koalabear.Vector, polId int) { + zero := uint32(0) + for j := 0; j < r.Degree; j++ { + l, ok := it.NextLimb() + if !ok { + // we need to pad; note that we should use a deterministic padding + // other than 0, but it is not an issue for the current use cases. + for m := j; m < r.Degree; m++ { + k[m].SetZero() + } + break } - - r.Domain.FFT(k, fft.DIF, fft.OnCoset(), fft.WithNbTasks(1)) - mulModAcc(res, r.Ag[i], k) + zero |= l + k[j].SetZero() + k[j][0] = l + } + if zero == 0 { + // means m[i*r.Degree : (i+1)*r.Degree] == [0...0] + // we can skip this, FFT(0) = 0 + return } + r.Domain.FFT(k, fft.DIF, fft.OnCoset(), fft.WithNbTasks(1)) + mulModAcc(res, r.Ag[polId], k) } // mulModAcc computes p * q in ℤ_{p}[X]/Xᵈ+1. @@ -212,6 +215,10 @@ type VectorIterator struct { i int } +func NewVectorIterator(v koalabear.Vector) *VectorIterator { + return &VectorIterator{v: v} +} + func (vi *VectorIterator) Next() (koalabear.Element, bool) { if vi.i == len(vi.v) { return koalabear.Element{}, false @@ -256,13 +263,21 @@ func NewLimbIterator(it ElementIterator, limbSize int) *LimbIterator { // NextLimb returns the next limb of the vector. // This does not perform any bound check, may trigger an out of bound panic. // If underlying vector is "out of limb" -func (vr *LimbIterator) NextLimb() uint32 { +func (vr *LimbIterator) NextLimb() (uint32, bool) { if vr.j == koalabear.Bytes { + next, ok := vr.it.Next() + if !ok { + return 0, false + } vr.j = 0 - next, _ := vr.it.Next() koalabear.LittleEndian.PutElement(&vr.buf, next) } - return vr.next(vr.buf[:], &vr.j) + return vr.next(vr.buf[:], &vr.j), true +} + +func (vr *LimbIterator) Reset(it ElementIterator) { + vr.it = it + vr.j = koalabear.Bytes } func nextUint8(buf []byte, pos *int) uint32 { diff --git a/field/koalabear/sis/sis_test.go b/field/koalabear/sis/sis_test.go index a14fcadd92..d05364dea6 100644 --- a/field/koalabear/sis/sis_test.go +++ b/field/koalabear/sis/sis_test.go @@ -92,6 +92,7 @@ func TestReference(t *testing.T) { } func TestLimbDecomposeBytes(t *testing.T) { + assert := require.New(t) var montConstant koalabear.Element var bMontConstant big.Int @@ -110,8 +111,10 @@ func TestLimbDecomposeBytes(t *testing.T) { for cc := 0; cc < 1; cc++ { vr := NewLimbIterator(&VectorIterator{v: a}, logTwoBound/8) m := make(koalabear.Vector, nbElmts*koalabear.Bytes*8/logTwoBound) + var ok bool for i := 0; i < len(m); i++ { - m[i][0] = vr.NextLimb() + m[i][0], ok = vr.NextLimb() + assert.True(ok) } for i := 0; i < len(m); i++ { @@ -124,9 +127,7 @@ func TestLimbDecomposeBytes(t *testing.T) { coeffsPerFieldsElmt := koalabear.Bytes * 8 / logTwoBound for i := 0; i < nbElmts; i++ { r := eval(m[i*coeffsPerFieldsElmt:(i+1)*coeffsPerFieldsElmt], x) - if !r.Equal(&a[i]) { - t.Fatal("limbDecomposeBytes failed") - } + assert.True(r.Equal(&a[i]), "limbDecomposeBytes failed") } logTwoBound *= 2 }