Skip to content

Instantly share code, notes, and snippets.

@chenqiyue
Created May 3, 2018 13:33
Show Gist options
  • Save chenqiyue/3457cd962013247e50c91485efc92822 to your computer and use it in GitHub Desktop.
Save chenqiyue/3457cd962013247e50c91485efc92822 to your computer and use it in GitHub Desktop.
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9IntegerConverter;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.math.ec.ECAlgorithms;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve;
import org.web3j.crypto.ECDSASignature;
import org.web3j.crypto.Hash;
import org.web3j.crypto.Keys;
import org.web3j.utils.Numeric;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static org.web3j.utils.Assertions.verifyPrecondition;
public class EthUtils {
private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
static final ECDomainParameters CURVE = new ECDomainParameters(
CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(), CURVE_PARAMS.getH());
public static String recoverAddress(String signature, String message) {
return Keys.getAddress(recoverPublicKey(signature, message));
}
/**
*
* @param signature hexString
* @param msg hexString
* @return
*/
public static String recoverPublicKey(String signature, String msg) {
byte[] sig = Numeric.hexStringToByteArray(signature);
byte[] r = Arrays.copyOfRange(sig, 0, 32);
byte[] s = Arrays.copyOfRange(sig, 32, 64);
byte v = sig[64];
if (v >= 27) {
v -= 27;
}
byte[] messageHash = Numeric.containsHexPrefix(msg)
? Numeric.hexStringToByteArray(msg)
: hashMessage(msg);
ECDSASignature ecdsa = new ECDSASignature(
new BigInteger(1, r),
new BigInteger(1, s)
);
BigInteger publicKey = recoverFromSignature(v, ecdsa, messageHash);
return Numeric.toHexStringWithPrefix(publicKey);
}
public static byte[] hashMessage(String msg) {
return Hash.sha3(("\u0019Ethereum Signed Message:\n" + msg.length() + msg).getBytes(StandardCharsets.UTF_8));
}
public static void main(String[] args) {
String address = recoverAddress(
"0x30755ed65396facf86c53e6217c52b4daebe72aa4941d89635409de4c9c7f9466d4e9aaec7977f05e923889b33c0d0dd27d7226b6e6f56ce737465c5cfd04be400",
"xyz");
//0x135a7de83802408321b74c322f8558db1679ac20
System.out.println(address);
String msg = "Hello World";
// msg = Hash.sha3String(msg);
System.out.println(
Hash.sha3String("\u0019Ethereum Signed Message:\n" + msg.length() + msg)
);
}
static BigInteger recoverFromSignature(int recId, ECDSASignature sig, byte[] message) {
verifyPrecondition(recId >= 0, "recId must be positive");
verifyPrecondition(sig.r.signum() >= 0, "r must be positive");
verifyPrecondition(sig.s.signum() >= 0, "s must be positive");
verifyPrecondition(message != null, "message cannot be null");
// 1.0 For j from 0 to h (h == recId here and the loop is outside this function)
// 1.1 Let x = r + jn
BigInteger n = CURVE.getN(); // Curve order.
BigInteger i = BigInteger.valueOf((long) recId / 2);
BigInteger x = sig.r.add(i.multiply(n));
// 1.2. Convert the integer x to an octet string X of length mlen using the conversion
// routine specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉.
// 1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R
// using the conversion routine specified in Section 2.3.4. If this conversion
// routine outputs "invalid", then do another iteration of Step 1.
//
// More concisely, what these points mean is to use X as a compressed public key.
BigInteger prime = SecP256K1Curve.q;
if (x.compareTo(prime) >= 0) {
// Cannot have point co-ordinates larger than this as everything takes place modulo Q.
return null;
}
// Compressed keys require you to know an extra bit of data about the y-coord as there are
// two possibilities. So it's encoded in the recId.
ECPoint R = decompressKey(x, (recId & 1) == 1);
// 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers
// responsibility).
if (!R.multiply(n).isInfinity()) {
return null;
}
// 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification.
BigInteger e = new BigInteger(1, message);
// 1.6. For k from 1 to 2 do the following. (loop is outside this function via
// iterating recId)
// 1.6.1. Compute a candidate public key as:
// Q = mi(r) * (sR - eG)
//
// Where mi(x) is the modular multiplicative inverse. We transform this into the following:
// Q = (mi(r) * s ** R) + (mi(r) * -e ** G)
// Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n).
// In the above equation ** is point multiplication and + is point addition (the EC group
// operator).
//
// We can find the additive inverse by subtracting e from zero then taking the mod. For
// example the additive inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and
// -3 mod 11 = 8.
BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n);
BigInteger rInv = sig.r.modInverse(n);
BigInteger srInv = rInv.multiply(sig.s).mod(n);
BigInteger eInvrInv = rInv.multiply(eInv).mod(n);
ECPoint q = ECAlgorithms.sumOfTwoMultiplies(CURVE.getG(), eInvrInv, R, srInv);
byte[] qBytes = q.getEncoded(false);
// We remove the prefix
return new BigInteger(1, Arrays.copyOfRange(qBytes, 1, qBytes.length));
}
/**
* Decompress a compressed public key (x co-ord and low-bit of y-coord).
*/
private static ECPoint decompressKey(BigInteger xBN, boolean yBit) {
X9IntegerConverter x9 = new X9IntegerConverter();
byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE.getCurve()));
compEnc[0] = (byte) (yBit ? 0x03 : 0x02);
return CURVE.getCurve().decodePoint(compEnc);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment