Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add DKG verification window #1317

Merged
merged 122 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from 119 commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
9306dfa
update for wsts 11
xoloki Jan 15, 2025
c986d2a
use wsts 11.0.0 from crates
xoloki Jan 15, 2025
25ac3be
fix rebase
xoloki Jan 24, 2025
cf13953
use wsts-12.0.0 from crates
xoloki Jan 24, 2025
6cb670a
add FrostCoordinator to wsts_state_machine so we can run signing roun…
xoloki Jan 27, 2025
31726ee
wip
cylewitruk Jan 27, 2025
63731bb
Merge branch 'feat/frost-coordinator' into feat/require-all-signature…
cylewitruk Jan 27, 2025
105d9a7
run a dkg signing before deploying contracts
cylewitruk Jan 28, 2025
4d406ee
new wsts commit
cylewitruk Jan 28, 2025
1218eb6
wip
cylewitruk Jan 28, 2025
4179cd6
Merge branch 'main' into feat/require-all-signatures-for-rotate-keys
cylewitruk Jan 28, 2025
be05f5e
seems to work
cylewitruk Jan 28, 2025
43501cb
working version
cylewitruk Jan 29, 2025
a0ce153
Merge branch 'main' into feat/require-all-signatures-for-rotate-keys
cylewitruk Jan 29, 2025
a75f5a5
remove dead code
cylewitruk Jan 29, 2025
2214427
minor wsts logging tweaks
cylewitruk Jan 29, 2025
1511969
Merge branch 'main' into feat/require-all-signatures-for-rotate-keys
cylewitruk Jan 29, 2025
25d214a
rename wstsmessageid variant
cylewitruk Jan 29, 2025
18b69d2
it works, now need to clean up
cylewitruk Jan 30, 2025
7412f87
downgrade wsts
cylewitruk Jan 30, 2025
5a16608
proto backwards compatability fixes
cylewitruk Jan 30, 2025
0e624e6
Merge branch 'main' into feat/require-all-signatures-for-rotate-keys
cylewitruk Jan 30, 2025
e867b72
some wsts cleanup/refactor
cylewitruk Jan 30, 2025
c742af2
think that's it
cylewitruk Jan 30, 2025
347db71
add message support for a specific dkg wstsmessageid
cylewitruk Jan 30, 2025
df5111f
add a dkg wstsmessageid variant
cylewitruk Jan 30, 2025
e1173f6
merge changes from the refactor branch
cylewitruk Jan 31, 2025
758c6c2
use block hash instead of random data
cylewitruk Jan 31, 2025
8acccaf
fmt
cylewitruk Jan 31, 2025
00537be
remove 100% requirement for stacks signing of rotate keys
cylewitruk Jan 31, 2025
3049863
Merge branch 'main' into feat/require-all-signatures-for-rotate-keys
cylewitruk Jan 31, 2025
7a23839
bump p256k1 to 7.2.2
cylewitruk Jan 31, 2025
b0cf9a2
pop stash
cylewitruk Jan 31, 2025
623bda8
wip
cylewitruk Feb 2, 2025
1526b83
working verifications
cylewitruk Feb 2, 2025
dc4da30
saving
cylewitruk Feb 3, 2025
2c3b83d
fixing tests
cylewitruk Feb 3, 2025
b7bddbf
seems to work
cylewitruk Feb 4, 2025
6a7073e
remove const generic, use 0 amount and add test for random keypair
cylewitruk Feb 4, 2025
3af03bb
vet bitcoinconsensus
cylewitruk Feb 4, 2025
15bc545
working on cleaning up
cylewitruk Feb 4, 2025
1ceb947
audit log
cylewitruk Feb 4, 2025
8f84016
fix merge artifacts
cylewitruk Feb 4, 2025
8cdb739
tracing fields
cylewitruk Feb 4, 2025
328ecb0
Merge branch 'refactor/wsts-cleanup' into feat/require-all-signatures…
cylewitruk Feb 4, 2025
3349fba
Merge branch 'feat/require-all-signatures-for-rotate-keys' into feat/…
cylewitruk Feb 4, 2025
2742370
migration needs to update block hash/height
cylewitruk Feb 4, 2025
267c5a4
storage mut
cylewitruk Feb 4, 2025
716e5c2
lovely cascading changes
cylewitruk Feb 4, 2025
f3587e0
Merge branch 'main' into refactor/wsts-cleanup
cylewitruk Feb 5, 2025
d22ed61
remove wstsmessage.txid infavor of id
cylewitruk Feb 5, 2025
76b9b49
remove save and prefer trait default impls
cylewitruk Feb 5, 2025
cb95417
remove unused trait methods
cylewitruk Feb 5, 2025
a38d1c9
logging stuff
cylewitruk Feb 5, 2025
5f99a29
rename message ids
cylewitruk Feb 5, 2025
b31f6f7
Merge branch 'refactor/wsts-cleanup' into feat/require-all-signatures…
cylewitruk Feb 5, 2025
92dbf17
use tracing constants
cylewitruk Feb 5, 2025
51a39d3
remove stale comment
cylewitruk Feb 5, 2025
2fe82e6
remove box from some types
cylewitruk Feb 5, 2025
6598b11
missed dkg_begin_pause
cylewitruk Feb 5, 2025
576a004
leftover dbg!()
cylewitruk Feb 5, 2025
5c0304b
refactor some validation
cylewitruk Feb 5, 2025
5d2f807
various pr comments
cylewitruk Feb 5, 2025
1e74dbf
mut thing
cylewitruk Feb 5, 2025
68bf8f5
remove unneeded allow(deprecated)
cylewitruk Feb 5, 2025
d30a0c7
Merge branch 'refactor/wsts-cleanup' into feat/require-all-signatures…
cylewitruk Feb 5, 2025
978a83e
Merge branch 'refactor/wsts-cleanup' into feat/mock-signing
cylewitruk Feb 5, 2025
abaf93f
import some changes manually from parent branch to help a confused me…
cylewitruk Feb 5, 2025
2aff467
more diff-reducing imports
cylewitruk Feb 5, 2025
f350a7f
Merge branch 'main' into feat/require-all-signatures-for-rotate-keys
cylewitruk Feb 5, 2025
dea3625
confused merge tool
cylewitruk Feb 5, 2025
15ca645
reduce diff
cylewitruk Feb 5, 2025
13595bf
Merge branch 'feat/require-all-signatures-for-rotate-keys' into feat/…
cylewitruk Feb 5, 2025
2c46ea5
add validation for nonceresponse + signatureshareresponse
cylewitruk Feb 5, 2025
3fd47be
newline
cylewitruk Feb 5, 2025
7e621d7
pr comments
cylewitruk Feb 5, 2025
4642e66
pr comments utxo
cylewitruk Feb 5, 2025
372b55d
utxo comments
cylewitruk Feb 6, 2025
3f7b9ad
remove error conversion method
cylewitruk Feb 6, 2025
5b98412
Change the DKG shares status type.
djordon Feb 6, 2025
377c4f7
Update the migration query
djordon Feb 6, 2025
bd8216c
Fix up remaining queries
djordon Feb 6, 2025
0d24de2
Oops forgot this one
djordon Feb 6, 2025
5838bf6
Forgot this rename
djordon Feb 6, 2025
17bf002
Rename the new status field to
djordon Feb 6, 2025
1f18d30
Fix up the tests and add a new one
djordon Feb 6, 2025
32316d2
Change the return value of some of the
djordon Feb 6, 2025
2af9534
Change it back
djordon Feb 6, 2025
b6a4a28
Update the tests
djordon Feb 6, 2025
696839c
feat: add DKG verification window
matteojug Feb 6, 2025
d8f90e9
fix: less optimistic timeout
matteojug Feb 6, 2025
480b10a
Squashed commit of the following:
cylewitruk Feb 7, 2025
b88100b
Merge branch 'main' into feat/mock-signing
cylewitruk Feb 7, 2025
1fb3ffa
Clean up the comments in the shares enum
djordon Feb 7, 2025
6570d00
rename the rotate keys error variant
djordon Feb 7, 2025
36424ec
Use a better error variant when extracting
djordon Feb 7, 2025
9a66038
Remove some of our unused error variants
djordon Feb 7, 2025
97142e3
Match the behavior in the in memory store
djordon Feb 7, 2025
262b86a
We do not need these errors anymore either
djordon Feb 7, 2025
b71eddc
Merge remote-tracking branch 'origin/1313-validate-rotate-keys-contra…
matteojug Feb 7, 2025
bf615de
chore: CR nits
matteojug Feb 7, 2025
7878be8
Change the DKG shares status type.
djordon Feb 6, 2025
daf4946
Update the migration query
djordon Feb 6, 2025
83b96b0
Fix up remaining queries
djordon Feb 6, 2025
ee46b3e
Oops forgot this one
djordon Feb 6, 2025
aea867b
Forgot this rename
djordon Feb 6, 2025
27a3bfa
Rename the new status field to
djordon Feb 6, 2025
5b7cc5d
Fix up the tests and add a new one
djordon Feb 6, 2025
d290aa8
Change the return value of some of the
djordon Feb 6, 2025
0ce46d3
Change it back
djordon Feb 6, 2025
3c837d8
Update the tests
djordon Feb 6, 2025
76f6754
Clean up the comments in the shares enum
djordon Feb 7, 2025
e487615
rename the rotate keys error variant
djordon Feb 7, 2025
4ad9835
Use a better error variant when extracting
djordon Feb 7, 2025
21cd44b
Remove some of our unused error variants
djordon Feb 7, 2025
102a3d5
Match the behavior in the in memory store
djordon Feb 7, 2025
45c8299
We do not need these errors anymore either
djordon Feb 7, 2025
367491d
Merge remote-tracking branch 'origin/feat/mock-signing' into feat/dkg…
matteojug Feb 7, 2025
6606988
chore: nit comments
matteojug Feb 7, 2025
ea00224
address nits
Jiloc Feb 7, 2025
7d06bff
Merge remote-tracking branch 'origin/feat/mock-signing' into feat/dkg…
matteojug Feb 7, 2025
ba294cb
Merge remote-tracking branch 'origin/main' into feat/dkg-validity-window
matteojug Feb 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ backoff = { version = "0.4.0", default-features = false, features = ["tokio"] }
base64 = { version = "0.22.1", default-features = false, features = ["alloc"] }
bincode = { version = "1.3.3", default-features = false }
bitcoin = { version = "0.32.5", default-features = false, features = ["serde", "rand-std"] }
bitcoinconsensus = { version = "0.106.0", default-features = false }
bitcoincore-rpc = { version = "0.19.0", default-features = false }
bitcoincore-rpc-json = { version = "0.19.0", default-features = false }
bitcoincore-zmq = { version = "1.5.2", default-features = false, features = ["async"] }
Expand Down
8 changes: 8 additions & 0 deletions docker/sbtc/signer/signer-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,14 @@ bootstrap_signatures_required = 2
# Environment: SIGNER_SIGNER__DKG_TARGET_ROUNDS
# dkg_target_rounds = 1

# The number of bitcoin blocks after a DKG start where we attempt to verify the
# shares. After this many blocks, we mark the shares as failed. Please only use
# this parameter when instructed to by the sBTC team.
#
# Required: false
# Environment: SIGNER_SIGNER__DKG_VERIFICATION_WINDOW
# dkg_verification_window = 10

# !! ==============================================================================
# !! Stacks Event Observer Configuration
# !!
Expand Down
1 change: 1 addition & 0 deletions signer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ testing = ["fake", "mockall", "sbtc/testing"]
aquamarine.workspace = true
axum.workspace = true
bitcoin.workspace = true
bitcoinconsensus.workspace = true
bitcoincore-rpc.workspace = true
bitcoincore-rpc-json.workspace = true
bitcoincore-zmq.workspace = true
Expand Down
52 changes: 52 additions & 0 deletions signer/migrations/0012__dkg_verification_extensions.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

CREATE TYPE sbtc_signer.dkg_shares_status AS ENUM (
'unverified',
'verified',
'failed'
);

-- Add the new columns to the `dkg_shares` table. We're not adding indexes for
-- now because the table is so small that the overhead likely outweighs the
-- benefits.
ALTER TABLE sbtc_signer.dkg_shares
ADD COLUMN dkg_shares_status sbtc_signer.dkg_shares_status,
ADD COLUMN started_at_bitcoin_block_hash BYTEA,
ADD COLUMN started_at_bitcoin_block_height BIGINT;


UPDATE sbtc_signer.dkg_shares
SET dkg_shares_status = 'unverified';

-- These are all DKG shares associated scriptPubKeys that have been successfully spent
UPDATE sbtc_signer.dkg_shares
SET dkg_shares_status = 'verified'
FROM sbtc_signer.bitcoin_tx_inputs
WHERE sbtc_signer.dkg_shares.script_pubkey = sbtc_signer.bitcoin_tx_inputs.script_pubkey;


-- Fill in the started at bitcoin blockhash and block height. The timestamp
-- of when we write the DKG shares row to the database should correspond
-- with the tenure of the block that started the DKG round.
WITH block_times AS (
SELECT
bb1.block_hash
, bb1.block_height
, bb1.created_at
, bb2.created_at AS ended_at
FROM sbtc_signer.bitcoin_blocks AS bb2
JOIN sbtc_signer.bitcoin_blocks AS bb1
ON bb2.parent_hash = bb1.block_hash
)
UPDATE sbtc_signer.dkg_shares
SET
started_at_bitcoin_block_hash = block_times.block_hash
, started_at_bitcoin_block_height = block_times.block_height
FROM block_times
WHERE sbtc_signer.dkg_shares.created_at >= block_times.created_at
AND sbtc_signer.dkg_shares.created_at < block_times.ended_at;

-- Make the new column `NOT NULL` now that they should all have a value.
ALTER TABLE sbtc_signer.dkg_shares
ALTER COLUMN dkg_shares_status SET NOT NULL,
ALTER COLUMN started_at_bitcoin_block_hash SET NOT NULL,
ALTER COLUMN started_at_bitcoin_block_height SET NOT NULL;
188 changes: 188 additions & 0 deletions signer/src/bitcoin/utxo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::ops::Deref as _;
use std::sync::LazyLock;

use bitcoin::absolute::LockTime;
use bitcoin::consensus::Encodable;
use bitcoin::hashes::Hash as _;
use bitcoin::sighash::Prevouts;
use bitcoin::sighash::SighashCache;
Expand Down Expand Up @@ -712,6 +713,22 @@ impl SignerUtxo {
}
}

/// A struct for constructing a mock transaction that can be signed. This is
/// used as part of the verification process after a new DKG round has been
/// completed.
///
/// The Bitcoin transaction has the following layout:
/// 1. The first input is spending the signers' UTXO.
/// 2. There is only one output which is an OP_RETURN with the bytes [0x01,
/// 0x02, 0x03] as the data and amount equal to the UTXO's value (i.e. the
/// transaction has a zero-fee).
pub struct UnsignedMockTransaction {
/// The Bitcoin transaction that needs to be signed.
tx: Transaction,
/// The signers' UTXO used as an input to this transaction.
utxo: SignerUtxo,
}

/// Given a set of requests, create a BTC transaction that can be signed.
///
/// This BTC transaction in this struct has correct amounts but no witness
Expand Down Expand Up @@ -802,6 +819,94 @@ impl SignatureHashes<'_> {
}
}

impl UnsignedMockTransaction {
const AMOUNT: u64 = 0;

/// Construct an unsigned mock transaction.
///
/// This will use the provided `aggregate_key` and `amount` to
/// construct a [`Transaction`] with a single input and output.
pub fn new(signer_public_key: XOnlyPublicKey) -> Self {
let utxo = SignerUtxo {
outpoint: OutPoint::null(),
amount: Self::AMOUNT,
public_key: signer_public_key,
};

let tx = Transaction {
version: Version::TWO,
lock_time: LockTime::ZERO,
input: vec![utxo.as_tx_input(&DUMMY_SIGNATURE)],
output: vec![TxOut {
value: Amount::from_sat(Self::AMOUNT),
script_pubkey: ScriptBuf::new_op_return([]),
}],
};

Self { tx, utxo }
}

/// Gets the sighash for the signers' input UTXO which needs to be signed
/// before the transaction can be broadcast.
pub fn compute_sighash(&self) -> Result<TapSighash, Error> {
let prevouts = [self.utxo.as_tx_output()];
let mut sighasher = SighashCache::new(&self.tx);

sighasher
.taproot_key_spend_signature_hash(0, &Prevouts::All(&prevouts), TapSighashType::All)
.map_err(Into::into)
}

/// Tests if the provided taproot [`Signature`] is valid for spending the
/// signers' UTXO. This function will return [`Error::BitcoinConsensus`]
/// error if the signature fails verification, passing the underlying error
/// from [`bitcoinconsensus`].
pub fn verify_signature(&self, signature: &Signature) -> Result<(), Error> {
// Create a copy of the transaction so that we don't modify the
// transaction stored in the struct.
let mut tx = self.tx.clone();

// Set the witness data on the input from the provided signature.
tx.input[0].witness = Witness::p2tr_key_spend(signature);

// Encode the transaction to bytes (needed by the bitcoinconsensus
// library).
let mut tx_bytes: Vec<u8> = Vec::new();
tx.consensus_encode(&mut tx_bytes)
.map_err(Error::BitcoinIo)?;

// Get the prevout for the signers' UTXO.
let prevout = self.utxo.as_tx_output();
let prevout_script_bytes = prevout.script_pubkey.as_script().as_bytes();

// Create the bitcoinconsensus UTXO object.
let prevout_utxo = bitcoinconsensus::Utxo {
script_pubkey: prevout_script_bytes.as_ptr(),
script_pubkey_len: prevout_script_bytes.len() as u32,
value: Self::AMOUNT as i64,
};

// We specify the flags to include all pre-taproot and taproot
// verifications explicitly.
// https://github.com/rust-bitcoin/rust-bitcoinconsensus/blob/master/src/lib.rs
let flags = bitcoinconsensus::VERIFY_ALL_PRE_TAPROOT | bitcoinconsensus::VERIFY_TAPROOT;

// Verify that the transaction updated with the provided signature can
// successfully spend the signers' UTXO. Note that the amount is not
// used in the verification process for taproot spends, only the
// signature.
bitcoinconsensus::verify_with_flags(
prevout_script_bytes,
Self::AMOUNT,
&tx_bytes,
Some(&[prevout_utxo]),
0,
flags,
)
.map_err(Error::BitcoinConsensus)
}
}

impl<'a> UnsignedTransaction<'a> {
/// Construct an unsigned transaction.
///
Expand Down Expand Up @@ -1457,6 +1562,7 @@ mod tests {
use std::str::FromStr;

use super::*;
use bitcoin::key::TapTweak;
use bitcoin::BlockHash;
use bitcoin::CompressedPublicKey;
use bitcoin::Txid;
Expand Down Expand Up @@ -1652,6 +1758,88 @@ mod tests {
}
}

/// This test verifies that our implementation of Bitcoin script
/// verification using [`bitcoinconsensus`] works as expected. This
/// functionality is used in the verification of WSTS signing after a new
/// DKG round has completed.
#[test]
fn mock_signer_utxo_signing_and_spending_verification() {
let secp = secp256k1::Secp256k1::new();

// Generate a key pair which will serve as the signers' aggregate key.
let secret_key = SecretKey::new(&mut OsRng);
let keypair = secp256k1::Keypair::from_secret_key(&secp, &secret_key);
let tweaked = keypair.tap_tweak(&secp, None);
let (aggregate_key, _) = keypair.x_only_public_key();

// Create a new transaction using the aggregate key.
let unsigned = UnsignedMockTransaction::new(aggregate_key);

let tapsig = unsigned
.compute_sighash()
.expect("failed to compute taproot sighash");

// Sign the taproot sighash.
let message = secp256k1::Message::from_digest_slice(tapsig.as_byte_array())
.expect("Failed to create message");

// [1] Verify the correct signature, which should succeed.
let schnorr_sig = secp.sign_schnorr(&message, &tweaked.to_inner());
let taproot_sig = bitcoin::taproot::Signature {
signature: schnorr_sig,
sighash_type: TapSighashType::All,
};
unsigned
.verify_signature(&taproot_sig)
.expect("signature verification failed");

// [2] Verify the correct signature, but with a different sighash type,
// which should fail.
let taproot_sig = bitcoin::taproot::Signature {
signature: schnorr_sig,
sighash_type: TapSighashType::None,
};
unsigned
.verify_signature(&taproot_sig)
.expect_err("signature verification should have failed");

// [3] Verify an incorrect signature with the correct sighash type,
// which should fail. In this case we've created the signature using
// the untweaked keypair.
let schnorr_sig = secp.sign_schnorr(&message, &keypair);
let taproot_sig = bitcoin::taproot::Signature {
signature: schnorr_sig,
sighash_type: TapSighashType::All,
};
unsigned
.verify_signature(&taproot_sig)
.expect_err("signature verification should have failed");

// [4] Verify an incorrect signature with the correct sighash type, which
// should fail. In this case we use a completely newly generated keypair.
let secret_key = SecretKey::new(&mut OsRng);
let keypair = secp256k1::Keypair::from_secret_key(&secp, &secret_key);
let schnorr_sig = secp.sign_schnorr(&message, &keypair);
let taproot_sig = bitcoin::taproot::Signature {
signature: schnorr_sig,
sighash_type: TapSighashType::All,
};
unsigned
.verify_signature(&taproot_sig)
.expect_err("signature verification should have failed");

// [5] Same as [4], but using its tweaked key.
let tweaked = keypair.tap_tweak(&secp, None);
let schnorr_sig = secp.sign_schnorr(&message, &tweaked.to_inner());
let taproot_sig = bitcoin::taproot::Signature {
signature: schnorr_sig,
sighash_type: TapSighashType::All,
};
unsigned
.verify_signature(&taproot_sig)
.expect_err("signature verification should have failed");
}

#[ignore = "For generating the SOLO_(DEPOSIT|WITHDRAWAL)_SIZE constants"]
#[test]
fn create_deposit_only_tx() {
Expand Down
Loading