Created
May 3, 2018 13:33
-
-
Save chenqiyue/3457cd962013247e50c91485efc92822 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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