mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-11-02 18:26:43 +00:00
refactor ECKey and TransactionSignature to support schnorr
This commit is contained in:
parent
e53574ea54
commit
f1ce2ec939
11 changed files with 517 additions and 573 deletions
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
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.*;
|
||||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
import org.bitcoin.NativeSecp256k1;
|
||||||
import com.sparrowwallet.drongo.protocol.SignatureDecodeException;
|
import org.bitcoin.NativeSecp256k1Util;
|
||||||
import com.sparrowwallet.drongo.protocol.VarInt;
|
import org.bitcoin.Secp256k1Context;
|
||||||
import org.bouncycastle.asn1.*;
|
import org.bouncycastle.asn1.*;
|
||||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||||
import org.bouncycastle.asn1.x9.X9IntegerConverter;
|
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.crypto.signers.ECDSASigner;
|
||||||
import org.bouncycastle.math.ec.*;
|
import org.bouncycastle.math.ec.*;
|
||||||
import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve;
|
import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve;
|
||||||
import org.bouncycastle.util.Properties;
|
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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
|
* 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>
|
* 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);
|
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.
|
// The parameters of the secp256k1 curve that Bitcoin uses.
|
||||||
private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
|
private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
|
||||||
|
|
||||||
|
@ -106,13 +93,6 @@ public class ECKey implements EncryptableItem {
|
||||||
protected final BigInteger priv;
|
protected final BigInteger priv;
|
||||||
protected final LazyECPoint pub;
|
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;
|
private byte[] pubKeyHash;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -157,18 +137,6 @@ public class ECKey implements EncryptableItem {
|
||||||
this.pub = pub;
|
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.
|
* 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.
|
* See the ECKey class docs for a discussion of point compression.
|
||||||
|
@ -266,11 +234,6 @@ public class ECKey implements EncryptableItem {
|
||||||
return priv != null;
|
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
|
* Output this ECKey as an ASN.1 encoded private key, as understood by OpenSSL or used by Bitcoin Core
|
||||||
* in its wallet storage format.
|
* 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
|
* Signs the given hash and returns the R and S components as an ECDSASignature.
|
||||||
* 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 static class ECDSASignature {
|
public ECDSASignature signEcdsa(Sha256Hash input) {
|
||||||
/** The two components of the signature. */
|
if(priv == null) {
|
||||||
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.
|
|
||||||
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");
|
throw new IllegalArgumentException("Private key cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -539,7 +346,7 @@ public class ECKey implements EncryptableItem {
|
||||||
Integer counter = null;
|
Integer counter = null;
|
||||||
do {
|
do {
|
||||||
ECDSASigner signer = new ECDSASigner(new HMacDSANonceKCalculator(new SHA256Digest(), counter));
|
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);
|
signer.init(true, privKey);
|
||||||
BigInteger[] components = signer.generateSignature(input.getBytes());
|
BigInteger[] components = signer.generateSignature(input.getBytes());
|
||||||
signature = new ECDSASignature(components[0], components[1]).toCanonicalised();
|
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>
|
* Signs the given hash and returns the R and S components as a SchnorrSignature.
|
||||||
*
|
|
||||||
* <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.
|
|
||||||
*/
|
*/
|
||||||
public static boolean verify(byte[] data, ECDSASignature signature, byte[] pub) {
|
public SchnorrSignature signSchnorr(Sha256Hash input) {
|
||||||
ECDSASigner signer = new ECDSASigner();
|
if(priv == null) {
|
||||||
ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE);
|
throw new IllegalArgumentException("Private key cannot be null");
|
||||||
signer.init(false, params);
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!Secp256k1Context.isEnabled()) {
|
||||||
|
throw new IllegalStateException("libsecp256k1 is not enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
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.
|
* Verifies the given TransactionSignature against the provided byte array 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.
|
|
||||||
*/
|
*/
|
||||||
public static boolean verify(byte[] data, byte[] signature, byte[] pub) throws SignatureDecodeException {
|
public boolean verify(byte[] data, TransactionSignature signature) {
|
||||||
return verify(data, ECDSASignature.decodeFromDER(signature), pub);
|
return signature.verify(data, this);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies the given R/S pair (signature) against a hash using the public key.
|
* Verifies the given R/S pair (signature) against a hash using the public key.
|
||||||
*/
|
*/
|
||||||
public boolean verify(Sha256Hash sigHash, ECDSASignature signature) {
|
public boolean verify(Sha256Hash sigHash, TransactionSignature signature) {
|
||||||
return ECKey.verify(sigHash.getBytes(), signature, getPubKey());
|
return verify(sigHash.getBytes(), signature);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 ECKey getTweakedOutputKey() {
|
public ECKey getTweakedOutputKey() {
|
||||||
|
@ -757,12 +523,11 @@ public class ECKey implements EncryptableItem {
|
||||||
* encoded string.
|
* encoded string.
|
||||||
*
|
*
|
||||||
* @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.
|
|
||||||
*/
|
*/
|
||||||
public String signMessage(String message, ScriptType scriptType, Key aesKey) throws KeyCrypterException {
|
public String signMessage(String message, ScriptType scriptType) {
|
||||||
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 = signEcdsa(hash);
|
||||||
byte recId = findRecoveryId(hash, sig);
|
byte recId = findRecoveryId(hash, sig);
|
||||||
int headerByte = recId + getSigningTypeConstant(scriptType);
|
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
|
||||||
|
@ -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 MissingPrivateKeyException extends RuntimeException {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class KeyIsEncryptedException extends MissingPrivateKeyException {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -110,8 +110,8 @@ public class ScriptChunk {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ECKey.ECDSASignature.decodeFromDER(data);
|
TransactionSignature.decodeFromBitcoin(data, false);
|
||||||
} catch(SignatureDecodeException e) {
|
} catch(Exception e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ public class ScriptChunk {
|
||||||
|
|
||||||
public TransactionSignature getSignature() {
|
public TransactionSignature getSignature() {
|
||||||
try {
|
try {
|
||||||
return TransactionSignature.decodeFromBitcoin(data, false, false);
|
return TransactionSignature.decodeFromBitcoin(data, false);
|
||||||
} catch(SignatureDecodeException e) {
|
} catch(SignatureDecodeException e) {
|
||||||
throw new ProtocolException("Could not decode signature", e);
|
throw new ProtocolException("Could not decode signature", e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,11 @@ public enum ScriptType {
|
||||||
throw new ProtocolException(getName() + " is not a multisig script type");
|
throw new ProtocolException(getName() + " is not a multisig script type");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransactionSignature.Type getSignatureType() {
|
||||||
|
return TransactionSignature.Type.ECDSA;
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PolicyType> getAllowedPolicyTypes() {
|
public List<PolicyType> getAllowedPolicyTypes() {
|
||||||
return List.of(SINGLE);
|
return List.of(SINGLE);
|
||||||
|
@ -239,6 +244,11 @@ public enum ScriptType {
|
||||||
throw new ProtocolException(getName() + " is not a multisig script type");
|
throw new ProtocolException(getName() + " is not a multisig script type");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransactionSignature.Type getSignatureType() {
|
||||||
|
return TransactionSignature.Type.ECDSA;
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PolicyType> getAllowedPolicyTypes() {
|
public List<PolicyType> getAllowedPolicyTypes() {
|
||||||
return List.of(SINGLE);
|
return List.of(SINGLE);
|
||||||
|
@ -425,6 +435,11 @@ public enum ScriptType {
|
||||||
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig);
|
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransactionSignature.Type getSignatureType() {
|
||||||
|
return TransactionSignature.Type.ECDSA;
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PolicyType> getAllowedPolicyTypes() {
|
public List<PolicyType> getAllowedPolicyTypes() {
|
||||||
return List.of(MULTI);
|
return List.of(MULTI);
|
||||||
|
@ -550,6 +565,11 @@ public enum ScriptType {
|
||||||
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig);
|
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransactionSignature.Type getSignatureType() {
|
||||||
|
return TransactionSignature.Type.ECDSA;
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PolicyType> getAllowedPolicyTypes() {
|
public List<PolicyType> getAllowedPolicyTypes() {
|
||||||
return List.of(MULTI);
|
return List.of(MULTI);
|
||||||
|
@ -653,6 +673,11 @@ public enum ScriptType {
|
||||||
throw new ProtocolException(getName() + " is not a multisig script type");
|
throw new ProtocolException(getName() + " is not a multisig script type");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransactionSignature.Type getSignatureType() {
|
||||||
|
return TransactionSignature.Type.ECDSA;
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PolicyType> getAllowedPolicyTypes() {
|
public List<PolicyType> getAllowedPolicyTypes() {
|
||||||
return List.of(SINGLE);
|
return List.of(SINGLE);
|
||||||
|
@ -754,6 +779,11 @@ public enum ScriptType {
|
||||||
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig, witness);
|
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig, witness);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransactionSignature.Type getSignatureType() {
|
||||||
|
return TransactionSignature.Type.ECDSA;
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PolicyType> getAllowedPolicyTypes() {
|
public List<PolicyType> getAllowedPolicyTypes() {
|
||||||
return List.of(MULTI, CUSTOM);
|
return List.of(MULTI, CUSTOM);
|
||||||
|
@ -859,6 +889,11 @@ public enum ScriptType {
|
||||||
throw new ProtocolException(getName() + " is not a multisig script type");
|
throw new ProtocolException(getName() + " is not a multisig script type");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransactionSignature.Type getSignatureType() {
|
||||||
|
return TransactionSignature.Type.ECDSA;
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PolicyType> getAllowedPolicyTypes() {
|
public List<PolicyType> getAllowedPolicyTypes() {
|
||||||
return List.of(SINGLE);
|
return List.of(SINGLE);
|
||||||
|
@ -970,6 +1005,11 @@ public enum ScriptType {
|
||||||
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig, witness);
|
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig, witness);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransactionSignature.Type getSignatureType() {
|
||||||
|
return TransactionSignature.Type.ECDSA;
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PolicyType> getAllowedPolicyTypes() {
|
public List<PolicyType> getAllowedPolicyTypes() {
|
||||||
return List.of(MULTI, CUSTOM);
|
return List.of(MULTI, CUSTOM);
|
||||||
|
@ -1078,6 +1118,11 @@ public enum ScriptType {
|
||||||
throw new UnsupportedOperationException("Constructing Taproot inputs is not yet supported");
|
throw new UnsupportedOperationException("Constructing Taproot inputs is not yet supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransactionSignature.Type getSignatureType() {
|
||||||
|
return TransactionSignature.Type.SCHNORR;
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PolicyType> getAllowedPolicyTypes() {
|
public List<PolicyType> getAllowedPolicyTypes() {
|
||||||
return Network.get() == Network.REGTEST || Network.get() == Network.SIGNET ? List.of(SINGLE) : Collections.emptyList();
|
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 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_KEY_TYPES = {P2PK, P2TR};
|
||||||
|
|
||||||
public static final ScriptType[] SINGLE_HASH_TYPES = {P2PKH, P2SH, P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH};
|
public static final ScriptType[] SINGLE_HASH_TYPES = {P2PKH, P2SH, P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH};
|
||||||
|
|
|
@ -12,7 +12,7 @@ public enum SigHash {
|
||||||
ANYONECANPAY_ALL("All + Anyone Can Pay", (byte)0x81),
|
ANYONECANPAY_ALL("All + Anyone Can Pay", (byte)0x81),
|
||||||
ANYONECANPAY_NONE("None + Anyone Can Pay", (byte)0x82),
|
ANYONECANPAY_NONE("None + Anyone Can Pay", (byte)0x82),
|
||||||
ANYONECANPAY_SINGLE("Single + Anyone Can Pay", (byte)0x83),
|
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;
|
private final String name;
|
||||||
public final byte value;
|
public final byte value;
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
package com.sparrowwallet.drongo.protocol;
|
package com.sparrowwallet.drongo.protocol;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.crypto.ECDSASignature;
|
||||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||||
|
import com.sparrowwallet.drongo.crypto.SchnorrSignature;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Objects;
|
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
|
* 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.
|
* 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;
|
public final byte sighashFlags;
|
||||||
|
|
||||||
/** Constructs a signature with the given components and SIGHASH_ALL. */
|
/** Constructs a signature with the given components of the given type and SIGHASH_ALL. */
|
||||||
public TransactionSignature(BigInteger r, BigInteger s) {
|
public TransactionSignature(BigInteger r, BigInteger s, Type type) {
|
||||||
this(r, s, SigHash.ALL.value);
|
this(r, s, type, 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 transaction signature based on the ECDSA signature. */
|
/** Constructs a transaction signature based on the ECDSA signature. */
|
||||||
public TransactionSignature(ECKey.ECDSASignature signature, SigHash sigHash) {
|
public TransactionSignature(ECDSASignature signature, SigHash sigHash) {
|
||||||
super(signature.r, signature.s);
|
this(signature.r, signature.s, Type.ECDSA, sigHash.value);
|
||||||
sighashFlags = 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
|
* right size (e.g. for fee calculations) but don't have the requisite signing key yet and will fill out the
|
||||||
* real signature later.
|
* real signature later.
|
||||||
*/
|
*/
|
||||||
public static TransactionSignature dummy() {
|
public static TransactionSignature dummy(Type type) {
|
||||||
BigInteger val = ECKey.HALF_CURVE_ORDER;
|
BigInteger val = ECKey.HALF_CURVE_ORDER;
|
||||||
return new TransactionSignature(val, val);
|
return new TransactionSignature(val, val, type);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean anyoneCanPay() {
|
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.
|
* components into a structure, and then we append a byte to the end for the sighash flags.
|
||||||
*/
|
*/
|
||||||
public byte[] encodeToBitcoin() {
|
public byte[] encodeToBitcoin() {
|
||||||
try {
|
if(ecdsaSignature != null) {
|
||||||
ByteArrayOutputStream bos = derByteStream();
|
try {
|
||||||
bos.write(sighashFlags);
|
ByteArrayOutputStream bos = ecdsaSignature.derByteStream();
|
||||||
return bos.toByteArray();
|
bos.write(sighashFlags);
|
||||||
} catch (IOException e) {
|
return bos.toByteArray();
|
||||||
throw new RuntimeException(e); // Cannot happen.
|
} 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new IllegalStateException("TransactionSignature has no values");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static TransactionSignature decodeFromBitcoin(byte[] bytes, boolean requireCanonicalEncoding) throws SignatureDecodeException {
|
||||||
public ECKey.ECDSASignature toCanonicalised() {
|
if(bytes.length == 64 || bytes.length == 65) {
|
||||||
return new TransactionSignature(super.toCanonicalised(), getSigHash());
|
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
|
@Override
|
||||||
|
@ -140,39 +132,16 @@ public class TransactionSignature extends ECKey.ECDSASignature {
|
||||||
if(o == null || getClass() != o.getClass()) {
|
if(o == null || getClass() != o.getClass()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(!super.equals(o)) {
|
TransactionSignature that = (TransactionSignature) o;
|
||||||
return false;
|
return sighashFlags == that.sighashFlags && Objects.equals(ecdsaSignature, that.ecdsaSignature) && Objects.equals(schnorrSignature, that.schnorrSignature);
|
||||||
}
|
|
||||||
TransactionSignature signature = (TransactionSignature) o;
|
|
||||||
return sighashFlags == signature.sighashFlags;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(super.hashCode(), sighashFlags);
|
return Objects.hash(ecdsaSignature, schnorrSignature, sighashFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public enum Type {
|
||||||
* Returns a decoded signature.
|
ECDSA, SCHNORR
|
||||||
*
|
|
||||||
* @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]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ package com.sparrowwallet.drongo.psbt;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.KeyDerivation;
|
import com.sparrowwallet.drongo.KeyDerivation;
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
|
import com.sparrowwallet.drongo.crypto.ECDSASignature;
|
||||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||||
import com.sparrowwallet.drongo.protocol.*;
|
import com.sparrowwallet.drongo.protocol.*;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -113,7 +114,7 @@ public class PSBTInput {
|
||||||
entry.checkOneBytePlusPubKey();
|
entry.checkOneBytePlusPubKey();
|
||||||
ECKey sigPublicKey = ECKey.fromPublicOnly(entry.getKeyData());
|
ECKey sigPublicKey = ECKey.fromPublicOnly(entry.getKeyData());
|
||||||
//TODO: Verify signature
|
//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);
|
this.partialSignatures.put(sigPublicKey, signature);
|
||||||
log.debug("Found input partial signature with public key " + sigPublicKey + " signature " + Utils.bytesToHex(entry.getData()));
|
log.debug("Found input partial signature with public key " + sigPublicKey + " signature " + Utils.bytesToHex(entry.getData()));
|
||||||
break;
|
break;
|
||||||
|
@ -419,7 +420,7 @@ public class PSBTInput {
|
||||||
Script signingScript = getSigningScript();
|
Script signingScript = getSigningScript();
|
||||||
if(signingScript != null) {
|
if(signingScript != null) {
|
||||||
Sha256Hash hash = getHashForSignature(signingScript, localSigHash);
|
Sha256Hash hash = getHashForSignature(signingScript, localSigHash);
|
||||||
ECKey.ECDSASignature ecdsaSignature = privKey.sign(hash);
|
ECDSASignature ecdsaSignature = privKey.signEcdsa(hash);
|
||||||
TransactionSignature transactionSignature = new TransactionSignature(ecdsaSignature, localSigHash);
|
TransactionSignature transactionSignature = new TransactionSignature(ecdsaSignature, localSigHash);
|
||||||
|
|
||||||
ECKey pubKey = ECKey.fromPublicOnly(privKey);
|
ECKey pubKey = ECKey.fromPublicOnly(privKey);
|
||||||
|
|
|
@ -475,14 +475,14 @@ public class Wallet extends Persistable {
|
||||||
TransactionInput txInput = null;
|
TransactionInput txInput = null;
|
||||||
if(getPolicyType().equals(PolicyType.SINGLE)) {
|
if(getPolicyType().equals(PolicyType.SINGLE)) {
|
||||||
ECKey pubKey = getPubKey(receiveNode);
|
ECKey pubKey = getPubKey(receiveNode);
|
||||||
TransactionSignature signature = TransactionSignature.dummy();
|
TransactionSignature signature = TransactionSignature.dummy(getScriptType().getSignatureType());
|
||||||
txInput = getScriptType().addSpendingInput(transaction, prevTxOut, pubKey, signature);
|
txInput = getScriptType().addSpendingInput(transaction, prevTxOut, pubKey, signature);
|
||||||
} else if(getPolicyType().equals(PolicyType.MULTI)) {
|
} else if(getPolicyType().equals(PolicyType.MULTI)) {
|
||||||
List<ECKey> pubKeys = getPubKeys(receiveNode);
|
List<ECKey> pubKeys = getPubKeys(receiveNode);
|
||||||
int threshold = getDefaultPolicy().getNumSignaturesRequired();
|
int threshold = getDefaultPolicy().getNumSignaturesRequired();
|
||||||
Map<ECKey, TransactionSignature> pubKeySignatures = new TreeMap<>(new ECKey.LexicographicECKeyComparator());
|
Map<ECKey, TransactionSignature> pubKeySignatures = new TreeMap<>(new ECKey.LexicographicECKeyComparator());
|
||||||
for(int i = 0; i < pubKeys.size(); i++) {
|
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);
|
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) {
|
public TransactionInput addDummySpendingInput(Transaction transaction, WalletNode walletNode, TransactionOutput prevTxOut) {
|
||||||
if(getPolicyType().equals(PolicyType.SINGLE)) {
|
if(getPolicyType().equals(PolicyType.SINGLE)) {
|
||||||
ECKey pubKey = getPubKey(walletNode);
|
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)) {
|
} else if(getPolicyType().equals(PolicyType.MULTI)) {
|
||||||
List<ECKey> pubKeys = getPubKeys(walletNode);
|
List<ECKey> pubKeys = getPubKeys(walletNode);
|
||||||
int threshold = getDefaultPolicy().getNumSignaturesRequired();
|
int threshold = getDefaultPolicy().getNumSignaturesRequired();
|
||||||
Map<ECKey, TransactionSignature> pubKeySignatures = new TreeMap<>(new ECKey.LexicographicECKeyComparator());
|
Map<ECKey, TransactionSignature> pubKeySignatures = new TreeMap<>(new ECKey.LexicographicECKeyComparator());
|
||||||
for(int i = 0; i < pubKeys.size(); i++) {
|
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);
|
return getScriptType().addMultisigSpendingInput(transaction, prevTxOut, threshold, pubKeySignatures);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -27,15 +27,15 @@ public class ECKeyTest {
|
||||||
ECKey privKey = keystore.getKey(firstReceive);
|
ECKey privKey = keystore.getKey(firstReceive);
|
||||||
|
|
||||||
//1 attempt required for low R
|
//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);
|
Assert.assertEquals("IHra0jSywF1TjIJ5uf7IDECae438cr4o3VmG6Ri7hYlDL+pUEXyUfwLwpiAfUQVqQFLgs6OaX0KsoydpuwRI71o=", signature1);
|
||||||
|
|
||||||
//2 attempts required for low R
|
//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);
|
Assert.assertEquals("IDgMx1ljPhLHlKUOwnO/jBIgK+K8n8mvDUDROzTgU8gOaPDMs+eYXJpNXXINUx5WpeV605p5uO6B3TzBVcvs478=", signature2);
|
||||||
|
|
||||||
//3 attempts required for low R
|
//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);
|
Assert.assertEquals("IEt/v9K95YVFuRtRtWaabPVwWOFv1FSA/e874I8ABgYMbRyVvHhSwLFz0RZuO87ukxDd4TOsRdofQwMEA90LCgI=", signature3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ public class TransactionTest {
|
||||||
|
|
||||||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357"));
|
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357"));
|
||||||
Sha256Hash hash = transaction.hashForWitnessSignature(1, ScriptType.P2PKH.getOutputScript(pubKey.getPubKeyHash()),600000000L, SigHash.ALL);
|
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));
|
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ public class TransactionTest {
|
||||||
|
|
||||||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873"));
|
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873"));
|
||||||
Sha256Hash hash = transaction.hashForWitnessSignature(0, ScriptType.P2PKH.getOutputScript(pubKey.getPubKeyHash()),1000000000L, SigHash.ALL);
|
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));
|
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ public class TransactionTest {
|
||||||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880ae"));
|
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880ae"));
|
||||||
Script script = new Script(Utils.hexToBytes("21026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac"));
|
Script script = new Script(Utils.hexToBytes("21026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac"));
|
||||||
Sha256Hash hash = transaction.hashForWitnessSignature(1, script,4900000000L, SigHash.SINGLE);
|
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));
|
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ public class TransactionTest {
|
||||||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("0392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98"));
|
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("0392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98"));
|
||||||
Script script = new Script(Utils.hexToBytes("68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac"));
|
Script script = new Script(Utils.hexToBytes("68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac"));
|
||||||
Sha256Hash hash = transaction.hashForWitnessSignature(1, script,16777215L, SigHash.ANYONECANPAY_SINGLE);
|
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));
|
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ public class TransactionTest {
|
||||||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3"));
|
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3"));
|
||||||
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
||||||
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.ALL);
|
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));
|
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ public class TransactionTest {
|
||||||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b"));
|
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b"));
|
||||||
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
||||||
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.NONE);
|
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));
|
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ public class TransactionTest {
|
||||||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a"));
|
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a"));
|
||||||
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
||||||
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.SINGLE);
|
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));
|
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ public class TransactionTest {
|
||||||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4"));
|
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4"));
|
||||||
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
||||||
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.ANYONECANPAY_ALL);
|
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));
|
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ public class TransactionTest {
|
||||||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16"));
|
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16"));
|
||||||
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
||||||
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.ANYONECANPAY_NONE);
|
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));
|
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ public class TransactionTest {
|
||||||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b"));
|
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b"));
|
||||||
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
||||||
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.ANYONECANPAY_SINGLE);
|
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));
|
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,7 +340,7 @@ public class TransactionTest {
|
||||||
TransactionInput input0 = spendingTransaction.getInputs().get(0);
|
TransactionInput input0 = spendingTransaction.getInputs().get(0);
|
||||||
Script spendingScript = input0.getScriptSig();
|
Script spendingScript = input0.getScriptSig();
|
||||||
TransactionWitness witness0 = input0.getWitness();
|
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));
|
ECKey pubKey0 = ECKey.fromPublicOnly(witness0.getPushes().get(1));
|
||||||
|
|
||||||
Transaction transaction = new Transaction();
|
Transaction transaction = new Transaction();
|
||||||
|
@ -375,8 +375,8 @@ public class TransactionTest {
|
||||||
|
|
||||||
TransactionInput input0 = spendingTransaction.getInputs().get(0);
|
TransactionInput input0 = spendingTransaction.getInputs().get(0);
|
||||||
TransactionWitness witness0 = input0.getWitness();
|
TransactionWitness witness0 = input0.getWitness();
|
||||||
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(1), false, false);
|
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(1), false);
|
||||||
TransactionSignature signature1 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(2), false, false);
|
TransactionSignature signature1 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(2), false);
|
||||||
Script witnessScript = new Script(witness0.getPushes().get(3));
|
Script witnessScript = new Script(witness0.getPushes().get(3));
|
||||||
ECKey key0 = witnessScript.getChunks().get(1).getPubKey();
|
ECKey key0 = witnessScript.getChunks().get(1).getPubKey();
|
||||||
ECKey key1 = witnessScript.getChunks().get(2).getPubKey();
|
ECKey key1 = witnessScript.getChunks().get(2).getPubKey();
|
||||||
|
@ -417,7 +417,7 @@ public class TransactionTest {
|
||||||
|
|
||||||
TransactionInput input0 = spendingTransaction.getInputs().get(0);
|
TransactionInput input0 = spendingTransaction.getInputs().get(0);
|
||||||
TransactionWitness witness0 = input0.getWitness();
|
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));
|
ECKey key0 = ECKey.fromPublicOnly(witness0.getPushes().get(1));
|
||||||
|
|
||||||
Transaction transaction = new Transaction();
|
Transaction transaction = new Transaction();
|
||||||
|
@ -451,8 +451,8 @@ public class TransactionTest {
|
||||||
|
|
||||||
TransactionInput input0 = spendingTransaction.getInputs().get(0);
|
TransactionInput input0 = spendingTransaction.getInputs().get(0);
|
||||||
TransactionWitness witness0 = input0.getWitness();
|
TransactionWitness witness0 = input0.getWitness();
|
||||||
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(1), false, false);
|
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(1), false);
|
||||||
TransactionSignature signature1 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(2), false, false);
|
TransactionSignature signature1 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(2), false);
|
||||||
Script witnessScript = new Script(witness0.getPushes().get(3));
|
Script witnessScript = new Script(witness0.getPushes().get(3));
|
||||||
ECKey key0 = witnessScript.getChunks().get(1).getPubKey();
|
ECKey key0 = witnessScript.getChunks().get(1).getPubKey();
|
||||||
ECKey key1 = witnessScript.getChunks().get(2).getPubKey();
|
ECKey key1 = witnessScript.getChunks().get(2).getPubKey();
|
||||||
|
|
Loading…
Reference in a new issue