Skip to content

Commit fcbef0b

Browse files
committed
Adds test vectors to hashToCurve and expanders.
1 parent 92a0ed0 commit fcbef0b

18 files changed

+1373
-135
lines changed

group/expander.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package group
2+
3+
import (
4+
"crypto"
5+
"encoding/binary"
6+
"errors"
7+
"io"
8+
9+
"github.com/cloudflare/circl/xof"
10+
)
11+
12+
type Expander interface {
13+
// Expand generates a pseudo-random byte string of a determined length by
14+
// expanding an input string.
15+
Expand(in []byte, length uint) (pseudo []byte)
16+
}
17+
18+
type expanderMD struct {
19+
h crypto.Hash
20+
dst []byte
21+
}
22+
23+
// NewExpanderMD returns a hash function based on a Merkle-Damgård hash function.
24+
func NewExpanderMD(h crypto.Hash, dst []byte) *expanderMD {
25+
return &expanderMD{h, dst}
26+
}
27+
28+
func (e *expanderMD) calcDSTPrime() []byte {
29+
var dstPrime []byte
30+
if l := len(e.dst); l > maxDSTLength {
31+
H := e.h.New()
32+
mustWrite(H, longDSTPrefix[:])
33+
mustWrite(H, e.dst)
34+
dstPrime = H.Sum(nil)
35+
} else {
36+
dstPrime = make([]byte, l, l+1)
37+
copy(dstPrime, e.dst)
38+
}
39+
return append(dstPrime, byte(len(dstPrime)))
40+
}
41+
42+
func (e *expanderMD) Expand(in []byte, n uint) []byte {
43+
H := e.h.New()
44+
bLen := uint(H.Size())
45+
ell := (n + (bLen - 1)) / bLen
46+
if ell > 255 {
47+
panic(errorLongOutput)
48+
}
49+
50+
zPad := make([]byte, H.BlockSize())
51+
libStr := []byte{0, 0}
52+
libStr[0] = byte((n >> 8) & 0xFF)
53+
libStr[1] = byte(n & 0xFF)
54+
dstPrime := e.calcDSTPrime()
55+
56+
H.Reset()
57+
mustWrite(H, zPad)
58+
mustWrite(H, in)
59+
mustWrite(H, libStr)
60+
mustWrite(H, []byte{0})
61+
mustWrite(H, dstPrime)
62+
b0 := H.Sum(nil)
63+
64+
H.Reset()
65+
mustWrite(H, b0)
66+
mustWrite(H, []byte{1})
67+
mustWrite(H, dstPrime)
68+
bi := H.Sum(nil)
69+
pseudo := append([]byte{}, bi...)
70+
for i := uint(2); i <= ell; i++ {
71+
H.Reset()
72+
for i := range b0 {
73+
bi[i] ^= b0[i]
74+
}
75+
mustWrite(H, bi)
76+
mustWrite(H, []byte{byte(i)})
77+
mustWrite(H, dstPrime)
78+
bi = H.Sum(nil)
79+
pseudo = append(pseudo, bi...)
80+
}
81+
return pseudo[0:n]
82+
}
83+
84+
// expanderXOF is based on an extendable output function.
85+
type expanderXOF struct {
86+
id xof.ID
87+
kSecLevel uint
88+
dst []byte
89+
}
90+
91+
// NewExpanderXOF returns an Expander based on an extendable output function.
92+
// The kSecLevel parameter is the target security level in bits, and dst is
93+
// a domain separation string.
94+
func NewExpanderXOF(id xof.ID, kSecLevel uint, dst []byte) *expanderXOF {
95+
return &expanderXOF{id, kSecLevel, dst}
96+
}
97+
98+
// Expand panics if output's length is longer than 2^16 bytes.
99+
func (e *expanderXOF) Expand(in []byte, n uint) []byte {
100+
bLen := []byte{0, 0}
101+
binary.BigEndian.PutUint16(bLen, uint16(n))
102+
pseudo := make([]byte, n)
103+
dstPrime := e.calcDSTPrime()
104+
105+
H := e.id.New()
106+
mustWrite(H, in)
107+
mustWrite(H, bLen)
108+
mustWrite(H, dstPrime)
109+
mustReadFull(H, pseudo)
110+
return pseudo
111+
}
112+
113+
func (e *expanderXOF) calcDSTPrime() []byte {
114+
var dstPrime []byte
115+
if l := len(e.dst); l > maxDSTLength {
116+
H := e.id.New()
117+
mustWrite(H, longDSTPrefix[:])
118+
mustWrite(H, e.dst)
119+
max := ((2 * e.kSecLevel) + 7) / 8
120+
dstPrime = make([]byte, max, max+1)
121+
mustReadFull(H, dstPrime)
122+
} else {
123+
dstPrime = make([]byte, l, l+1)
124+
copy(dstPrime, e.dst)
125+
}
126+
return append(dstPrime, byte(len(dstPrime)))
127+
}
128+
129+
func mustWrite(w io.Writer, b []byte) {
130+
if n, err := w.Write(b); err != nil || n != len(b) {
131+
panic(err)
132+
}
133+
}
134+
135+
func mustReadFull(r io.Reader, b []byte) {
136+
if n, err := io.ReadFull(r, b); err != nil || n != len(b) {
137+
panic(err)
138+
}
139+
}
140+
141+
const maxDSTLength = 255
142+
143+
var (
144+
longDSTPrefix = [17]byte{'H', '2', 'C', '-', 'O', 'V', 'E', 'R', 'S', 'I', 'Z', 'E', '-', 'D', 'S', 'T', '-'}
145+
146+
errorLongOutput = errors.New("requested too many bytes")
147+
)

group/expander_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package group_test
2+
3+
import (
4+
"bytes"
5+
"crypto"
6+
"encoding/hex"
7+
"encoding/json"
8+
"fmt"
9+
"os"
10+
"path/filepath"
11+
"strconv"
12+
"testing"
13+
14+
"github.com/cloudflare/circl/group"
15+
"github.com/cloudflare/circl/internal/test"
16+
"github.com/cloudflare/circl/xof"
17+
)
18+
19+
func TestExpander(t *testing.T) {
20+
fileNames, err := filepath.Glob("./testdata/expand*.json")
21+
if err != nil {
22+
t.Fatal(err)
23+
}
24+
25+
for _, fileName := range fileNames {
26+
f, err := os.Open(fileName)
27+
if err != nil {
28+
t.Fatal(err)
29+
}
30+
dec := json.NewDecoder(f)
31+
var v vectorExpanderSuite
32+
err = dec.Decode(&v)
33+
if err != nil {
34+
t.Fatal(err)
35+
}
36+
f.Close()
37+
38+
t.Run(v.Name+"/"+v.Hash, func(t *testing.T) { testExpander(t, &v) })
39+
}
40+
}
41+
42+
func testExpander(t *testing.T, vs *vectorExpanderSuite) {
43+
var exp group.Expander
44+
switch vs.Hash {
45+
case "SHA256":
46+
exp = group.NewExpanderMD(crypto.SHA256, []byte(vs.DST))
47+
case "SHA512":
48+
exp = group.NewExpanderMD(crypto.SHA512, []byte(vs.DST))
49+
case "SHAKE128":
50+
exp = group.NewExpanderXOF(xof.SHAKE128, 0, []byte(vs.DST))
51+
case "SHAKE256":
52+
exp = group.NewExpanderXOF(xof.SHAKE256, 0, []byte(vs.DST))
53+
default:
54+
t.Skip("hash not supported: " + vs.Hash)
55+
}
56+
57+
for i, v := range vs.Tests {
58+
lenBytes, err := strconv.ParseUint(v.Len, 0, 64)
59+
if err != nil {
60+
t.Fatal(err)
61+
}
62+
63+
got := exp.Expand([]byte(v.Msg), uint(lenBytes))
64+
want, err := hex.DecodeString(v.UniformBytes)
65+
if err != nil {
66+
t.Fatal(err)
67+
}
68+
69+
if !bytes.Equal(got, want) {
70+
test.ReportError(t, got, want, i)
71+
}
72+
}
73+
}
74+
75+
type vectorExpanderSuite struct {
76+
DST string `json:"DST"`
77+
Hash string `json:"hash"`
78+
Name string `json:"name"`
79+
Tests []struct {
80+
DstPrime string `json:"DST_prime"`
81+
Len string `json:"len_in_bytes"`
82+
Msg string `json:"msg"`
83+
MsgPrime string `json:"msg_prime"`
84+
UniformBytes string `json:"uniform_bytes"`
85+
} `json:"tests"`
86+
}
87+
88+
func BenchmarkExpander(b *testing.B) {
89+
in := []byte("input")
90+
dst := []byte("dst")
91+
92+
for _, v := range []struct {
93+
Name string
94+
Exp group.Expander
95+
}{
96+
{"XMD", group.NewExpanderMD(crypto.SHA256, dst)},
97+
{"XOF", group.NewExpanderXOF(xof.SHAKE128, 0, dst)},
98+
} {
99+
exp := v.Exp
100+
for l := 8; l <= 10; l++ {
101+
max := int64(1) << uint(l)
102+
103+
b.Run(fmt.Sprintf("%v/%v", v.Name, max), func(b *testing.B) {
104+
b.SetBytes(max)
105+
b.ResetTimer()
106+
for i := 0; i < b.N; i++ {
107+
exp.Expand(in, uint(max))
108+
}
109+
})
110+
}
111+
}
112+
}

group/group.go

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,9 @@
22
package group
33

44
import (
5-
"crypto/elliptic"
65
"encoding"
76
"errors"
87
"io"
9-
10-
"github.com/cloudflare/circl/ecc/p384"
11-
)
12-
13-
var (
14-
// P256 is the group generated by P-256 elliptic curve.
15-
P256 Group = wG{elliptic.P256()}
16-
// P384 is the group generated by P-384 elliptic curve.
17-
P384 Group = wG{p384.P384()}
18-
// P521 is the group generated by P-521 elliptic curve.
19-
P521 Group = wG{elliptic.P521()}
208
)
219

2210
type Params struct {
@@ -36,6 +24,7 @@ type Group interface {
3624
RandomElement(io.Reader) Element
3725
RandomScalar(io.Reader) Scalar
3826
HashToElement(data, dst []byte) Element
27+
HashToElementNonUniform(b, dst []byte) Element
3928
HashToScalar(data, dst []byte) Scalar
4029
}
4130

group/group_test.go

Lines changed: 15 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ import (
1010
"github.com/cloudflare/circl/internal/test"
1111
)
1212

13+
var allGroups = []group.Group{
14+
group.P256,
15+
group.P384,
16+
group.P521,
17+
group.Ristretto255,
18+
}
19+
1320
func TestGroup(t *testing.T) {
1421
const testTimes = 1 << 7
15-
for _, g := range []group.Group{
16-
group.P256,
17-
group.P384,
18-
group.P521,
19-
group.Ristretto255,
20-
} {
22+
for _, g := range allGroups {
2123
g := g
2224
n := g.(fmt.Stringer).String()
2325
t.Run(n+"/Add", func(tt *testing.T) { testAdd(tt, testTimes, g) })
@@ -126,22 +128,24 @@ func isZero(b []byte) bool {
126128
func testMarshal(t *testing.T, testTimes int, g group.Group) {
127129
params := g.Params()
128130
I := g.Identity()
129-
got, _ := I.MarshalBinary()
131+
got, err := I.MarshalBinary()
132+
test.CheckNoErr(t, err, "error on MarshalBinary")
130133
if !isZero(got) {
131134
test.ReportError(t, got, "Non-zero identity")
132135
}
133136
if l := uint(len(got)); !(l == 1 || l == params.ElementLength) {
134137
test.ReportError(t, l, params.ElementLength)
135138
}
136-
got, _ = I.MarshalBinaryCompress()
139+
got, err = I.MarshalBinaryCompress()
140+
test.CheckNoErr(t, err, "error on MarshalBinaryCompress")
137141
if !isZero(got) {
138142
test.ReportError(t, got, "Non-zero identity")
139143
}
140144
if l := uint(len(got)); !(l == 1 || l == params.CompressedElementLength) {
141145
test.ReportError(t, l, params.CompressedElementLength)
142146
}
143147
II := g.NewElement()
144-
err := II.UnmarshalBinary(got)
148+
err = II.UnmarshalBinary(got)
145149
if err != nil || !I.IsEqual(II) {
146150
test.ReportError(t, I, II)
147151
}
@@ -203,11 +207,7 @@ func testScalar(t *testing.T, testTimes int, g group.Group) {
203207
}
204208

205209
func BenchmarkElement(b *testing.B) {
206-
for _, g := range []group.Group{
207-
group.P256,
208-
group.P384,
209-
group.P521,
210-
} {
210+
for _, g := range allGroups {
211211
x := g.RandomElement(rand.Reader)
212212
y := g.RandomElement(rand.Reader)
213213
n := g.RandomScalar(rand.Reader)
@@ -236,11 +236,7 @@ func BenchmarkElement(b *testing.B) {
236236
}
237237

238238
func BenchmarkScalar(b *testing.B) {
239-
for _, g := range []group.Group{
240-
group.P256,
241-
group.P384,
242-
group.P521,
243-
} {
239+
for _, g := range allGroups {
244240
x := g.RandomScalar(rand.Reader)
245241
y := g.RandomScalar(rand.Reader)
246242
name := g.(fmt.Stringer).String()
@@ -261,24 +257,3 @@ func BenchmarkScalar(b *testing.B) {
261257
})
262258
}
263259
}
264-
265-
func BenchmarkHash(b *testing.B) {
266-
for _, g := range []group.Group{
267-
group.P256,
268-
group.P384,
269-
group.P521,
270-
} {
271-
g := g
272-
name := g.(fmt.Stringer).String()
273-
b.Run(name+"/HashToElement", func(b *testing.B) {
274-
for i := 0; i < b.N; i++ {
275-
g.HashToElement(nil, nil)
276-
}
277-
})
278-
b.Run(name+"/HashToScalar", func(b *testing.B) {
279-
for i := 0; i < b.N; i++ {
280-
g.HashToScalar(nil, nil)
281-
}
282-
})
283-
}
284-
}

0 commit comments

Comments
 (0)