mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-25 17:46:44 +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;
|
||||
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||
import com.sparrowwallet.drongo.protocol.SignatureDecodeException;
|
||||
import com.sparrowwallet.drongo.protocol.VarInt;
|
||||
import com.sparrowwallet.drongo.protocol.*;
|
||||
import org.bitcoin.NativeSecp256k1;
|
||||
import org.bitcoin.NativeSecp256k1Util;
|
||||
import org.bitcoin.Secp256k1Context;
|
||||
import org.bouncycastle.asn1.*;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.asn1.x9.X9IntegerConverter;
|
||||
|
@ -19,7 +19,6 @@ import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
|||
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||
import org.bouncycastle.math.ec.*;
|
||||
import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve;
|
||||
import org.bouncycastle.util.Properties;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -64,21 +63,9 @@ import java.util.Objects;
|
|||
* this class so round-tripping preserves state. Unless you're working with old software or doing unusual things, you
|
||||
* can usually ignore the compressed/uncompressed distinction.</p>
|
||||
*/
|
||||
public class ECKey implements EncryptableItem {
|
||||
public class ECKey {
|
||||
private static final Logger log = LoggerFactory.getLogger(ECKey.class);
|
||||
|
||||
/** Sorts oldest keys first, newest last. */
|
||||
public static final Comparator<ECKey> AGE_COMPARATOR = new Comparator<ECKey>() {
|
||||
|
||||
@Override
|
||||
public int compare(ECKey k1, ECKey k2) {
|
||||
if (k1.creationTimeSeconds == k2.creationTimeSeconds)
|
||||
return 0;
|
||||
else
|
||||
return k1.creationTimeSeconds > k2.creationTimeSeconds ? 1 : -1;
|
||||
}
|
||||
};
|
||||
|
||||
// The parameters of the secp256k1 curve that Bitcoin uses.
|
||||
private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
|
||||
|
||||
|
@ -106,13 +93,6 @@ public class ECKey implements EncryptableItem {
|
|||
protected final BigInteger priv;
|
||||
protected final LazyECPoint pub;
|
||||
|
||||
// Creation time of the key in seconds since the epoch, or zero if the key was deserialized from a version that did
|
||||
// not have this field.
|
||||
protected long creationTimeSeconds;
|
||||
|
||||
protected KeyCrypter keyCrypter;
|
||||
protected EncryptedData encryptedPrivateKey;
|
||||
|
||||
private byte[] pubKeyHash;
|
||||
|
||||
/**
|
||||
|
@ -157,18 +137,6 @@ public class ECKey implements EncryptableItem {
|
|||
this.pub = pub;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a key that has an encrypted private component. The given object wraps encrypted bytes and an
|
||||
* initialization vector. Note that the key will not be decrypted during this call: the returned ECKey is
|
||||
* unusable for signing unless a decryption key is supplied.
|
||||
*/
|
||||
public static ECKey fromEncrypted(EncryptedData encryptedPrivateKey, KeyCrypter crypter, byte[] pubKey) {
|
||||
ECKey key = fromPublicOnly(pubKey);
|
||||
key.encryptedPrivateKey = encryptedPrivateKey;
|
||||
key.keyCrypter = crypter;
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility for compressing an elliptic curve point. Returns the same point if it's already compressed.
|
||||
* See the ECKey class docs for a discussion of point compression.
|
||||
|
@ -266,11 +234,6 @@ public class ECKey implements EncryptableItem {
|
|||
return priv != null;
|
||||
}
|
||||
|
||||
/** Returns true if this key is watch only, meaning it has a public key but no private key. */
|
||||
public boolean isWatching() {
|
||||
return isPubKeyOnly() && !isEncrypted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Output this ECKey as an ASN.1 encoded private key, as understood by OpenSSL or used by Bitcoin Core
|
||||
* in its wallet storage format.
|
||||
|
@ -372,166 +335,10 @@ public class ECKey implements EncryptableItem {
|
|||
}
|
||||
|
||||
/**
|
||||
* Groups the two components that make up a signature, and provides a way to encode to DER form, which is
|
||||
* how ECDSA signatures are represented when embedded in other data structures in the Bitcoin protocol. The raw
|
||||
* components can be useful for doing further EC maths on them.
|
||||
* Signs the given hash and returns the R and S components as an ECDSASignature.
|
||||
*/
|
||||
public static class ECDSASignature {
|
||||
/** The two components of the signature. */
|
||||
public final BigInteger r, s;
|
||||
|
||||
/**
|
||||
* Constructs a signature with the given components. Does NOT automatically canonicalise the signature.
|
||||
*/
|
||||
public ECDSASignature(BigInteger r, BigInteger s) {
|
||||
this.r = r;
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the S component is "low", that means it is below {@link ECKey#HALF_CURVE_ORDER}. See <a
|
||||
* href="https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#Low_S_values_in_signatures">BIP62</a>.
|
||||
*/
|
||||
public boolean isCanonical() {
|
||||
return s.compareTo(HALF_CURVE_ORDER) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will automatically adjust the S component to be less than or equal to half the curve order, if necessary.
|
||||
* This is required because for every signature (r,s) the signature (r, -s (mod N)) is a valid signature of
|
||||
* the same message. However, we dislike the ability to modify the bits of a Bitcoin transaction after it's
|
||||
* been signed, as that violates various assumed invariants. Thus in future only one of those forms will be
|
||||
* considered legal and the other will be banned.
|
||||
*/
|
||||
public ECDSASignature toCanonicalised() {
|
||||
if (!isCanonical()) {
|
||||
// The order of the curve is the number of valid points that exist on that curve. If S is in the upper
|
||||
// half of the number of valid points, then bring it back to the lower half. Otherwise, imagine that
|
||||
// N = 10
|
||||
// s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are valid solutions.
|
||||
// 10 - 8 == 2, giving us always the latter solution, which is canonical.
|
||||
return new ECDSASignature(r, CURVE.getN().subtract(s));
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DER is an international standard for serializing data structures which is widely used in cryptography.
|
||||
* It's somewhat like protocol buffers but less convenient. This method returns a standard DER encoding
|
||||
* of the signature, as recognized by OpenSSL and other libraries.
|
||||
*/
|
||||
public byte[] encodeToDER() {
|
||||
try {
|
||||
return derByteStream().toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // Cannot happen.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SignatureDecodeException if the signature is unparseable in some way.
|
||||
*/
|
||||
public static ECDSASignature decodeFromDER(byte[] bytes) throws SignatureDecodeException {
|
||||
ASN1InputStream decoder = null;
|
||||
try {
|
||||
// BouncyCastle by default is strict about parsing ASN.1 integers. We relax this check, because some
|
||||
// Bitcoin signatures would not parse.
|
||||
Properties.setThreadOverride("org.bouncycastle.asn1.allow_unsafe_integer", true);
|
||||
decoder = new ASN1InputStream(bytes);
|
||||
final ASN1Primitive seqObj = decoder.readObject();
|
||||
if (seqObj == null)
|
||||
throw new SignatureDecodeException("Reached past end of ASN.1 stream.");
|
||||
if (!(seqObj instanceof DLSequence))
|
||||
throw new SignatureDecodeException("Read unexpected class: " + seqObj.getClass().getName());
|
||||
final DLSequence seq = (DLSequence) seqObj;
|
||||
ASN1Integer r, s;
|
||||
try {
|
||||
r = (ASN1Integer) seq.getObjectAt(0);
|
||||
s = (ASN1Integer) seq.getObjectAt(1);
|
||||
} catch (ClassCastException e) {
|
||||
throw new SignatureDecodeException(e);
|
||||
}
|
||||
// OpenSSL deviates from the DER spec by interpreting these values as unsigned, though they should not be
|
||||
// Thus, we always use the positive versions. See: http://r6.ca/blog/20111119T211504Z.html
|
||||
return new ECDSASignature(r.getPositiveValue(), s.getPositiveValue());
|
||||
} catch (IOException e) {
|
||||
throw new SignatureDecodeException(e);
|
||||
} finally {
|
||||
if (decoder != null)
|
||||
try { decoder.close(); } catch (IOException x) {}
|
||||
Properties.removeThreadOverride("org.bouncycastle.asn1.allow_unsafe_integer");
|
||||
}
|
||||
}
|
||||
|
||||
protected ByteArrayOutputStream derByteStream() throws IOException {
|
||||
// Usually 70-72 bytes.
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(72);
|
||||
DERSequenceGenerator seq = new DERSequenceGenerator(bos);
|
||||
seq.addObject(new ASN1Integer(r));
|
||||
seq.addObject(new ASN1Integer(s));
|
||||
seq.close();
|
||||
return bos;
|
||||
}
|
||||
|
||||
protected boolean hasLowR() {
|
||||
//A low R signature will have less than 71 bytes when encoded to DER
|
||||
return toCanonicalised().encodeToDER().length < 71;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ECDSASignature other = (ECDSASignature) o;
|
||||
return r.equals(other.r) && s.equals(other.s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(r, s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs the given hash and returns the R and S components as BigIntegers. In the Bitcoin protocol, they are
|
||||
* usually encoded using ASN.1 format, so you want {@link ECKey.ECDSASignature#toASN1()}
|
||||
* instead. However sometimes the independent components can be useful, for instance, if you're going to do
|
||||
* further EC maths on them.
|
||||
* @throws KeyCrypterException if this ECKey doesn't have a private part.
|
||||
*/
|
||||
public ECDSASignature sign(Sha256Hash input) throws KeyCrypterException {
|
||||
return sign(input, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs the given hash and returns the R and S components as BigIntegers. In the Bitcoin protocol, they are
|
||||
* usually encoded using DER format, so you want {@link ECKey.ECDSASignature#encodeToDER()}
|
||||
* instead. However sometimes the independent components can be useful, for instance, if you're doing to do further
|
||||
* EC maths on them.
|
||||
*
|
||||
* @param aesKey The AES key to use for decryption of the private key. If null then no decryption is required.
|
||||
* @throws KeyCrypterException if there's something wrong with aesKey.
|
||||
* @throws ECKey.MissingPrivateKeyException if this key cannot sign because it's pubkey only.
|
||||
*/
|
||||
public ECDSASignature sign(Sha256Hash input, Key aesKey) throws KeyCrypterException {
|
||||
KeyCrypter crypter = getKeyCrypter();
|
||||
if (crypter != null) {
|
||||
if (aesKey == null) {
|
||||
throw new KeyIsEncryptedException();
|
||||
}
|
||||
return decrypt(aesKey).sign(input);
|
||||
} else {
|
||||
// No decryption of private key required.
|
||||
if (priv == null) {
|
||||
throw new MissingPrivateKeyException();
|
||||
}
|
||||
}
|
||||
return doSign(input, priv);
|
||||
}
|
||||
|
||||
protected ECDSASignature doSign(Sha256Hash input, BigInteger privateKeyForSigning) {
|
||||
if(privateKeyForSigning == null) {
|
||||
public ECDSASignature signEcdsa(Sha256Hash input) {
|
||||
if(priv == null) {
|
||||
throw new IllegalArgumentException("Private key cannot be null");
|
||||
}
|
||||
|
||||
|
@ -539,7 +346,7 @@ public class ECKey implements EncryptableItem {
|
|||
Integer counter = null;
|
||||
do {
|
||||
ECDSASigner signer = new ECDSASigner(new HMacDSANonceKCalculator(new SHA256Digest(), counter));
|
||||
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(privateKeyForSigning, CURVE);
|
||||
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(priv, CURVE);
|
||||
signer.init(true, privKey);
|
||||
BigInteger[] components = signer.generateSignature(input.getBytes());
|
||||
signature = new ECDSASignature(components[0], components[1]).toCanonicalised();
|
||||
|
@ -550,80 +357,39 @@ public class ECKey implements EncryptableItem {
|
|||
}
|
||||
|
||||
/**
|
||||
* <p>Verifies the given ECDSA signature against the message bytes using the public key bytes.</p>
|
||||
*
|
||||
* <p>When using native ECDSA verification, data must be 32 bytes, and no element may be
|
||||
* larger than 520 bytes.</p>
|
||||
*
|
||||
* @param data Hash of the data to verify.
|
||||
* @param signature ASN.1 encoded signature.
|
||||
* @param pub The public key bytes to use.
|
||||
* Signs the given hash and returns the R and S components as a SchnorrSignature.
|
||||
*/
|
||||
public static boolean verify(byte[] data, ECDSASignature signature, byte[] pub) {
|
||||
ECDSASigner signer = new ECDSASigner();
|
||||
ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE);
|
||||
signer.init(false, params);
|
||||
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;
|
||||
public SchnorrSignature signSchnorr(Sha256Hash input) {
|
||||
if(priv == null) {
|
||||
throw new IllegalArgumentException("Private key cannot be null");
|
||||
}
|
||||
|
||||
if(!Secp256k1Context.isEnabled()) {
|
||||
throw new IllegalStateException("libsecp256k1 is not enabled");
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] sigBytes = NativeSecp256k1.schnorrSign(input.getBytes(), priv.toByteArray(), null);
|
||||
return SchnorrSignature.decode(sigBytes);
|
||||
} catch(NativeSecp256k1Util.AssertFailException e) {
|
||||
log.error("Error signing schnorr", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key.
|
||||
*
|
||||
* @param data Hash of the data to verify.
|
||||
* @param signature ASN.1 encoded signature.
|
||||
* @param pub The public key bytes to use.
|
||||
* @throws SignatureDecodeException if the signature is unparseable in some way.
|
||||
* Verifies the given TransactionSignature against the provided byte array using the public key.
|
||||
*/
|
||||
public static boolean verify(byte[] data, byte[] signature, byte[] pub) throws SignatureDecodeException {
|
||||
return verify(data, ECDSASignature.decodeFromDER(signature), pub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key.
|
||||
*
|
||||
* @param hash Hash of the data to verify.
|
||||
* @param signature ASN.1 encoded signature.
|
||||
* @throws SignatureDecodeException if the signature is unparseable in some way.
|
||||
*/
|
||||
public boolean verify(byte[] hash, byte[] signature) throws SignatureDecodeException {
|
||||
return ECKey.verify(hash, signature, getPubKey());
|
||||
public boolean verify(byte[] data, TransactionSignature signature) {
|
||||
return signature.verify(data, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the given R/S pair (signature) against a hash using the public key.
|
||||
*/
|
||||
public boolean verify(Sha256Hash sigHash, ECDSASignature signature) {
|
||||
return ECKey.verify(sigHash.getBytes(), signature, getPubKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key, and throws an exception
|
||||
* if the signature doesn't match
|
||||
* @throws SignatureDecodeException if the signature is unparseable in some way.
|
||||
* @throws java.security.SignatureException if the signature does not match.
|
||||
*/
|
||||
public void verifyOrThrow(byte[] hash, byte[] signature) throws SignatureDecodeException, SignatureException {
|
||||
if (!verify(hash, signature)) {
|
||||
throw new SignatureException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the given R/S pair (signature) against a hash using the public key, and throws an exception
|
||||
* if the signature doesn't match
|
||||
* @throws java.security.SignatureException if the signature does not match.
|
||||
*/
|
||||
public void verifyOrThrow(Sha256Hash sigHash, ECDSASignature signature) throws SignatureException {
|
||||
if (!ECKey.verify(sigHash.getBytes(), signature, getPubKey())) {
|
||||
throw new SignatureException();
|
||||
}
|
||||
public boolean verify(Sha256Hash sigHash, TransactionSignature signature) {
|
||||
return verify(sigHash.getBytes(), signature);
|
||||
}
|
||||
|
||||
public ECKey getTweakedOutputKey() {
|
||||
|
@ -757,12 +523,11 @@ public class ECKey implements EncryptableItem {
|
|||
* encoded string.
|
||||
*
|
||||
* @throws IllegalStateException if this ECKey does not have the private part.
|
||||
* @throws KeyCrypterException if this ECKey is encrypted and no AESKey is provided or it does not decrypt the ECKey.
|
||||
*/
|
||||
public String signMessage(String message, ScriptType scriptType, Key aesKey) throws KeyCrypterException {
|
||||
public String signMessage(String message, ScriptType scriptType) {
|
||||
byte[] data = formatMessageForSigning(message);
|
||||
Sha256Hash hash = Sha256Hash.twiceOf(data);
|
||||
ECDSASignature sig = sign(hash, aesKey);
|
||||
ECDSASignature sig = signEcdsa(hash);
|
||||
byte recId = findRecoveryId(hash, sig);
|
||||
int headerByte = recId + getSigningTypeConstant(scriptType);
|
||||
byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S
|
||||
|
@ -981,181 +746,9 @@ public class ECKey implements EncryptableItem {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the creation time of this key or zero if the key was deserialized from a version that did not store
|
||||
* that data.
|
||||
*/
|
||||
@Override
|
||||
public long getCreationTimeSeconds() {
|
||||
return creationTimeSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the creation time of this key. Zero is a convention to mean "unavailable". This method can be useful when
|
||||
* you have a raw key you are importing from somewhere else.
|
||||
*/
|
||||
public void setCreationTimeSeconds(long newCreationTimeSeconds) {
|
||||
if (newCreationTimeSeconds < 0) {
|
||||
throw new IllegalArgumentException("Cannot set creation time to negative value: " + newCreationTimeSeconds);
|
||||
}
|
||||
creationTimeSeconds = newCreationTimeSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an encrypted private key with the keyCrypter and the AES key supplied.
|
||||
* This method returns a new encrypted key and leaves the original unchanged.
|
||||
*
|
||||
* @param keyCrypter The keyCrypter that specifies exactly how the encrypted bytes are created.
|
||||
* @param aesKey The Key with the AES encryption key (usually constructed with keyCrypter#deriveKey and cached as it is slow to create).
|
||||
* @return encryptedKey
|
||||
*/
|
||||
public ECKey encrypt(KeyCrypter keyCrypter, Key aesKey) throws KeyCrypterException {
|
||||
if(keyCrypter == null) {
|
||||
throw new KeyCrypterException("Keycrypter cannot be null");
|
||||
}
|
||||
|
||||
final byte[] privKeyBytes = getPrivKeyBytes();
|
||||
EncryptedData encryptedPrivateKey = keyCrypter.encrypt(privKeyBytes, null, aesKey);
|
||||
ECKey result = ECKey.fromEncrypted(encryptedPrivateKey, keyCrypter, getPubKey());
|
||||
result.setCreationTimeSeconds(creationTimeSeconds);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a decrypted private key with the keyCrypter and AES key supplied. Note that if the aesKey is wrong, this
|
||||
* has some chance of throwing KeyCrypterException due to the corrupted padding that will result, but it can also
|
||||
* just yield a garbage key.
|
||||
*
|
||||
* @param keyCrypter The keyCrypter that specifies exactly how the decrypted bytes are created.
|
||||
* @param aesKey The Key with the AES encryption key (usually constructed with keyCrypter#deriveKey and cached).
|
||||
*/
|
||||
public ECKey decrypt(KeyCrypter keyCrypter, Key aesKey) throws KeyCrypterException {
|
||||
if(keyCrypter == null) {
|
||||
throw new KeyCrypterException("Keycrypter cannot be null");
|
||||
}
|
||||
|
||||
// Check that the keyCrypter matches the one used to encrypt the keys, if set.
|
||||
if (this.keyCrypter != null && !this.keyCrypter.equals(keyCrypter)) {
|
||||
throw new KeyCrypterException("The keyCrypter being used to decrypt the key is different to the one that was used to encrypt it");
|
||||
}
|
||||
|
||||
if(encryptedPrivateKey == null) {
|
||||
throw new IllegalArgumentException("This key is not encrypted");
|
||||
}
|
||||
|
||||
byte[] unencryptedPrivateKey = keyCrypter.decrypt(encryptedPrivateKey, aesKey);
|
||||
ECKey key = ECKey.fromPrivate(unencryptedPrivateKey);
|
||||
|
||||
if (!Arrays.equals(key.getPubKey(), getPubKey())) {
|
||||
throw new KeyCrypterException("Provided AES key is wrong");
|
||||
}
|
||||
|
||||
key.setCreationTimeSeconds(creationTimeSeconds);
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a decrypted private key with AES key. Note that if the AES key is wrong, this
|
||||
* has some chance of throwing KeyCrypterException due to the corrupted padding that will result, but it can also
|
||||
* just yield a garbage key.
|
||||
*
|
||||
* @param aesKey The Key with the AES encryption key (usually constructed with keyCrypter#deriveKey and cached).
|
||||
*/
|
||||
public ECKey decrypt(Key aesKey) throws KeyCrypterException {
|
||||
final KeyCrypter crypter = getKeyCrypter();
|
||||
if (crypter == null) {
|
||||
throw new KeyCrypterException("No key crypter available");
|
||||
}
|
||||
|
||||
return decrypt(crypter, aesKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates decrypted private key if needed.
|
||||
*/
|
||||
public ECKey maybeDecrypt(Key aesKey) throws KeyCrypterException {
|
||||
return isEncrypted() && aesKey != null ? decrypt(aesKey) : this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Check that it is possible to decrypt the key with the keyCrypter and that the original key is returned.</p>
|
||||
*
|
||||
* <p>Because it is a critical failure if the private keys cannot be decrypted successfully (resulting of loss of all
|
||||
* bitcoins controlled by the private key) you can use this method to check when you *encrypt* a wallet that
|
||||
* it can definitely be decrypted successfully.</p>
|
||||
*
|
||||
* @return true if the encrypted key can be decrypted back to the original key successfully.
|
||||
*/
|
||||
public static boolean encryptionIsReversible(ECKey originalKey, ECKey encryptedKey, KeyCrypter keyCrypter, Key aesKey) {
|
||||
try {
|
||||
ECKey rebornUnencryptedKey = encryptedKey.decrypt(keyCrypter, aesKey);
|
||||
byte[] originalPrivateKeyBytes = originalKey.getPrivKeyBytes();
|
||||
byte[] rebornKeyBytes = rebornUnencryptedKey.getPrivKeyBytes();
|
||||
if (!Arrays.equals(originalPrivateKeyBytes, rebornKeyBytes)) {
|
||||
log.error("The check that encryption could be reversed failed for {}", originalKey);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (KeyCrypterException kce) {
|
||||
log.error(kce.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the private key is encrypted (true) or not (false).
|
||||
* A private key is deemed to be encrypted when there is both a KeyCrypter and the encryptedPrivateKey is non-zero.
|
||||
*/
|
||||
@Override
|
||||
public boolean isEncrypted() {
|
||||
return keyCrypter != null && encryptedPrivateKey != null && encryptedPrivateKey.getEncryptedBytes().length > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EncryptionType getEncryptionType() {
|
||||
return new EncryptionType(EncryptionType.Deriver.SCRYPT, keyCrypter != null ? keyCrypter.getCrypterType() : EncryptionType.Crypter.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper for {@link #getPrivKeyBytes()} that returns null if the private key bytes are missing or would have
|
||||
* to be derived (for the HD key case).
|
||||
*/
|
||||
@Override
|
||||
public byte[] getSecretBytes() {
|
||||
if (hasPrivKey()) {
|
||||
return getPrivKeyBytes();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** An alias for {@link #getEncryptedPrivateKey()} */
|
||||
@Override
|
||||
public EncryptedData getEncryptedData() {
|
||||
return getEncryptedPrivateKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the the encrypted private key bytes and initialisation vector for this ECKey, or null if the ECKey
|
||||
* is not encrypted.
|
||||
*/
|
||||
public EncryptedData getEncryptedPrivateKey() {
|
||||
return encryptedPrivateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the KeyCrypter that was used to encrypt to encrypt this ECKey. You need this to decrypt the ECKey.
|
||||
*/
|
||||
public KeyCrypter getKeyCrypter() {
|
||||
return keyCrypter;
|
||||
}
|
||||
|
||||
public static class MissingPrivateKeyException extends RuntimeException {
|
||||
}
|
||||
|
||||
public static class KeyIsEncryptedException extends MissingPrivateKeyException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -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 {
|
||||
ECKey.ECDSASignature.decodeFromDER(data);
|
||||
} catch(SignatureDecodeException e) {
|
||||
TransactionSignature.decodeFromBitcoin(data, false);
|
||||
} catch(Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,7 @@ public class ScriptChunk {
|
|||
|
||||
public TransactionSignature getSignature() {
|
||||
try {
|
||||
return TransactionSignature.decodeFromBitcoin(data, false, false);
|
||||
return TransactionSignature.decodeFromBitcoin(data, false);
|
||||
} catch(SignatureDecodeException e) {
|
||||
throw new ProtocolException("Could not decode signature", e);
|
||||
}
|
||||
|
|
|
@ -126,6 +126,11 @@ public enum ScriptType {
|
|||
throw new ProtocolException(getName() + " is not a multisig script type");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionSignature.Type getSignatureType() {
|
||||
return TransactionSignature.Type.ECDSA;
|
||||
};
|
||||
|
||||
@Override
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return List.of(SINGLE);
|
||||
|
@ -239,6 +244,11 @@ public enum ScriptType {
|
|||
throw new ProtocolException(getName() + " is not a multisig script type");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionSignature.Type getSignatureType() {
|
||||
return TransactionSignature.Type.ECDSA;
|
||||
};
|
||||
|
||||
@Override
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return List.of(SINGLE);
|
||||
|
@ -425,6 +435,11 @@ public enum ScriptType {
|
|||
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionSignature.Type getSignatureType() {
|
||||
return TransactionSignature.Type.ECDSA;
|
||||
};
|
||||
|
||||
@Override
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return List.of(MULTI);
|
||||
|
@ -550,6 +565,11 @@ public enum ScriptType {
|
|||
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionSignature.Type getSignatureType() {
|
||||
return TransactionSignature.Type.ECDSA;
|
||||
};
|
||||
|
||||
@Override
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return List.of(MULTI);
|
||||
|
@ -653,6 +673,11 @@ public enum ScriptType {
|
|||
throw new ProtocolException(getName() + " is not a multisig script type");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionSignature.Type getSignatureType() {
|
||||
return TransactionSignature.Type.ECDSA;
|
||||
};
|
||||
|
||||
@Override
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return List.of(SINGLE);
|
||||
|
@ -754,6 +779,11 @@ public enum ScriptType {
|
|||
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig, witness);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionSignature.Type getSignatureType() {
|
||||
return TransactionSignature.Type.ECDSA;
|
||||
};
|
||||
|
||||
@Override
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return List.of(MULTI, CUSTOM);
|
||||
|
@ -859,6 +889,11 @@ public enum ScriptType {
|
|||
throw new ProtocolException(getName() + " is not a multisig script type");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionSignature.Type getSignatureType() {
|
||||
return TransactionSignature.Type.ECDSA;
|
||||
};
|
||||
|
||||
@Override
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return List.of(SINGLE);
|
||||
|
@ -970,6 +1005,11 @@ public enum ScriptType {
|
|||
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig, witness);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionSignature.Type getSignatureType() {
|
||||
return TransactionSignature.Type.ECDSA;
|
||||
};
|
||||
|
||||
@Override
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return List.of(MULTI, CUSTOM);
|
||||
|
@ -1078,6 +1118,11 @@ public enum ScriptType {
|
|||
throw new UnsupportedOperationException("Constructing Taproot inputs is not yet supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionSignature.Type getSignatureType() {
|
||||
return TransactionSignature.Type.SCHNORR;
|
||||
};
|
||||
|
||||
@Override
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return Network.get() == Network.REGTEST || Network.get() == Network.SIGNET ? List.of(SINGLE) : Collections.emptyList();
|
||||
|
@ -1195,6 +1240,8 @@ public enum ScriptType {
|
|||
|
||||
public abstract TransactionInput addMultisigSpendingInput(Transaction transaction, TransactionOutput prevOutput, int threshold, Map<ECKey, TransactionSignature> pubKeySignatures);
|
||||
|
||||
public abstract TransactionSignature.Type getSignatureType();
|
||||
|
||||
public static final ScriptType[] SINGLE_KEY_TYPES = {P2PK, P2TR};
|
||||
|
||||
public static final ScriptType[] SINGLE_HASH_TYPES = {P2PKH, P2SH, P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH};
|
||||
|
|
|
@ -12,7 +12,7 @@ public enum SigHash {
|
|||
ANYONECANPAY_ALL("All + Anyone Can Pay", (byte)0x81),
|
||||
ANYONECANPAY_NONE("None + Anyone Can Pay", (byte)0x82),
|
||||
ANYONECANPAY_SINGLE("Single + Anyone Can Pay", (byte)0x83),
|
||||
UNSET("Unset", (byte)0); // Caution: Using this type in isolation is non-standard. Treated similar to ALL.
|
||||
ALL_TAPROOT("All (Taproot)", (byte)0);
|
||||
|
||||
private final String name;
|
||||
public final byte value;
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
package com.sparrowwallet.drongo.protocol;
|
||||
|
||||
import com.sparrowwallet.drongo.crypto.ECDSASignature;
|
||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||
import com.sparrowwallet.drongo.crypto.SchnorrSignature;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Objects;
|
||||
|
||||
public class TransactionSignature extends ECKey.ECDSASignature {
|
||||
public class TransactionSignature {
|
||||
private final ECDSASignature ecdsaSignature;
|
||||
private final SchnorrSignature schnorrSignature;
|
||||
|
||||
/**
|
||||
* A byte that controls which parts of a transaction are signed. This is exposed because signatures
|
||||
* parsed off the wire may have sighash flags that aren't "normal" serializations of the enum values.
|
||||
|
@ -16,21 +22,26 @@ public class TransactionSignature extends ECKey.ECDSASignature {
|
|||
*/
|
||||
public final byte sighashFlags;
|
||||
|
||||
/** Constructs a signature with the given components and SIGHASH_ALL. */
|
||||
public TransactionSignature(BigInteger r, BigInteger s) {
|
||||
this(r, s, SigHash.ALL.value);
|
||||
}
|
||||
|
||||
/** Constructs a signature with the given components and raw sighash flag bytes (needed for rule compatibility). */
|
||||
public TransactionSignature(BigInteger r, BigInteger s, byte sighashFlags) {
|
||||
super(r, s);
|
||||
this.sighashFlags = sighashFlags;
|
||||
/** Constructs a signature with the given components of the given type and SIGHASH_ALL. */
|
||||
public TransactionSignature(BigInteger r, BigInteger s, Type type) {
|
||||
this(r, s, type, SigHash.ALL.value);
|
||||
}
|
||||
|
||||
/** Constructs a transaction signature based on the ECDSA signature. */
|
||||
public TransactionSignature(ECKey.ECDSASignature signature, SigHash sigHash) {
|
||||
super(signature.r, signature.s);
|
||||
sighashFlags = sigHash.value;
|
||||
public TransactionSignature(ECDSASignature signature, SigHash sigHash) {
|
||||
this(signature.r, signature.s, Type.ECDSA, sigHash.value);
|
||||
}
|
||||
|
||||
/** Constructs a transaction signature based on the Schnorr signature. */
|
||||
public TransactionSignature(SchnorrSignature signature, SigHash sigHash) {
|
||||
this(signature.r, signature.s, Type.SCHNORR, sigHash.value);
|
||||
}
|
||||
|
||||
/** Constructs a signature with the given components, type and raw sighash flag bytes (needed for rule compatibility). */
|
||||
public TransactionSignature(BigInteger r, BigInteger s, Type type, byte sighashFlags) {
|
||||
ecdsaSignature = type == Type.ECDSA ? new ECDSASignature(r, s) : null;
|
||||
schnorrSignature = type == Type.SCHNORR ? new SchnorrSignature(r, s) : null;
|
||||
this.sighashFlags = sighashFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,61 +50,9 @@ public class TransactionSignature extends ECKey.ECDSASignature {
|
|||
* right size (e.g. for fee calculations) but don't have the requisite signing key yet and will fill out the
|
||||
* real signature later.
|
||||
*/
|
||||
public static TransactionSignature dummy() {
|
||||
public static TransactionSignature dummy(Type type) {
|
||||
BigInteger val = ECKey.HALF_CURVE_ORDER;
|
||||
return new TransactionSignature(val, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given signature is has canonical encoding, and will thus be accepted as standard by
|
||||
* Bitcoin Core. DER and the SIGHASH encoding allow for quite some flexibility in how the same structures
|
||||
* are encoded, and this can open up novel attacks in which a man in the middle takes a transaction and then
|
||||
* changes its signature such that the transaction hash is different but it's still valid. This can confuse wallets
|
||||
* and generally violates people's mental model of how Bitcoin should work, thus, non-canonical signatures are now
|
||||
* not relayed by default.
|
||||
*/
|
||||
public static boolean isEncodingCanonical(byte[] signature) {
|
||||
// See Bitcoin Core's IsCanonicalSignature, https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
|
||||
// A canonical signature exists of: <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
|
||||
// Where R and S are not negative (their first byte has its highest bit not set), and not
|
||||
// excessively padded (do not start with a 0 byte, unless an otherwise negative number follows,
|
||||
// in which case a single 0 byte is necessary and even required).
|
||||
|
||||
// Empty signatures, while not strictly DER encoded, are allowed.
|
||||
if (signature.length == 0)
|
||||
return true;
|
||||
|
||||
if (signature.length < 9 || signature.length > 73)
|
||||
return false;
|
||||
|
||||
int hashType = (signature[signature.length-1] & 0xff) & ~SigHash.ANYONECANPAY.value; // mask the byte to prevent sign-extension hurting us
|
||||
if (hashType < SigHash.ALL.value || hashType > SigHash.SINGLE.value)
|
||||
return false;
|
||||
|
||||
// "wrong type" "wrong length marker"
|
||||
if ((signature[0] & 0xff) != 0x30 || (signature[1] & 0xff) != signature.length-3)
|
||||
return false;
|
||||
|
||||
int lenR = signature[3] & 0xff;
|
||||
if (5 + lenR >= signature.length || lenR == 0)
|
||||
return false;
|
||||
int lenS = signature[5+lenR] & 0xff;
|
||||
if (lenR + lenS + 7 != signature.length || lenS == 0)
|
||||
return false;
|
||||
|
||||
// R value type mismatch R value negative
|
||||
if (signature[4-2] != 0x02 || (signature[4] & 0x80) == 0x80)
|
||||
return false;
|
||||
if (lenR > 1 && signature[4] == 0x00 && (signature[4+1] & 0x80) != 0x80)
|
||||
return false; // R value excessively padded
|
||||
|
||||
// S value type mismatch S value negative
|
||||
if (signature[6 + lenR - 2] != 0x02 || (signature[6 + lenR] & 0x80) == 0x80)
|
||||
return false;
|
||||
if (lenS > 1 && signature[6 + lenR] == 0x00 && (signature[6 + lenR + 1] & 0x80) != 0x80)
|
||||
return false; // S value excessively padded
|
||||
|
||||
return true;
|
||||
return new TransactionSignature(val, val, type);
|
||||
}
|
||||
|
||||
public boolean anyoneCanPay() {
|
||||
|
@ -118,18 +77,51 @@ public class TransactionSignature extends ECKey.ECDSASignature {
|
|||
* components into a structure, and then we append a byte to the end for the sighash flags.
|
||||
*/
|
||||
public byte[] encodeToBitcoin() {
|
||||
try {
|
||||
ByteArrayOutputStream bos = derByteStream();
|
||||
bos.write(sighashFlags);
|
||||
return bos.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // Cannot happen.
|
||||
if(ecdsaSignature != null) {
|
||||
try {
|
||||
ByteArrayOutputStream bos = ecdsaSignature.derByteStream();
|
||||
bos.write(sighashFlags);
|
||||
return bos.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // Cannot happen.
|
||||
}
|
||||
} else if(schnorrSignature != null) {
|
||||
SigHash sigHash = getSigHash();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(sigHash == SigHash.ALL ? 64 : 65);
|
||||
buffer.put(schnorrSignature.encode());
|
||||
if(sigHash != SigHash.ALL) {
|
||||
buffer.put(sighashFlags);
|
||||
}
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
throw new IllegalStateException("TransactionSignature has no values");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ECKey.ECDSASignature toCanonicalised() {
|
||||
return new TransactionSignature(super.toCanonicalised(), getSigHash());
|
||||
public static TransactionSignature decodeFromBitcoin(byte[] bytes, boolean requireCanonicalEncoding) throws SignatureDecodeException {
|
||||
if(bytes.length == 64 || bytes.length == 65) {
|
||||
return decodeFromBitcoin(Type.SCHNORR, bytes, requireCanonicalEncoding);
|
||||
}
|
||||
|
||||
return decodeFromBitcoin(Type.ECDSA, bytes, requireCanonicalEncoding);
|
||||
}
|
||||
|
||||
public static TransactionSignature decodeFromBitcoin(Type type, byte[] bytes, boolean requireCanonicalEncoding) throws SignatureDecodeException {
|
||||
if(type == Type.ECDSA) {
|
||||
return ECDSASignature.decodeFromBitcoin(bytes, requireCanonicalEncoding, false);
|
||||
} else if(type == Type.SCHNORR) {
|
||||
return SchnorrSignature.decodeFromBitcoin(bytes);
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Unknown TransactionSignature type " + type);
|
||||
}
|
||||
|
||||
public boolean verify(byte[] data, ECKey pubKey) {
|
||||
if(ecdsaSignature != null) {
|
||||
return ecdsaSignature.verify(data, pubKey.getPubKey());
|
||||
} else {
|
||||
return schnorrSignature.verify(data, pubKey.getPubKeyXCoord());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -140,39 +132,16 @@ public class TransactionSignature extends ECKey.ECDSASignature {
|
|||
if(o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if(!super.equals(o)) {
|
||||
return false;
|
||||
}
|
||||
TransactionSignature signature = (TransactionSignature) o;
|
||||
return sighashFlags == signature.sighashFlags;
|
||||
TransactionSignature that = (TransactionSignature) o;
|
||||
return sighashFlags == that.sighashFlags && Objects.equals(ecdsaSignature, that.ecdsaSignature) && Objects.equals(schnorrSignature, that.schnorrSignature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), sighashFlags);
|
||||
return Objects.hash(ecdsaSignature, schnorrSignature, sighashFlags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a decoded signature.
|
||||
*
|
||||
* @param requireCanonicalEncoding if the encoding of the signature must
|
||||
* be canonical.
|
||||
* @param requireCanonicalSValue if the S-value must be canonical (below half
|
||||
* the order of the curve).
|
||||
* @throws SignatureDecodeException if the signature is unparseable in some way.
|
||||
* @throws VerificationException if the signature is invalid.
|
||||
*/
|
||||
public static TransactionSignature decodeFromBitcoin(byte[] bytes, boolean requireCanonicalEncoding,
|
||||
boolean requireCanonicalSValue) throws SignatureDecodeException, VerificationException {
|
||||
// Bitcoin encoding is DER signature + sighash byte.
|
||||
if (requireCanonicalEncoding && !isEncodingCanonical(bytes))
|
||||
throw new VerificationException.NoncanonicalSignature();
|
||||
ECKey.ECDSASignature sig = ECKey.ECDSASignature.decodeFromDER(bytes);
|
||||
if (requireCanonicalSValue && !sig.isCanonical())
|
||||
throw new VerificationException("S-value is not canonical.");
|
||||
|
||||
// In Bitcoin, any value of the final byte is valid, but not necessarily canonical. See javadocs for
|
||||
// isEncodingCanonical to learn more about this. So we must store the exact byte found.
|
||||
return new TransactionSignature(sig.r, sig.s, bytes[bytes.length - 1]);
|
||||
public enum Type {
|
||||
ECDSA, SCHNORR
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package com.sparrowwallet.drongo.psbt;
|
|||
|
||||
import com.sparrowwallet.drongo.KeyDerivation;
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.crypto.ECDSASignature;
|
||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||
import com.sparrowwallet.drongo.protocol.*;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -113,7 +114,7 @@ public class PSBTInput {
|
|||
entry.checkOneBytePlusPubKey();
|
||||
ECKey sigPublicKey = ECKey.fromPublicOnly(entry.getKeyData());
|
||||
//TODO: Verify signature
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(entry.getData(), true, false);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(TransactionSignature.Type.ECDSA, entry.getData(), true);
|
||||
this.partialSignatures.put(sigPublicKey, signature);
|
||||
log.debug("Found input partial signature with public key " + sigPublicKey + " signature " + Utils.bytesToHex(entry.getData()));
|
||||
break;
|
||||
|
@ -419,7 +420,7 @@ public class PSBTInput {
|
|||
Script signingScript = getSigningScript();
|
||||
if(signingScript != null) {
|
||||
Sha256Hash hash = getHashForSignature(signingScript, localSigHash);
|
||||
ECKey.ECDSASignature ecdsaSignature = privKey.sign(hash);
|
||||
ECDSASignature ecdsaSignature = privKey.signEcdsa(hash);
|
||||
TransactionSignature transactionSignature = new TransactionSignature(ecdsaSignature, localSigHash);
|
||||
|
||||
ECKey pubKey = ECKey.fromPublicOnly(privKey);
|
||||
|
|
|
@ -475,14 +475,14 @@ public class Wallet extends Persistable {
|
|||
TransactionInput txInput = null;
|
||||
if(getPolicyType().equals(PolicyType.SINGLE)) {
|
||||
ECKey pubKey = getPubKey(receiveNode);
|
||||
TransactionSignature signature = TransactionSignature.dummy();
|
||||
TransactionSignature signature = TransactionSignature.dummy(getScriptType().getSignatureType());
|
||||
txInput = getScriptType().addSpendingInput(transaction, prevTxOut, pubKey, signature);
|
||||
} else if(getPolicyType().equals(PolicyType.MULTI)) {
|
||||
List<ECKey> pubKeys = getPubKeys(receiveNode);
|
||||
int threshold = getDefaultPolicy().getNumSignaturesRequired();
|
||||
Map<ECKey, TransactionSignature> pubKeySignatures = new TreeMap<>(new ECKey.LexicographicECKeyComparator());
|
||||
for(int i = 0; i < pubKeys.size(); i++) {
|
||||
pubKeySignatures.put(pubKeys.get(i), i < threshold ? TransactionSignature.dummy() : null);
|
||||
pubKeySignatures.put(pubKeys.get(i), i < threshold ? TransactionSignature.dummy(getScriptType().getSignatureType()) : null);
|
||||
}
|
||||
txInput = getScriptType().addMultisigSpendingInput(transaction, prevTxOut, threshold, pubKeySignatures);
|
||||
}
|
||||
|
@ -618,13 +618,13 @@ public class Wallet extends Persistable {
|
|||
public TransactionInput addDummySpendingInput(Transaction transaction, WalletNode walletNode, TransactionOutput prevTxOut) {
|
||||
if(getPolicyType().equals(PolicyType.SINGLE)) {
|
||||
ECKey pubKey = getPubKey(walletNode);
|
||||
return getScriptType().addSpendingInput(transaction, prevTxOut, pubKey, TransactionSignature.dummy());
|
||||
return getScriptType().addSpendingInput(transaction, prevTxOut, pubKey, TransactionSignature.dummy(getScriptType().getSignatureType()));
|
||||
} else if(getPolicyType().equals(PolicyType.MULTI)) {
|
||||
List<ECKey> pubKeys = getPubKeys(walletNode);
|
||||
int threshold = getDefaultPolicy().getNumSignaturesRequired();
|
||||
Map<ECKey, TransactionSignature> pubKeySignatures = new TreeMap<>(new ECKey.LexicographicECKeyComparator());
|
||||
for(int i = 0; i < pubKeys.size(); i++) {
|
||||
pubKeySignatures.put(pubKeys.get(i), i < threshold ? TransactionSignature.dummy() : null);
|
||||
pubKeySignatures.put(pubKeys.get(i), i < threshold ? TransactionSignature.dummy(getScriptType().getSignatureType()) : null);
|
||||
}
|
||||
return getScriptType().addMultisigSpendingInput(transaction, prevTxOut, threshold, pubKeySignatures);
|
||||
} else {
|
||||
|
|
|
@ -27,15 +27,15 @@ public class ECKeyTest {
|
|||
ECKey privKey = keystore.getKey(firstReceive);
|
||||
|
||||
//1 attempt required for low R
|
||||
String signature1 = privKey.signMessage("Test2", ScriptType.P2PKH, null);
|
||||
String signature1 = privKey.signMessage("Test2", ScriptType.P2PKH);
|
||||
Assert.assertEquals("IHra0jSywF1TjIJ5uf7IDECae438cr4o3VmG6Ri7hYlDL+pUEXyUfwLwpiAfUQVqQFLgs6OaX0KsoydpuwRI71o=", signature1);
|
||||
|
||||
//2 attempts required for low R
|
||||
String signature2 = privKey.signMessage("Test", ScriptType.P2PKH, null);
|
||||
String signature2 = privKey.signMessage("Test", ScriptType.P2PKH);
|
||||
Assert.assertEquals("IDgMx1ljPhLHlKUOwnO/jBIgK+K8n8mvDUDROzTgU8gOaPDMs+eYXJpNXXINUx5WpeV605p5uO6B3TzBVcvs478=", signature2);
|
||||
|
||||
//3 attempts required for low R
|
||||
String signature3 = privKey.signMessage("Test1", ScriptType.P2PKH, null);
|
||||
String signature3 = privKey.signMessage("Test1", ScriptType.P2PKH);
|
||||
Assert.assertEquals("IEt/v9K95YVFuRtRtWaabPVwWOFv1FSA/e874I8ABgYMbRyVvHhSwLFz0RZuO87ukxDd4TOsRdofQwMEA90LCgI=", signature3);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ public class TransactionTest {
|
|||
|
||||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357"));
|
||||
Sha256Hash hash = transaction.hashForWitnessSignature(1, ScriptType.P2PKH.getOutputScript(pubKey.getPubKeyHash()),600000000L, SigHash.ALL);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee"), false, true);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee"), false);
|
||||
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ public class TransactionTest {
|
|||
|
||||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873"));
|
||||
Sha256Hash hash = transaction.hashForWitnessSignature(0, ScriptType.P2PKH.getOutputScript(pubKey.getPubKeyHash()),1000000000L, SigHash.ALL);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb01"), true, true);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb01"), true);
|
||||
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ public class TransactionTest {
|
|||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880ae"));
|
||||
Script script = new Script(Utils.hexToBytes("21026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac"));
|
||||
Sha256Hash hash = transaction.hashForWitnessSignature(1, script,4900000000L, SigHash.SINGLE);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e2703"), true, true);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e2703"), true);
|
||||
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@ public class TransactionTest {
|
|||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("0392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98"));
|
||||
Script script = new Script(Utils.hexToBytes("68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac"));
|
||||
Sha256Hash hash = transaction.hashForWitnessSignature(1, script,16777215L, SigHash.ANYONECANPAY_SINGLE);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("30440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83"), true, true);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("30440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83"), true);
|
||||
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ public class TransactionTest {
|
|||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3"));
|
||||
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
||||
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.ALL);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01"), true, true);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01"), true);
|
||||
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ public class TransactionTest {
|
|||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b"));
|
||||
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
||||
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.NONE);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502"), true, true);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502"), true);
|
||||
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@ public class TransactionTest {
|
|||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a"));
|
||||
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
||||
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.SINGLE);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403"), true, true);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403"), true);
|
||||
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ public class TransactionTest {
|
|||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4"));
|
||||
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
||||
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.ANYONECANPAY_ALL);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381"), true, true);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381"), true);
|
||||
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||
}
|
||||
|
||||
|
@ -115,7 +115,7 @@ public class TransactionTest {
|
|||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16"));
|
||||
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
||||
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.ANYONECANPAY_NONE);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a0882"), true, true);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a0882"), true);
|
||||
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||
}
|
||||
|
||||
|
@ -127,7 +127,7 @@ public class TransactionTest {
|
|||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b"));
|
||||
Script script = new Script(Utils.hexToBytes("56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"));
|
||||
Sha256Hash hash = transaction.hashForWitnessSignature(0, script,987654321L, SigHash.ANYONECANPAY_SINGLE);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("30440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783"), true, true);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("30440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783"), true);
|
||||
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||
}
|
||||
|
||||
|
@ -340,7 +340,7 @@ public class TransactionTest {
|
|||
TransactionInput input0 = spendingTransaction.getInputs().get(0);
|
||||
Script spendingScript = input0.getScriptSig();
|
||||
TransactionWitness witness0 = input0.getWitness();
|
||||
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(0), false, false);
|
||||
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(0), false);
|
||||
ECKey pubKey0 = ECKey.fromPublicOnly(witness0.getPushes().get(1));
|
||||
|
||||
Transaction transaction = new Transaction();
|
||||
|
@ -375,8 +375,8 @@ public class TransactionTest {
|
|||
|
||||
TransactionInput input0 = spendingTransaction.getInputs().get(0);
|
||||
TransactionWitness witness0 = input0.getWitness();
|
||||
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(1), false, false);
|
||||
TransactionSignature signature1 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(2), false, false);
|
||||
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(1), false);
|
||||
TransactionSignature signature1 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(2), false);
|
||||
Script witnessScript = new Script(witness0.getPushes().get(3));
|
||||
ECKey key0 = witnessScript.getChunks().get(1).getPubKey();
|
||||
ECKey key1 = witnessScript.getChunks().get(2).getPubKey();
|
||||
|
@ -417,7 +417,7 @@ public class TransactionTest {
|
|||
|
||||
TransactionInput input0 = spendingTransaction.getInputs().get(0);
|
||||
TransactionWitness witness0 = input0.getWitness();
|
||||
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(0), false, false);
|
||||
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(0), false);
|
||||
ECKey key0 = ECKey.fromPublicOnly(witness0.getPushes().get(1));
|
||||
|
||||
Transaction transaction = new Transaction();
|
||||
|
@ -451,8 +451,8 @@ public class TransactionTest {
|
|||
|
||||
TransactionInput input0 = spendingTransaction.getInputs().get(0);
|
||||
TransactionWitness witness0 = input0.getWitness();
|
||||
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(1), false, false);
|
||||
TransactionSignature signature1 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(2), false, false);
|
||||
TransactionSignature signature0 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(1), false);
|
||||
TransactionSignature signature1 = TransactionSignature.decodeFromBitcoin(witness0.getPushes().get(2), false);
|
||||
Script witnessScript = new Script(witness0.getPushes().get(3));
|
||||
ECKey key0 = witnessScript.getChunks().get(1).getPubKey();
|
||||
ECKey key1 = witnessScript.getChunks().get(2).getPubKey();
|
||||
|
|
Loading…
Reference in a new issue