Coverage Summary for Class: Secp256k1ServiceBC (org.ethereum.crypto.signature)
Class |
Class, %
|
Method, %
|
Line, %
|
Secp256k1ServiceBC |
100%
(1/1)
|
80%
(4/5)
|
76.9%
(30/39)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2020 RSK Labs Ltd.
4 * (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>)
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 package org.ethereum.crypto.signature;
21
22 import org.bouncycastle.asn1.sec.SECNamedCurves;
23 import org.bouncycastle.asn1.x9.X9ECParameters;
24 import org.bouncycastle.asn1.x9.X9IntegerConverter;
25 import org.bouncycastle.crypto.params.ECDomainParameters;
26 import org.bouncycastle.crypto.params.ECPublicKeyParameters;
27 import org.bouncycastle.crypto.signers.ECDSASigner;
28 import org.bouncycastle.math.ec.ECAlgorithms;
29 import org.bouncycastle.math.ec.ECCurve;
30 import org.bouncycastle.math.ec.ECPoint;
31 import org.ethereum.crypto.ECKey;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import javax.annotation.Nullable;
36 import java.math.BigInteger;
37
38 /**
39 * Implementation of SignatureService with Bouncy Castle.
40 */
41 class Secp256k1ServiceBC implements Secp256k1Service {
42
43 private static final Logger logger = LoggerFactory.getLogger(Secp256k1ServiceBC.class);
44 /**
45 * The parameters of the secp256k1 curve that Ethereum uses.
46 */
47 public static final ECDomainParameters CURVE;
48 public static final BigInteger HALF_CURVE_ORDER;
49
50 static {
51 // All clients must agree on the curve to use by agreement. Ethereum uses secp256k1.
52 X9ECParameters params = SECNamedCurves.getByName("secp256k1");
53 CURVE = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH());
54 HALF_CURVE_ORDER = params.getN().shiftRight(1);
55 }
56
57 /**
58 * Part of the Singleton Signature service.
59 * {@link Secp256k1#getInstance()}
60 */
61 Secp256k1ServiceBC() {
62 }
63
64 @Nullable
65 @Override
66 public ECKey recoverFromSignature(int recId, ECDSASignature sig, byte[] messageHash, boolean compressed) {
67 check(recId >= 0, "recId must be positive");
68 check(sig.getR().signum() >= 0, "r must be positive");
69 check(sig.getS().signum() >= 0, "s must be positive");
70 check(messageHash != null, "messageHash must not be null");
71 // 1.0 For j from 0 to h (h == recId here and the loop is outside this function)
72 // 1.1 Let x = r + jn
73 BigInteger n = CURVE.getN(); // Curve order.
74 BigInteger i = BigInteger.valueOf((long) recId / 2);
75 BigInteger x = sig.getR().add(i.multiply(n));
76 // 1.2. Convert the integer x to an octet string X of length mlen using the conversion routine
77 // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉.
78 // 1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R using the
79 // conversion routine specified in Section 2.3.4. If this conversion routine outputs “invalid”, then
80 // do another iteration of Step 1.
81 //
82 // More concisely, what these points mean is to use X as a compressed public key.
83 ECCurve.Fp curve = (ECCurve.Fp) CURVE.getCurve();
84 BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent about the letter it uses for the prime.
85 if (x.compareTo(prime) >= 0) {
86 // Cannot have point co-ordinates larger than this as everything takes place modulo Q.
87 return null;
88 }
89 // Compressed keys require you to know an extra bit of data about the y-coord as there are two possibilities.
90 // So it's encoded in the recId.
91 ECPoint r = decompressKey(x, (recId & 1) == 1);
92 // 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers responsibility).
93 if (!r.multiply(n).isInfinity()) {
94 return null;
95 }
96 // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification.
97 BigInteger e = new BigInteger(1, messageHash);
98 // 1.6. For k from 1 to 2 do the following. (loop is outside this function via iterating recId)
99 // 1.6.1. Compute a candidate public key as:
100 // Q = mi(r) * (sR - eG)
101 //
102 // Where mi(x) is the modular multiplicative inverse. We transform this into the following:
103 // Q = (mi(r) * s ** R) + (mi(r) * -e ** G)
104 // Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n). In the above equation
105 // ** is point multiplication and + is point addition (the EC group operator).
106 //
107 // We can find the additive inverse by subtracting e from zero then taking the mod. For example the additive
108 // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod 11 = 8.
109 BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n);
110 BigInteger rInv = sig.getR().modInverse(n);
111 BigInteger srInv = rInv.multiply(sig.getS()).mod(n);
112 BigInteger eInvrInv = rInv.multiply(eInv).mod(n);
113 ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(CURVE.getG(), eInvrInv, r, srInv);
114 return ECKey.fromPublicOnly(q.getEncoded(compressed));
115 }
116
117 @Override
118 public boolean verify(byte[] data, ECDSASignature signature, byte[] pub) {
119 ECDSASigner signer = new ECDSASigner();
120 ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE);
121 signer.init(false, params);
122 try {
123 return signer.verifySignature(data, signature.getR(), signature.getS());
124 } catch (NullPointerException npe) {
125 // Bouncy Castle contains a bug that can cause NPEs given specially crafted signatures.
126 // Those signatures are inherently invalid/attack sigs so we just fail them here rather than crash the thread.
127 logger.error("Caught NPE inside bouncy castle", npe);
128 return false;
129 }
130 }
131
132 /**
133 * Decompress a compressed public key (x co-ord and low-bit of y-coord).
134 *
135 * @param xBN -
136 * @param yBit -
137 * @return -
138 */
139 private ECPoint decompressKey(BigInteger xBN, boolean yBit) {
140 X9IntegerConverter x9 = new X9IntegerConverter();
141 byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE.getCurve()));
142 compEnc[0] = (byte) (yBit ? 0x03 : 0x02);
143 return CURVE.getCurve().decodePoint(compEnc);
144 }
145
146 }