Skip to content

Commit

Permalink
Use TX IDs for Bitcoin Eventualities
Browse files Browse the repository at this point in the history
They're a bit more binding, smaller, provided by the Rust bitcoin library,
sane, and we don't have to worry about malleability since all of our inputs are
SegWit.
  • Loading branch information
kayabaNerve committed Dec 6, 2023
1 parent 62fa31d commit 3a6c7ad
Show file tree
Hide file tree
Showing 5 changed files with 27 additions and 49 deletions.
4 changes: 2 additions & 2 deletions coins/bitcoin/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,11 @@ impl Rpc {

/// Get the hash of a block by the block's number.
pub async fn get_block_hash(&self, number: usize) -> Result<[u8; 32], RpcError> {
let mut hash = *self
let mut hash = self
.rpc_call::<BlockHash>("getblockhash", json!([number]))
.await?
.as_raw_hash()
.as_byte_array();
.to_byte_array();
// bitcoin stores the inner bytes in reverse order.
hash.reverse();
Ok(hash)
Expand Down
8 changes: 8 additions & 0 deletions coins/bitcoin/src/wallet/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use k256::{elliptic_curve::sec1::ToEncodedPoint, Scalar};
use frost::{curve::Secp256k1, Participant, ThresholdKeys, FrostError, sign::*};

use bitcoin::{
hashes::Hash,
sighash::{TapSighashType, SighashCache, Prevouts},
absolute::LockTime,
script::{PushBytesBuf, ScriptBuf},
Expand Down Expand Up @@ -245,6 +246,13 @@ impl SignableTransaction {
})
}

/// Returns the TX ID of the transaction this will create.
pub fn txid(&self) -> [u8; 32] {
let mut res = self.tx.txid().to_byte_array();
res.reverse();
res
}

/// Returns the outputs this transaction will create.
pub fn outputs(&self) -> &[TxOut] {
&self.tx.output
Expand Down
2 changes: 2 additions & 0 deletions coins/bitcoin/tests/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ async_sequential! {
FEE
).unwrap();
let needed_fee = tx.needed_fee();
let expected_id = tx.txid();
let tx = sign(&keys, tx);

assert_eq!(tx.output.len(), 3);
Expand Down Expand Up @@ -322,6 +323,7 @@ async_sequential! {
let mut hash = *tx.txid().as_raw_hash().as_byte_array();
hash.reverse();
assert_eq!(tx, rpc.get_transaction(&hash).await.unwrap());
assert_eq!(expected_id, hash);
}

async fn test_data() {
Expand Down
60 changes: 14 additions & 46 deletions processor/src/networks/bitcoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use bitcoin_serai::{
consensus::{Encodable, Decodable},
script::Instruction,
address::{NetworkChecked, Address as BAddress},
OutPoint, TxOut, Transaction, Block, Network as BNetwork,
Transaction, Block, Network as BNetwork,
},
wallet::{
tweak_keys, address_payload, ReceivedOutput, Scanner, TransactionError,
Expand All @@ -37,7 +37,7 @@ use bitcoin_serai::bitcoin::{
sighash::{EcdsaSighashType, SighashCache},
script::{PushBytesBuf, Builder},
absolute::LockTime,
Sequence, Script, Witness, TxIn, Amount as BAmount,
Amount as BAmount, Sequence, Script, Witness, OutPoint, TxOut, TxIn,
transaction::Version,
};

Expand Down Expand Up @@ -206,32 +206,22 @@ impl TransactionTrait<Bitcoin> for Transaction {
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Eventuality {
// We need to bind to the plan. While we could bind to the plan ID via an OP_RETURN, plans will
// use distinct inputs and this is accordingly valid as a binding to a specific plan.
plan_binding_input: OutPoint,
outputs: Vec<TxOut>,
}
pub struct Eventuality([u8; 32]);

impl EventualityTrait for Eventuality {
fn lookup(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(32 + 4);
self.plan_binding_input.consensus_encode(&mut buf).unwrap();
buf
self.0.to_vec()
}

fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
let plan_binding_input = OutPoint::consensus_decode(reader)
.map_err(|_| io::Error::other("couldn't decode outpoint in eventuality"))?;
let outputs = Vec::<TxOut>::consensus_decode(reader)
.map_err(|_| io::Error::other("couldn't decode outputs in eventuality"))?;
Ok(Eventuality { plan_binding_input, outputs })
let mut id = [0; 32];
reader
.read_exact(&mut id)
.map_err(|_| io::Error::other("couldn't decode ID in eventuality"))?;
Ok(Eventuality(id))
}
fn serialize(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(32 + 4 + 4 + (self.outputs.len() * (8 + 32)));
self.plan_binding_input.consensus_encode(&mut buf).unwrap();
self.outputs.consensus_encode(&mut buf).unwrap();
buf
self.0.to_vec()
}
}

Expand Down Expand Up @@ -653,23 +643,7 @@ impl Network for Bitcoin {
res: &mut HashMap<[u8; 32], (usize, Transaction)>,
) {
for tx in &block.txdata[1 ..] {
let input = &tx.input[0].previous_output;
let mut lookup = Vec::with_capacity(4 + 32);
input.consensus_encode(&mut lookup).unwrap();
if let Some((plan, eventuality)) = eventualities.map.remove(&lookup) {
// Sanity, as this is guaranteed by how the lookup is performed
assert_eq!(input, &eventuality.plan_binding_input);
// If the multisig is honest, then the Eventuality's outputs should match the outputs of
// this transaction
// This panic is fine as this multisig being dishonest will require intervention on
// Substrate to trigger a slash, and then an update to the processor to handle the exact
// adjustments needed
// Panicking here is effectively triggering the halt we need to perform anyways
assert_eq!(
tx.output, eventuality.outputs,
"dishonest multisig spent input on distinct set of outputs"
);

if let Some((plan, _)) = eventualities.map.remove(tx.id().as_slice()) {
res.insert(plan, (eventualities.block_number, tx.clone()));
}
}
Expand Down Expand Up @@ -744,13 +718,8 @@ impl Network for Bitcoin {
RecommendedTranscript::new(b"Serai Processor Bitcoin Transaction Transcript");
transcript.append_message(b"plan", plan_id);

let plan_binding_input = *inputs[0].output.outpoint();
let outputs = signable.outputs().to_vec();

(
SignableTransaction { transcript, actual: signable },
Eventuality { plan_binding_input, outputs },
)
let eventuality = Eventuality(signable.txid());
(SignableTransaction { transcript, actual: signable }, eventuality)
},
))
}
Expand Down Expand Up @@ -785,8 +754,7 @@ impl Network for Bitcoin {
}

fn confirm_completion(&self, eventuality: &Self::Eventuality, tx: &Transaction) -> bool {
(eventuality.plan_binding_input == tx.input[0].previous_output) &&
(eventuality.outputs == tx.output)
eventuality.0 == tx.id()
}

#[cfg(test)]
Expand Down
2 changes: 1 addition & 1 deletion tests/full-stack/src/tests/mint_and_burn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async fn mint_and_burn_test() {

// Helper to mine a block on each network
async fn mine_blocks(
handles: &Vec<Handles>,
handles: &[Handles],
ops: &DockerOperations,
producer: &mut usize,
count: usize,
Expand Down

0 comments on commit 3a6c7ad

Please sign in to comment.