/
ECKey.scala
345 lines (287 loc) · 12.1 KB
/
ECKey.scala
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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
package org.bitcoins.crypto
import java.math.BigInteger
import java.security.SecureRandom
import org.bitcoin.NativeSecp256k1
import org.bouncycastle.crypto.AsymmetricCipherKeyPair
import org.bouncycastle.crypto.generators.ECKeyPairGenerator
import org.bouncycastle.crypto.params.{
ECKeyGenerationParameters,
ECPrivateKeyParameters
}
import org.bouncycastle.math.ec.ECPoint
import scodec.bits.ByteVector
import scala.annotation.tailrec
import scala.concurrent.ExecutionContext.Implicits
import scala.concurrent.{ExecutionContext, Future}
/**
* Created by chris on 2/16/16.
*/
sealed abstract class BaseECKey extends NetworkElement
/**
* Created by chris on 2/16/16.
*/
sealed abstract class ECPrivateKey
extends BaseECKey
with Sign
with MaskedToString {
override def signFunction: ByteVector => Future[ECDigitalSignature] = {
bytes =>
import scala.concurrent.ExecutionContext.Implicits.global
Future(sign(bytes))
}
/**
* Signs a given sequence of bytes with the signingKey
* @param dataToSign the bytes to be signed
* @return the digital signature
*/
override def sign(dataToSign: ByteVector): ECDigitalSignature = {
sign(dataToSign, CryptoContext.default)
}
def sign(
dataToSign: ByteVector,
context: CryptoContext): ECDigitalSignature = {
require(dataToSign.length == 32 && bytes.length <= 32)
context match {
case CryptoContext.LibSecp256k1 => signWithSecp(dataToSign)
case CryptoContext.BouncyCastle => signWithBouncyCastle(dataToSign)
}
}
def signWithSecp(dataToSign: ByteVector): ECDigitalSignature = {
val signature =
NativeSecp256k1.sign(dataToSign.toArray, bytes.toArray)
ECDigitalSignature(ByteVector(signature))
}
def signWithBouncyCastle(dataToSign: ByteVector): ECDigitalSignature = {
BouncyCastleUtil.sign(dataToSign, this)
}
def sign(hash: HashDigest): ECDigitalSignature = sign(hash.bytes)
def signFuture(hash: HashDigest)(
implicit ec: ExecutionContext): Future[ECDigitalSignature] =
Future(sign(hash))
/** Signifies if the this private key corresponds to a compressed public key */
def isCompressed: Boolean
override def publicKey: ECPublicKey = publicKey(CryptoContext.default)
/** Derives the public for a the private key */
def publicKey(context: CryptoContext): ECPublicKey = {
context match {
case CryptoContext.LibSecp256k1 => publicKeyWithSecp
case CryptoContext.BouncyCastle => publicKeyWithBouncyCastle
}
}
def publicKeyWithSecp: ECPublicKey = {
val pubKeyBytes: Array[Byte] =
NativeSecp256k1.computePubkey(bytes.toArray, isCompressed)
val pubBytes = ByteVector(pubKeyBytes)
require(
ECPublicKey.isFullyValid(pubBytes),
s"secp256k1 failed to generate a valid public key, got: ${CryptoBytesUtil
.encodeHex(pubBytes)}")
ECPublicKey(pubBytes)
}
def publicKeyWithBouncyCastle: ECPublicKey = {
BouncyCastleUtil.computePublicKey(this)
}
override def toStringSensitive: String = s"ECPrivateKey($hex,$isCompressed)"
}
object ECPrivateKey extends Factory[ECPrivateKey] {
private case class ECPrivateKeyImpl(
override val bytes: ByteVector,
isCompressed: Boolean,
ec: ExecutionContext)
extends ECPrivateKey {
CryptoContext.default match {
case CryptoContext.LibSecp256k1 =>
require(NativeSecp256k1.secKeyVerify(bytes.toArray),
s"Invalid key according to secp256k1, hex: ${bytes.toHex}")
case CryptoContext.BouncyCastle =>
require(CryptoParams.curve.getCurve
.isValidFieldElement(new BigInteger(1, bytes.toArray)),
s"Invalid key according to Bouncy Castle, hex: ${bytes.toHex}")
}
}
def apply(bytes: ByteVector, isCompressed: Boolean)(
implicit ec: ExecutionContext): ECPrivateKey = {
ECPrivateKeyImpl(bytes, isCompressed, ec)
}
override def fromBytes(bytes: ByteVector): ECPrivateKey =
fromBytes(bytes, isCompressed = true)
@tailrec
def fromBytes(bytes: ByteVector, isCompressed: Boolean): ECPrivateKey = {
if (bytes.size == 32)
ECPrivateKeyImpl(bytes, isCompressed, Implicits.global)
else if (bytes.size < 32) {
//means we need to pad the private key with 0 bytes so we have 32 bytes
ECPrivateKey.fromBytes(bytes.padLeft(32), isCompressed)
} //this is for the case when java serialies a BigInteger to 33 bytes to hold the signed num representation
else if (bytes.size == 33)
ECPrivateKey.fromBytes(bytes.slice(1, 33), isCompressed)
else
throw new IllegalArgumentException(
"Private keys cannot be greater than 33 bytes in size, got: " +
CryptoBytesUtil.encodeHex(bytes) + " which is of size: " + bytes.size)
}
def fromHex(hex: String, isCompressed: Boolean): ECPrivateKey =
fromBytes(CryptoBytesUtil.decodeHex(hex), isCompressed)
/** Generates a fresh [[org.bitcoins.crypto.ECPrivateKey ECPrivateKey]] that has not been used before. */
def apply(): ECPrivateKey = ECPrivateKey(true)
def apply(isCompressed: Boolean): ECPrivateKey = freshPrivateKey(isCompressed)
/** Generates a fresh [[org.bitcoins.crypto.ECPrivateKey ECPrivateKey]] that has not been used before. */
def freshPrivateKey: ECPrivateKey = freshPrivateKey(true)
def freshPrivateKey(isCompressed: Boolean): ECPrivateKey = {
val secureRandom = new SecureRandom
val generator: ECKeyPairGenerator = new ECKeyPairGenerator
val keyGenParams: ECKeyGenerationParameters =
new ECKeyGenerationParameters(CryptoParams.curve, secureRandom)
generator.init(keyGenParams)
val keypair: AsymmetricCipherKeyPair = generator.generateKeyPair
val privParams: ECPrivateKeyParameters =
keypair.getPrivate.asInstanceOf[ECPrivateKeyParameters]
val priv: BigInteger = privParams.getD
val bytes = ByteVector(priv.toByteArray)
ECPrivateKey.fromBytes(bytes, isCompressed)
}
}
/**
* Created by chris on 2/16/16.
*/
sealed abstract class ECPublicKey extends BaseECKey {
def verify(hash: HashDigest, signature: ECDigitalSignature): Boolean =
verify(hash.bytes, signature)
/** Verifies if a given piece of data is signed by the
* [[org.bitcoins.crypto.ECPrivateKey ECPrivateKey]]'s corresponding
* [[org.bitcoins.crypto.ECPublicKey ECPublicKey]]. */
def verify(data: ByteVector, signature: ECDigitalSignature): Boolean = {
verify(data, signature, CryptoContext.default)
}
def verify(
data: ByteVector,
signature: ECDigitalSignature,
context: CryptoContext): Boolean = {
context match {
case CryptoContext.LibSecp256k1 => verifyWithSecp(data, signature)
case CryptoContext.BouncyCastle => verifyWithBouncyCastle(data, signature)
}
}
def verifyWithSecp(
data: ByteVector,
signature: ECDigitalSignature): Boolean = {
val result =
NativeSecp256k1.verify(data.toArray,
signature.bytes.toArray,
bytes.toArray)
if (!result) {
//if signature verification fails with libsecp256k1 we need to use our old
//verification function from spongy castle, this is needed because early blockchain
//transactions can have weird non strict der encoded digital signatures
//bitcoin core implements this functionality here:
//https://github.com/bitcoin/bitcoin/blob/master/src/pubkey.cpp#L16-L165
verifyWithBouncyCastle(data, signature)
} else result
}
def verifyWithBouncyCastle(
data: ByteVector,
signature: ECDigitalSignature): Boolean = {
BouncyCastleUtil.verifyDigitalSignature(data, this, signature)
}
def verify(hex: String, signature: ECDigitalSignature): Boolean =
verify(CryptoBytesUtil.decodeHex(hex), signature)
override def toString: String = "ECPublicKey(" + hex + ")"
/** Checks if the [[org.bitcoins.crypto.ECPublicKey ECPublicKey]] is compressed */
def isCompressed: Boolean = bytes.size == 33
/** Checks if the [[org.bitcoins.crypto.ECPublicKey ECPublicKey]] is valid according to secp256k1 */
def isFullyValid: Boolean = ECPublicKey.isFullyValid(bytes)
/** Returns the decompressed version of this [[org.bitcoins.crypto.ECPublicKey ECPublicKey]] */
def decompressed: ECPublicKey = decompressed(CryptoContext.default)
def decompressed(context: CryptoContext): ECPublicKey = {
context match {
case CryptoContext.LibSecp256k1 => decompressedWithSecp
case CryptoContext.BouncyCastle => decompressedWithBouncyCastle
}
}
def decompressedWithSecp: ECPublicKey = {
if (isCompressed) {
val decompressed = NativeSecp256k1.decompress(bytes.toArray)
ECPublicKey.fromBytes(ByteVector(decompressed))
} else this
}
def decompressedWithBouncyCastle: ECPublicKey = {
BouncyCastleUtil.decompressPublicKey(this)
}
/** Decodes a [[org.bitcoins.crypto.ECPublicKey ECPublicKey]] in bitcoin-s
* to a [[org.bouncycastle.math.ec.ECPoint ECPoint]] data structure that is internal to the
* bouncy castle library
* @return
*/
def toPoint: ECPoint = {
BouncyCastleUtil.decodePoint(bytes)
}
/** Adds this ECPublicKey to another as points and returns the resulting ECPublicKey.
*
* Note: if this ever becomes a bottleneck, secp256k1_ec_pubkey_combine should
* get wrapped in NativeSecp256k1 to speed things up.
*/
def add(otherKey: ECPublicKey): ECPublicKey = {
addWithBouncyCastle(otherKey)
}
def addWithBouncyCastle(otherKey: ECPublicKey): ECPublicKey = {
val sumPoint = toPoint.add(otherKey.toPoint)
ECPublicKey.fromPoint(sumPoint)
}
}
object ECPublicKey extends Factory[ECPublicKey] {
private case class ECPublicKeyImpl(
override val bytes: ByteVector,
ec: ExecutionContext)
extends ECPublicKey {
//unfortunately we cannot place ANY invariants here
//because of old transactions on the blockchain that have weirdly formatted public keys. Look at example in script_tests.json
//https://github.com/bitcoin/bitcoin/blob/master/src/test/data/script_tests.json#L457
//bitcoin core only checks CPubKey::IsValid()
//this means we can have public keys with only one byte i.e. 0x00 or no bytes.
//Eventually we would like this to be CPubKey::IsFullyValid() but since we are remaining backwards compatible
//we cannot do this. If there ever is a hard fork this would be a good thing to add.
}
override def fromBytes(bytes: ByteVector): ECPublicKey = {
ECPublicKeyImpl(bytes, Implicits.global)
}
def apply(): ECPublicKey = freshPublicKey
/** Generates a fresh [[org.bitcoins.crypto.ECPublicKey ECPublicKey]] that has not been used before. */
def freshPublicKey: ECPublicKey = ECPrivateKey.freshPrivateKey.publicKey
/**
* Checks if the public key is valid according to secp256k1
* Mimics this function in bitcoin core
* [[https://github.com/bitcoin/bitcoin/blob/27765b6403cece54320374b37afb01a0cfe571c3/src/pubkey.cpp#L207-L212]]
*/
def isFullyValid(bytes: ByteVector): Boolean = {
isFullyValid(bytes, CryptoContext.default)
}
def isFullyValid(bytes: ByteVector, context: CryptoContext): Boolean = {
context match {
case CryptoContext.LibSecp256k1 => isFullyValidWithSecp(bytes)
case CryptoContext.BouncyCastle => isFullyValidWithBouncyCastle(bytes)
}
}
def isFullyValidWithSecp(bytes: ByteVector): Boolean = {
try {
NativeSecp256k1.isValidPubKey(bytes.toArray) && isValid(bytes)
} catch {
case scala.util.control.NonFatal(_) =>
false
}
}
def isFullyValidWithBouncyCastle(bytes: ByteVector): Boolean = {
BouncyCastleUtil.validatePublicKey(bytes) && isValid(bytes)
}
/**
* Mimics the CPubKey::IsValid function in Bitcoin core, this is a consensus rule
* [[https://github.com/bitcoin/bitcoin/blob/27765b6403cece54320374b37afb01a0cfe571c3/src/pubkey.h#L158]]
*/
def isValid(bytes: ByteVector): Boolean = bytes.nonEmpty
/** Creates a [[org.bitcoins.crypto.ECPublicKey ECPublicKey]] from the
* [[org.bouncycastle.math.ec.ECPoint ECPoint]] data structure used internally inside of Bouncy Castle
*/
def fromPoint(p: ECPoint, isCompressed: Boolean = true): ECPublicKey = {
val bytes = p.getEncoded(isCompressed)
ECPublicKey.fromBytes(ByteVector(bytes))
}
}