3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-11 17:55:53 +00:00

Merge remote-tracking branch 'upstream/master' into rebase

Conflicts:
	core/src/main/java/com/dogecoin/dogecoinj/wallet/DefaultRiskAnalysis.java
	core/src/test/java/com/dogecoin/dogecoinj/core/ECKeyTest.java
	core/src/test/java/com/dogecoin/dogecoinj/wallet/DefaultRiskAnalysisTest.java
	tools/src/main/java/com/dogecoin/dogecoinj/tools/WatchMempool.java
This commit is contained in:
langerhans 2015-01-04 20:33:24 +01:00
commit e6fc3deb50
12 changed files with 159 additions and 34 deletions

View File

@ -112,8 +112,8 @@ public class ECKey implements EncryptableItem, Serializable {
}
};
/** The parameters of the secp256k1 curve that Bitcoin uses. */
public static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
// The parameters of the secp256k1 curve that Bitcoin uses.
private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
/** The parameters of the secp256k1 curve that Bitcoin uses. */
public static final ECDomainParameters CURVE;
@ -495,6 +495,14 @@ public class ECKey implements EncryptableItem, Serializable {
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
@ -503,7 +511,7 @@ public class ECKey implements EncryptableItem, Serializable {
* considered legal and the other will be banned.
*/
public ECDSASignature toCanonicalised() {
if (s.compareTo(HALF_CURVE_ORDER) > 0) {
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
@ -1204,6 +1212,8 @@ public class ECKey implements EncryptableItem, Serializable {
builder.append(address.toString());
builder.append(" hash160:");
builder.append(Utils.HEX.encode(getPubKeyHash()));
if (creationTimeSeconds > 0)
builder.append(" creationTimeSeconds:").append(creationTimeSeconds);
builder.append("\n");
if (includePrivateKeys) {
builder.append(" ");

View File

@ -103,7 +103,7 @@ public class TransactionBroadcast {
List<Peer> peers = peerGroup.getConnectedPeers(); // snapshots
// We intern the tx here so we are using a canonical version of the object (as it's unfortunately mutable).
// TODO: Once confidence state is moved out of Transaction we can kill off this step.
pinnedTx = context != null ? context.getConfidenceTable().intern(tx) : pinnedTx;
pinnedTx = context != null ? context.getConfidenceTable().intern(tx) : tx;
// Prepare to send the transaction by adding a listener that'll be called when confidence changes.
// Only bother with this if we might actually hear back:
if (minConnections > 1)

View File

@ -60,7 +60,7 @@ public class TransactionInput extends ChildMessage implements Serializable {
transient private WeakReference<Script> scriptSig;
/** Value of the output connected to the input, if known. This field does not participate in equals()/hashCode(). */
@Nullable
private final Coin value;
private Coin value;
/**
* Creates an input that connects to nothing - used only in creation of coinbase transactions.
@ -375,6 +375,7 @@ public class TransactionInput extends ChildMessage implements Serializable {
public void connect(TransactionOutput out) {
outpoint.fromTx = out.getParentTransaction();
out.markAsSpent(this);
value = out.getValue();
}
/**

View File

@ -2477,6 +2477,25 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
}
/**
* Prepares the wallet for a blockchain replay. Removes all transactions (as they would get in the way of the
* replay) and makes the wallet think it has never seen a block. {@link WalletEventListener#onWalletChanged()} will
* be fired.
*/
public void reset() {
lock.lock();
try {
clearTransactions();
lastBlockSeenHash = null;
lastBlockSeenHeight = -1; // Magic value for 'never'.
lastBlockSeenTimeSecs = 0;
saveLater();
maybeQueueOnWalletChanged();
} finally {
lock.unlock();
}
}
/**
* Deletes transactions which appeared above the given block height from the wallet, but does not touch the keys.
* This is useful if you have some keys and wish to replay the block chain into the wallet in order to pick them up.
@ -2486,11 +2505,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
lock.lock();
try {
if (fromHeight == 0) {
unspent.clear();
spent.clear();
pending.clear();
dead.clear();
transactions.clear();
clearTransactions();
saveLater();
} else {
throw new UnsupportedOperationException();
@ -2500,6 +2515,14 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
}
private void clearTransactions() {
unspent.clear();
spent.clear();
pending.clear();
dead.clear();
transactions.clear();
}
/**
* Returns all the outputs that match addresses or scripts added via {@link #addWatchedAddress(Address)} or
* {@link #addWatchedScripts(java.util.List)}.

View File

@ -107,7 +107,8 @@ public interface WalletEventListener extends KeyChainEventListener {
* <li>A pending transaction changes confidence due to some non-new-block related event, such as being
* announced by more peers or by a double-spend conflict being observed.</li>
* <li>A re-organize occurs. Call occurs only if the re-org modified any of our transactions.</li>
* <li>A new spend is committed to the wallet</li>
* <li>A new spend is committed to the wallet.</li>
* <li>The wallet is reset and all transactions removed.<li>
* </ol>
*
* <p>When this is called you can refresh the UI contents from the wallet contents. It's more efficient to use

View File

@ -153,7 +153,7 @@ public class BIP38PrivateKey extends VersionedChecksummedBytes {
BigInteger seedFactor = new BigInteger(1, Sha256Hash.createDouble(seed).getBytes());
checkState(passFactor.signum() >= 0);
checkState(seedFactor.signum() >= 0);
BigInteger priv = passFactor.multiply(seedFactor).mod(ECKey.CURVE_PARAMS.getN());
BigInteger priv = passFactor.multiply(seedFactor).mod(ECKey.CURVE.getN());
return ECKey.fromPrivate(priv, compressed);
} catch (GeneralSecurityException x) {

View File

@ -18,12 +18,15 @@
package com.dogecoin.dogecoinj.wallet;
import com.dogecoin.dogecoinj.core.Coin;
import com.dogecoin.dogecoinj.core.ECKey;
import com.dogecoin.dogecoinj.core.ECKey.ECDSASignature;
import com.dogecoin.dogecoinj.core.NetworkParameters;
import com.dogecoin.dogecoinj.core.Transaction;
import com.dogecoin.dogecoinj.core.TransactionConfidence;
import com.dogecoin.dogecoinj.core.TransactionInput;
import com.dogecoin.dogecoinj.core.TransactionOutput;
import com.dogecoin.dogecoinj.core.Wallet;
import com.dogecoin.dogecoinj.crypto.TransactionSignature;
import com.dogecoin.dogecoinj.script.ScriptChunk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -51,7 +54,7 @@ public class DefaultRiskAnalysis implements RiskAnalysis {
protected final Transaction tx;
protected final List<Transaction> dependencies;
protected final Wallet wallet;
protected final @Nullable Wallet wallet;
private Transaction nonStandard;
protected Transaction nonFinal;
@ -69,17 +72,20 @@ public class DefaultRiskAnalysis implements RiskAnalysis {
analyzed = true;
Result result = analyzeIsFinal();
if (result != Result.OK)
if (result != null && result != Result.OK)
return result;
return analyzeIsStandard();
}
private Result analyzeIsFinal() {
private @Nullable Result analyzeIsFinal() {
// Transactions we create ourselves are, by definition, not at risk of double spending against us.
if (tx.getConfidence().getSource() == TransactionConfidence.Source.SELF)
return Result.OK;
if (wallet == null)
return null;
final int height = wallet.getLastBlockSeenHeight();
final long time = wallet.getLastBlockSeenTimeSecs();
// If the transaction has a lock time specified in blocks, we consider that if the tx would become final in the
@ -108,7 +114,8 @@ public class DefaultRiskAnalysis implements RiskAnalysis {
VERSION,
DUST,
SHORTEST_POSSIBLE_PUSHDATA,
NONEMPTY_STACK // Not yet implemented (for post 0.12)
NONEMPTY_STACK, // Not yet implemented (for post 0.12)
SIGNATURE_CANONICAL_ENCODING
}
/**
@ -165,6 +172,19 @@ public class DefaultRiskAnalysis implements RiskAnalysis {
for (ScriptChunk chunk : input.getScriptSig().getChunks()) {
if (chunk.data != null && !chunk.isShortestPossiblePushData())
return RuleViolation.SHORTEST_POSSIBLE_PUSHDATA;
if (chunk.isPushData()) {
ECDSASignature signature;
try {
signature = ECKey.ECDSASignature.decodeFromDER(chunk.data);
} catch (RuntimeException x) {
// Doesn't look like a signature.
signature = null;
}
if (signature != null) {
if (!TransactionSignature.isEncodingCanonical(chunk.data))
return RuleViolation.SIGNATURE_CANONICAL_ENCODING;
}
}
}
return RuleViolation.NONE;
}
@ -172,7 +192,7 @@ public class DefaultRiskAnalysis implements RiskAnalysis {
private Result analyzeIsStandard() {
// The IsStandard rules don't apply on testnet, because they're just a safety mechanism and we don't want to
// crush innovation with valueless test coins.
if (!wallet.getNetworkParameters().getId().equals(NetworkParameters.ID_MAINNET))
if (wallet != null && !wallet.getNetworkParameters().getId().equals(NetworkParameters.ID_MAINNET))
return Result.OK;
RuleViolation ruleViolation = isStandard(tx);

View File

@ -244,6 +244,8 @@ public class MarriedKeyChain extends DeterministicKeyChain {
builder.append(script.getToAddress(params));
builder.append(" hash160:");
builder.append(Utils.HEX.encode(script.getPubKeyHash()));
if (script.getCreationTimeSeconds() > 0)
builder.append(" creationTimeSeconds:").append(script.getCreationTimeSeconds());
builder.append("\n");
}

View File

@ -17,6 +17,7 @@
package com.dogecoin.dogecoinj.core;
import com.dogecoin.dogecoinj.core.ECKey.ECDSASignature;
import com.dogecoin.dogecoinj.crypto.EncryptedData;
import com.dogecoin.dogecoinj.crypto.KeyCrypter;
import com.dogecoin.dogecoinj.crypto.KeyCrypterScrypt;
@ -96,11 +97,15 @@ public class ECKeyTest {
}
List<ECKey.ECDSASignature> sigs = Futures.allAsList(sigFutures).get();
for (ECKey.ECDSASignature signature : sigs) {
assertTrue(signature.s.compareTo(ECKey.HALF_CURVE_ORDER) <= 0);
assertTrue(signature.isCanonical());
}
final ECKey.ECDSASignature duplicate = new ECKey.ECDSASignature(sigs.get(0).r, sigs.get(0).s);
assertEquals(sigs.get(0), duplicate);
assertEquals(sigs.get(0).hashCode(), duplicate.hashCode());
final ECDSASignature first = sigs.get(0);
final ECKey.ECDSASignature duplicate = new ECKey.ECDSASignature(first.r, first.s);
assertEquals(first, duplicate);
assertEquals(first.hashCode(), duplicate.hashCode());
final ECKey.ECDSASignature highS = new ECKey.ECDSASignature(first.r, ECKey.CURVE.getN().subtract(first.s));
assertFalse(highS.isCanonical());
}
@Test

View File

@ -17,12 +17,15 @@
package com.dogecoin.dogecoinj.wallet;
import com.google.common.collect.Lists;
import java.util.Arrays;
import com.dogecoin.dogecoinj.core.*;
import com.dogecoin.dogecoinj.crypto.TransactionSignature;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.script.Script;
import com.dogecoin.dogecoinj.script.ScriptBuilder;
import com.dogecoin.dogecoinj.script.ScriptChunk;
import com.google.common.collect.ImmutableList;
import com.dogecoin.dogecoinj.wallet.DefaultRiskAnalysis.RuleViolation;
import org.junit.Before;
import org.junit.Test;
@ -160,6 +163,22 @@ public class DefaultRiskAnalysisTest {
assertEquals(DefaultRiskAnalysis.RuleViolation.SHORTEST_POSSIBLE_PUSHDATA, DefaultRiskAnalysis.isStandard(tx));
}
@Test
public void canonicalSignature() {
TransactionSignature sig = TransactionSignature.dummy();
Script scriptOk = ScriptBuilder.createInputScript(sig);
assertEquals(RuleViolation.NONE,
DefaultRiskAnalysis.isInputStandard(new TransactionInput(params, null, scriptOk.getProgram())));
byte[] sigBytes = sig.encodeToBitcoin();
// Appending a zero byte makes the signature uncanonical without violating DER encoding.
Script scriptUncanonicalEncoding = new ScriptBuilder().data(Arrays.copyOf(sigBytes, sigBytes.length + 1))
.build();
assertEquals(RuleViolation.SIGNATURE_CANONICAL_ENCODING,
DefaultRiskAnalysis.isInputStandard(new TransactionInput(params, null, scriptUncanonicalEncoding
.getProgram())));
}
@Test
public void standardOutputs() throws Exception {
Transaction tx = new Transaction(params);

View File

@ -17,35 +17,73 @@
package com.dogecoin.dogecoinj.tools;
import java.util.HashMap;
import java.util.Map;
import com.dogecoin.dogecoinj.core.AbstractPeerEventListener;
import com.dogecoin.dogecoinj.core.NetworkParameters;
import com.dogecoin.dogecoinj.core.*;
import com.dogecoin.dogecoinj.core.PeerGroup;
import com.dogecoin.dogecoinj.core.Transaction;
import com.dogecoin.dogecoinj.net.discovery.DnsDiscovery;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.utils.BriefLogFormatter;
import com.dogecoin.dogecoinj.wallet.DefaultRiskAnalysis;
import com.dogecoin.dogecoinj.wallet.RiskAnalysis.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableList;
public class WatchMempool {
private static Logger log = LoggerFactory.getLogger(WatchMempool.class);
private static final NetworkParameters PARAMS = MainNetParams.get();
private static final ImmutableList<Transaction> NO_DEPS = ImmutableList.of();
private static final Map<String, Integer> counters = new HashMap<String, Integer>();
private static final String TOTAL_KEY = "TOTAL";
private static final long START_MS = System.currentTimeMillis();
private static final long STATISTICS_FREQUENCY_MS = 1000 * 5;
public static void main(String[] args) throws InterruptedException {
BriefLogFormatter.init();
NetworkParameters params = MainNetParams.get();
PeerGroup peerGroup = new PeerGroup(params);
peerGroup.addPeerDiscovery(new DnsDiscovery(params));
PeerGroup peerGroup = new PeerGroup(PARAMS);
peerGroup.setMaxConnections(32);
peerGroup.addPeerDiscovery(new DnsDiscovery(PARAMS));
peerGroup.addEventListener(new AbstractPeerEventListener() {
@Override
public void onTransaction(Peer peer, Transaction tx) {
try {
log.info("tx {}", tx.getHash());
if (tx.getOutputs().size() != 1) return;
if (!tx.getOutput(0).getScriptPubKey().isSentToRawPubKey()) return;
log.info("Saw raw pay to pubkey {}", tx);
} catch (ScriptException e) {
e.printStackTrace();
}
Result result = DefaultRiskAnalysis.FACTORY.create(null, tx, NO_DEPS).analyze();
incrementCounter(TOTAL_KEY);
log.info("tx {} result {}", tx.getHash(), result);
incrementCounter(result.name());
if (result == Result.NON_STANDARD)
incrementCounter(Result.NON_STANDARD + "-" + DefaultRiskAnalysis.isStandard(tx));
}
});
peerGroup.start();
Thread.sleep(Long.MAX_VALUE);
while (true) {
Thread.sleep(STATISTICS_FREQUENCY_MS);
printCounters();
}
}
private synchronized static void incrementCounter(String name) {
Integer count = counters.get(name);
if (count == null)
count = 0;
count++;
counters.put(name, count);
}
private synchronized static void printCounters() {
System.out.printf("Runtime: %d minutes\n", (System.currentTimeMillis() - START_MS) / 1000 / 60);
Integer total = counters.get(TOTAL_KEY);
if (total == null)
return;
for (Map.Entry<String, Integer> counter : counters.entrySet()) {
System.out.printf(" %-40s%6d (%d%% of total)\n", counter.getKey(), counter.getValue(),
(int) counter.getValue() * 100 / total);
}
}
}

View File

@ -40,6 +40,12 @@
</filters>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>bundled</shadedClassifierName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>${artifactId}.Main</mainClass>
</transformer>
</transformers>
<outputFile>target/${artifactId}-shaded.jar</outputFile>
</configuration>
</execution>
</executions>