mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 18:16:45 +00:00
support bip 137 for message signing, but handle electrum approach as well when verifying
This commit is contained in:
parent
ee49ddd94b
commit
10ebfe463d
1 changed files with 36 additions and 7 deletions
|
@ -1,6 +1,7 @@
|
||||||
package com.sparrowwallet.drongo.crypto;
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
|
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||||
import com.sparrowwallet.drongo.protocol.SignatureDecodeException;
|
import com.sparrowwallet.drongo.protocol.SignatureDecodeException;
|
||||||
import com.sparrowwallet.drongo.protocol.VarInt;
|
import com.sparrowwallet.drongo.protocol.VarInt;
|
||||||
|
@ -716,12 +717,12 @@ public class ECKey implements EncryptableItem {
|
||||||
* @throws IllegalStateException if this ECKey does not have the private part.
|
* @throws IllegalStateException if this ECKey does not have the private part.
|
||||||
* @throws KeyCrypterException if this ECKey is encrypted and no AESKey is provided or it does not decrypt the ECKey.
|
* @throws KeyCrypterException if this ECKey is encrypted and no AESKey is provided or it does not decrypt the ECKey.
|
||||||
*/
|
*/
|
||||||
public String signMessage(String message, Key aesKey) throws KeyCrypterException {
|
public String signMessage(String message, ScriptType scriptType, Key aesKey) throws KeyCrypterException {
|
||||||
byte[] data = formatMessageForSigning(message);
|
byte[] data = formatMessageForSigning(message);
|
||||||
Sha256Hash hash = Sha256Hash.twiceOf(data);
|
Sha256Hash hash = Sha256Hash.twiceOf(data);
|
||||||
ECDSASignature sig = sign(hash, aesKey);
|
ECDSASignature sig = sign(hash, aesKey);
|
||||||
byte recId = findRecoveryId(hash, sig);
|
byte recId = findRecoveryId(hash, sig);
|
||||||
int headerByte = recId + 27 + (isCompressed() ? 4 : 0);
|
int headerByte = recId + getSigningTypeConstant(scriptType);
|
||||||
byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S
|
byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S
|
||||||
sigData[0] = (byte) headerByte;
|
sigData[0] = (byte) headerByte;
|
||||||
System.arraycopy(Utils.bigIntegerToBytes(sig.r, 32), 0, sigData, 1, 32);
|
System.arraycopy(Utils.bigIntegerToBytes(sig.r, 32), 0, sigData, 1, 32);
|
||||||
|
@ -729,6 +730,25 @@ public class ECKey implements EncryptableItem {
|
||||||
return new String(Base64.getEncoder().encode(sigData), StandardCharsets.UTF_8);
|
return new String(Base64.getEncoder().encode(sigData), StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Although no standard has yet been decided on, we follow Trezor's approach for now as documented in
|
||||||
|
* https://github.com/bitcoin/bips/blob/master/bip-0137.mediawiki
|
||||||
|
*
|
||||||
|
* @param scriptType The script type of the address used to sign
|
||||||
|
* @return A constant used to alter the header of the signature in order to distinguish between different script types
|
||||||
|
*/
|
||||||
|
private int getSigningTypeConstant(ScriptType scriptType) {
|
||||||
|
if(scriptType == ScriptType.P2PKH) {
|
||||||
|
return 27 + (isCompressed() ? 4 : 0);
|
||||||
|
} else if(scriptType == ScriptType.P2SH_P2WPKH) {
|
||||||
|
return 35;
|
||||||
|
} else if(scriptType == ScriptType.P2WPKH) {
|
||||||
|
return 39;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Script type of " + scriptType + " is not supported for message signing");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an arbitrary piece of text and a Bitcoin-format message signature encoded in base64, returns an ECKey
|
* Given an arbitrary piece of text and a Bitcoin-format message signature encoded in base64, returns an ECKey
|
||||||
* containing the public key that was used to sign it. This can then be compared to the expected public key to
|
* containing the public key that was used to sign it. This can then be compared to the expected public key to
|
||||||
|
@ -738,9 +758,10 @@ public class ECKey implements EncryptableItem {
|
||||||
*
|
*
|
||||||
* @param message Some piece of human readable text.
|
* @param message Some piece of human readable text.
|
||||||
* @param signatureBase64 The Bitcoin-format message signature in base64
|
* @param signatureBase64 The Bitcoin-format message signature in base64
|
||||||
|
* @param electrumFormat Whether to generate a key following Electrum's approach of regarding P2SH-P2WSH as the same as P2PKH uncompressed
|
||||||
* @throws SignatureException If the public key could not be recovered or if there was a signature format error.
|
* @throws SignatureException If the public key could not be recovered or if there was a signature format error.
|
||||||
*/
|
*/
|
||||||
public static ECKey signedMessageToKey(String message, String signatureBase64) throws SignatureException {
|
public static ECKey signedMessageToKey(String message, String signatureBase64, boolean electrumFormat) throws SignatureException {
|
||||||
byte[] signatureEncoded;
|
byte[] signatureEncoded;
|
||||||
try {
|
try {
|
||||||
signatureEncoded = Base64.getDecoder().decode(signatureBase64);
|
signatureEncoded = Base64.getDecoder().decode(signatureBase64);
|
||||||
|
@ -755,7 +776,7 @@ public class ECKey implements EncryptableItem {
|
||||||
int header = signatureEncoded[0] & 0xFF;
|
int header = signatureEncoded[0] & 0xFF;
|
||||||
// The header byte: 0x1B = first key with even y, 0x1C = first key with odd y,
|
// The header byte: 0x1B = first key with even y, 0x1C = first key with odd y,
|
||||||
// 0x1D = second key with even y, 0x1E = second key with odd y
|
// 0x1D = second key with even y, 0x1E = second key with odd y
|
||||||
if(header < 27 || header > 34) {
|
if(header < 27 || header > 42) {
|
||||||
throw new SignatureException("Header byte out of range: " + header);
|
throw new SignatureException("Header byte out of range: " + header);
|
||||||
}
|
}
|
||||||
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signatureEncoded, 1, 33));
|
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signatureEncoded, 1, 33));
|
||||||
|
@ -766,7 +787,15 @@ public class ECKey implements EncryptableItem {
|
||||||
// JSON-SPIRIT hands back. Assume UTF-8 for now.
|
// JSON-SPIRIT hands back. Assume UTF-8 for now.
|
||||||
Sha256Hash messageHash = Sha256Hash.twiceOf(messageBytes);
|
Sha256Hash messageHash = Sha256Hash.twiceOf(messageBytes);
|
||||||
boolean compressed = false;
|
boolean compressed = false;
|
||||||
if(header >= 31) {
|
if(header >= 39) { // this is a bech32 signature
|
||||||
|
header -= 12;
|
||||||
|
compressed = true;
|
||||||
|
}
|
||||||
|
else if(header >= 35 && !electrumFormat) { // this is a segwit p2sh signature
|
||||||
|
compressed = true;
|
||||||
|
header -= 8;
|
||||||
|
}
|
||||||
|
else if(header >= 31) { // this is a compressed key signature
|
||||||
compressed = true;
|
compressed = true;
|
||||||
header -= 4;
|
header -= 4;
|
||||||
}
|
}
|
||||||
|
@ -779,11 +808,11 @@ public class ECKey implements EncryptableItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience wrapper around {@link ECKey#signedMessageToKey(String, String)}. If the key derived from the
|
* Convenience wrapper around {@link ECKey#signedMessageToKey(String, String, boolean)}. If the key derived from the
|
||||||
* signature is not the same as this one, throws a SignatureException.
|
* signature is not the same as this one, throws a SignatureException.
|
||||||
*/
|
*/
|
||||||
public void verifyMessage(String message, String signatureBase64) throws SignatureException {
|
public void verifyMessage(String message, String signatureBase64) throws SignatureException {
|
||||||
ECKey key = ECKey.signedMessageToKey(message, signatureBase64);
|
ECKey key = ECKey.signedMessageToKey(message, signatureBase64, false);
|
||||||
if(!key.pub.equals(pub)) {
|
if(!key.pub.equals(pub)) {
|
||||||
throw new SignatureException("Signature did not match for message");
|
throw new SignatureException("Signature did not match for message");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue