This repository has been archived by the owner on Oct 11, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 151
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* SECG1 Encode/Decode Encode and Decode elliptic curve points in compressed format. This will be superceeded by golang/go#34105 in Go 1.15
- Loading branch information
Showing
2 changed files
with
178 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package vrf | ||
|
||
import ( | ||
"bytes" | ||
"crypto/elliptic" | ||
"errors" | ||
"math/big" | ||
) | ||
|
||
// i2osp converts a nonnegative integer to an octet string of a specified length. | ||
// RFC8017 section-4.1 (big endian representation) | ||
func i2osp(x *big.Int, rLen uint) []byte { | ||
// 1. If x >= 256^rLen, output "integer too large" and stop. | ||
upperBound := new(big.Int).Lsh(big.NewInt(1), rLen*8) | ||
if x.Cmp(upperBound) >= 0 { | ||
panic("integer too large") | ||
} | ||
// 2. Write the integer x in its unique rLen-digit representation in base 256: | ||
// x = x_(rLen-1) 256^(rLen-1) + x_(rLen-2) 256^(rLen-2) + ... + x_1 256 + x_0, | ||
// where 0 <= x_i < 256 | ||
// (note that one or more leading digits will be zero if x is less than 256^(rLen-1)). | ||
// 3. Let the octet X_i have the integer value x_(rLen-i) for 1 <= i <= rLen. | ||
// Output the octet string X = X_1 X_2 ... X_rLen. | ||
|
||
var b bytes.Buffer | ||
xLen := (uint(x.BitLen()) + 7) >> 3 | ||
if rLen > xLen { | ||
b.Write(make([]byte, rLen-xLen)) // prepend 0s | ||
} | ||
b.Write(x.Bytes()) | ||
return b.Bytes()[uint(b.Len())-rLen:] // The rightmost rLen bytes. | ||
} | ||
|
||
// SECG1EncodeCompressed converts an EC point to an octet string according to | ||
// the encoding specified in Section 2.3.3 of [SECG1] with point compression | ||
// on. This implies ptLen = 2n + 1 = 33. | ||
// | ||
// SECG1 Section 2.3.3 https://www.secg.org/sec1-v1.99.dif.pdf | ||
// | ||
// (Note that certain software implementations do not introduce a separate | ||
// elliptic curve point type and instead directly treat the EC point as an | ||
// octet string per above encoding. When using such an implementation, the | ||
// point_to_string function can be treated as the identity function.) | ||
func secg1EncodeCompressed(curve elliptic.Curve, x, y *big.Int) []byte { | ||
byteLen := (curve.Params().BitSize + 7) >> 3 | ||
ret := make([]byte, 1+byteLen) | ||
ret[0] = 2 // compressed point | ||
|
||
xBytes := x.Bytes() | ||
copy(ret[1+byteLen-len(xBytes):], xBytes) | ||
ret[0] += byte(y.Bit(0)) | ||
return ret | ||
} | ||
|
||
// This file implements compressed point unmarshaling. Preferably this | ||
// functionality would be in a standard library. Code borrowed from: | ||
// https://go-review.googlesource.com/#/c/1883/2/src/crypto/elliptic/elliptic.go | ||
|
||
// SECG1Decode decodes a EC point, given as a compressed string. | ||
// If the decoding fails x and y will be nil. | ||
// | ||
// http://www.secg.org/sec1-v2.pdf | ||
// https://tools.ietf.org/html/rfc8032#section-5.1.3 | ||
// Section 4.3.6 of ANSI X9.62. | ||
|
||
var errInvalidPoint = errors.New("invalid point") | ||
|
||
func secg1Decode(curve elliptic.Curve, data []byte) (x, y *big.Int, err error) { | ||
byteLen := (curve.Params().BitSize + 7) >> 3 | ||
if (data[0] &^ 1) != 2 { | ||
return nil, nil, errors.New("unrecognized point encoding") | ||
} | ||
if len(data) != 1+byteLen { | ||
return nil, nil, errors.New("invalid length for curve") | ||
} | ||
|
||
// Based on Routine 2.2.4 in NIST Mathematical routines paper | ||
params := curve.Params() | ||
tx := new(big.Int).SetBytes(data[1 : 1+byteLen]) | ||
y2 := y2(params, tx) | ||
sqrt := defaultSqrt | ||
ty := sqrt(y2, params.P) | ||
if ty == nil { | ||
return nil, nil, errInvalidPoint // "y^2" is not a square | ||
} | ||
var y2c big.Int | ||
y2c.Mul(ty, ty).Mod(&y2c, params.P) | ||
if y2c.Cmp(y2) != 0 { | ||
return nil, nil, errInvalidPoint // sqrt(y2)^2 != y2: invalid point | ||
} | ||
if ty.Bit(0) != uint(data[0]&1) { | ||
ty.Sub(params.P, ty) | ||
} | ||
|
||
return tx, ty, nil // valid point: return it | ||
} | ||
|
||
// Use the curve equation to calculate y² given x. | ||
// only applies to curves of the form y² = x³ - 3x + b. | ||
func y2(curve *elliptic.CurveParams, x *big.Int) *big.Int { | ||
// y² = x³ - 3x + b | ||
x3 := new(big.Int).Mul(x, x) | ||
x3.Mul(x3, x) | ||
|
||
threeX := new(big.Int).Lsh(x, 1) | ||
threeX.Add(threeX, x) | ||
|
||
y2 := new(big.Int).Sub(x3, threeX) | ||
y2.Add(y2, curve.B) | ||
y2.Mod(y2, curve.P) | ||
return y2 | ||
} | ||
|
||
func defaultSqrt(x, p *big.Int) *big.Int { | ||
var r big.Int | ||
if nil == r.ModSqrt(x, p) { | ||
return nil // x is not a square | ||
} | ||
return &r | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package vrf | ||
|
||
import ( | ||
"bytes" | ||
"crypto/elliptic" | ||
"crypto/rand" | ||
"fmt" | ||
"math/big" | ||
"testing" | ||
) | ||
|
||
func TestI2OSP(t *testing.T) { | ||
for i, tc := range []struct { | ||
x int64 | ||
xLen uint | ||
want []byte | ||
wantPanic bool | ||
}{ | ||
{x: 1, xLen: 1, want: []byte{0x01}}, | ||
{x: 2, xLen: 1, want: []byte{0x02}}, | ||
{x: 2, xLen: 2, want: []byte{0, 2}}, | ||
{x: 256, xLen: 8, want: []byte{0, 0, 0, 0, 0, 0, 1, 0}}, | ||
{x: 256, xLen: 1, wantPanic: true}, | ||
{x: 255, xLen: 1, want: []byte{0xff}}, | ||
} { | ||
t.Run(fmt.Sprintf("%v", i), func(t *testing.T) { | ||
defer func() { | ||
r := recover() | ||
if panicked := r != nil; panicked != tc.wantPanic { | ||
t.Errorf("Panicked: %v, wantPanic %v", r, tc.wantPanic) | ||
} | ||
}() | ||
if got := i2osp(big.NewInt(tc.x), tc.xLen); !bytes.Equal(got, tc.want) { | ||
t.Errorf("I2OSP(%v, %v): %v, want %v", tc.x, tc.xLen, got, tc.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestSEG1EncodeDecode(t *testing.T) { | ||
c := elliptic.P256() | ||
_, Ax, Ay, err := elliptic.GenerateKey(c, rand.Reader) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
b := secg1EncodeCompressed(c, Ax, Ay) | ||
Bx, By, err := secg1Decode(c, b) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if Bx.Cmp(Ax) != 0 { | ||
t.Fatalf("Bx: %v, want %v", Bx, Ax) | ||
} | ||
if By.Cmp(Ay) != 0 { | ||
t.Fatalf("By: %v, want %v", By, Ay) | ||
} | ||
} |