Skip to content

Instantly share code, notes, and snippets.

@devrandom
Created February 24, 2015 22:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save devrandom/bed9f51ae971a438e4ac to your computer and use it in GitHub Desktop.
Save devrandom/bed9f51ae971a438e4ac to your computer and use it in GitHub Desktop.
One transactions, two wallets
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