|
| 1 | +package org.web3j.crypto; |
| 2 | + |
| 3 | +import org.bouncycastle.asn1.x9.X9IntegerConverter; |
| 4 | +import org.bouncycastle.math.ec.ECAlgorithms; |
| 5 | +import org.bouncycastle.math.ec.ECPoint; |
| 6 | +import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve; |
| 7 | + |
| 8 | +import java.math.BigInteger; |
| 9 | +import java.util.Arrays; |
| 10 | + |
| 11 | +import static org.web3j.utils.Assertions.verifyPrecondition; |
| 12 | + |
| 13 | +public class RecoverFromSignature { |
| 14 | + /** |
| 15 | + * <p>Given the components of a signature and a selector value, recover and return the public |
| 16 | + * key that generated the signature according to the algorithm in SEC1v2 section 4.1.6.</p> |
| 17 | + * |
| 18 | + * <p>The recId is an index from 0 to 3 which indicates which of the 4 possible keys is the |
| 19 | + * correct one. Because the key recovery operation yields multiple potential keys, the correct |
| 20 | + * key must either be stored alongside the |
| 21 | + * signature, or you must be willing to try each recId in turn until you find one that outputs |
| 22 | + * the key you are expecting.</p> |
| 23 | + * |
| 24 | + * <p>If this method returns null it means recovery was not possible and recId should be |
| 25 | + * iterated.</p> |
| 26 | + * |
| 27 | + * <p>Given the above two points, a correct usage of this method is inside a for loop from |
| 28 | + * 0 to 3, and if the output is null OR a key that is not the one you expect, you try again |
| 29 | + * with the next recId.</p> |
| 30 | + * |
| 31 | + * @param recId Which possible key to recover. |
| 32 | + * @param sig the R and S components of the signature, wrapped. |
| 33 | + * @param message Hash of the data that was signed. |
| 34 | + * @return An ECKey containing only the public part, or null if recovery wasn't possible. |
| 35 | + */ |
| 36 | + public static BigInteger recoverFromSignature(int recId, ECDSASignature sig, byte[] message) { |
| 37 | + verifyPrecondition(recId >= 0, "recId must be positive"); |
| 38 | + verifyPrecondition(sig.r.signum() >= 0, "r must be positive"); |
| 39 | + verifyPrecondition(sig.s.signum() >= 0, "s must be positive"); |
| 40 | + verifyPrecondition(message != null, "message cannot be null"); |
| 41 | + |
| 42 | + // 1.0 For j from 0 to h (h == recId here and the loop is outside this function) |
| 43 | + // 1.1 Let x = r + jn |
| 44 | + BigInteger n = Sign.CURVE.getN(); // Curve order. |
| 45 | + BigInteger i = BigInteger.valueOf((long) recId / 2); |
| 46 | + BigInteger x = sig.r.add(i.multiply(n)); |
| 47 | + // 1.2. Convert the integer x to an octet string X of length mlen using the conversion |
| 48 | + // routine specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉. |
| 49 | + // 1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R |
| 50 | + // using the conversion routine specified in Section 2.3.4. If this conversion |
| 51 | + // routine outputs "invalid", then do another iteration of Step 1. |
| 52 | + // |
| 53 | + // More concisely, what these points mean is to use X as a compressed public key. |
| 54 | + BigInteger prime = SecP256K1Curve.q; |
| 55 | + if (x.compareTo(prime) >= 0) { |
| 56 | + // Cannot have point co-ordinates larger than this as everything takes place modulo Q. |
| 57 | + return null; |
| 58 | + } |
| 59 | + // Compressed keys require you to know an extra bit of data about the y-coord as there are |
| 60 | + // two possibilities. So it's encoded in the recId. |
| 61 | + ECPoint R = decompressKey(x, (recId & 1) == 1); |
| 62 | + // 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers |
| 63 | + // responsibility). |
| 64 | + if (!R.multiply(n).isInfinity()) { |
| 65 | + return null; |
| 66 | + } |
| 67 | + // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification. |
| 68 | + BigInteger e = new BigInteger(1, message); |
| 69 | + // 1.6. For k from 1 to 2 do the following. (loop is outside this function via |
| 70 | + // iterating recId) |
| 71 | + // 1.6.1. Compute a candidate public key as: |
| 72 | + // Q = mi(r) * (sR - eG) |
| 73 | + // |
| 74 | + // Where mi(x) is the modular multiplicative inverse. We transform this into the following: |
| 75 | + // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) |
| 76 | + // Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n). |
| 77 | + // In the above equation ** is point multiplication and + is point addition (the EC group |
| 78 | + // operator). |
| 79 | + // |
| 80 | + // We can find the additive inverse by subtracting e from zero then taking the mod. For |
| 81 | + // example the additive inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and |
| 82 | + // -3 mod 11 = 8. |
| 83 | + BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n); |
| 84 | + BigInteger rInv = sig.r.modInverse(n); |
| 85 | + BigInteger srInv = rInv.multiply(sig.s).mod(n); |
| 86 | + BigInteger eInvrInv = rInv.multiply(eInv).mod(n); |
| 87 | + ECPoint q = ECAlgorithms.sumOfTwoMultiplies(Sign.CURVE.getG(), eInvrInv, R, srInv); |
| 88 | + |
| 89 | + byte[] qBytes = q.getEncoded(false); |
| 90 | + // We remove the prefix |
| 91 | + return new BigInteger(1, Arrays.copyOfRange(qBytes, 1, qBytes.length)); |
| 92 | + } |
| 93 | + |
| 94 | + /** Decompress a compressed public key (x co-ord and low-bit of y-coord). */ |
| 95 | + private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { |
| 96 | + X9IntegerConverter x9 = new X9IntegerConverter(); |
| 97 | + byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(Sign.CURVE.getCurve())); |
| 98 | + compEnc[0] = (byte)(yBit ? 0x03 : 0x02); |
| 99 | + return Sign.CURVE.getCurve().decodePoint(compEnc); |
| 100 | + } |
| 101 | +} |
0 commit comments