Skip to content
This repository was archived by the owner on Oct 11, 2024. It is now read-only.

Commit 6115a89

Browse files
authored
SECG1 Encode/Decode (#1513)
* SECG1 Encode/Decode Encode and Decode elliptic curve points in compressed format. This will be superceeded by golang/go#34105 in Go 1.15
1 parent edd1ff1 commit 6115a89

File tree

2 files changed

+178
-0
lines changed

2 files changed

+178
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package vrf
2+
3+
import (
4+
"bytes"
5+
"crypto/elliptic"
6+
"errors"
7+
"math/big"
8+
)
9+
10+
// i2osp converts a nonnegative integer to an octet string of a specified length.
11+
// RFC8017 section-4.1 (big endian representation)
12+
func i2osp(x *big.Int, rLen uint) []byte {
13+
// 1. If x >= 256^rLen, output "integer too large" and stop.
14+
upperBound := new(big.Int).Lsh(big.NewInt(1), rLen*8)
15+
if x.Cmp(upperBound) >= 0 {
16+
panic("integer too large")
17+
}
18+
// 2. Write the integer x in its unique rLen-digit representation in base 256:
19+
// x = x_(rLen-1) 256^(rLen-1) + x_(rLen-2) 256^(rLen-2) + ... + x_1 256 + x_0,
20+
// where 0 <= x_i < 256
21+
// (note that one or more leading digits will be zero if x is less than 256^(rLen-1)).
22+
// 3. Let the octet X_i have the integer value x_(rLen-i) for 1 <= i <= rLen.
23+
// Output the octet string X = X_1 X_2 ... X_rLen.
24+
25+
var b bytes.Buffer
26+
xLen := (uint(x.BitLen()) + 7) >> 3
27+
if rLen > xLen {
28+
b.Write(make([]byte, rLen-xLen)) // prepend 0s
29+
}
30+
b.Write(x.Bytes())
31+
return b.Bytes()[uint(b.Len())-rLen:] // The rightmost rLen bytes.
32+
}
33+
34+
// SECG1EncodeCompressed converts an EC point to an octet string according to
35+
// the encoding specified in Section 2.3.3 of [SECG1] with point compression
36+
// on. This implies ptLen = 2n + 1 = 33.
37+
//
38+
// SECG1 Section 2.3.3 https://www.secg.org/sec1-v1.99.dif.pdf
39+
//
40+
// (Note that certain software implementations do not introduce a separate
41+
// elliptic curve point type and instead directly treat the EC point as an
42+
// octet string per above encoding. When using such an implementation, the
43+
// point_to_string function can be treated as the identity function.)
44+
func secg1EncodeCompressed(curve elliptic.Curve, x, y *big.Int) []byte {
45+
byteLen := (curve.Params().BitSize + 7) >> 3
46+
ret := make([]byte, 1+byteLen)
47+
ret[0] = 2 // compressed point
48+
49+
xBytes := x.Bytes()
50+
copy(ret[1+byteLen-len(xBytes):], xBytes)
51+
ret[0] += byte(y.Bit(0))
52+
return ret
53+
}
54+
55+
// This file implements compressed point unmarshaling. Preferably this
56+
// functionality would be in a standard library. Code borrowed from:
57+
// https://go-review.googlesource.com/#/c/1883/2/src/crypto/elliptic/elliptic.go
58+
59+
// SECG1Decode decodes a EC point, given as a compressed string.
60+
// If the decoding fails x and y will be nil.
61+
//
62+
// http://www.secg.org/sec1-v2.pdf
63+
// https://tools.ietf.org/html/rfc8032#section-5.1.3
64+
// Section 4.3.6 of ANSI X9.62.
65+
66+
var errInvalidPoint = errors.New("invalid point")
67+
68+
func secg1Decode(curve elliptic.Curve, data []byte) (x, y *big.Int, err error) {
69+
byteLen := (curve.Params().BitSize + 7) >> 3
70+
if (data[0] &^ 1) != 2 {
71+
return nil, nil, errors.New("unrecognized point encoding")
72+
}
73+
if len(data) != 1+byteLen {
74+
return nil, nil, errors.New("invalid length for curve")
75+
}
76+
77+
// Based on Routine 2.2.4 in NIST Mathematical routines paper
78+
params := curve.Params()
79+
tx := new(big.Int).SetBytes(data[1 : 1+byteLen])
80+
y2 := y2(params, tx)
81+
sqrt := defaultSqrt
82+
ty := sqrt(y2, params.P)
83+
if ty == nil {
84+
return nil, nil, errInvalidPoint // "y^2" is not a square
85+
}
86+
var y2c big.Int
87+
y2c.Mul(ty, ty).Mod(&y2c, params.P)
88+
if y2c.Cmp(y2) != 0 {
89+
return nil, nil, errInvalidPoint // sqrt(y2)^2 != y2: invalid point
90+
}
91+
if ty.Bit(0) != uint(data[0]&1) {
92+
ty.Sub(params.P, ty)
93+
}
94+
95+
return tx, ty, nil // valid point: return it
96+
}
97+
98+
// Use the curve equation to calculate y² given x.
99+
// only applies to curves of the form y² = x³ - 3x + b.
100+
func y2(curve *elliptic.CurveParams, x *big.Int) *big.Int {
101+
// y² = x³ - 3x + b
102+
x3 := new(big.Int).Mul(x, x)
103+
x3.Mul(x3, x)
104+
105+
threeX := new(big.Int).Lsh(x, 1)
106+
threeX.Add(threeX, x)
107+
108+
y2 := new(big.Int).Sub(x3, threeX)
109+
y2.Add(y2, curve.B)
110+
y2.Mod(y2, curve.P)
111+
return y2
112+
}
113+
114+
func defaultSqrt(x, p *big.Int) *big.Int {
115+
var r big.Int
116+
if nil == r.ModSqrt(x, p) {
117+
return nil // x is not a square
118+
}
119+
return &r
120+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package vrf
2+
3+
import (
4+
"bytes"
5+
"crypto/elliptic"
6+
"crypto/rand"
7+
"fmt"
8+
"math/big"
9+
"testing"
10+
)
11+
12+
func TestI2OSP(t *testing.T) {
13+
for i, tc := range []struct {
14+
x int64
15+
xLen uint
16+
want []byte
17+
wantPanic bool
18+
}{
19+
{x: 1, xLen: 1, want: []byte{0x01}},
20+
{x: 2, xLen: 1, want: []byte{0x02}},
21+
{x: 2, xLen: 2, want: []byte{0, 2}},
22+
{x: 256, xLen: 8, want: []byte{0, 0, 0, 0, 0, 0, 1, 0}},
23+
{x: 256, xLen: 1, wantPanic: true},
24+
{x: 255, xLen: 1, want: []byte{0xff}},
25+
} {
26+
t.Run(fmt.Sprintf("%v", i), func(t *testing.T) {
27+
defer func() {
28+
r := recover()
29+
if panicked := r != nil; panicked != tc.wantPanic {
30+
t.Errorf("Panicked: %v, wantPanic %v", r, tc.wantPanic)
31+
}
32+
}()
33+
if got := i2osp(big.NewInt(tc.x), tc.xLen); !bytes.Equal(got, tc.want) {
34+
t.Errorf("I2OSP(%v, %v): %v, want %v", tc.x, tc.xLen, got, tc.want)
35+
}
36+
})
37+
}
38+
}
39+
40+
func TestSEG1EncodeDecode(t *testing.T) {
41+
c := elliptic.P256()
42+
_, Ax, Ay, err := elliptic.GenerateKey(c, rand.Reader)
43+
if err != nil {
44+
t.Fatal(err)
45+
}
46+
47+
b := secg1EncodeCompressed(c, Ax, Ay)
48+
Bx, By, err := secg1Decode(c, b)
49+
if err != nil {
50+
t.Fatal(err)
51+
}
52+
if Bx.Cmp(Ax) != 0 {
53+
t.Fatalf("Bx: %v, want %v", Bx, Ax)
54+
}
55+
if By.Cmp(Ay) != 0 {
56+
t.Fatalf("By: %v, want %v", By, Ay)
57+
}
58+
}

0 commit comments

Comments
 (0)