Separate vtxo tree signing and forfeit tx signing

This commit is contained in:
Steven Roose
2024-02-09 14:39:12 +00:00
parent f8e483c401
commit 4958af8839
7 changed files with 421 additions and 200 deletions

View File

@@ -23,7 +23,7 @@ const NODE3_TX_VSIZE: u64 = 197;
const NODE4_TX_VSIZE: u64 = 240; const NODE4_TX_VSIZE: u64 = 240;
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub struct VtxoTreeSpec { pub struct VtxoTreeSpec {
pub cosigners: Vec<PublicKey>, pub cosigners: Vec<PublicKey>,
pub vtxos: Vec<VtxoRequest>, pub vtxos: Vec<VtxoRequest>,
@@ -251,7 +251,7 @@ impl VtxoTreeSpec {
} }
} }
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub struct SignedVtxoTree { pub struct SignedVtxoTree {
pub spec: VtxoTreeSpec, pub spec: VtxoTreeSpec,
pub utxo: OutPoint, pub utxo: OutPoint,
@@ -286,7 +286,7 @@ impl SignedVtxoTree {
} }
/// Validate the signatures. /// Validate the signatures.
pub fn validate(&self) -> Result<(), String> { pub fn validate_signatures(&self) -> Result<(), String> {
let pk = self.spec.cosign_taproot().output_key().to_inner(); let pk = self.spec.cosign_taproot().output_key().to_inner();
let sighashes = self.spec.sighashes(self.utxo); let sighashes = self.spec.sighashes(self.utxo);
for (i, (sighash, sig)) in sighashes.into_iter().rev().zip(self.signatures.iter()).enumerate() { for (i, (sighash, sig)) in sighashes.into_iter().rev().zip(self.signatures.iter()).enumerate() {

View File

@@ -63,17 +63,30 @@ pub struct ForfeitNonces {
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct RoundProposal { pub struct VtxoProposal {
#[prost(uint64, tag = "1")] #[prost(uint64, tag = "1")]
pub round_id: u64, pub round_id: u64,
#[prost(bytes = "vec", tag = "2")] #[prost(bytes = "vec", tag = "2")]
pub vtxos_spec: ::prost::alloc::vec::Vec<u8>, pub vtxos_spec: ::prost::alloc::vec::Vec<u8>,
/// / The unsigned round tx.
#[prost(bytes = "vec", tag = "3")] #[prost(bytes = "vec", tag = "3")]
pub round_tx: ::prost::alloc::vec::Vec<u8>, pub round_tx: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes = "vec", repeated, tag = "4")] #[prost(bytes = "vec", repeated, tag = "4")]
pub vtxos_signers: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>, pub vtxos_signers: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
#[prost(bytes = "vec", repeated, tag = "5")] #[prost(bytes = "vec", repeated, tag = "5")]
pub vtxos_agg_nonces: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>, pub vtxos_agg_nonces: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RoundProposal {
#[prost(uint64, tag = "1")]
pub round_id: u64,
/// / Completely signed vtxo tree.
#[prost(bytes = "vec", tag = "2")]
pub signed_vtxos: ::prost::alloc::vec::Vec<u8>,
/// / The unsigned round tx.
#[prost(bytes = "vec", tag = "3")]
pub round_tx: ::prost::alloc::vec::Vec<u8>,
#[prost(message, repeated, tag = "6")] #[prost(message, repeated, tag = "6")]
pub forfeit_nonces: ::prost::alloc::vec::Vec<ForfeitNonces>, pub forfeit_nonces: ::prost::alloc::vec::Vec<ForfeitNonces>,
} }
@@ -82,15 +95,17 @@ pub struct RoundProposal {
pub struct RoundFinished { pub struct RoundFinished {
#[prost(uint64, tag = "1")] #[prost(uint64, tag = "1")]
pub round_id: u64, pub round_id: u64,
/// / Completely signed vtxo tree.
#[prost(bytes = "vec", tag = "2")] #[prost(bytes = "vec", tag = "2")]
pub signed_vtxos: ::prost::alloc::vec::Vec<u8>, pub signed_vtxos: ::prost::alloc::vec::Vec<u8>,
/// / The signed round tx.
#[prost(bytes = "vec", tag = "3")] #[prost(bytes = "vec", tag = "3")]
pub round_tx: ::prost::alloc::vec::Vec<u8>, pub round_tx: ::prost::alloc::vec::Vec<u8>,
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct RoundEvent { pub struct RoundEvent {
#[prost(oneof = "round_event::Event", tags = "1, 2, 3")] #[prost(oneof = "round_event::Event", tags = "1, 2, 3, 4")]
pub event: ::core::option::Option<round_event::Event>, pub event: ::core::option::Option<round_event::Event>,
} }
/// Nested message and enum types in `RoundEvent`. /// Nested message and enum types in `RoundEvent`.
@@ -101,8 +116,10 @@ pub mod round_event {
#[prost(message, tag = "1")] #[prost(message, tag = "1")]
Start(super::RoundStart), Start(super::RoundStart),
#[prost(message, tag = "2")] #[prost(message, tag = "2")]
Proposal(super::RoundProposal), VtxoProposal(super::VtxoProposal),
#[prost(message, tag = "3")] #[prost(message, tag = "3")]
RoundProposal(super::RoundProposal),
#[prost(message, tag = "4")]
Finished(super::RoundFinished), Finished(super::RoundFinished),
} }
} }
@@ -150,7 +167,14 @@ pub struct ForfeitSignatures {
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct VtxoSignatures { pub struct ForfeitSignaturesRequest {
#[prost(message, repeated, tag = "1")]
pub signatures: ::prost::alloc::vec::Vec<ForfeitSignatures>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct VtxoSignaturesRequest {
/// / The cosign pubkey these signatures are for.
#[prost(bytes = "vec", tag = "1")] #[prost(bytes = "vec", tag = "1")]
pub pubkey: ::prost::alloc::vec::Vec<u8>, pub pubkey: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes = "vec", repeated, tag = "2")] #[prost(bytes = "vec", repeated, tag = "2")]
@@ -158,14 +182,6 @@ pub struct VtxoSignatures {
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct RoundSignatures {
#[prost(message, repeated, tag = "1")]
pub forfeit: ::prost::alloc::vec::Vec<ForfeitSignatures>,
#[prost(message, optional, tag = "2")]
pub vtxo: ::core::option::Option<VtxoSignatures>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Empty {} pub struct Empty {}
/// Generated client implementations. /// Generated client implementations.
pub mod ark_service_client { pub mod ark_service_client {
@@ -388,9 +404,9 @@ pub mod ark_service_client {
.insert(GrpcMethod::new("arkd.ArkService", "SubmitPayment")); .insert(GrpcMethod::new("arkd.ArkService", "SubmitPayment"));
self.inner.unary(req, path, codec).await self.inner.unary(req, path, codec).await
} }
pub async fn provide_signatures( pub async fn provide_vtxo_signatures(
&mut self, &mut self,
request: impl tonic::IntoRequest<super::RoundSignatures>, request: impl tonic::IntoRequest<super::VtxoSignaturesRequest>,
) -> std::result::Result<tonic::Response<super::Empty>, tonic::Status> { ) -> std::result::Result<tonic::Response<super::Empty>, tonic::Status> {
self.inner self.inner
.ready() .ready()
@@ -403,11 +419,33 @@ pub mod ark_service_client {
})?; })?;
let codec = tonic::codec::ProstCodec::default(); let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static( let path = http::uri::PathAndQuery::from_static(
"/arkd.ArkService/ProvideSignatures", "/arkd.ArkService/ProvideVtxoSignatures",
); );
let mut req = request.into_request(); let mut req = request.into_request();
req.extensions_mut() req.extensions_mut()
.insert(GrpcMethod::new("arkd.ArkService", "ProvideSignatures")); .insert(GrpcMethod::new("arkd.ArkService", "ProvideVtxoSignatures"));
self.inner.unary(req, path, codec).await
}
pub async fn provide_forfeit_signatures(
&mut self,
request: impl tonic::IntoRequest<super::ForfeitSignaturesRequest>,
) -> std::result::Result<tonic::Response<super::Empty>, tonic::Status> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::new(
tonic::Code::Unknown,
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/arkd.ArkService/ProvideForfeitSignatures",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("arkd.ArkService", "ProvideForfeitSignatures"));
self.inner.unary(req, path, codec).await self.inner.unary(req, path, codec).await
} }
} }

View File

@@ -14,7 +14,8 @@ service ArkService {
rpc SubscribeRounds(Empty) returns (stream RoundEvent) {} rpc SubscribeRounds(Empty) returns (stream RoundEvent) {}
rpc SubmitPayment(SubmitPaymentRequest) returns (Empty) {} rpc SubmitPayment(SubmitPaymentRequest) returns (Empty) {}
rpc ProvideSignatures(RoundSignatures) returns (Empty) {} rpc ProvideVtxoSignatures(VtxoSignaturesRequest) returns (Empty) {}
rpc ProvideForfeitSignatures(ForfeitSignaturesRequest) returns (Empty) {}
} }
message ArkInfo { message ArkInfo {
@@ -57,26 +58,38 @@ message ForfeitNonces {
repeated bytes pub_nonces = 2; repeated bytes pub_nonces = 2;
} }
message RoundProposal { message VtxoProposal {
uint64 round_id = 1; uint64 round_id = 1;
bytes vtxos_spec = 2; bytes vtxos_spec = 2;
/// The unsigned round tx.
bytes round_tx = 3; bytes round_tx = 3;
repeated bytes vtxos_signers = 4; repeated bytes vtxos_signers = 4;
repeated bytes vtxos_agg_nonces = 5; repeated bytes vtxos_agg_nonces = 5;
}
message RoundProposal {
uint64 round_id = 1;
/// Completely signed vtxo tree.
bytes signed_vtxos = 2;
/// The unsigned round tx.
bytes round_tx = 3;
repeated ForfeitNonces forfeit_nonces = 6; repeated ForfeitNonces forfeit_nonces = 6;
} }
message RoundFinished { message RoundFinished {
uint64 round_id = 1; uint64 round_id = 1;
/// Completely signed vtxo tree.
bytes signed_vtxos = 2; bytes signed_vtxos = 2;
/// The signed round tx.
bytes round_tx = 3; bytes round_tx = 3;
} }
message RoundEvent { message RoundEvent {
oneof event { oneof event {
RoundStart start = 1; RoundStart start = 1;
RoundProposal proposal = 2; VtxoProposal vtxo_proposal = 2;
RoundFinished finished = 3; RoundProposal round_proposal = 3;
RoundFinished finished = 4;
}; };
} }
@@ -102,14 +115,14 @@ message ForfeitSignatures {
repeated bytes signatures = 3; repeated bytes signatures = 3;
} }
message VtxoSignatures { message ForfeitSignaturesRequest {
bytes pubkey = 1; repeated ForfeitSignatures signatures = 1;
repeated bytes signatures = 2;
} }
message RoundSignatures { message VtxoSignaturesRequest {
repeated ForfeitSignatures forfeit = 1; /// The cosign pubkey these signatures are for.
VtxoSignatures vtxo = 2; bytes pubkey = 1;
repeated bytes signatures = 2;
} }

View File

@@ -24,18 +24,23 @@ pub enum RoundEvent {
id: u64, id: u64,
offboard_feerate: FeeRate, offboard_feerate: FeeRate,
}, },
Proposal { VtxoProposal {
id: u64, id: u64,
vtxos_spec: VtxoTreeSpec,
round_tx: Transaction, round_tx: Transaction,
vtxos_spec: VtxoTreeSpec,
vtxos_signers: Vec<PublicKey>, vtxos_signers: Vec<PublicKey>,
vtxos_agg_nonces: Vec<musig::MusigAggNonce>, vtxos_agg_nonces: Vec<musig::MusigAggNonce>,
},
RoundProposal {
id: u64,
round_tx: Transaction,
vtxos: SignedVtxoTree,
forfeit_nonces: HashMap<VtxoId, Vec<musig::MusigPubNonce>>, forfeit_nonces: HashMap<VtxoId, Vec<musig::MusigPubNonce>>,
}, },
Finished { Finished {
id: u64, id: u64,
vtxos: SignedVtxoTree,
round_tx: Transaction, round_tx: Transaction,
vtxos: SignedVtxoTree,
}, },
} }
@@ -48,10 +53,12 @@ pub enum RoundInput {
cosign_pubkey: PublicKey, cosign_pubkey: PublicKey,
public_nonces: Vec<musig::MusigPubNonce>, public_nonces: Vec<musig::MusigPubNonce>,
}, },
Signatures { VtxoSignatures {
vtxo_pubkey: PublicKey, pubkey: PublicKey,
vtxo_signatures: Vec<musig::MusigPartialSignature>, signatures: Vec<musig::MusigPartialSignature>,
forfeit: HashMap<VtxoId, (Vec<musig::MusigPubNonce>, Vec<musig::MusigPartialSignature>)>, },
ForfeitSignatures {
signatures: HashMap<VtxoId, (Vec<musig::MusigPubNonce>, Vec<musig::MusigPartialSignature>)>,
}, },
} }
@@ -249,9 +256,10 @@ pub async fn run_round_scheduler(
}); });
} }
// Start vtxo tree and connector chain construction
//
// **************************************************************** // ****************************************************************
// * Vtxo tree construction and signing
// *
// * - We will always store vtxo tx data from top to bottom, // * - We will always store vtxo tx data from top to bottom,
// * meaning from the root tx down to the leaves. // * meaning from the root tx down to the leaves.
// **************************************************************** // ****************************************************************
@@ -317,6 +325,96 @@ pub async fn run_round_scheduler(
let vtxo_sighashes = vtxos_spec.sighashes(vtxos_utxo); let vtxo_sighashes = vtxos_spec.sighashes(vtxos_utxo);
assert_eq!(vtxo_sighashes.len(), agg_vtxo_nonces.len()); assert_eq!(vtxo_sighashes.len(), agg_vtxo_nonces.len());
// Send out vtxo proposal to signers.
let _ = app.round_event_tx.send(RoundEvent::VtxoProposal {
id: round_id,
round_tx: round_tx.clone(),
vtxos_spec: vtxos_spec.clone(),
vtxos_signers: cosigners.iter().copied().collect(),
vtxos_agg_nonces: agg_vtxo_nonces.clone(),
});
// Wait for signatures from users.
//TODO(stevenroose) we need a check to see when we have all data we need so we can skip
// timeout
let mut vtxo_part_sigs = HashMap::with_capacity(cosigners.len());
tokio::pin! { let timeout = tokio::time::sleep(cfg.round_sign_time); }
'receive: loop {
tokio::select! {
_ = &mut timeout => break 'receive,
input = round_input_rx.recv() => match input.expect("broken channel") {
RoundInput::VtxoSignatures { pubkey, signatures } => {
if !cosigners.contains(&pubkey) {
debug!("Received signatures from non-signer: {}", pubkey);
continue 'receive;
}
trace!("Received signatures from cosigner {}", pubkey);
if validate_partial_vtxo_sigs(
cosigners.iter().copied(),
&agg_vtxo_nonces,
&vtxo_sighashes,
vtxos_spec.cosign_taptweak().to_byte_array(),
pubkey,
vtxo_pub_nonces.get(&pubkey).expect("user is cosigner"),
&signatures,
) {
vtxo_part_sigs.insert(pubkey, signatures);
} else {
debug!("Received invalid partial vtxo sigs from signer: {}", pubkey);
continue 'receive;
}
},
v => debug!("Received unexpected input: {:?}", v),
}
}
}
//TODO(stevenroose) kick out signers that didn't sign and retry
if cosigners.len() - 1 != vtxo_part_sigs.len() {
error!("Not enough vtxo partial signatures! ({} != {})",
cosigners.len() - 1, vtxo_part_sigs.len());
continue 'round;
}
// Combine the vtxo signatures.
#[cfg(debug_assertions)]
let mut partial_sigs = Vec::with_capacity(nb_nodes);
let mut final_vtxo_sigs = Vec::with_capacity(nb_nodes);
for (i, sec_nonce) in sec_vtxo_nonces.into_iter().enumerate() {
let others = vtxo_part_sigs.values().map(|s| s[i].clone()).collect::<Vec<_>>();
let (_partial, final_sig) = musig::partial_sign(
cosigners.iter().copied(),
agg_vtxo_nonces[i],
&cosign_key,
sec_nonce,
vtxo_sighashes[i].to_byte_array(),
Some(vtxos_spec.cosign_taptweak().to_byte_array()),
Some(&others),
);
final_vtxo_sigs.push(final_sig.expect("we provided others"));
#[cfg(debug_assertions)]
partial_sigs.push(_partial);
}
debug_assert!(validate_partial_vtxo_sigs(
cosigners.iter().copied(),
&agg_vtxo_nonces,
&vtxo_sighashes,
vtxos_spec.cosign_taptweak().to_byte_array(),
cosign_key.public_key(),
&pub_vtxo_nonces,
&partial_sigs,
), "our own partial signatures were wrong");
// Then construct the final signed vtxo tree.
let signed_vtxos = SignedVtxoTree::new(vtxos_spec, vtxos_utxo, final_vtxo_sigs);
debug_assert!(signed_vtxos.validate_signatures().is_ok(), "invalid signed vtxo tree");
// ****************************************************************
// * Broadcast signed vtxo tree and gather forfeit signatures
// ****************************************************************
// Prepare nonces for forfeit txs. // Prepare nonces for forfeit txs.
// We need to prepare N nonces for each of N inputs. // We need to prepare N nonces for each of N inputs.
let mut forfeit_pub_nonces = HashMap::with_capacity(all_inputs.len()); let mut forfeit_pub_nonces = HashMap::with_capacity(all_inputs.len());
@@ -333,51 +431,27 @@ pub async fn run_round_scheduler(
forfeit_sec_nonces.insert(input.id(), secs); forfeit_sec_nonces.insert(input.id(), secs);
} }
// Send out proposal to signers. // Send out round proposal to signers.
let _ = app.round_event_tx.send(RoundEvent::Proposal { let _ = app.round_event_tx.send(RoundEvent::RoundProposal {
id: round_id, id: round_id,
vtxos_spec: vtxos_spec.clone(),
round_tx: round_tx.clone(), round_tx: round_tx.clone(),
vtxos_signers: cosigners.iter().copied().collect(), vtxos: signed_vtxos.clone(),
vtxos_agg_nonces: agg_vtxo_nonces.clone(),
forfeit_nonces: forfeit_pub_nonces.clone(), forfeit_nonces: forfeit_pub_nonces.clone(),
}); });
// Wait for signatures from users. // Wait for signatures from users.
//TODO(stevenroose) we need a check to see when we have all data we need so we can skip //TODO(stevenroose) we need a check to see when we have all data we need so we can skip
// timeout // timeout
let mut vtxo_part_sigs = HashMap::with_capacity(cosigners.len());
let mut forfeit_part_sigs = HashMap::with_capacity(all_inputs.len()); let mut forfeit_part_sigs = HashMap::with_capacity(all_inputs.len());
tokio::pin! { let timeout = tokio::time::sleep(cfg.round_sign_time); } tokio::pin! { let timeout = tokio::time::sleep(cfg.round_sign_time); }
'receive: loop { 'receive: loop {
tokio::select! { tokio::select! {
_ = &mut timeout => break 'receive, _ = &mut timeout => break 'receive,
input = round_input_rx.recv() => match input.expect("broken channel") { input = round_input_rx.recv() => match input.expect("broken channel") {
RoundInput::Signatures { vtxo_pubkey, vtxo_signatures, forfeit } => { RoundInput::ForfeitSignatures { signatures } => {
if !cosigners.contains(&vtxo_pubkey) {
debug!("Received signatures from non-signer: {}", vtxo_pubkey);
continue 'receive;
}
trace!("Received signatures from cosigner {}", vtxo_pubkey);
if validate_partial_vtxo_sigs(
cosigners.iter().copied(),
&agg_vtxo_nonces,
&vtxo_sighashes,
vtxos_spec.cosign_taptweak().to_byte_array(),
vtxo_pubkey,
vtxo_pub_nonces.get(&vtxo_pubkey).expect("user is cosigner"),
&vtxo_signatures,
) {
vtxo_part_sigs.insert(vtxo_pubkey, vtxo_signatures);
} else {
debug!("Received invalid partial vtxo sigs from signer: {}", vtxo_pubkey);
continue 'receive;
}
//TODO(stevenroose) validate forfeit txs //TODO(stevenroose) validate forfeit txs
let mut ok = true; let mut ok = true;
for (id, (nonces, sigs)) in &forfeit { for (id, (nonces, sigs)) in &signatures {
if nonces.len() != all_inputs.len() || sigs.len() != all_inputs.len() { if nonces.len() != all_inputs.len() || sigs.len() != all_inputs.len() {
warn!("User didn't provide enough forfeit sigs for {}", id); warn!("User didn't provide enough forfeit sigs for {}", id);
ok = false; ok = false;
@@ -386,7 +460,7 @@ pub async fn run_round_scheduler(
if ok { if ok {
//TODO(stevenroose) actually check if the forfeit sigs are //TODO(stevenroose) actually check if the forfeit sigs are
//for actual inputs in the round //for actual inputs in the round
forfeit_part_sigs.extend(forfeit.into_iter()); forfeit_part_sigs.extend(signatures.into_iter());
} }
// Check whether we have all and can skip the loop. // Check whether we have all and can skip the loop.
@@ -402,11 +476,6 @@ pub async fn run_round_scheduler(
} }
//TODO(stevenroose) kick out signers that didn't sign and retry //TODO(stevenroose) kick out signers that didn't sign and retry
if cosigners.len() - 1 != vtxo_part_sigs.len() {
error!("Not enough vtxo partial signatures! ({} != {})",
cosigners.len() - 1, vtxo_part_sigs.len());
continue 'round;
}
if forfeit_part_sigs.len() != all_inputs.len() { if forfeit_part_sigs.len() != all_inputs.len() {
error!("Not enough forfeit partial signatures! ({} != {})", error!("Not enough forfeit partial signatures! ({} != {})",
forfeit_part_sigs.len(), all_inputs.len()); forfeit_part_sigs.len(), all_inputs.len());
@@ -447,42 +516,12 @@ pub async fn run_round_scheduler(
} }
//TODO(stevenroose) if missing forfeits, ban inputs and restart round //TODO(stevenroose) if missing forfeits, ban inputs and restart round
// Combine the vtxo signatures.
#[cfg(debug_assertions)]
let mut partial_sigs = Vec::with_capacity(nb_nodes);
let mut final_vtxo_sigs = Vec::with_capacity(nb_nodes);
for (i, sec_nonce) in sec_vtxo_nonces.into_iter().enumerate() {
let others = vtxo_part_sigs.values().map(|s| s[i].clone()).collect::<Vec<_>>();
let (_partial, final_sig) = musig::partial_sign(
cosigners.iter().copied(),
agg_vtxo_nonces[i],
&cosign_key,
sec_nonce,
vtxo_sighashes[i].to_byte_array(),
Some(vtxos_spec.cosign_taptweak().to_byte_array()),
Some(&others),
);
final_vtxo_sigs.push(final_sig.expect("we provided others"));
#[cfg(debug_assertions)]
partial_sigs.push(_partial);
}
debug_assert!(validate_partial_vtxo_sigs(
cosigners.iter().copied(),
&agg_vtxo_nonces,
&vtxo_sighashes,
vtxos_spec.cosign_taptweak().to_byte_array(),
cosign_key.public_key(),
&pub_vtxo_nonces,
&partial_sigs,
), "our own partial signatures were wrong");
// Then construct the final signed vtxo tree. // ****************************************************************
let signed_vtxos = SignedVtxoTree::new(vtxos_spec, vtxos_utxo, final_vtxo_sigs); // * Finish the round
if let Err(e) = signed_vtxos.validate() { // ****************************************************************
bail!("We created an incorrect vtxo tree: {}", e);
}
// And sign the on-chain tx. // Sign the on-chain tx.
let finalized = wallet.sign(&mut round_tx_psbt, bdk::SignOptions::default())?; let finalized = wallet.sign(&mut round_tx_psbt, bdk::SignOptions::default())?;
assert!(finalized); assert!(finalized);
let round_tx = round_tx_psbt.extract_tx(); let round_tx = round_tx_psbt.extract_tx();
@@ -504,7 +543,6 @@ pub async fn run_round_scheduler(
round_tx: round_tx.clone(), round_tx: round_tx.clone(),
}); });
// Store forfeit txs and round info in database. // Store forfeit txs and round info in database.
let round_id = round_tx.txid(); let round_id = round_tx.txid();
for vtxo in all_inputs { for vtxo in all_inputs {

View File

@@ -63,17 +63,30 @@ pub struct ForfeitNonces {
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct RoundProposal { pub struct VtxoProposal {
#[prost(uint64, tag = "1")] #[prost(uint64, tag = "1")]
pub round_id: u64, pub round_id: u64,
#[prost(bytes = "vec", tag = "2")] #[prost(bytes = "vec", tag = "2")]
pub vtxos_spec: ::prost::alloc::vec::Vec<u8>, pub vtxos_spec: ::prost::alloc::vec::Vec<u8>,
/// / The unsigned round tx.
#[prost(bytes = "vec", tag = "3")] #[prost(bytes = "vec", tag = "3")]
pub round_tx: ::prost::alloc::vec::Vec<u8>, pub round_tx: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes = "vec", repeated, tag = "4")] #[prost(bytes = "vec", repeated, tag = "4")]
pub vtxos_signers: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>, pub vtxos_signers: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
#[prost(bytes = "vec", repeated, tag = "5")] #[prost(bytes = "vec", repeated, tag = "5")]
pub vtxos_agg_nonces: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>, pub vtxos_agg_nonces: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RoundProposal {
#[prost(uint64, tag = "1")]
pub round_id: u64,
/// / Completely signed vtxo tree.
#[prost(bytes = "vec", tag = "2")]
pub signed_vtxos: ::prost::alloc::vec::Vec<u8>,
/// / The unsigned round tx.
#[prost(bytes = "vec", tag = "3")]
pub round_tx: ::prost::alloc::vec::Vec<u8>,
#[prost(message, repeated, tag = "6")] #[prost(message, repeated, tag = "6")]
pub forfeit_nonces: ::prost::alloc::vec::Vec<ForfeitNonces>, pub forfeit_nonces: ::prost::alloc::vec::Vec<ForfeitNonces>,
} }
@@ -82,15 +95,17 @@ pub struct RoundProposal {
pub struct RoundFinished { pub struct RoundFinished {
#[prost(uint64, tag = "1")] #[prost(uint64, tag = "1")]
pub round_id: u64, pub round_id: u64,
/// / Completely signed vtxo tree.
#[prost(bytes = "vec", tag = "2")] #[prost(bytes = "vec", tag = "2")]
pub signed_vtxos: ::prost::alloc::vec::Vec<u8>, pub signed_vtxos: ::prost::alloc::vec::Vec<u8>,
/// / The signed round tx.
#[prost(bytes = "vec", tag = "3")] #[prost(bytes = "vec", tag = "3")]
pub round_tx: ::prost::alloc::vec::Vec<u8>, pub round_tx: ::prost::alloc::vec::Vec<u8>,
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct RoundEvent { pub struct RoundEvent {
#[prost(oneof = "round_event::Event", tags = "1, 2, 3")] #[prost(oneof = "round_event::Event", tags = "1, 2, 3, 4")]
pub event: ::core::option::Option<round_event::Event>, pub event: ::core::option::Option<round_event::Event>,
} }
/// Nested message and enum types in `RoundEvent`. /// Nested message and enum types in `RoundEvent`.
@@ -101,8 +116,10 @@ pub mod round_event {
#[prost(message, tag = "1")] #[prost(message, tag = "1")]
Start(super::RoundStart), Start(super::RoundStart),
#[prost(message, tag = "2")] #[prost(message, tag = "2")]
Proposal(super::RoundProposal), VtxoProposal(super::VtxoProposal),
#[prost(message, tag = "3")] #[prost(message, tag = "3")]
RoundProposal(super::RoundProposal),
#[prost(message, tag = "4")]
Finished(super::RoundFinished), Finished(super::RoundFinished),
} }
} }
@@ -150,7 +167,14 @@ pub struct ForfeitSignatures {
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct VtxoSignatures { pub struct ForfeitSignaturesRequest {
#[prost(message, repeated, tag = "1")]
pub signatures: ::prost::alloc::vec::Vec<ForfeitSignatures>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct VtxoSignaturesRequest {
/// / The cosign pubkey these signatures are for.
#[prost(bytes = "vec", tag = "1")] #[prost(bytes = "vec", tag = "1")]
pub pubkey: ::prost::alloc::vec::Vec<u8>, pub pubkey: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes = "vec", repeated, tag = "2")] #[prost(bytes = "vec", repeated, tag = "2")]
@@ -158,14 +182,6 @@ pub struct VtxoSignatures {
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct RoundSignatures {
#[prost(message, repeated, tag = "1")]
pub forfeit: ::prost::alloc::vec::Vec<ForfeitSignatures>,
#[prost(message, optional, tag = "2")]
pub vtxo: ::core::option::Option<VtxoSignatures>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Empty {} pub struct Empty {}
/// Generated server implementations. /// Generated server implementations.
pub mod ark_service_server { pub mod ark_service_server {
@@ -210,9 +226,13 @@ pub mod ark_service_server {
&self, &self,
request: tonic::Request<super::SubmitPaymentRequest>, request: tonic::Request<super::SubmitPaymentRequest>,
) -> std::result::Result<tonic::Response<super::Empty>, tonic::Status>; ) -> std::result::Result<tonic::Response<super::Empty>, tonic::Status>;
async fn provide_signatures( async fn provide_vtxo_signatures(
&self, &self,
request: tonic::Request<super::RoundSignatures>, request: tonic::Request<super::VtxoSignaturesRequest>,
) -> std::result::Result<tonic::Response<super::Empty>, tonic::Status>;
async fn provide_forfeit_signatures(
&self,
request: tonic::Request<super::ForfeitSignaturesRequest>,
) -> std::result::Result<tonic::Response<super::Empty>, tonic::Status>; ) -> std::result::Result<tonic::Response<super::Empty>, tonic::Status>;
} }
/// / Public ark service for arkd. /// / Public ark service for arkd.
@@ -567,13 +587,13 @@ pub mod ark_service_server {
}; };
Box::pin(fut) Box::pin(fut)
} }
"/arkd.ArkService/ProvideSignatures" => { "/arkd.ArkService/ProvideVtxoSignatures" => {
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
struct ProvideSignaturesSvc<T: ArkService>(pub Arc<T>); struct ProvideVtxoSignaturesSvc<T: ArkService>(pub Arc<T>);
impl< impl<
T: ArkService, T: ArkService,
> tonic::server::UnaryService<super::RoundSignatures> > tonic::server::UnaryService<super::VtxoSignaturesRequest>
for ProvideSignaturesSvc<T> { for ProvideVtxoSignaturesSvc<T> {
type Response = super::Empty; type Response = super::Empty;
type Future = BoxFuture< type Future = BoxFuture<
tonic::Response<Self::Response>, tonic::Response<Self::Response>,
@@ -581,11 +601,12 @@ pub mod ark_service_server {
>; >;
fn call( fn call(
&mut self, &mut self,
request: tonic::Request<super::RoundSignatures>, request: tonic::Request<super::VtxoSignaturesRequest>,
) -> Self::Future { ) -> Self::Future {
let inner = Arc::clone(&self.0); let inner = Arc::clone(&self.0);
let fut = async move { let fut = async move {
<T as ArkService>::provide_signatures(&inner, request).await <T as ArkService>::provide_vtxo_signatures(&inner, request)
.await
}; };
Box::pin(fut) Box::pin(fut)
} }
@@ -597,7 +618,57 @@ pub mod ark_service_server {
let inner = self.inner.clone(); let inner = self.inner.clone();
let fut = async move { let fut = async move {
let inner = inner.0; let inner = inner.0;
let method = ProvideSignaturesSvc(inner); let method = ProvideVtxoSignaturesSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/arkd.ArkService/ProvideForfeitSignatures" => {
#[allow(non_camel_case_types)]
struct ProvideForfeitSignaturesSvc<T: ArkService>(pub Arc<T>);
impl<
T: ArkService,
> tonic::server::UnaryService<super::ForfeitSignaturesRequest>
for ProvideForfeitSignaturesSvc<T> {
type Response = super::Empty;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::ForfeitSignaturesRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as ArkService>::provide_forfeit_signatures(
&inner,
request,
)
.await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let inner = inner.0;
let method = ProvideForfeitSignaturesSvc(inner);
let codec = tonic::codec::ProstCodec::default(); let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec) let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config( .apply_compression_config(

View File

@@ -124,15 +124,22 @@ impl rpc::ArkService for Arc<App> {
offboard_feerate_sat_vkb: offboard_feerate.to_sat_per_kwu() * 4, offboard_feerate_sat_vkb: offboard_feerate.to_sat_per_kwu() * 4,
}) })
}, },
RoundEvent::Proposal { RoundEvent::VtxoProposal {
id, vtxos_spec, round_tx, vtxos_signers, vtxos_agg_nonces, forfeit_nonces, id, vtxos_spec, round_tx, vtxos_signers, vtxos_agg_nonces,
} => { } => {
rpc::round_event::Event::Proposal(rpc::RoundProposal { rpc::round_event::Event::VtxoProposal(rpc::VtxoProposal {
round_id: id, round_id: id,
vtxos_spec: vtxos_spec.encode(), vtxos_spec: vtxos_spec.encode(),
round_tx: bitcoin::consensus::serialize(&round_tx), round_tx: bitcoin::consensus::serialize(&round_tx),
vtxos_signers: vtxos_signers.into_iter().map(|k| k.serialize().to_vec()).collect(), vtxos_signers: vtxos_signers.into_iter().map(|k| k.serialize().to_vec()).collect(),
vtxos_agg_nonces: vtxos_agg_nonces.into_iter().map(|n| n.serialize().to_vec()).collect(), vtxos_agg_nonces: vtxos_agg_nonces.into_iter().map(|n| n.serialize().to_vec()).collect(),
})
},
RoundEvent::RoundProposal { id, vtxos, round_tx, forfeit_nonces } => {
rpc::round_event::Event::RoundProposal(rpc::RoundProposal {
round_id: id,
signed_vtxos: vtxos.encode(),
round_tx: bitcoin::consensus::serialize(&round_tx),
forfeit_nonces: forfeit_nonces.into_iter().map(|(id, nonces)| { forfeit_nonces: forfeit_nonces.into_iter().map(|(id, nonces)| {
rpc::ForfeitNonces { rpc::ForfeitNonces {
input_vtxo_id: id.bytes().to_vec(), input_vtxo_id: id.bytes().to_vec(),
@@ -206,35 +213,42 @@ impl rpc::ArkService for Arc<App> {
Ok(tonic::Response::new(rpc::Empty {})) Ok(tonic::Response::new(rpc::Empty {}))
} }
async fn provide_signatures( async fn provide_vtxo_signatures(
&self, &self,
req: tonic::Request<rpc::RoundSignatures>, req: tonic::Request<rpc::VtxoSignaturesRequest>,
) -> Result<tonic::Response<rpc::Empty>, tonic::Status> { ) -> Result<tonic::Response<rpc::Empty>, tonic::Status> {
let req = req.into_inner(); let req = req.into_inner();
let forfeit = req.forfeit.into_iter().map(|forfeit| { let inp = RoundInput::VtxoSignatures {
let id = VtxoId::from_slice(&forfeit.input_vtxo_id) pubkey: PublicKey::from_slice(&req.pubkey)
.map_err(|e| badarg!("invalid vtxo id: {}", e))?;
let nonces = forfeit.pub_nonces.into_iter().map(|n| {
musig::MusigPubNonce::from_slice(&n)
.map_err(|e| badarg!("invalid forfeit nonce: {}", e))
}).collect::<Result<_, tonic::Status>>()?;
let signatures = forfeit.signatures.into_iter().map(|s| {
musig::MusigPartialSignature::from_slice(&s)
.map_err(|e| badarg!("invalid forfeit sig: {}", e))
}).collect::<Result<_, tonic::Status>>()?;
Ok((id, (nonces, signatures)))
}).collect::<Result<_, tonic::Status>>()?;
let vtxo = req.vtxo.ok_or_else(|| badarg!("vtxo signatures missing"))?;
let inp = RoundInput::Signatures {
vtxo_pubkey: PublicKey::from_slice(&vtxo.pubkey)
.map_err(|e| badarg!("invalid pubkey: {}", e))?, .map_err(|e| badarg!("invalid pubkey: {}", e))?,
vtxo_signatures: vtxo.signatures.into_iter().map(|s| { signatures: req.signatures.into_iter().map(|s| {
musig::MusigPartialSignature::from_slice(&s) musig::MusigPartialSignature::from_slice(&s)
.map_err(|e| badarg!("invalid signature: {}", e)) .map_err(|e| badarg!("invalid signature: {}", e))
}).collect::<Result<_, tonic::Status>>()?, }).collect::<Result<_, tonic::Status>>()?,
forfeit: forfeit, };
self.round_input_tx.send(inp).expect("input channel closed");
Ok(tonic::Response::new(rpc::Empty {}))
}
async fn provide_forfeit_signatures(
&self,
req: tonic::Request<rpc::ForfeitSignaturesRequest>,
) -> Result<tonic::Response<rpc::Empty>, tonic::Status> {
let inp = RoundInput::ForfeitSignatures {
signatures: req.into_inner().signatures.into_iter().map(|forfeit| {
let id = VtxoId::from_slice(&forfeit.input_vtxo_id)
.map_err(|e| badarg!("invalid vtxo id: {}", e))?;
let nonces = forfeit.pub_nonces.into_iter().map(|n| {
musig::MusigPubNonce::from_slice(&n)
.map_err(|e| badarg!("invalid forfeit nonce: {}", e))
}).collect::<Result<_, tonic::Status>>()?;
let signatures = forfeit.signatures.into_iter().map(|s| {
musig::MusigPartialSignature::from_slice(&s)
.map_err(|e| badarg!("invalid forfeit sig: {}", e))
}).collect::<Result<_, tonic::Status>>()?;
Ok((id, (nonces, signatures)))
}).collect::<Result<_, tonic::Status>>()?
}; };
self.round_input_tx.send(inp).expect("input channel closed"); self.round_input_tx.send(inp).expect("input channel closed");
Ok(tonic::Response::new(rpc::Empty {})) Ok(tonic::Response::new(rpc::Empty {}))

View File

@@ -473,13 +473,17 @@ impl Wallet {
public_nonces: pub_nonces.iter().map(|n| n.serialize().to_vec()).collect(), public_nonces: pub_nonces.iter().map(|n| n.serialize().to_vec()).collect(),
}).await.context("submitting payment to asp")?; }).await.context("submitting payment to asp")?;
// Wait for proposal from asp.
let (vtxo_tree, round_tx, vtxo_signers, vtxo_agg_nonces, forfeit_nonces) = loop { // ****************************************************************
// * Wait for vtxo proposal from asp.
// ****************************************************************
let (vtxo_tree, round_tx, vtxo_signers, vtxo_agg_nonces) = loop {
//TODO(stevenroose) should we really gracefully handle ASP malformed data? //TODO(stevenroose) should we really gracefully handle ASP malformed data?
// panicking seems kinda ok since if we can't understand the ASP, // panicking seems kinda ok since if we can't understand the ASP,
// what are we even doing? // what are we even doing?
match events.next().await.context("events stream broke")??.event.unwrap() { match events.next().await.context("events stream broke")??.event.unwrap() {
rpc::round_event::Event::Proposal(p) => { rpc::round_event::Event::VtxoProposal(p) => {
assert_eq!(p.round_id, round_id, "missing messages"); assert_eq!(p.round_id, round_id, "missing messages");
let vtxos = VtxoTreeSpec::decode(&p.vtxos_spec) let vtxos = VtxoTreeSpec::decode(&p.vtxos_spec)
.context("decoding vtxo spec")?; .context("decoding vtxo spec")?;
@@ -492,22 +496,7 @@ impl Wallet {
musig::MusigAggNonce::from_slice(&k).context("invalid agg nonce") musig::MusigAggNonce::from_slice(&k).context("invalid agg nonce")
}).collect::<anyhow::Result<Vec<_>>>()?; }).collect::<anyhow::Result<Vec<_>>>()?;
// Directly filter the forfeit nonces only for out inputs. break (vtxos, tx, cosigners, vtxo_nonces);
let forfeit_nonces = p.forfeit_nonces.into_iter().filter_map(|f| {
let id = VtxoId::from_slice(&f.input_vtxo_id)
.expect("invalid vtxoid from asp"); //TODO(stevenroose) maybe handle?
if vtxo_ids.contains(&id) {
let nonces = f.pub_nonces.into_iter().map(|s| {
musig::MusigPubNonce::from_slice(&s)
.expect("invalid forfeit nonce from asp")
}).collect::<Vec<_>>();
Some((id, nonces))
} else {
None
}
}).collect::<HashMap<_, _>>();
break (vtxos, tx, cosigners, vtxo_nonces, forfeit_nonces);
}, },
// If a new round started meanwhile, pick up on that one. // If a new round started meanwhile, pick up on that one.
rpc::round_event::Event::Start(rpc::RoundStart { round_id: id, .. }) => { rpc::round_event::Event::Start(rpc::RoundStart { round_id: id, .. }) => {
@@ -548,6 +537,81 @@ impl Wallet {
bail!("asp didn't include our cosign key in the vtxo tree"); bail!("asp didn't include our cosign key in the vtxo tree");
} }
// Make vtxo signatures from top to bottom, just like sighashes are returned.
let sighashes = vtxo_tree.sighashes(vtxos_utxo);
assert_eq!(sighashes.len(), vtxo_agg_nonces.len());
let signatures = iter::zip(sec_nonces.into_iter(), iter::zip(sighashes, vtxo_agg_nonces))
.map(|(sec_nonce, (sighash, agg_nonce))| {
musig::partial_sign(
vtxo_signers.iter().copied(),
agg_nonce,
&cosign_key,
sec_nonce,
sighash.to_byte_array(),
Some(vtxo_tree.cosign_taptweak().to_byte_array()),
None,
).0
}).collect::<Vec<_>>();
self.asp.provide_vtxo_signatures(rpc::VtxoSignaturesRequest {
pubkey: cosign_key.public_key().serialize().to_vec(),
signatures: signatures.iter().map(|s| s.serialize().to_vec()).collect(),
}).await.context("providing signatures to asp")?;
// ****************************************************************
// * Then proceed to get a round proposal and sign forfeits
// ****************************************************************
// Wait for vtxo proposal from asp.
let (vtxos, new_round_tx, forfeit_nonces) = loop {
//TODO(stevenroose) should we really gracefully handle ASP malformed data?
// panicking seems kinda ok since if we can't understand the ASP,
// what are we even doing?
match events.next().await.context("events stream broke")??.event.unwrap() {
rpc::round_event::Event::RoundProposal(p) => {
assert_eq!(p.round_id, round_id, "missing messages");
let tx = bitcoin::consensus::deserialize::<Transaction>(&p.round_tx)
.context("decoding round tx")?;
let vtxos = SignedVtxoTree::decode(&p.signed_vtxos)
.context("decoding vtxo spec")?;
// Directly filter the forfeit nonces only for out inputs.
let forfeit_nonces = p.forfeit_nonces.into_iter().filter_map(|f| {
let id = VtxoId::from_slice(&f.input_vtxo_id)
.expect("invalid vtxoid from asp"); //TODO(stevenroose) maybe handle?
if vtxo_ids.contains(&id) {
let nonces = f.pub_nonces.into_iter().map(|s| {
musig::MusigPubNonce::from_slice(&s)
.expect("invalid forfeit nonce from asp")
}).collect::<Vec<_>>();
Some((id, nonces))
} else {
None
}
}).collect::<HashMap<_, _>>();
break (vtxos, tx, forfeit_nonces);
},
// If a new round started meanwhile, pick up on that one.
rpc::round_event::Event::Start(rpc::RoundStart { round_id: id, .. }) => {
error!("Unexpected new round start...");
round_id = id;
continue 'round;
},
//TODO(stevenroose) make this robust
other => panic!("Unexpected message: {:?}", other),
}
};
if round_tx != new_round_tx {
bail!("ASP changed the round tx halfway the round.");
}
// Validate the vtxo tree.
if let Err(e) = vtxos.validate_signatures() {
bail!("Received incorrect signed vtxo tree from asp: {}", e);
}
// Make forfeit signatures. // Make forfeit signatures.
let connectors = ConnectorChain::new( let connectors = ConnectorChain::new(
forfeit_nonces.values().next().unwrap().len(), forfeit_nonces.values().next().unwrap().len(),
@@ -573,39 +637,23 @@ impl Wallet {
}).collect::<anyhow::Result<Vec<_>>>()?; }).collect::<anyhow::Result<Vec<_>>>()?;
Ok((v.id(), sigs)) Ok((v.id(), sigs))
}).collect::<anyhow::Result<HashMap<_, _>>>()?; }).collect::<anyhow::Result<HashMap<_, _>>>()?;
self.asp.provide_forfeit_signatures(rpc::ForfeitSignaturesRequest {
// Make vtxo signatures from top to bottom, just like sighashes are returned. signatures: forfeit_signatures.into_iter().map(|(id, sigs)| {
let sighashes = vtxo_tree.sighashes(vtxos_utxo);
assert_eq!(sighashes.len(), vtxo_agg_nonces.len());
let signatures = iter::zip(sec_nonces.into_iter(), iter::zip(sighashes, vtxo_agg_nonces))
.map(|(sec_nonce, (sighash, agg_nonce))| {
musig::partial_sign(
vtxo_signers.iter().copied(),
agg_nonce,
&cosign_key,
sec_nonce,
sighash.to_byte_array(),
Some(vtxo_tree.cosign_taptweak().to_byte_array()),
None,
).0
}).collect::<Vec<_>>();
self.asp.provide_signatures(rpc::RoundSignatures {
forfeit: forfeit_signatures.into_iter().map(|(id, sigs)| {
rpc::ForfeitSignatures { rpc::ForfeitSignatures {
input_vtxo_id: id.bytes().to_vec(), input_vtxo_id: id.bytes().to_vec(),
pub_nonces: sigs.iter().map(|s| s.0.serialize().to_vec()).collect(), pub_nonces: sigs.iter().map(|s| s.0.serialize().to_vec()).collect(),
signatures: sigs.iter().map(|s| s.1.serialize().to_vec()).collect(), signatures: sigs.iter().map(|s| s.1.serialize().to_vec()).collect(),
} }
}).collect(), }).collect(),
vtxo: Some(rpc::VtxoSignatures {
pubkey: cosign_key.public_key().serialize().to_vec(),
signatures: signatures.iter().map(|s| s.serialize().to_vec()).collect(),
}),
}).await.context("providing signatures to asp")?; }).await.context("providing signatures to asp")?;
// Wait for the finishing of the round.
// ****************************************************************
// * Wait for the finishing of the round.
// ****************************************************************
trace!("Waiting for round finish..."); trace!("Waiting for round finish...");
let (vtxos, round_tx) = match events.next().await.context("events stream broke")??.event.unwrap() { let (new_vtxos, round_tx) = match events.next().await.context("events stream broke")??.event.unwrap() {
rpc::round_event::Event::Finished(f) => { rpc::round_event::Event::Finished(f) => {
if f.round_id != round_id { if f.round_id != round_id {
bail!("Unexpected round ID from round finished event: {} != {}", bail!("Unexpected round ID from round finished event: {} != {}",
@@ -627,9 +675,8 @@ impl Wallet {
other => panic!("Unexpected message: {:?}", other), other => panic!("Unexpected message: {:?}", other),
}; };
// Validate the vtxo tree. if vtxos != new_vtxos {
if let Err(e) = vtxos.validate() { bail!("ASP changed the vtxo tree halfway the round");
bail!("Received incorrect signed vtxo tree from asp: {}", e);
} }
// We also broadcast the tx, just to have it go around faster. // We also broadcast the tx, just to have it go around faster.