Created
February 24, 2015 22:58
-
-
Save devrandom/bed9f51ae971a438e4ac to your computer and use it in GitHub Desktop.
One transactions, two wallets
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
import org.junit.Before | |
import org.junit.Test | |
import kotlin.test.fail | |
import org.bitcoinj.core.Transaction | |
import org.bitcoinj.params.MainNetParams | |
import org.bitcoinj.core.NetworkParameters | |
import org.bitcoinj.core.ECKey | |
import org.bitcoinj.core.Address | |
import org.bitcoinj.testing.FakeTxBuilder | |
import org.bitcoinj.core.Coin | |
import org.bitcoinj.core.Wallet | |
import org.bitcoinj.core.AbstractBlockChain | |
import org.bitcoinj.core.VerificationException | |
import org.bitcoinj.store.MemoryBlockStore | |
import org.bitcoinj.params.RegTestParams | |
import org.bitcoinj.core.TransactionInput | |
import org.bitcoinj.core.ScriptException | |
import org.bitcoinj.script.Script | |
import org.bitcoinj.wallet.RedeemData | |
import com.google.common.base.Preconditions | |
import org.bitcoinj.signers.TransactionSigner | |
import org.bitcoinj.core.ECKey | |
import org.bitcoinj.core.ScriptException | |
import org.bitcoinj.core.Transaction | |
import org.bitcoinj.core.TransactionInput | |
import org.bitcoinj.crypto.DeterministicKey | |
import org.bitcoinj.crypto.TransactionSignature | |
import org.bitcoinj.script.Script | |
import org.bitcoinj.wallet.KeyBag | |
import org.bitcoinj.wallet.RedeemData | |
import org.slf4j.Logger | |
import org.slf4j.LoggerFactory | |
import org.bitcoinj.signers.StatelessTransactionSigner | |
import org.bitcoinj.core.Utils | |
/** | |
* Created by devrandom on 2015-02-24. | |
*/ | |
private val log = LoggerFactory.getLogger("test_atomic") | |
public class LenientTransactionSigner : StatelessTransactionSigner() { | |
override fun isReady(): Boolean { | |
return true | |
} | |
override fun signInputs(propTx: TransactionSigner.ProposedTransaction, keyBag: KeyBag): Boolean { | |
val tx = propTx.partialTx | |
val numInputs = tx.getInputs().size() | |
for (i in 0..numInputs - 1) { | |
val txIn = tx.getInput(i.toLong()) | |
if (txIn.getConnectedOutput() == null) { | |
log.warn("Missing connected output, assuming input {} is already signed.", i) | |
continue | |
} | |
try { | |
// We assume if its already signed, its hopefully got a SIGHASH type that will not invalidate when | |
// we sign missing pieces (to check this would require either assuming any signatures are signing | |
// standard output types or a way to get processed signatures out of script execution) | |
txIn.getScriptSig().correctlySpends(tx, i.toLong(), txIn.getConnectedOutput()!!.getScriptPubKey()) | |
log.warn("Input {} already correctly spends output, assuming SIGHASH type used will be safe and skipping signing.", i) | |
continue | |
} catch (e: ScriptException) { | |
// Expected. | |
} | |
val redeemData = txIn.getConnectedRedeemData(keyBag) | |
if (redeemData == null) | |
continue | |
val scriptPubKey = txIn.getConnectedOutput()!!.getScriptPubKey() | |
// For P2SH inputs we need to share derivation path of the signing key with other signers, so that they | |
// use correct key to calculate their signatures. | |
// Married keys all have the same derivation path, so we can safely just take first one here. | |
val pubKey = redeemData.keys.get(0) | |
if (pubKey is DeterministicKey) | |
propTx.keyPaths.put(scriptPubKey, ((pubKey as DeterministicKey).getPath())) | |
val key: ECKey | |
// locate private key in redeem data. For pay-to-address and pay-to-key inputs RedeemData will always contain | |
// only one key (with private bytes). For P2SH inputs RedeemData will contain multiple keys, one of which MAY | |
// have private bytes | |
key = redeemData.getFullKey() | |
if (key == null) { | |
log.warn("No local key found for input {}", i) | |
continue | |
} | |
var inputScript = txIn.getScriptSig() | |
// script here would be either a standard CHECKSIG program for pay-to-address or pay-to-pubkey inputs or | |
// a CHECKMULTISIG program for P2SH inputs | |
val script = redeemData.redeemScript.getProgram() | |
try { | |
val signature = tx.calculateSignature(i, key, script, Transaction.SigHash.ALL, false) | |
// at this point we have incomplete inputScript with OP_0 in place of one or more signatures. We already | |
// have calculated the signature using the local key and now need to insert it in the correct place | |
// within inputScript. For pay-to-address and pay-to-key script there is only one signature and it always | |
// goes first in an inputScript (sigIndex = 0). In P2SH input scripts we need to figure out our relative | |
// position relative to other signers. Since we don't have that information at this point, and since | |
// we always run first, we have to depend on the other signers rearranging the signatures as needed. | |
// Therefore, always place as first signature. | |
val sigIndex = 0 | |
inputScript = scriptPubKey.getScriptSigWithSignature(inputScript, signature.encodeToBitcoin(), sigIndex) | |
txIn.setScriptSig(inputScript) | |
} catch (e: ECKey.KeyIsEncryptedException) { | |
throw e | |
} catch (e: ECKey.MissingPrivateKeyException) { | |
log.warn("No private key in keypair for input {}", i) | |
} | |
} | |
return true | |
} | |
} | |
class AtomicSwapTest() { | |
var wallet1: Wallet? = null | |
var wallet2: Wallet? = null | |
val params: NetworkParameters = RegTestParams.get() | |
val blockStore = MemoryBlockStore(params) | |
Before fun setUp() { | |
wallet1 = Wallet(params) | |
wallet2 = Wallet(params) | |
sendMoneyToWallet(wallet1!!, FakeTxBuilder.createFakeTx(params, Coin.FIFTY_COINS, wallet1!!.currentReceiveAddress())) | |
sendMoneyToWallet(wallet2!!, FakeTxBuilder.createFakeTx(params, Coin.FIFTY_COINS, wallet2!!.currentReceiveAddress())) | |
} | |
fun sendMoneyToWallet(wallet: Wallet, tx: Transaction){ | |
var bp = FakeTxBuilder.createFakeBlock(blockStore, tx); | |
wallet.receiveFromBlock(tx, bp.storedBlock, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0); | |
wallet.notifyNewBestBlock(bp.storedBlock); | |
} | |
Test fun testAtomic() { | |
val s1 = ECKey() | |
val s2 = ECKey() | |
val destKey = ECKey() | |
val dest = destKey.toAddress(params) | |
val req1 = Wallet.SendRequest.to(dest, Coin.CENT) | |
val req2 = Wallet.SendRequest.to(dest, Coin.COIN) | |
req1.signInputs = false | |
req2.signInputs = false | |
wallet1!!.completeTx(req1) | |
wallet2!!.completeTx(req2) | |
val tx = Transaction(params) | |
req1.tx.getInputs().forEach { tx.addInput(it) } | |
req2.tx.getInputs().forEach { tx.addInput(it) } | |
req1.tx.getOutputs().forEach { tx.addOutput(it) } | |
req2.tx.getOutputs().forEach { tx.addOutput(it) } | |
val req = Wallet.SendRequest.forTx(tx) | |
req.signInputs = false | |
wallet2!!.completeTx(req) | |
val numInputs = tx.getInputs().size() | |
for (i in 0L..numInputs - 1) { | |
val wallet = if (i < req1.tx.getInputs().size()) wallet1!! else wallet2!! | |
val txIn = tx.getInput(i) | |
if (txIn.getConnectedOutput() == null) { | |
// Missing connected output, assuming already signed. | |
continue | |
} | |
val scriptPubKey = txIn.getConnectedOutput()!!.getScriptPubKey() | |
val redeemData = txIn.getConnectedRedeemData(wallet)!! | |
txIn.setScriptSig(scriptPubKey.createEmptyInputScript(redeemData.keys.get(0), redeemData.redeemScript)) | |
} | |
val proposal = TransactionSigner.ProposedTransaction(tx) | |
LenientTransactionSigner().signInputs(proposal, wallet1!!) | |
LenientTransactionSigner().signInputs(proposal, wallet2!!) | |
tx.verify() | |
wallet1!!.commitTx(tx) | |
wallet2!!.commitTx(tx) | |
println(Utils.HEX.encode(tx.bitcoinSerialize())) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment