forked from nspcc-dev/neofs-crypto
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ecdsa.go
218 lines (176 loc) · 5.75 KB
/
ecdsa.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package crypto
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha512"
"crypto/x509"
"math/big"
"github.com/nspcc-dev/neofs-crypto/internal"
"github.com/pkg/errors"
)
const (
// ErrEmptyPublicKey when PK passed to Verify method is nil.
ErrEmptyPublicKey = internal.Error("empty public key")
// ErrInvalidSignature when signature passed to Verify method is mismatch.
ErrInvalidSignature = internal.Error("invalid signature")
// ErrCannotUnmarshal when signature ([]byte) passed to Verify method has wrong format
// and cannot be parsed.
ErrCannotUnmarshal = internal.Error("could not unmarshal signature")
// PrivateKeyCompressedSize is constant with compressed size of private key (SK).
// D coordinate stored, recover PK by formula x, y = curve.ScalarBaseMul(d,bytes).
PrivateKeyCompressedSize = 32
// PublicKeyCompressedSize is constant with compressed size of public key (PK).
PublicKeyCompressedSize = 33
// PublicKeyUncompressedSize is constant with uncompressed size of public key (PK).
// First byte always should be 0x4 other 64 bytes is X and Y (32 bytes per coordinate).
// 2 * 32 + 1
PublicKeyUncompressedSize = 65
)
// P256 is base elliptic curve.
var curve = elliptic.P256()
// Marshal converts a points into the uncompressed form specified in section 4.3.6 of ANSI X9.62.
func marshalXY(x, y *big.Int) []byte {
return elliptic.Marshal(curve, x, y)
}
// unmarshalXY converts a point, serialized by Marshal, into an x, y pair.
// It is an error if the point is not in uncompressed form.
// On error, x,y = nil.
// Unlike the original version of the code, we ignore that x or y not on the curve
// --------------
// It's copy-paste elliptic.Unmarshal(curve, data) stdlib function, without last line
// of code.
// Link - https://golang.org/pkg/crypto/elliptic/#Unmarshal
func unmarshalXY(data []byte) (x *big.Int, y *big.Int) {
if len(data) != PublicKeyUncompressedSize {
return
} else if data[0] != 4 { // uncompressed form
return
}
p := curve.Params().P
x = new(big.Int).SetBytes(data[1:PublicKeyCompressedSize])
y = new(big.Int).SetBytes(data[PublicKeyCompressedSize:])
if x.Cmp(p) >= 0 || y.Cmp(p) >= 0 {
x, y = nil, nil
}
return
}
// decompressPoints using formula y² = x³ - 3x + b
// crypto/elliptic/elliptic.go:55
func decompressPoints(x *big.Int, yBit uint) (*big.Int, *big.Int) {
params := curve.Params()
x3 := new(big.Int).Mul(x, x)
x3.Mul(x3, x)
threeX := new(big.Int).Lsh(x, 1)
threeX.Add(threeX, x)
x3.Sub(x3, threeX)
x3.Add(x3, params.B)
x3.Mod(x3, params.P)
// y = √(x³ - 3x + b) mod p
y := new(big.Int).ModSqrt(x3, params.P)
// big.Int.Jacobi(a, b) can return nil
if y == nil {
return nil, nil
}
if y.Bit(0) != (yBit & 0x1) {
y.Neg(y)
y.Mod(y, params.P)
}
return x, y
}
func encodePoint(x, y *big.Int) []byte {
data := make([]byte, PublicKeyCompressedSize)
i := PublicKeyCompressedSize - len(x.Bytes())
copy(data[i:], x.Bytes())
if y.Bit(0) == 0x1 {
data[0] = 0x3
} else {
data[0] = 0x2
}
return data
}
func decodePoint(data []byte) (*big.Int, *big.Int) {
// empty data
if len(data) == 0 {
return nil, nil
}
switch prefix := data[0]; prefix {
case 0x02, 0x03: // compressed key
// Incorrect length for compressed encoding
if len(data) != PublicKeyCompressedSize {
return nil, nil
}
return decompressPoints(new(big.Int).SetBytes(data[1:]), uint(prefix))
case 0x04: // uncompressed key
// To get the public key, besides getting it from the data and checking,
// we also must to check that the points are on an elliptic curve
return unmarshalXY(data)
}
// unknown type
return nil, nil
}
// MarshalPublicKey to bytes.
func MarshalPublicKey(key *ecdsa.PublicKey) []byte {
if key == nil || key.X == nil || key.Y == nil {
return nil
}
return encodePoint(key.X, key.Y)
}
// UnmarshalPublicKey from bytes.
func UnmarshalPublicKey(data []byte) *ecdsa.PublicKey {
if x, y := decodePoint(data); x != nil && y != nil && curve.IsOnCurve(x, y) {
return &ecdsa.PublicKey{
Curve: curve,
X: x,
Y: y,
}
}
return nil
}
// UnmarshalPrivateKey from bytes.
// It is similar to `ecdsa.Generate()` but uses pre-defined big.Int and
// curve for NEO Blockchain (elliptic.P256)
// Link - https://golang.org/pkg/crypto/ecdsa/#GenerateKey
func UnmarshalPrivateKey(data []byte) (*ecdsa.PrivateKey, error) {
if len(data) == PrivateKeyCompressedSize { // todo: consider using only NEO blockchain private keys
d := new(big.Int).SetBytes(data)
priv := new(ecdsa.PrivateKey)
priv.PublicKey.Curve = curve
priv.D = d
priv.PublicKey.X, priv.PublicKey.Y = curve.ScalarBaseMult(data)
return priv, nil
}
return x509.ParseECPrivateKey(data)
}
// MarshalPrivateKey to bytes.
func MarshalPrivateKey(key *ecdsa.PrivateKey) []byte {
return key.D.Bytes()
}
// hashBytes returns the sha512 sum.
func hashBytes(data []byte) []byte {
buf := sha512.Sum512(data)
return buf[:]
}
// Verify verifies the signature of msg using the public key pub. It returns
// nil only if signature is valid.
func Verify(pub *ecdsa.PublicKey, msg, sig []byte) error {
if r, s := unmarshalXY(sig); r == nil || s == nil {
return ErrCannotUnmarshal
} else if pub == nil {
return ErrEmptyPublicKey
} else if !ecdsa.Verify(pub, hashBytes(msg), r, s) {
return errors.Wrapf(ErrInvalidSignature, "%0x : %0x", r, s)
}
return nil
}
// Sign signs a message using the private key. If the sha512 hash of msg
// is longer than the bit-length of the private key's curve order, the hash
// will be truncated to that length. It returns the signature as slice bytes.
// The security of the private key depends on the entropy of rand.
func Sign(key *ecdsa.PrivateKey, msg []byte) ([]byte, error) {
x, y, err := ecdsa.Sign(rand.Reader, key, hashBytes(msg))
if err != nil {
return nil, err
}
return marshalXY(x, y), nil
}