-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added LOW_S script flag test case generator
- Loading branch information
1 parent
344c4c3
commit 92df23c
Showing
1 changed file
with
170 additions
and
0 deletions.
There are no files selected for viewing
170 changes: 170 additions & 0 deletions
170
examples/src/main/java/org/bitcoinj/examples/GenerateLowSTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
/* | ||
* Copyright 2015 Ross Nicoll. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.bitcoinj.examples; | ||
|
||
import java.io.IOException; | ||
import java.math.BigInteger; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.security.SecureRandom; | ||
import java.util.EnumSet; | ||
|
||
import static com.google.common.base.Preconditions.checkNotNull; | ||
|
||
import org.bitcoinj.core.Coin; | ||
import org.bitcoinj.core.ECKey; | ||
import org.bitcoinj.core.NetworkParameters; | ||
import org.bitcoinj.core.ScriptException; | ||
import org.bitcoinj.core.Transaction; | ||
import org.bitcoinj.core.TransactionInput; | ||
import org.bitcoinj.core.TransactionOutput; | ||
import org.bitcoinj.core.Utils; | ||
import org.bitcoinj.crypto.TransactionSignature; | ||
import org.bitcoinj.params.MainNetParams; | ||
import org.bitcoinj.script.Script; | ||
import org.bitcoinj.script.ScriptBuilder; | ||
import org.bitcoinj.script.ScriptChunk; | ||
import static org.bitcoinj.script.ScriptOpCodes.getOpCodeName; | ||
import org.bitcoinj.signers.LocalTransactionSigner; | ||
import org.bitcoinj.signers.TransactionSigner.ProposedTransaction; | ||
import org.bitcoinj.wallet.KeyBag; | ||
import org.bitcoinj.wallet.RedeemData; | ||
|
||
/** | ||
* Test case generator for transactions with low-S and high-S signatures, to | ||
* test the LOW_S script validation flag. | ||
* | ||
* @author Ross Nicoll | ||
*/ | ||
public class GenerateLowSTests { | ||
public static final BigInteger HIGH_S_DIFFERENCE = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16); | ||
|
||
public static void main(final String[] argv) throws NoSuchAlgorithmException, IOException { | ||
final NetworkParameters params = new MainNetParams(); | ||
final LocalTransactionSigner signer = new LocalTransactionSigner(); | ||
final SecureRandom secureRandom = SecureRandom.getInstanceStrong(); | ||
final ECKey key = new ECKey(secureRandom); | ||
final KeyBag bag = new KeyBag() { | ||
@Override | ||
public ECKey findKeyFromPubHash(byte[] pubkeyHash) { | ||
return key; | ||
} | ||
|
||
@Override | ||
public ECKey findKeyFromPubKey(byte[] pubkey) { | ||
return key; | ||
} | ||
|
||
@Override | ||
public RedeemData findRedeemDataFromScriptHash(byte[] scriptHash) { | ||
return null; | ||
} | ||
|
||
}; | ||
|
||
// Generate a fictional output transaction we take values from, and | ||
// an input transaction for the test case | ||
|
||
final Transaction outputTransaction = new Transaction(params); | ||
final Transaction inputTransaction = new Transaction(params); | ||
final TransactionOutput output = new TransactionOutput(params, inputTransaction, Coin.ZERO, key.toAddress(params)); | ||
|
||
inputTransaction.addOutput(output); | ||
outputTransaction.addInput(output); | ||
outputTransaction.addOutput(Coin.ZERO, new ECKey(secureRandom).toAddress(params)); | ||
|
||
addOutputs(outputTransaction, bag); | ||
|
||
// Sign the transaction | ||
final ProposedTransaction proposedTransaction = new ProposedTransaction(outputTransaction); | ||
signer.signInputs(proposedTransaction, bag); | ||
final TransactionInput input = proposedTransaction.partialTx.getInput(0); | ||
|
||
input.verify(output); | ||
input.getScriptSig().correctlySpends(outputTransaction, 0, output.getScriptPubKey(), | ||
EnumSet.of(Script.VerifyFlag.DERSIG, Script.VerifyFlag.P2SH)); | ||
|
||
final Script scriptSig = input.getScriptSig(); | ||
final TransactionSignature signature = TransactionSignature.decodeFromBitcoin(scriptSig.getChunks().get(0).data, true, false); | ||
|
||
// First output a conventional low-S transaction with the LOW_S flag, for the tx_valid.json set | ||
System.out.println("[\"A transaction with a low-S signature.\"],"); | ||
System.out.println("[[[\"" | ||
+ inputTransaction.getHashAsString() + "\", " | ||
+ output.getIndex() + ", \"" | ||
+ scriptToString(output.getScriptPubKey()) + "\"]],\n" | ||
+ "\"" + Utils.HEX.encode(proposedTransaction.partialTx.bitcoinSerialize()) + "\", \"" | ||
+ Script.VerifyFlag.P2SH.name() + "," + Script.VerifyFlag.LOW_S.name() + "\"],"); | ||
|
||
final BigInteger highS = HIGH_S_DIFFERENCE.subtract(signature.s); | ||
final TransactionSignature highSig = new TransactionSignature(signature.r, highS); | ||
input.setScriptSig(new ScriptBuilder().data(highSig.encodeToBitcoin()).data(scriptSig.getChunks().get(1).data).build()); | ||
input.getScriptSig().correctlySpends(outputTransaction, 0, output.getScriptPubKey(), | ||
EnumSet.of(Script.VerifyFlag.P2SH)); | ||
|
||
// A high-S transaction without the LOW_S flag, for the tx_valid.json set | ||
System.out.println("[\"A transaction with a high-S signature.\"],"); | ||
System.out.println("[[[\"" | ||
+ inputTransaction.getHashAsString() + "\", " | ||
+ output.getIndex() + ", \"" | ||
+ scriptToString(output.getScriptPubKey()) + "\"]],\n" | ||
+ "\"" + Utils.HEX.encode(proposedTransaction.partialTx.bitcoinSerialize()) + "\", \"" | ||
+ Script.VerifyFlag.P2SH.name() + "\"],"); | ||
|
||
// Lastly a conventional high-S transaction with the LOW_S flag, for the tx_invalid.json set | ||
System.out.println("[\"A transaction with a high-S signature.\"],"); | ||
System.out.println("[[[\"" | ||
+ inputTransaction.getHashAsString() + "\", " | ||
+ output.getIndex() + ", \"" | ||
+ scriptToString(output.getScriptPubKey()) + "\"]],\n" | ||
+ "\"" + Utils.HEX.encode(proposedTransaction.partialTx.bitcoinSerialize()) + "\", \"" | ||
+ Script.VerifyFlag.P2SH.name() + "," + Script.VerifyFlag.LOW_S.name() + "\"],"); | ||
} | ||
|
||
private static void addOutputs(final Transaction outputTransaction, final KeyBag bag) throws ScriptException { | ||
int numInputs = outputTransaction.getInputs().size(); | ||
for (int i = 0; i < numInputs; i++) { | ||
TransactionInput txIn = outputTransaction.getInput(i); | ||
Script scriptPubKey = txIn.getConnectedOutput().getScriptPubKey(); | ||
RedeemData redeemData = txIn.getConnectedRedeemData(bag); | ||
checkNotNull(redeemData, "Transaction exists in wallet that we cannot redeem: %s", txIn.getOutpoint().getHash()); | ||
txIn.setScriptSig(scriptPubKey.createEmptyInputScript(redeemData.keys.get(0), redeemData.redeemScript)); | ||
} | ||
} | ||
|
||
/** | ||
* Convert a script to a string format that suits the style expected in | ||
* tx_valid.json and tx_invalid.json. | ||
*/ | ||
private static String scriptToString(Script scriptPubKey) { | ||
final StringBuilder buf = new StringBuilder(); | ||
for (ScriptChunk chunk: scriptPubKey.getChunks()) { | ||
if (buf.length() > 0) { | ||
buf.append(" "); | ||
} | ||
if (chunk.isOpCode()) { | ||
buf.append(getOpCodeName(chunk.opcode)); | ||
} else if (chunk.data != null) { | ||
// Data chunk | ||
buf.append("0x") | ||
.append(Integer.toString(chunk.opcode, 16)).append(" 0x") | ||
.append(Utils.HEX.encode(chunk.data)); | ||
} else { | ||
buf.append(chunk.toString()); | ||
} | ||
} | ||
return buf.toString(); | ||
} | ||
} |