mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-02-23 22:46:56 +01:00
ECKey: add BIP 137 support for verifying signatures from segwit addresses
BIP 137 extended the allowed range of values for the header byte of signatures (for signatures created from segwit addresses). This commit reflects these changes and adds the newly allowed header byte values to ECKey.signedMessageToKey(...). Also adds test cases verifying signatures created with segwit addresses.
This commit is contained in:
parent
bc27caaae0
commit
e4f6dbd866
2 changed files with 64 additions and 6 deletions
|
@ -24,7 +24,6 @@ import com.google.common.primitives.UnsignedBytes;
|
|||
import org.bitcoin.NativeSecp256k1;
|
||||
import org.bitcoin.NativeSecp256k1Util;
|
||||
import org.bitcoin.Secp256k1Context;
|
||||
import org.bitcoinj.base.BitcoinNetwork;
|
||||
import org.bitcoinj.base.Network;
|
||||
import org.bitcoinj.base.ScriptType;
|
||||
import org.bitcoinj.base.Sha256Hash;
|
||||
|
@ -826,9 +825,8 @@ public class ECKey implements EncryptableItem {
|
|||
if (signatureEncoded.length < 65)
|
||||
throw new SignatureException("Signature truncated, expected 65 bytes and got " + signatureEncoded.length);
|
||||
int header = signatureEncoded[0] & 0xFF;
|
||||
// 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
|
||||
if (header < 27 || header > 34)
|
||||
// allowed range of valid header byte values as defined in BIP 137:
|
||||
if (header < 27 || header > 42)
|
||||
throw new SignatureException("Header byte out of range: " + header);
|
||||
BigInteger r = ByteUtils.bytesToBigInteger(Arrays.copyOfRange(signatureEncoded, 1, 33));
|
||||
BigInteger s = ByteUtils.bytesToBigInteger(Arrays.copyOfRange(signatureEncoded, 33, 65));
|
||||
|
@ -838,10 +836,35 @@ public class ECKey implements EncryptableItem {
|
|||
// JSON-SPIRIT hands back. Assume UTF-8 for now.
|
||||
Sha256Hash messageHash = Sha256Hash.twiceOf(messageBytes);
|
||||
boolean compressed = false;
|
||||
if (header >= 31) {
|
||||
// Meaning of header byte ranges:
|
||||
// * 27-30: P2PKH uncompressed, recId 0-3
|
||||
// * 31-34: P2PKH compressed, recId 0-3
|
||||
// * 35-38: Segwit P2SH (always compressed), recId 0-3
|
||||
// * 39-42: Segwit Bech32 (always compressed), recId 0-3
|
||||
// as defined in https://github.com/bitcoin/bips/blob/master/bip-0137.mediawiki#procedure-for-signingverifying-a-signature
|
||||
|
||||
// this is a signature created with a Segwit bech32 address
|
||||
if (header >= 39) {
|
||||
header -= 12;
|
||||
compressed = true; // by definition in BIP 141
|
||||
}
|
||||
// this is a signature created with a Segwit p2sh (p2sh-p2wpkh) address
|
||||
else if (header >= 35) {
|
||||
header -= 8;
|
||||
compressed = true; // by definition in BIP 141
|
||||
}
|
||||
// this is a signature created with a compressed p2pkh address
|
||||
else if (header >= 31) {
|
||||
compressed = true;
|
||||
header -= 4;
|
||||
}
|
||||
// else: signature created with an uncompressed p2pkh address
|
||||
|
||||
// As the recovery of a pubkey from an ECDSA signature will recover several possible
|
||||
// public keys, the header byte is used to carry information, which of the possible
|
||||
// keys was the key used to create the signature (see also BIP 137):
|
||||
// header byte: 27 = first key with even y, 28 = first key with odd y,
|
||||
// 29 = second key with even y, 30 = second key with odd y
|
||||
int recId = header - 27;
|
||||
ECKey key = ECKey.recoverFromSignature(recId, sig, messageHash, compressed);
|
||||
if (key == null)
|
||||
|
@ -905,6 +928,7 @@ public class ECKey implements EncryptableItem {
|
|||
Preconditions.checkArgument(sig.r.signum() >= 0, "r must be positive");
|
||||
Preconditions.checkArgument(sig.s.signum() >= 0, "s must be positive");
|
||||
Preconditions.checkNotNull(message);
|
||||
// see https://www.secg.org/sec1-v2.pdf, section 4.1.6
|
||||
// 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.
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.bitcoinj.core;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import org.bitcoinj.base.BitcoinNetwork;
|
||||
import org.bitcoinj.base.ScriptType;
|
||||
import org.bitcoinj.base.Sha256Hash;
|
||||
|
@ -227,7 +228,7 @@ public class ECKeyTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void verifyMessage() throws Exception {
|
||||
public void verifyMessageSignedWithCompressedP2PKHaddress() throws Exception {
|
||||
// Test vector generated by Bitcoin-Qt.
|
||||
String message = "hello";
|
||||
String sigBase64 = "HxNZdo6ggZ41hd3mM3gfJRqOQPZYcO8z8qdX2BwmpbF11CaOQV+QiZGGQxaYOncKoNW61oRuSMMF8udfK54XqI8=";
|
||||
|
@ -237,6 +238,39 @@ public class ECKeyTest {
|
|||
assertEquals(expectedAddress, gotAddress);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyMessageSignedWithUncompressedP2PKHaddress() throws Exception {
|
||||
String message = "message signed using an p2pkh address derived from an uncompressed public key";
|
||||
String sigBase64 = "HDoME2gqLJApQTOLnce4J7BZcO1yIxUSP6tdKIUBLO99E+BH3uABshRoFzIdVYZo16zpAGtiHq8Xq9YbswDVR1M=";
|
||||
Address expectedAddress = LegacyAddress.fromBase58(BitcoinNetwork.MAINNET, "1C6SjmutxV21sPdqQAWLJbvznfyCoU2zWc");
|
||||
ECKey key = ECKey.signedMessageToKey(message, sigBase64);
|
||||
Address gotAddress = key.toAddress(ScriptType.P2PKH, BitcoinNetwork.MAINNET);
|
||||
assertEquals(expectedAddress, gotAddress);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyMessageSignedWithNativeSegwitP2WPKHAddress() throws Exception {
|
||||
String message = "This msg was signed with a native SegWit v0 address, the signature header byte therefore is in the range 39-42 (according to BIP 137).";
|
||||
String sigBase64 = "KH4/rrraZsPwuuW6pSKVnZVdZXmzLPBOPSS9zz6QLZnTGhO2mHFAs53QLPp94Hahz7kTgNiO6VYZpehMbNHIvNA=";
|
||||
Address expectedAddress = SegwitAddress.fromBech32(BitcoinNetwork.MAINNET, "bc1qvcl0z7f25sf2u8up5wplk7arwclghh7de8fy6l");
|
||||
ECKey key = ECKey.signedMessageToKey(message, sigBase64);
|
||||
Address gotAddress = key.toAddress(ScriptType.P2WPKH, BitcoinNetwork.MAINNET);
|
||||
assertEquals(expectedAddress, gotAddress);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyMessageSignedLegacySegwitP2SH_P2WPKHAddress() throws Exception {
|
||||
String message = "This message was signed with a P2SH-P2WPKH address, the signature header byte therefore is in the range 35-38 (according to BIP 137).";
|
||||
String sigBase64 = "I6CwPW9ErVV8SphnQbHnOfYcwcqMdJaZRkym5QHzykpzVw38SrftZFaWoqMl+pvJ92hOyj8PjDOQOT2hDXtk5V0=";
|
||||
Address expectedAddress = LegacyAddress.fromBase58(BitcoinNetwork.MAINNET, "3HnHC8dJCqixUBFNYXdz2LFXQwvAkkTR3m");
|
||||
ECKey key = ECKey.signedMessageToKey(message, sigBase64);
|
||||
final byte[] segwitV0_OpPush20 = {0x00, 0x14};
|
||||
byte[] segwitV0ScriptPubKey = Bytes.concat(segwitV0_OpPush20, key.getPubKeyHash()); // as defined in BIP 141
|
||||
byte[] scriptHashOfSegwitScript = Utils.sha256hash160(segwitV0ScriptPubKey);
|
||||
Address gotAddress = LegacyAddress.fromScriptHash(BitcoinNetwork.MAINNET, scriptHashOfSegwitScript);
|
||||
assertEquals(expectedAddress, gotAddress);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findRecoveryId() {
|
||||
ECKey key = new ECKey();
|
||||
|
|
Loading…
Add table
Reference in a new issue