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 }