refactor ECKey and TransactionSignature to support schnorr

This commit is contained in:
Craig Raw 2021-07-12 08:55:31 +02:00
parent e53574ea54
commit f1ce2ec939
11 changed files with 517 additions and 573 deletions

View file

@ -0,0 +1,254 @@
package com.sparrowwallet.drongo.crypto;
import com.sparrowwallet.drongo.protocol.SigHash;
import com.sparrowwallet.drongo.protocol.SignatureDecodeException;
import com.sparrowwallet.drongo.protocol.TransactionSignature;
import com.sparrowwallet.drongo.protocol.VerificationException;
import org.bouncycastle.asn1.*;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Objects;
import static com.sparrowwallet.drongo.crypto.ECKey.CURVE;
/**
* Groups the two components that make up a signature, and provides a way to encode to DER form, which is
* how ECDSA signatures are represented when embedded in other data structures in the Bitcoin protocol. The raw
* components can be useful for doing further EC maths on them.
*/
public class ECDSASignature {
private static final Logger log = LoggerFactory.getLogger(ECDSASignature.class);
/**
* The two components of the signature.
*/
public final BigInteger r, s;
/**
* Constructs a signature with the given components. Does NOT automatically canonicalise the signature.
*/
public ECDSASignature(BigInteger r, BigInteger s) {
this.r = r;
this.s = s;
}
/**
* Returns true if the S component is "low", that means it is below {@link ECKey#HALF_CURVE_ORDER}. See <a
* href="https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#Low_S_values_in_signatures">BIP62</a>.
*/
public boolean isCanonical() {
return s.compareTo(ECKey.HALF_CURVE_ORDER) <= 0;
}
/**
* Will automatically adjust the S component to be less than or equal to half the curve order, if necessary.
* This is required because for every signature (r,s) the signature (r, -s (mod N)) is a valid signature of
* the same message. However, we dislike the ability to modify the bits of a Bitcoin transaction after it's
* been signed, as that violates various assumed invariants. Thus in future only one of those forms will be
* considered legal and the other will be banned.
*/
public ECDSASignature toCanonicalised() {
if(!isCanonical()) {
// The order of the curve is the number of valid points that exist on that curve. If S is in the upper
// half of the number of valid points, then bring it back to the lower half. Otherwise, imagine that
// N = 10
// s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are valid solutions.
// 10 - 8 == 2, giving us always the latter solution, which is canonical.
return new ECDSASignature(r, CURVE.getN().subtract(s));
} else {
return this;
}
}
/**
* DER is an international standard for serializing data structures which is widely used in cryptography.
* It's somewhat like protocol buffers but less convenient. This method returns a standard DER encoding
* of the signature, as recognized by OpenSSL and other libraries.
*/
public byte[] encodeToDER() {
try {
return derByteStream().toByteArray();
} catch(IOException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
/**
* @throws SignatureDecodeException if the signature is unparseable in some way.
*/
public static ECDSASignature decodeFromDER(byte[] bytes) throws SignatureDecodeException {
ASN1InputStream decoder = null;
try {
// BouncyCastle by default is strict about parsing ASN.1 integers. We relax this check, because some
// Bitcoin signatures would not parse.
Properties.setThreadOverride("org.bouncycastle.asn1.allow_unsafe_integer", true);
decoder = new ASN1InputStream(bytes);
final ASN1Primitive seqObj = decoder.readObject();
if(seqObj == null) {
throw new SignatureDecodeException("Reached past end of ASN.1 stream.");
}
if(!(seqObj instanceof DLSequence)) {
throw new SignatureDecodeException("Read unexpected class: " + seqObj.getClass().getName());
}
final DLSequence seq = (DLSequence) seqObj;
ASN1Integer r, s;
try {
r = (ASN1Integer) seq.getObjectAt(0);
s = (ASN1Integer) seq.getObjectAt(1);
} catch(ClassCastException e) {
throw new SignatureDecodeException(e);
}
// OpenSSL deviates from the DER spec by interpreting these values as unsigned, though they should not be
// Thus, we always use the positive versions. See: http://r6.ca/blog/20111119T211504Z.html
return new ECDSASignature(r.getPositiveValue(), s.getPositiveValue());
} catch(IOException e) {
throw new SignatureDecodeException(e);
} finally {
if(decoder != null) {
try {
decoder.close();
} catch(IOException x) {
}
}
Properties.removeThreadOverride("org.bouncycastle.asn1.allow_unsafe_integer");
}
}
/**
* <p>Verifies the given ECDSA signature against the message bytes using the public key bytes.</p>
*
* <p>When using native ECDSA verification, data must be 32 bytes, and no element may be
* larger than 520 bytes.</p>
*
* @param data Hash of the data to verify.
* @param pub The public key bytes to use.
*/
public boolean verify(byte[] data, byte[] pub) {
ECDSASigner signer = new ECDSASigner();
ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE);
signer.init(false, params);
try {
return signer.verifySignature(data, r, s);
} catch (NullPointerException e) {
// Bouncy Castle contains a bug that can cause NPEs given specially crafted signatures. Those signatures
// are inherently invalid/attack sigs so we just fail them here rather than crash the thread.
log.error("Caught NPE inside bouncy castle", e);
return false;
}
}
public ByteArrayOutputStream derByteStream() throws IOException {
// Usually 70-72 bytes.
ByteArrayOutputStream bos = new ByteArrayOutputStream(72);
DERSequenceGenerator seq = new DERSequenceGenerator(bos);
seq.addObject(new ASN1Integer(r));
seq.addObject(new ASN1Integer(s));
seq.close();
return bos;
}
protected boolean hasLowR() {
//A low R signature will have less than 71 bytes when encoded to DER
return toCanonicalised().encodeToDER().length < 71;
}
@Override
public boolean equals(Object o) {
if(this == o) {
return true;
}
if(o == null || getClass() != o.getClass()) {
return false;
}
ECDSASignature other = (ECDSASignature) o;
return r.equals(other.r) && s.equals(other.s);
}
@Override
public int hashCode() {
return Objects.hash(r, s);
}
/**
* Returns a decoded signature.
*
* @param requireCanonicalEncoding if the encoding of the signature must
* be canonical.
* @param requireCanonicalSValue if the S-value must be canonical (below half
* the order of the curve).
* @throws SignatureDecodeException if the signature is unparseable in some way.
* @throws VerificationException if the signature is invalid.
*/
public static TransactionSignature decodeFromBitcoin(byte[] bytes, boolean requireCanonicalEncoding,
boolean requireCanonicalSValue) throws SignatureDecodeException, VerificationException {
// Bitcoin encoding is DER signature + sighash byte.
if (requireCanonicalEncoding && !isEncodingCanonical(bytes))
throw new VerificationException.NoncanonicalSignature();
ECDSASignature sig = ECDSASignature.decodeFromDER(bytes);
if (requireCanonicalSValue && !sig.isCanonical())
throw new VerificationException("S-value is not canonical.");
// In Bitcoin, any value of the final byte is valid, but not necessarily canonical. See javadocs for
// isEncodingCanonical to learn more about this. So we must store the exact byte found.
return new TransactionSignature(sig.r, sig.s, TransactionSignature.Type.ECDSA, bytes[bytes.length - 1]);
}
/**
* Returns true if the given signature is has canonical encoding, and will thus be accepted as standard by
* Bitcoin Core. DER and the SIGHASH encoding allow for quite some flexibility in how the same structures
* are encoded, and this can open up novel attacks in which a man in the middle takes a transaction and then
* changes its signature such that the transaction hash is different but it's still valid. This can confuse wallets
* and generally violates people's mental model of how Bitcoin should work, thus, non-canonical signatures are now
* not relayed by default.
*/
public static boolean isEncodingCanonical(byte[] signature) {
// See Bitcoin Core's IsCanonicalSignature, https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
// A canonical signature exists of: <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
// Where R and S are not negative (their first byte has its highest bit not set), and not
// excessively padded (do not start with a 0 byte, unless an otherwise negative number follows,
// in which case a single 0 byte is necessary and even required).
// Empty signatures, while not strictly DER encoded, are allowed.
if (signature.length == 0)
return true;
if (signature.length < 9 || signature.length > 73)
return false;
int hashType = (signature[signature.length-1] & 0xff) & ~SigHash.ANYONECANPAY.value; // mask the byte to prevent sign-extension hurting us
if (hashType < SigHash.ALL.value || hashType > SigHash.SINGLE.value)
return false;
// "wrong type" "wrong length marker"
if ((signature[0] & 0xff) != 0x30 || (signature[1] & 0xff) != signature.length-3)
return false;
int lenR = signature[3] & 0xff;
if (5 + lenR >= signature.length || lenR == 0)
return false;
int lenS = signature[5+lenR] & 0xff;
if (lenR + lenS + 7 != signature.length || lenS == 0)
return false;
// R value type mismatch R value negative
if (signature[4-2] != 0x02 || (signature[4] & 0x80) == 0x80)
return false;
if (lenR > 1 && signature[4] == 0x00 && (signature[4+1] & 0x80) != 0x80)
return false; // R value excessively padded
// S value type mismatch S value negative
if (signature[6 + lenR - 2] != 0x02 || (signature[6 + lenR] & 0x80) == 0x80)
return false;
if (lenS > 1 && signature[6 + lenR] == 0x00 && (signature[6 + lenR + 1] & 0x80) != 0x80)
return false; // S value excessively padded
return true;
}
}

View file

@ -1,10 +1,10 @@
package com.sparrowwallet.drongo.crypto;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.protocol.SignatureDecodeException;
import com.sparrowwallet.drongo.protocol.VarInt;
import com.sparrowwallet.drongo.protocol.*;
import org.bitcoin.NativeSecp256k1;
import org.bitcoin.NativeSecp256k1Util;
import org.bitcoin.Secp256k1Context;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9IntegerConverter;
@ -19,7 +19,6 @@ import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.math.ec.*;
import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve;
import org.bouncycastle.util.Properties;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -64,21 +63,9 @@ import java.util.Objects;
* this class so round-tripping preserves state. Unless you're working with old software or doing unusual things, you
* can usually ignore the compressed/uncompressed distinction.</p>
*/
public class ECKey implements EncryptableItem {
public class ECKey {
private static final Logger log = LoggerFactory.getLogger(ECKey.class);
/** Sorts oldest keys first, newest last. */
public static final Comparator<ECKey> AGE_COMPARATOR = new Comparator<ECKey>() {
@Override
public int compare(ECKey k1, ECKey k2) {
if (k1.creationTimeSeconds == k2.creationTimeSeconds)
return 0;
else
return k1.creationTimeSeconds > k2.creationTimeSeconds ? 1 : -1;
}
};
// The parameters of the secp256k1 curve that Bitcoin uses.
private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
@ -106,13 +93,6 @@ public class ECKey implements EncryptableItem {
protected final BigInteger priv;
protected final LazyECPoint pub;
// Creation time of the key in seconds since the epoch, or zero if the key was deserialized from a version that did
// not have this field.
protected long creationTimeSeconds;
protected KeyCrypter keyCrypter;
protected EncryptedData encryptedPrivateKey;
private byte[] pubKeyHash;
/**
@ -157,18 +137,6 @@ public class ECKey implements EncryptableItem {
this.pub = pub;
}
/**
* Constructs a key that has an encrypted private component. The given object wraps encrypted bytes and an
* initialization vector. Note that the key will not be decrypted during this call: the returned ECKey is
* unusable for signing unless a decryption key is supplied.
*/
public static ECKey fromEncrypted(EncryptedData encryptedPrivateKey, KeyCrypter crypter, byte[] pubKey) {
ECKey key = fromPublicOnly(pubKey);
key.encryptedPrivateKey = encryptedPrivateKey;
key.keyCrypter = crypter;
return key;
}
/**
* Utility for compressing an elliptic curve point. Returns the same point if it's already compressed.
* See the ECKey class docs for a discussion of point compression.
@ -266,11 +234,6 @@ public class ECKey implements EncryptableItem {
return priv != null;
}
/** Returns true if this key is watch only, meaning it has a public key but no private key. */
public boolean isWatching() {
return isPubKeyOnly() && !isEncrypted();
}
/**
* Output this ECKey as an ASN.1 encoded private key, as understood by OpenSSL or used by Bitcoin Core
* in its wallet storage format.
@ -372,166 +335,10 @@ public class ECKey implements EncryptableItem {
}
/**
* Groups the two components that make up a signature, and provides a way to encode to DER form, which is
* how ECDSA signatures are represented when embedded in other data structures in the Bitcoin protocol. The raw
* components can be useful for doing further EC maths on them.
* Signs the given hash and returns the R and S components as an ECDSASignature.
*/
public static class ECDSASignature {
/** The two components of the signature. */
public final BigInteger r, s;
/**
* Constructs a signature with the given components. Does NOT automatically canonicalise the signature.
*/
public ECDSASignature(BigInteger r, BigInteger s) {
this.r = r;
this.s = s;
}
/**
* Returns true if the S component is "low", that means it is below {@link ECKey#HALF_CURVE_ORDER}. See <a
* href="https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#Low_S_values_in_signatures">BIP62</a>.
*/
public boolean isCanonical() {
return s.compareTo(HALF_CURVE_ORDER) <= 0;
}
/**
* Will automatically adjust the S component to be less than or equal to half the curve order, if necessary.
* This is required because for every signature (r,s) the signature (r, -s (mod N)) is a valid signature of
* the same message. However, we dislike the ability to modify the bits of a Bitcoin transaction after it's
* been signed, as that violates various assumed invariants. Thus in future only one of those forms will be
* considered legal and the other will be banned.
*/
public ECDSASignature toCanonicalised() {
if (!isCanonical()) {
// The order of the curve is the number of valid points that exist on that curve. If S is in the upper
// half of the number of valid points, then bring it back to the lower half. Otherwise, imagine that
// N = 10
// s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are valid solutions.
// 10 - 8 == 2, giving us always the latter solution, which is canonical.
return new ECDSASignature(r, CURVE.getN().subtract(s));
} else {
return this;
}
}
/**
* DER is an international standard for serializing data structures which is widely used in cryptography.
* It's somewhat like protocol buffers but less convenient. This method returns a standard DER encoding
* of the signature, as recognized by OpenSSL and other libraries.
*/
public byte[] encodeToDER() {
try {
return derByteStream().toByteArray();
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
/**
* @throws SignatureDecodeException if the signature is unparseable in some way.
*/
public static ECDSASignature decodeFromDER(byte[] bytes) throws SignatureDecodeException {
ASN1InputStream decoder = null;
try {
// BouncyCastle by default is strict about parsing ASN.1 integers. We relax this check, because some
// Bitcoin signatures would not parse.
Properties.setThreadOverride("org.bouncycastle.asn1.allow_unsafe_integer", true);
decoder = new ASN1InputStream(bytes);
final ASN1Primitive seqObj = decoder.readObject();
if (seqObj == null)
throw new SignatureDecodeException("Reached past end of ASN.1 stream.");
if (!(seqObj instanceof DLSequence))
throw new SignatureDecodeException("Read unexpected class: " + seqObj.getClass().getName());
final DLSequence seq = (DLSequence) seqObj;
ASN1Integer r, s;
try {
r = (ASN1Integer) seq.getObjectAt(0);
s = (ASN1Integer) seq.getObjectAt(1);
} catch (ClassCastException e) {
throw new SignatureDecodeException(e);
}
// OpenSSL deviates from the DER spec by interpreting these values as unsigned, though they should not be
// Thus, we always use the positive versions. See: http://r6.ca/blog/20111119T211504Z.html
return new ECDSASignature(r.getPositiveValue(), s.getPositiveValue());
} catch (IOException e) {
throw new SignatureDecodeException(e);
} finally {
if (decoder != null)
try { decoder.close(); } catch (IOException x) {}
Properties.removeThreadOverride("org.bouncycastle.asn1.allow_unsafe_integer");
}
}
protected ByteArrayOutputStream derByteStream() throws IOException {
// Usually 70-72 bytes.
ByteArrayOutputStream bos = new ByteArrayOutputStream(72);
DERSequenceGenerator seq = new DERSequenceGenerator(bos);
seq.addObject(new ASN1Integer(r));
seq.addObject(new ASN1Integer(s));
seq.close();
return bos;
}
protected boolean hasLowR() {
//A low R signature will have less than 71 bytes when encoded to DER
return toCanonicalised().encodeToDER().length < 71;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ECDSASignature other = (ECDSASignature) o;
return r.equals(other.r) && s.equals(other.s);
}
@Override
public int hashCode() {
return Objects.hash(r, s);
}
}
/**
* Signs the given hash and returns the R and S components as BigIntegers. In the Bitcoin protocol, they are
* usually encoded using ASN.1 format, so you want {@link ECKey.ECDSASignature#toASN1()}
* instead. However sometimes the independent components can be useful, for instance, if you're going to do
* further EC maths on them.
* @throws KeyCrypterException if this ECKey doesn't have a private part.
*/
public ECDSASignature sign(Sha256Hash input) throws KeyCrypterException {
return sign(input, null);
}
/**
* Signs the given hash and returns the R and S components as BigIntegers. In the Bitcoin protocol, they are
* usually encoded using DER format, so you want {@link ECKey.ECDSASignature#encodeToDER()}
* instead. However sometimes the independent components can be useful, for instance, if you're doing to do further
* EC maths on them.
*
* @param aesKey The AES key to use for decryption of the private key. If null then no decryption is required.
* @throws KeyCrypterException if there's something wrong with aesKey.
* @throws ECKey.MissingPrivateKeyException if this key cannot sign because it's pubkey only.
*/
public ECDSASignature sign(Sha256Hash input, Key aesKey) throws KeyCrypterException {
KeyCrypter crypter = getKeyCrypter();
if (crypter != null) {
if (aesKey == null) {
throw new KeyIsEncryptedException();
}
return decrypt(aesKey).sign(input);
} else {
// No decryption of private key required.
public ECDSASignature signEcdsa(Sha256Hash input) {
if(priv == null) {
throw new MissingPrivateKeyException();
}
}
return doSign(input, priv);
}
protected ECDSASignature doSign(Sha256Hash input, BigInteger privateKeyForSigning) {
if(privateKeyForSigning == null) {
throw new IllegalArgumentException("Private key cannot be null");
}
@ -539,7 +346,7 @@ public class ECKey implements EncryptableItem {
Integer counter = null;
do {
ECDSASigner signer = new ECDSASigner(new HMacDSANonceKCalculator(new SHA256Digest(), counter));
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(privateKeyForSigning, CURVE);
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(priv, CURVE);
signer.init(true, privKey);
BigInteger[] components = signer.generateSignature(input.getBytes());
signature = new ECDSASignature(components[0], components[1]).toCanonicalised();
@ -550,80 +357,39 @@ public class ECKey implements EncryptableItem {
}
/**
* <p>Verifies the given ECDSA signature against the message bytes using the public key bytes.</p>
*
* <p>When using native ECDSA verification, data must be 32 bytes, and no element may be
* larger than 520 bytes.</p>
*
* @param data Hash of the data to verify.
* @param signature ASN.1 encoded signature.
* @param pub The public key bytes to use.
* Signs the given hash and returns the R and S components as a SchnorrSignature.
*/
public static boolean verify(byte[] data, ECDSASignature signature, byte[] pub) {
ECDSASigner signer = new ECDSASigner();
ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE);
signer.init(false, params);
public SchnorrSignature signSchnorr(Sha256Hash input) {
if(priv == null) {
throw new IllegalArgumentException("Private key cannot be null");
}
if(!Secp256k1Context.isEnabled()) {
throw new IllegalStateException("libsecp256k1 is not enabled");
}
try {
return signer.verifySignature(data, signature.r, signature.s);
} catch (NullPointerException e) {
// Bouncy Castle contains a bug that can cause NPEs given specially crafted signatures. Those signatures
// are inherently invalid/attack sigs so we just fail them here rather than crash the thread.
log.error("Caught NPE inside bouncy castle", e);
return false;
byte[] sigBytes = NativeSecp256k1.schnorrSign(input.getBytes(), priv.toByteArray(), null);
return SchnorrSignature.decode(sigBytes);
} catch(NativeSecp256k1Util.AssertFailException e) {
log.error("Error signing schnorr", e);
}
return null;
}
/**
* Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key.
*
* @param data Hash of the data to verify.
* @param signature ASN.1 encoded signature.
* @param pub The public key bytes to use.
* @throws SignatureDecodeException if the signature is unparseable in some way.
* Verifies the given TransactionSignature against the provided byte array using the public key.
*/
public static boolean verify(byte[] data, byte[] signature, byte[] pub) throws SignatureDecodeException {
return verify(data, ECDSASignature.decodeFromDER(signature), pub);
}
/**
* Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key.
*
* @param hash Hash of the data to verify.
* @param signature ASN.1 encoded signature.
* @throws SignatureDecodeException if the signature is unparseable in some way.
*/
public boolean verify(byte[] hash, byte[] signature) throws SignatureDecodeException {
return ECKey.verify(hash, signature, getPubKey());
public boolean verify(byte[] data, TransactionSignature signature) {
return signature.verify(data, this);
}
/**
* Verifies the given R/S pair (signature) against a hash using the public key.
*/
public boolean verify(Sha256Hash sigHash, ECDSASignature signature) {
return ECKey.verify(sigHash.getBytes(), signature, getPubKey());
}
/**
* Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key, and throws an exception
* if the signature doesn't match
* @throws SignatureDecodeException if the signature is unparseable in some way.
* @throws java.security.SignatureException if the signature does not match.
*/
public void verifyOrThrow(byte[] hash, byte[] signature) throws SignatureDecodeException, SignatureException {
if (!verify(hash, signature)) {
throw new SignatureException();
}
}
/**
* Verifies the given R/S pair (signature) against a hash using the public key, and throws an exception
* if the signature doesn't match
* @throws java.security.SignatureException if the signature does not match.
*/
public void verifyOrThrow(Sha256Hash sigHash, ECDSASignature signature) throws SignatureException {
if (!ECKey.verify(sigHash.getBytes(), signature, getPubKey())) {
throw new SignatureException();
}
public boolean verify(Sha256Hash sigHash, TransactionSignature signature) {
return verify(sigHash.getBytes(), signature);
}
public ECKey getTweakedOutputKey() {
@ -757,12 +523,11 @@ public class ECKey implements EncryptableItem {
* encoded string.
*
* @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.
*/
public String signMessage(String message, ScriptType scriptType, Key aesKey) throws KeyCrypterException {
public String signMessage(String message, ScriptType scriptType) {
byte[] data = formatMessageForSigning(message);
Sha256Hash hash = Sha256Hash.twiceOf(data);
ECDSASignature sig = sign(hash, aesKey);
ECDSASignature sig = signEcdsa(hash);
byte recId = findRecoveryId(hash, sig);
int headerByte = recId + getSigningTypeConstant(scriptType);
byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S
@ -981,181 +746,9 @@ public class ECKey implements EncryptableItem {
}
}
/**
* Returns the creation time of this key or zero if the key was deserialized from a version that did not store
* that data.
*/
@Override
public long getCreationTimeSeconds() {
return creationTimeSeconds;
}
/**
* Sets the creation time of this key. Zero is a convention to mean "unavailable". This method can be useful when
* you have a raw key you are importing from somewhere else.
*/
public void setCreationTimeSeconds(long newCreationTimeSeconds) {
if (newCreationTimeSeconds < 0) {
throw new IllegalArgumentException("Cannot set creation time to negative value: " + newCreationTimeSeconds);
}
creationTimeSeconds = newCreationTimeSeconds;
}
/**
* Create an encrypted private key with the keyCrypter and the AES key supplied.
* This method returns a new encrypted key and leaves the original unchanged.
*
* @param keyCrypter The keyCrypter that specifies exactly how the encrypted bytes are created.
* @param aesKey The Key with the AES encryption key (usually constructed with keyCrypter#deriveKey and cached as it is slow to create).
* @return encryptedKey
*/
public ECKey encrypt(KeyCrypter keyCrypter, Key aesKey) throws KeyCrypterException {
if(keyCrypter == null) {
throw new KeyCrypterException("Keycrypter cannot be null");
}
final byte[] privKeyBytes = getPrivKeyBytes();
EncryptedData encryptedPrivateKey = keyCrypter.encrypt(privKeyBytes, null, aesKey);
ECKey result = ECKey.fromEncrypted(encryptedPrivateKey, keyCrypter, getPubKey());
result.setCreationTimeSeconds(creationTimeSeconds);
return result;
}
/**
* Create a decrypted private key with the keyCrypter and AES key supplied. Note that if the aesKey is wrong, this
* has some chance of throwing KeyCrypterException due to the corrupted padding that will result, but it can also
* just yield a garbage key.
*
* @param keyCrypter The keyCrypter that specifies exactly how the decrypted bytes are created.
* @param aesKey The Key with the AES encryption key (usually constructed with keyCrypter#deriveKey and cached).
*/
public ECKey decrypt(KeyCrypter keyCrypter, Key aesKey) throws KeyCrypterException {
if(keyCrypter == null) {
throw new KeyCrypterException("Keycrypter cannot be null");
}
// Check that the keyCrypter matches the one used to encrypt the keys, if set.
if (this.keyCrypter != null && !this.keyCrypter.equals(keyCrypter)) {
throw new KeyCrypterException("The keyCrypter being used to decrypt the key is different to the one that was used to encrypt it");
}
if(encryptedPrivateKey == null) {
throw new IllegalArgumentException("This key is not encrypted");
}
byte[] unencryptedPrivateKey = keyCrypter.decrypt(encryptedPrivateKey, aesKey);
ECKey key = ECKey.fromPrivate(unencryptedPrivateKey);
if (!Arrays.equals(key.getPubKey(), getPubKey())) {
throw new KeyCrypterException("Provided AES key is wrong");
}
key.setCreationTimeSeconds(creationTimeSeconds);
return key;
}
/**
* Create a decrypted private key with AES key. Note that if the AES key is wrong, this
* has some chance of throwing KeyCrypterException due to the corrupted padding that will result, but it can also
* just yield a garbage key.
*
* @param aesKey The Key with the AES encryption key (usually constructed with keyCrypter#deriveKey and cached).
*/
public ECKey decrypt(Key aesKey) throws KeyCrypterException {
final KeyCrypter crypter = getKeyCrypter();
if (crypter == null) {
throw new KeyCrypterException("No key crypter available");
}
return decrypt(crypter, aesKey);
}
/**
* Creates decrypted private key if needed.
*/
public ECKey maybeDecrypt(Key aesKey) throws KeyCrypterException {
return isEncrypted() && aesKey != null ? decrypt(aesKey) : this;
}
/**
* <p>Check that it is possible to decrypt the key with the keyCrypter and that the original key is returned.</p>
*
* <p>Because it is a critical failure if the private keys cannot be decrypted successfully (resulting of loss of all
* bitcoins controlled by the private key) you can use this method to check when you *encrypt* a wallet that
* it can definitely be decrypted successfully.</p>
*
* @return true if the encrypted key can be decrypted back to the original key successfully.
*/
public static boolean encryptionIsReversible(ECKey originalKey, ECKey encryptedKey, KeyCrypter keyCrypter, Key aesKey) {
try {
ECKey rebornUnencryptedKey = encryptedKey.decrypt(keyCrypter, aesKey);
byte[] originalPrivateKeyBytes = originalKey.getPrivKeyBytes();
byte[] rebornKeyBytes = rebornUnencryptedKey.getPrivKeyBytes();
if (!Arrays.equals(originalPrivateKeyBytes, rebornKeyBytes)) {
log.error("The check that encryption could be reversed failed for {}", originalKey);
return false;
}
return true;
} catch (KeyCrypterException kce) {
log.error(kce.getMessage());
return false;
}
}
/**
* Indicates whether the private key is encrypted (true) or not (false).
* A private key is deemed to be encrypted when there is both a KeyCrypter and the encryptedPrivateKey is non-zero.
*/
@Override
public boolean isEncrypted() {
return keyCrypter != null && encryptedPrivateKey != null && encryptedPrivateKey.getEncryptedBytes().length > 0;
}
@Override
public EncryptionType getEncryptionType() {
return new EncryptionType(EncryptionType.Deriver.SCRYPT, keyCrypter != null ? keyCrypter.getCrypterType() : EncryptionType.Crypter.NONE);
}
/**
* A wrapper for {@link #getPrivKeyBytes()} that returns null if the private key bytes are missing or would have
* to be derived (for the HD key case).
*/
@Override
public byte[] getSecretBytes() {
if (hasPrivKey()) {
return getPrivKeyBytes();
} else {
return null;
}
}
/** An alias for {@link #getEncryptedPrivateKey()} */
@Override
public EncryptedData getEncryptedData() {
return getEncryptedPrivateKey();
}
/**
* Returns the the encrypted private key bytes and initialisation vector for this ECKey, or null if the ECKey
* is not encrypted.
*/
public EncryptedData getEncryptedPrivateKey() {
return encryptedPrivateKey;
}
/**
* Returns the KeyCrypter that was used to encrypt to encrypt this ECKey. You need this to decrypt the ECKey.
*/
public KeyCrypter getKeyCrypter() {
return keyCrypter;
}
public static class MissingPrivateKeyException extends RuntimeException {
}
public static class KeyIsEncryptedException extends MissingPrivateKeyException {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View file

@ -0,0 +1,80 @@
package com.sparrowwallet.drongo.crypto;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.TransactionSignature;
import org.bitcoin.NativeSecp256k1;
import org.bitcoin.NativeSecp256k1Util;
import org.bitcoin.Secp256k1Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* Groups the two components that make up a Schnorr signature
*/
public class SchnorrSignature {
private static final Logger log = LoggerFactory.getLogger(SchnorrSignature.class);
/**
* The two components of the signature.
*/
public final BigInteger r, s;
/**
* Constructs a signature with the given components. Does NOT automatically canonicalise the signature.
*/
public SchnorrSignature(BigInteger r, BigInteger s) {
this.r = r;
this.s = s;
}
public byte[] encode() {
ByteBuffer buffer = ByteBuffer.allocate(64);
buffer.put(Utils.bigIntegerToBytes(r, 32));
buffer.put(Utils.bigIntegerToBytes(s, 32));
return buffer.array();
}
public static SchnorrSignature decode(byte[] bytes) {
if(bytes.length != 64) {
throw new IllegalArgumentException("Invalid Schnorr signature length of " + bytes.length + " bytes");
}
BigInteger r = new BigInteger(1, Arrays.copyOfRange(bytes, 0, 32));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(bytes, 32, 64));
return new SchnorrSignature(r, s);
}
public static TransactionSignature decodeFromBitcoin(byte[] bytes) {
if(bytes.length < 64 || bytes.length > 65) {
throw new IllegalArgumentException("Invalid Schnorr signature length of " + bytes.length + " bytes");
}
BigInteger r = new BigInteger(1, Arrays.copyOfRange(bytes, 0, 32));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(bytes, 32, 64));
if(bytes.length == 65) {
return new TransactionSignature(r, s, TransactionSignature.Type.SCHNORR, bytes[64]);
}
return new TransactionSignature(r, s, TransactionSignature.Type.SCHNORR, (byte)0);
}
public boolean verify(byte[] data, byte[] pub) {
if(!Secp256k1Context.isEnabled()) {
throw new IllegalStateException("libsecp256k1 is not enabled");
}
try {
return NativeSecp256k1.schnorrVerify(encode(), data, pub);
} catch(NativeSecp256k1Util.AssertFailException e) {
log.error("Error verifying schnorr signature", e);
}
return false;
}
}

View file

@ -110,8 +110,8 @@ public class ScriptChunk {
}
try {
ECKey.ECDSASignature.decodeFromDER(data);
} catch(SignatureDecodeException e) {
TransactionSignature.decodeFromBitcoin(data, false);
} catch(Exception e) {
return false;
}
@ -120,7 +120,7 @@ public class ScriptChunk {
public TransactionSignature getSignature() {
try {
return TransactionSignature.decodeFromBitcoin(data, false, false);
return TransactionSignature.decodeFromBitcoin(data, false);
} catch(SignatureDecodeException e) {
throw new ProtocolException("Could not decode signature", e);
}

View file

@ -126,6 +126,11 @@ public enum ScriptType {
throw new ProtocolException(getName() + " is not a multisig script type");
}
@Override
public TransactionSignature.Type getSignatureType() {
return TransactionSignature.Type.ECDSA;
};
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return List.of(SINGLE);
@ -239,6 +244,11 @@ public enum ScriptType {
throw new ProtocolException(getName() + " is not a multisig script type");
}
@Override
public TransactionSignature.Type getSignatureType() {
return TransactionSignature.Type.ECDSA;
};
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return List.of(SINGLE);
@ -425,6 +435,11 @@ public enum ScriptType {
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig);
}
@Override
public TransactionSignature.Type getSignatureType() {
return TransactionSignature.Type.ECDSA;
};
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return List.of(MULTI);
@ -550,6 +565,11 @@ public enum ScriptType {
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig);
}
@Override
public TransactionSignature.Type getSignatureType() {
return TransactionSignature.Type.ECDSA;
};
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return List.of(MULTI);
@ -653,6 +673,11 @@ public enum ScriptType {
throw new ProtocolException(getName() + " is not a multisig script type");
}
@Override
public TransactionSignature.Type getSignatureType() {
return TransactionSignature.Type.ECDSA;
};
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return List.of(SINGLE);
@ -754,6 +779,11 @@ public enum ScriptType {
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig, witness);
}
@Override
public TransactionSignature.Type getSignatureType() {
return TransactionSignature.Type.ECDSA;
};
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return List.of(MULTI, CUSTOM);
@ -859,6 +889,11 @@ public enum ScriptType {
throw new ProtocolException(getName() + " is not a multisig script type");
}
@Override
public TransactionSignature.Type getSignatureType() {
return TransactionSignature.Type.ECDSA;
};
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return List.of(SINGLE);
@ -970,6 +1005,11 @@ public enum ScriptType {
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig, witness);
}
@Override
public TransactionSignature.Type getSignatureType() {
return TransactionSignature.Type.ECDSA;
};
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return List.of(MULTI, CUSTOM);
@ -1078,6 +1118,11 @@ public enum ScriptType {
throw new UnsupportedOperationException("Constructing Taproot inputs is not yet supported");
}
@Override
public TransactionSignature.Type getSignatureType() {
return TransactionSignature.Type.SCHNORR;
};
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return Network.get() == Network.REGTEST || Network.get() == Network.SIGNET ? List.of(SINGLE) : Collections.emptyList();
@ -1195,6 +1240,8 @@ public enum ScriptType {
public abstract TransactionInput addMultisigSpendingInput(Transaction transaction, TransactionOutput prevOutput, int threshold, Map<ECKey, TransactionSignature> pubKeySignatures);
public abstract TransactionSignature.Type getSignatureType();
public static final ScriptType[] SINGLE_KEY_TYPES = {P2PK, P2TR};
public static final ScriptType[] SINGLE_HASH_TYPES = {P2PKH, P2SH, P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH};

View file

@ -12,7 +12,7 @@ public enum SigHash {
ANYONECANPAY_ALL("All + Anyone Can Pay", (byte)0x81),
ANYONECANPAY_NONE("None + Anyone Can Pay", (byte)0x82),
ANYONECANPAY_SINGLE("Single + Anyone Can Pay", (byte)0x83),
UNSET("Unset", (byte)0); // Caution: Using this type in isolation is non-standard. Treated similar to ALL.
ALL_TAPROOT("All (Taproot)", (byte)0);
private final String name;
public final byte value;

View file

@ -1,13 +1,19 @@
package com.sparrowwallet.drongo.protocol;
import com.sparrowwallet.drongo.crypto.ECDSASignature;
import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.crypto.SchnorrSignature;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Objects;
public class TransactionSignature extends ECKey.ECDSASignature {
public class TransactionSignature {
private final ECDSASignature ecdsaSignature;
private final SchnorrSignature schnorrSignature;
/**
* A byte that controls which parts of a transaction are signed. This is exposed because signatures
* parsed off the wire may have sighash flags that aren't "normal" serializations of the enum values.
@ -16,21 +22,26 @@ public class TransactionSignature extends ECKey.ECDSASignature {
*/
public final byte sighashFlags;
/** Constructs a signature with the given components and SIGHASH_ALL. */
public TransactionSignature(BigInteger r, BigInteger s) {
this(r, s, SigHash.ALL.value);
}
/** Constructs a signature with the given components and raw sighash flag bytes (needed for rule compatibility). */
public TransactionSignature(BigInteger r, BigInteger s, byte sighashFlags) {
super(r, s);
this.sighashFlags = sighashFlags;
/** Constructs a signature with the given components of the given type and SIGHASH_ALL. */
public TransactionSignature(BigInteger r, BigInteger s, Type type) {
this(r, s, type, SigHash.ALL.value);
}
/** Constructs a transaction signature based on the ECDSA signature. */
public TransactionSignature(ECKey.ECDSASignature signature, SigHash sigHash) {
super(signature.r, signature.s);
sighashFlags = sigHash.value;
public TransactionSignature(ECDSASignature signature, SigHash sigHash) {
this(signature.r, signature.s, Type.ECDSA, sigHash.value);
}
/** Constructs a transaction signature based on the Schnorr signature. */
public TransactionSignature(SchnorrSignature signature, SigHash sigHash) {
this(signature.r, signature.s, Type.SCHNORR, sigHash.value);
}
/** Constructs a signature with the given components, type and raw sighash flag bytes (needed for rule compatibility). */
public TransactionSignature(BigInteger r, BigInteger s, Type type, byte sighashFlags) {
ecdsaSignature = type == Type.ECDSA ? new ECDSASignature(r, s) : null;
schnorrSignature = type == Type.SCHNORR ? new SchnorrSignature(r, s) : null;
this.sighashFlags = sighashFlags;
}
/**
@ -39,61 +50,9 @@ public class TransactionSignature extends ECKey.ECDSASignature {
* right size (e.g. for fee calculations) but don't have the requisite signing key yet and will fill out the
* real signature later.
*/
public static TransactionSignature dummy() {
public static TransactionSignature dummy(Type type) {
BigInteger val = ECKey.HALF_CURVE_ORDER;
return new TransactionSignature(val, val);
}
/**
* Returns true if the given signature is has canonical encoding, and will thus be accepted as standard by
* Bitcoin Core. DER and the SIGHASH encoding allow for quite some flexibility in how the same structures
* are encoded, and this can open up novel attacks in which a man in the middle takes a transaction and then
* changes its signature such that the transaction hash is different but it's still valid. This can confuse wallets
* and generally violates people's mental model of how Bitcoin should work, thus, non-canonical signatures are now
* not relayed by default.
*/
public static boolean isEncodingCanonical(byte[] signature) {
// See Bitcoin Core's IsCanonicalSignature, https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
// A canonical signature exists of: <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
// Where R and S are not negative (their first byte has its highest bit not set), and not
// excessively padded (do not start with a 0 byte, unless an otherwise negative number follows,
// in which case a single 0 byte is necessary and even required).
// Empty signatures, while not strictly DER encoded, are allowed.
if (signature.length == 0)
return true;
if (signature.length < 9 || signature.length > 73)
return false;
int hashType = (signature[signature.length-1] & 0xff) & ~SigHash.ANYONECANPAY.value; // mask the byte to prevent sign-extension hurting us
if (hashType < SigHash.ALL.value || hashType > SigHash.SINGLE.value)
return false;
// "wrong type" "wrong length marker"
if ((signature[0] & 0xff) != 0x30 || (signature[1] & 0xff) != signature.length-3)
return false;
int lenR = signature[3] & 0xff;
if (5 + lenR >= signature.length || lenR == 0)
return false;
int lenS = signature[5+lenR] & 0xff;
if (lenR + lenS + 7 != signature.length || lenS == 0)
return false;
// R value type mismatch R value negative
if (signature[4-2] != 0x02 || (signature[4] & 0x80) == 0x80)
return false;
if (lenR > 1 && signature[4] == 0x00 && (signature[4+1] & 0x80) != 0x80)
return false; // R value excessively padded
// S value type mismatch S value negative
if (signature[6 + lenR - 2] != 0x02 || (signature[6 + lenR] & 0x80) == 0x80)
return false;
if (lenS > 1 && signature[6 + lenR] == 0x00 && (signature[6 + lenR + 1] & 0x80) != 0x80)
return false; // S value excessively padded
return true;
return new TransactionSignature(val, val, type);
}
public boolean anyoneCanPay() {
@ -118,18 +77,51 @@ public class TransactionSignature extends ECKey.ECDSASignature {
* components into a structure, and then we append a byte to the end for the sighash flags.
*/
public byte[] encodeToBitcoin() {
if(ecdsaSignature != null) {
try {
ByteArrayOutputStream bos = derByteStream();
ByteArrayOutputStream bos = ecdsaSignature.derByteStream();
bos.write(sighashFlags);
return bos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
}
} else if(schnorrSignature != null) {
SigHash sigHash = getSigHash();
ByteBuffer buffer = ByteBuffer.allocate(sigHash == SigHash.ALL ? 64 : 65);
buffer.put(schnorrSignature.encode());
if(sigHash != SigHash.ALL) {
buffer.put(sighashFlags);
}
return buffer.array();
}
@Override
public ECKey.ECDSASignature toCanonicalised() {
return new TransactionSignature(super.toCanonicalised(), getSigHash());
throw new IllegalStateException("TransactionSignature has no values");
}
public static TransactionSignature decodeFromBitcoin(byte[] bytes, boolean requireCanonicalEncoding) throws SignatureDecodeException {
if(bytes.length == 64 || bytes.length == 65) {
return decodeFromBitcoin(Type.SCHNORR, bytes, requireCanonicalEncoding);
}
return decodeFromBitcoin(Type.ECDSA, bytes, requireCanonicalEncoding);
}
public static TransactionSignature decodeFromBitcoin(Type type, byte[] bytes, boolean requireCanonicalEncoding) throws SignatureDecodeException {
if(type == Type.ECDSA) {
return ECDSASignature.decodeFromBitcoin(bytes, requireCanonicalEncoding, false);
} else if(type == Type.SCHNORR) {
return SchnorrSignature.decodeFromBitcoin(bytes);
}
throw new IllegalStateException("Unknown TransactionSignature type " + type);
}
public boolean verify(byte[] data, ECKey pubKey) {
if(ecdsaSignature != null) {
return ecdsaSignature.verify(data, pubKey.getPubKey());
} else {
return schnorrSignature.verify(data, pubKey.getPubKeyXCoord());
}
}
@Override
@ -140,39 +132,16 @@ public class TransactionSignature extends ECKey.ECDSASignature {
if(o == null || getClass() != o.getClass()) {
return false;
}
if(!super.equals(o)) {
return false;
}
TransactionSignature signature = (TransactionSignature) o;
return sighashFlags == signature.sighashFlags;
TransactionSignature that = (TransactionSignature) o;
return sighashFlags == that.sighashFlags && Objects.equals(ecdsaSignature, that.ecdsaSignature) && Objects.equals(schnorrSignature, that.schnorrSignature);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), sighashFlags);
return Objects.hash(ecdsaSignature, schnorrSignature, sighashFlags);
}
/**
* Returns a decoded signature.
*
* @param requireCanonicalEncoding if the encoding of the signature must
* be canonical.
* @param requireCanonicalSValue if the S-value must be canonical (below half
* the order of the curve).
* @throws SignatureDecodeException if the signature is unparseable in some way.
* @throws VerificationException if the signature is invalid.
*/
public static TransactionSignature decodeFromBitcoin(byte[] bytes, boolean requireCanonicalEncoding,
boolean requireCanonicalSValue) throws SignatureDecodeException, VerificationException {
// Bitcoin encoding is DER signature + sighash byte.
if (requireCanonicalEncoding && !isEncodingCanonical(bytes))
throw new VerificationException.NoncanonicalSignature();
ECKey.ECDSASignature sig = ECKey.ECDSASignature.decodeFromDER(bytes);
if (requireCanonicalSValue && !sig.isCanonical())
throw new VerificationException("S-value is not canonical.");
// In Bitcoin, any value of the final byte is valid, but not necessarily canonical. See javadocs for
// isEncodingCanonical to learn more about this. So we must store the exact byte found.
return new TransactionSignature(sig.r, sig.s, bytes[bytes.length - 1]);
public enum Type {
ECDSA, SCHNORR
}
}

View file

@ -2,6 +2,7 @@ package com.sparrowwallet.drongo.psbt;
import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.crypto.ECDSASignature;
import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.protocol.*;
import org.slf4j.Logger;
@ -113,7 +114,7 @@ public class PSBTInput {
entry.checkOneBytePlusPubKey();
ECKey sigPublicKey = ECKey.fromPublicOnly(entry.getKeyData());
//TODO: Verify signature
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(entry.getData(), true, false);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(TransactionSignature.Type.ECDSA, entry.getData(), true);
this.partialSignatures.put(sigPublicKey, signature);
log.debug("Found input partial signature with public key " + sigPublicKey + " signature " + Utils.bytesToHex(entry.getData()));
break;
@ -419,7 +420,7 @@ public class PSBTInput {
Script signingScript = getSigningScript();
if(signingScript != null) {
Sha256Hash hash = getHashForSignature(signingScript, localSigHash);
ECKey.ECDSASignature ecdsaSignature = privKey.sign(hash);
ECDSASignature ecdsaSignature = privKey.signEcdsa(hash);
TransactionSignature transactionSignature = new TransactionSignature(ecdsaSignature, localSigHash);
ECKey pubKey = ECKey.fromPublicOnly(privKey);

View file

@ -475,14 +475,14 @@ public class Wallet extends Persistable {
TransactionInput txInput = null;
if(getPolicyType().equals(PolicyType.SINGLE)) {
ECKey pubKey = getPubKey(receiveNode);
TransactionSignature signature = TransactionSignature.dummy();
TransactionSignature signature = TransactionSignature.dummy(getScriptType().getSignatureType());
txInput = getScriptType().addSpendingInput(transaction, prevTxOut, pubKey, signature);
} else if(getPolicyType().equals(PolicyType.MULTI)) {
List<ECKey> pubKeys = getPubKeys(receiveNode);
int threshold = getDefaultPolicy().getNumSignaturesRequired();
Map<ECKey, TransactionSignature> pubKeySignatures = new TreeMap<>(new ECKey.LexicographicECKeyComparator());
for(int i = 0; i < pubKeys.size(); i++) {
pubKeySignatures.put(pubKeys.get(i), i < threshold ? TransactionSignature.dummy() : null);
pubKeySignatures.put(pubKeys.get(i), i < threshold ? TransactionSignature.dummy(getScriptType().getSignatureType()) : null);
}
txInput = getScriptType().addMultisigSpendingInput(transaction, prevTxOut, threshold, pubKeySignatures);
}
@ -618,13 +618,13 @@ public class Wallet extends Persistable {
public TransactionInput addDummySpendingInput(Transaction transaction, WalletNode walletNode, TransactionOutput prevTxOut) {
if(getPolicyType().equals(PolicyType.SINGLE)) {
ECKey pubKey = getPubKey(walletNode);
return getScriptType().addSpendingInput(transaction, prevTxOut, pubKey, TransactionSignature.dummy());
return getScriptType().addSpendingInput(transaction, prevTxOut, pubKey, TransactionSignature.dummy(getScriptType().getSignatureType()));
} else if(getPolicyType().equals(PolicyType.MULTI)) {
List<ECKey> pubKeys = getPubKeys(walletNode);
int threshold = getDefaultPolicy().getNumSignaturesRequired();
Map<ECKey, TransactionSignature> pubKeySignatures = new TreeMap<>(new ECKey.LexicographicECKeyComparator());
for(int i = 0; i < pubKeys.size(); i++) {
pubKeySignatures.put(pubKeys.get(i), i < threshold ? TransactionSignature.dummy() : null);
pubKeySignatures.put(pubKeys.get(i), i < threshold ? TransactionSignature.dummy(getScriptType().getSignatureType()) : null);
}
return getScriptType().addMultisigSpendingInput(transaction, prevTxOut, threshold, pubKeySignatures);
} else {

View file

@ -27,15 +27,15 @@ public class ECKeyTest {
ECKey privKey = keystore.getKey(firstReceive);
//1 attempt required for low R
String signature1 = privKey.signMessage("Test2", ScriptType.P2PKH, null);
String signature1 = privKey.signMessage("Test2", ScriptType.P2PKH);
Assert.assertEquals("IHra0jSywF1TjIJ5uf7IDECae438cr4o3VmG6Ri7hYlDL+pUEXyUfwLwpiAfUQVqQFLgs6OaX0KsoydpuwRI71o=", signature1);
//2 attempts required for low R
String signature2 = privKey.signMessage("Test", ScriptType.P2PKH, null);
String signature2 = privKey.signMessage("Test", ScriptType.P2PKH);
Assert.assertEquals("IDgMx1ljPhLHlKUOwnO/jBIgK+K8n8mvDUDROzTgU8gOaPDMs+eYXJpNXXINUx5WpeV605p5uO6B3TzBVcvs478=", signature2);
//3 attempts required for low R
String signature3 = privKey.signMessage("Test1", ScriptType.P2PKH, null);
String signature3 = privKey.signMessage("Test1", ScriptType.P2PKH);
Assert.assertEquals("IEt/v9K95YVFuRtRtWaabPVwWOFv1FSA/e874I8ABgYMbRyVvHhSwLFz0RZuO87ukxDd4TOsRdofQwMEA90LCgI=", signature3);
}
}

View file

@ -20,7 +20,7 @@ public class TransactionTest {
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357"));
Sha256Hash hash = transaction.hashForWitnessSignature(1, ScriptType.P2PKH.getOutputScript(pubKey.getPubKeyHash()),600000000L, SigHash.ALL);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee"), false, true);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee"), false);
Assert.assertTrue(pubKey.verify(hash, signature));
}
@ -31,7 +31,7 @@ public class TransactionTest {
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873"));
Sha256Hash hash = transaction.hashForWitnessSignature(0, ScriptType.P2PKH.getOutputScript(pubKey.getPubKeyHash()),1000000000L, SigHash.ALL);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb01"), true, true);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb01"), true);
Assert.assertTrue(pubKey.verify(hash, signature));
}
@ -43,7 +43,7 @@ public class TransactionTest {
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880ae"));
Script script = new Script(Utils.hexToBytes("21026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac"));
Sha256Hash hash = transaction.hashForWitnessSignature(1, script,4900000000L, SigHash.SINGLE);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e2703"), true, true);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e2703"), true);
Assert.assertTrue(pubKey.verify(hash, signature));
}
@ -55,7 +55,7 @@ public class TransactionTest {
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("0392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98"));
Script script = new Script(Utils.hexToBytes("68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac"));
Sha256Hash hash = transaction.hashForWitnessSignature(1, script,16777215L, SigHash.ANYONECANPAY_SINGLE);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("30440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83"), true, true);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("30440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83"), true);
Assert.assertTrue(pubKey.verify(hash, signature));
}
@ -67,7 +67,7 @@ public class TransactionTest {
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3"));
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.ALL);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01"), true, true);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01"), true);
Assert.assertTrue(pubKey.verify(hash, signature));
}
@ -79,7 +79,7 @@ public class TransactionTest {
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b"));
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.NONE);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502"), true, true);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502"), true);
Assert.assertTrue(pubKey.verify(hash, signature));
}
@ -91,7 +91,7 @@ public class TransactionTest {
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a"));
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.SINGLE);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403"), true, true);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403"), true);
Assert.assertTrue(pubKey.verify(hash, signature));
}
@ -103,7 +103,7 @@ public class TransactionTest {
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4"));
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.ANYONECANPAY_ALL);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381"), true, true);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381"), true);
Assert.assertTrue(pubKey.verify(hash, signature));
}
@ -115,7 +115,7 @@ public class TransactionTest {
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16"));
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.ANYONECANPAY_NONE);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a0882"), true, true);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a0882"), true);
Assert.assertTrue(pubKey.verify(hash, signature));
}
@ -127,7 +127,7 @@ public class TransactionTest {
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b"));
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.ANYONECANPAY_SINGLE);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("30440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783"), true, true);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("30440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783"), true);
Assert.assertTrue(pubKey.verify(hash, signature));
}
@ -340,7 +340,7 @@ public class TransactionTest {
TransactionInput input0 = spendingTransaction.getInputs().get(0);
Script spendingScript = input0.getScriptSig();
TransactionWitness witness0 = input0.getWitness();
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(0), false, false);
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(0), false);
ECKey pubKey0 = ECKey.fromPublicOnly(witness0.getPushes().get(1));
Transaction transaction = new Transaction();
@ -375,8 +375,8 @@ public class TransactionTest {
TransactionInput input0 = spendingTransaction.getInputs().get(0);
TransactionWitness witness0 = input0.getWitness();
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(1), false, false);
TransactionSignature signature1 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(2), false, false);
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(1), false);
TransactionSignature signature1 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(2), false);
Script witnessScript = new Script(witness0.getPushes().get(3));
ECKey key0 = witnessScript.getChunks().get(1).getPubKey();
ECKey key1 = witnessScript.getChunks().get(2).getPubKey();
@ -417,7 +417,7 @@ public class TransactionTest {
TransactionInput input0 = spendingTransaction.getInputs().get(0);
TransactionWitness witness0 = input0.getWitness();
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(0), false, false);
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(0), false);
ECKey key0 = ECKey.fromPublicOnly(witness0.getPushes().get(1));
Transaction transaction = new Transaction();
@ -451,8 +451,8 @@ public class TransactionTest {
TransactionInput input0 = spendingTransaction.getInputs().get(0);
TransactionWitness witness0 = input0.getWitness();
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(1), false, false);
TransactionSignature signature1 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(2), false, false);
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(1), false);
TransactionSignature signature1 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(2), false);
Script witnessScript = new Script(witness0.getPushes().get(3));
ECKey key0 = witnessScript.getChunks().get(1).getPubKey();
ECKey key1 = witnessScript.getChunks().get(2).getPubKey();