Skip to content

Commit

Permalink
Merge pull request #133 from h4sh3d/modify-segwit0-and-extract-witness
Browse files Browse the repository at this point in the history
Modify segwit0 and extract witness
  • Loading branch information
zkao authored Sep 20, 2021
2 parents 60b4e32 + c7a680e commit a99cebd
Show file tree
Hide file tree
Showing 16 changed files with 777 additions and 353 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:

- uses: Swatinem/rust-cache@v1.3.0

- run: cargo test --verbose
- run: cargo test --features serde --verbose

rpc-test:
strategy:
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ bitcoincore-rpc = "0.13.0"
rand_core = { version = "^0.6.3", features = ["getrandom"] }
secp256k1 = { version = "0.20.1", features = ["rand-std"] }
lazy_static = "1.4.0"
serde_yaml = "0.8"

[package.metadata.docs.rs]
all-features = true
Expand Down
34 changes: 11 additions & 23 deletions contrib/run-rpc-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,18 @@

cd ..

docker volume create --name bitcoind-data

ID=$(docker create -p 18443:18443\
--name bitcoind\
--env NETWORK=regtest\
--env FALLBACKFEE=0.00001\
--env RPC_PORT=18443\
-v bitcoind-data:/data\
ghcr.io/farcaster-project/containers/bitcoin-core:latest)

docker start $ID

docker run --rm\
--link bitcoind\
--volumes-from bitcoind\
-v "$PWD":/usr/src/myapp\
-w /usr/src/myapp\
--env RPC_HOST=bitcoind\
rust:1.54.0\
cargo test --test transactions --features rpc -- --test-threads=1
ID=$(docker run --rm -d -p 18443:18443 coblox/bitcoin-core\
-regtest\
-server\
-fallbackfee=0.00001\
-rpcbind=0.0.0.0\
-rpcallowip=0.0.0.0/0\
-rpcuser=test\
-rpcpassword=cEl2o3tHHgzYeuu3CiiZ2FjdgSiw9wNeMFzoNbFmx9k=)

export CI=false RPC_USER=test RPC_PASS=cEl2o3tHHgzYeuu3CiiZ2FjdgSiw9wNeMFzoNbFmx9k=
cargo test --test transactions --features rpc -- --test-threads=1

docker kill $ID
docker container rm $ID

docker volume rm bitcoind-data

cd -
103 changes: 53 additions & 50 deletions src/bitcoin/segwitv0/buy.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use std::marker::PhantomData;

use bitcoin::blockdata::script::Instruction;
use bitcoin::blockdata::transaction::{SigHashType, TxIn, TxOut};
use bitcoin::util::key::PublicKey;
use bitcoin::secp256k1::Signature;
use bitcoin::util::psbt::PartiallySignedTransaction;
use bitcoin::Address;

use crate::role::SwapRole;
use crate::script;
use crate::transaction::{Buyable, Error as FError, Lockable};

use crate::bitcoin::segwitv0::SegwitV0;
use crate::bitcoin::segwitv0::{CoopLock, SegwitV0};
use crate::bitcoin::transaction::{Error, MetadataOutput, SubTransaction, Tx};
use crate::bitcoin::Bitcoin;

Expand All @@ -23,45 +23,21 @@ impl SubTransaction for Buy {
.clone()
.ok_or(FError::MissingWitness)?;

let mut keys = script.instructions().skip(2).take(2);

psbt.inputs[0].final_script_witness = Some(vec![
vec![], // 0 for multisig
psbt.inputs[0]
.partial_sigs
.get(
&PublicKey::from_slice(
keys.next()
.ok_or(FError::MissingPublicKey)?
.map(|i| match i {
Instruction::PushBytes(b) => Ok(b),
_ => Err(FError::MissingPublicKey),
})
.map_err(Error::from)??,
)
.map_err(|_| FError::MissingPublicKey)?,
)
.ok_or(FError::MissingSignature)?
.clone(),
psbt.inputs[0]
.partial_sigs
.get(
&PublicKey::from_slice(
keys.next()
.ok_or(FError::MissingPublicKey)?
.map(|i| match i {
Instruction::PushBytes(b) => Ok(b),
_ => Err(FError::MissingPublicKey),
})
.map_err(Error::from)??,
)
.map_err(|_| FError::MissingPublicKey)?,
)
.ok_or(FError::MissingSignature)?
.clone(),
vec![1], // OP_TRUE
script.into_bytes(), // swaplock script
]);
let swaplock = CoopLock::from_script(&script)?;

let alice_sig = psbt.inputs[0]
.partial_sigs
.get(swaplock.get_pubkey(SwapRole::Alice))
.ok_or(FError::MissingSignature)?
.clone();

let bob_sig = psbt.inputs[0]
.partial_sigs
.get(swaplock.get_pubkey(SwapRole::Bob))
.ok_or(FError::MissingSignature)?
.clone();

psbt.inputs[0].final_script_witness = Some(vec![bob_sig, alice_sig, script.into_bytes()]);

Ok(())
}
Expand All @@ -75,12 +51,12 @@ impl Buyable<Bitcoin<SegwitV0>, MetadataOutput> for Tx<Buy> {
) -> Result<Self, FError> {
let output_metadata = prev.get_consumable_output()?;

let unsigned_tx = bitcoin::blockdata::transaction::Transaction {
let unsigned_tx = bitcoin::Transaction {
version: 2,
lock_time: 0,
input: vec![TxIn {
previous_output: output_metadata.out_point,
script_sig: bitcoin::blockdata::script::Script::default(),
script_sig: bitcoin::Script::default(),
sequence: 0,
witness: vec![],
}],
Expand All @@ -104,12 +80,39 @@ impl Buyable<Bitcoin<SegwitV0>, MetadataOutput> for Tx<Buy> {
})
}

fn verify_template(
&self,
_lock: script::DataLock<Bitcoin<SegwitV0>>,
_destination_target: Address,
) -> Result<(), FError> {
// FIXME
fn verify_template(&self, destination_target: Address) -> Result<(), FError> {
(self.psbt.global.unsigned_tx.version == 2)
.then(|| 0)
.ok_or(FError::WrongTemplate("Tx version is not 2"))?;
(self.psbt.global.unsigned_tx.lock_time == 0)
.then(|| 0)
.ok_or(FError::WrongTemplate("LockTime is not set to 0"))?;
(self.psbt.global.unsigned_tx.input.len() == 1)
.then(|| 0)
.ok_or(FError::WrongTemplate("Number of inputs is not 1"))?;
(self.psbt.global.unsigned_tx.output.len() == 1)
.then(|| 0)
.ok_or(FError::WrongTemplate("Number of outputs is not 1"))?;

let txin = &self.psbt.global.unsigned_tx.input[0];
(txin.sequence == 0)
.then(|| 0)
.ok_or(FError::WrongTemplate("Sequence is not set to 0"))?;

let txout = &self.psbt.global.unsigned_tx.output[0];
let script_pubkey = destination_target.script_pubkey();
(txout.script_pubkey == script_pubkey)
.then(|| 0)
.ok_or(FError::WrongTemplate("Script pubkey does not match"))?;

Ok(())
}

fn extract_witness(tx: bitcoin::Transaction) -> Signature {
let TxIn { witness, .. } = &tx.input[0];
let bytes: &[u8] = witness[0].as_ref();
// Remove SIGHASH type at the end of the signature
Signature::from_der(&bytes[..bytes.len() - 1])
.expect("Validated transaction on-chain, signature and witness position is correct.")
}
}
117 changes: 48 additions & 69 deletions src/bitcoin/segwitv0/cancel.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
use std::marker::PhantomData;

use bitcoin::blockdata::opcodes;
use bitcoin::blockdata::script::Builder;
use bitcoin::blockdata::script::Instruction;
use bitcoin::blockdata::transaction::{SigHashType, TxIn, TxOut};
use bitcoin::util::key::PublicKey;
use bitcoin::util::psbt::PartiallySignedTransaction;

use crate::role::SwapRole;
use crate::script;
use crate::transaction::{Cancelable, Error as FError, Lockable};

use crate::bitcoin::segwitv0::SegwitV0;
use crate::bitcoin::segwitv0::{CoopLock, PunishLock, SegwitV0};
use crate::bitcoin::transaction::{Error, MetadataOutput, SubTransaction, Tx};
use crate::bitcoin::Bitcoin;

Expand All @@ -24,45 +21,21 @@ impl SubTransaction for Cancel {
.clone()
.ok_or(FError::MissingWitness)?;

let mut keys = script.instructions().skip(11).take(2);

psbt.inputs[0].final_script_witness = Some(vec![
vec![], // 0 for multisig
psbt.inputs[0]
.partial_sigs
.get(
&PublicKey::from_slice(
keys.next()
.ok_or(FError::MissingPublicKey)?
.map(|i| match i {
Instruction::PushBytes(b) => Ok(b),
_ => Err(FError::MissingPublicKey),
})
.map_err(Error::from)??,
)
.map_err(|_| FError::MissingPublicKey)?,
)
.ok_or(FError::MissingSignature)?
.clone(),
psbt.inputs[0]
.partial_sigs
.get(
&PublicKey::from_slice(
keys.next()
.ok_or(FError::MissingPublicKey)?
.map(|i| match i {
Instruction::PushBytes(b) => Ok(b),
_ => Err(FError::MissingPublicKey),
})
.map_err(Error::from)??,
)
.map_err(|_| FError::MissingPublicKey)?,
)
.ok_or(FError::MissingSignature)?
.clone(),
vec![], // OP_FALSE
script.into_bytes(), // swaplock script
]);
let swaplock = CoopLock::from_script(&script)?;

let alice_sig = psbt.inputs[0]
.partial_sigs
.get(swaplock.get_pubkey(SwapRole::Alice))
.ok_or(FError::MissingSignature)?
.clone();

let bob_sig = psbt.inputs[0]
.partial_sigs
.get(swaplock.get_pubkey(SwapRole::Bob))
.ok_or(FError::MissingSignature)?
.clone();

psbt.inputs[0].final_script_witness = Some(vec![bob_sig, alice_sig, script.into_bytes()]);

Ok(())
}
Expand All @@ -74,34 +47,15 @@ impl Cancelable<Bitcoin<SegwitV0>, MetadataOutput> for Tx<Cancel> {
lock: script::DataLock<Bitcoin<SegwitV0>>,
punish_lock: script::DataPunishableLock<Bitcoin<SegwitV0>>,
) -> Result<Self, FError> {
let script = Builder::new()
.push_opcode(opcodes::all::OP_IF)
.push_opcode(opcodes::all::OP_PUSHNUM_2)
.push_key(&bitcoin::util::ecdsa::PublicKey::new(
punish_lock.success.alice,
))
.push_key(&bitcoin::util::ecdsa::PublicKey::new(
punish_lock.success.bob,
))
.push_opcode(opcodes::all::OP_PUSHNUM_2)
.push_opcode(opcodes::all::OP_CHECKMULTISIG)
.push_opcode(opcodes::all::OP_ELSE)
.push_int(punish_lock.timelock.as_u32().into())
.push_opcode(opcodes::all::OP_CSV)
.push_opcode(opcodes::all::OP_DROP)
.push_key(&bitcoin::util::ecdsa::PublicKey::new(punish_lock.failure))
.push_opcode(opcodes::all::OP_CHECKSIG)
.push_opcode(opcodes::all::OP_ENDIF)
.into_script();

let script = PunishLock::script(punish_lock);
let output_metadata = prev.get_consumable_output()?;

let unsigned_tx = bitcoin::blockdata::transaction::Transaction {
let unsigned_tx = bitcoin::Transaction {
version: 2,
lock_time: 0,
input: vec![TxIn {
previous_output: output_metadata.out_point,
script_sig: bitcoin::blockdata::script::Script::default(),
script_sig: bitcoin::Script::default(),
sequence: lock.timelock.as_u32(),
witness: vec![],
}],
Expand Down Expand Up @@ -130,10 +84,35 @@ impl Cancelable<Bitcoin<SegwitV0>, MetadataOutput> for Tx<Cancel> {

fn verify_template(
&self,
_lock: script::DataLock<Bitcoin<SegwitV0>>,
_punish_lock: script::DataPunishableLock<Bitcoin<SegwitV0>>,
lock: script::DataLock<Bitcoin<SegwitV0>>,
punish_lock: script::DataPunishableLock<Bitcoin<SegwitV0>>,
) -> Result<(), FError> {
// FIXME
(self.psbt.global.unsigned_tx.version == 2)
.then(|| 0)
.ok_or(FError::WrongTemplate("Tx version is not 2"))?;
(self.psbt.global.unsigned_tx.lock_time == 0)
.then(|| 0)
.ok_or(FError::WrongTemplate("LockTime is not set to 0"))?;
(self.psbt.global.unsigned_tx.input.len() == 1)
.then(|| 0)
.ok_or(FError::WrongTemplate("Number of inputs is not 1"))?;
(self.psbt.global.unsigned_tx.output.len() == 1)
.then(|| 0)
.ok_or(FError::WrongTemplate("Number of outputs is not 1"))?;

let txin = &self.psbt.global.unsigned_tx.input[0];
(txin.sequence == lock.timelock.as_u32())
.then(|| 0)
.ok_or(FError::WrongTemplate(
"Sequence is not set correctly for timelock",
))?;

let txout = &self.psbt.global.unsigned_tx.output[0];
let script_pubkey = PunishLock::v0_p2wsh(punish_lock);
(txout.script_pubkey == script_pubkey)
.then(|| 0)
.ok_or(FError::WrongTemplate("Script pubkey does not match"))?;

Ok(())
}
}
Loading

0 comments on commit a99cebd

Please sign in to comment.