mirror of
https://github.com/aljazceru/clArk.git
synced 2025-12-18 21:54:19 +01:00
Separate vtxo tree signing and forfeit tx signing
This commit is contained in:
@@ -23,7 +23,7 @@ const NODE3_TX_VSIZE: u64 = 197;
|
||||
const NODE4_TX_VSIZE: u64 = 240;
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
|
||||
pub struct VtxoTreeSpec {
|
||||
pub cosigners: Vec<PublicKey>,
|
||||
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 spec: VtxoTreeSpec,
|
||||
pub utxo: OutPoint,
|
||||
@@ -286,7 +286,7 @@ impl SignedVtxoTree {
|
||||
}
|
||||
|
||||
/// 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 sighashes = self.spec.sighashes(self.utxo);
|
||||
for (i, (sighash, sig)) in sighashes.into_iter().rev().zip(self.signatures.iter()).enumerate() {
|
||||
|
||||
@@ -63,17 +63,30 @@ pub struct ForfeitNonces {
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct RoundProposal {
|
||||
pub struct VtxoProposal {
|
||||
#[prost(uint64, tag = "1")]
|
||||
pub round_id: u64,
|
||||
#[prost(bytes = "vec", tag = "2")]
|
||||
pub vtxos_spec: ::prost::alloc::vec::Vec<u8>,
|
||||
/// / The unsigned round tx.
|
||||
#[prost(bytes = "vec", tag = "3")]
|
||||
pub round_tx: ::prost::alloc::vec::Vec<u8>,
|
||||
#[prost(bytes = "vec", repeated, tag = "4")]
|
||||
pub vtxos_signers: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
|
||||
#[prost(bytes = "vec", repeated, tag = "5")]
|
||||
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")]
|
||||
pub forfeit_nonces: ::prost::alloc::vec::Vec<ForfeitNonces>,
|
||||
}
|
||||
@@ -82,15 +95,17 @@ pub struct RoundProposal {
|
||||
pub struct RoundFinished {
|
||||
#[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 signed round tx.
|
||||
#[prost(bytes = "vec", tag = "3")]
|
||||
pub round_tx: ::prost::alloc::vec::Vec<u8>,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
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>,
|
||||
}
|
||||
/// Nested message and enum types in `RoundEvent`.
|
||||
@@ -101,8 +116,10 @@ pub mod round_event {
|
||||
#[prost(message, tag = "1")]
|
||||
Start(super::RoundStart),
|
||||
#[prost(message, tag = "2")]
|
||||
Proposal(super::RoundProposal),
|
||||
VtxoProposal(super::VtxoProposal),
|
||||
#[prost(message, tag = "3")]
|
||||
RoundProposal(super::RoundProposal),
|
||||
#[prost(message, tag = "4")]
|
||||
Finished(super::RoundFinished),
|
||||
}
|
||||
}
|
||||
@@ -150,7 +167,14 @@ pub struct ForfeitSignatures {
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[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")]
|
||||
pub pubkey: ::prost::alloc::vec::Vec<u8>,
|
||||
#[prost(bytes = "vec", repeated, tag = "2")]
|
||||
@@ -158,14 +182,6 @@ pub struct VtxoSignatures {
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[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 {}
|
||||
/// Generated client implementations.
|
||||
pub mod ark_service_client {
|
||||
@@ -388,9 +404,9 @@ pub mod ark_service_client {
|
||||
.insert(GrpcMethod::new("arkd.ArkService", "SubmitPayment"));
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn provide_signatures(
|
||||
pub async fn provide_vtxo_signatures(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::RoundSignatures>,
|
||||
request: impl tonic::IntoRequest<super::VtxoSignaturesRequest>,
|
||||
) -> std::result::Result<tonic::Response<super::Empty>, tonic::Status> {
|
||||
self.inner
|
||||
.ready()
|
||||
@@ -403,11 +419,33 @@ pub mod ark_service_client {
|
||||
})?;
|
||||
let codec = tonic::codec::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/arkd.ArkService/ProvideSignatures",
|
||||
"/arkd.ArkService/ProvideVtxoSignatures",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ service ArkService {
|
||||
|
||||
rpc SubscribeRounds(Empty) returns (stream RoundEvent) {}
|
||||
rpc SubmitPayment(SubmitPaymentRequest) returns (Empty) {}
|
||||
rpc ProvideSignatures(RoundSignatures) returns (Empty) {}
|
||||
rpc ProvideVtxoSignatures(VtxoSignaturesRequest) returns (Empty) {}
|
||||
rpc ProvideForfeitSignatures(ForfeitSignaturesRequest) returns (Empty) {}
|
||||
}
|
||||
|
||||
message ArkInfo {
|
||||
@@ -57,26 +58,38 @@ message ForfeitNonces {
|
||||
repeated bytes pub_nonces = 2;
|
||||
}
|
||||
|
||||
message RoundProposal {
|
||||
message VtxoProposal {
|
||||
uint64 round_id = 1;
|
||||
bytes vtxos_spec = 2;
|
||||
/// The unsigned round tx.
|
||||
bytes round_tx = 3;
|
||||
repeated bytes vtxos_signers = 4;
|
||||
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;
|
||||
}
|
||||
|
||||
message RoundFinished {
|
||||
uint64 round_id = 1;
|
||||
/// Completely signed vtxo tree.
|
||||
bytes signed_vtxos = 2;
|
||||
/// The signed round tx.
|
||||
bytes round_tx = 3;
|
||||
}
|
||||
|
||||
message RoundEvent {
|
||||
oneof event {
|
||||
RoundStart start = 1;
|
||||
RoundProposal proposal = 2;
|
||||
RoundFinished finished = 3;
|
||||
VtxoProposal vtxo_proposal = 2;
|
||||
RoundProposal round_proposal = 3;
|
||||
RoundFinished finished = 4;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -102,14 +115,14 @@ message ForfeitSignatures {
|
||||
repeated bytes signatures = 3;
|
||||
}
|
||||
|
||||
message VtxoSignatures {
|
||||
bytes pubkey = 1;
|
||||
repeated bytes signatures = 2;
|
||||
message ForfeitSignaturesRequest {
|
||||
repeated ForfeitSignatures signatures = 1;
|
||||
}
|
||||
|
||||
message RoundSignatures {
|
||||
repeated ForfeitSignatures forfeit = 1;
|
||||
VtxoSignatures vtxo = 2;
|
||||
message VtxoSignaturesRequest {
|
||||
/// The cosign pubkey these signatures are for.
|
||||
bytes pubkey = 1;
|
||||
repeated bytes signatures = 2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -24,18 +24,23 @@ pub enum RoundEvent {
|
||||
id: u64,
|
||||
offboard_feerate: FeeRate,
|
||||
},
|
||||
Proposal {
|
||||
VtxoProposal {
|
||||
id: u64,
|
||||
vtxos_spec: VtxoTreeSpec,
|
||||
round_tx: Transaction,
|
||||
vtxos_spec: VtxoTreeSpec,
|
||||
vtxos_signers: Vec<PublicKey>,
|
||||
vtxos_agg_nonces: Vec<musig::MusigAggNonce>,
|
||||
},
|
||||
RoundProposal {
|
||||
id: u64,
|
||||
round_tx: Transaction,
|
||||
vtxos: SignedVtxoTree,
|
||||
forfeit_nonces: HashMap<VtxoId, Vec<musig::MusigPubNonce>>,
|
||||
},
|
||||
Finished {
|
||||
id: u64,
|
||||
vtxos: SignedVtxoTree,
|
||||
round_tx: Transaction,
|
||||
vtxos: SignedVtxoTree,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -48,10 +53,12 @@ pub enum RoundInput {
|
||||
cosign_pubkey: PublicKey,
|
||||
public_nonces: Vec<musig::MusigPubNonce>,
|
||||
},
|
||||
Signatures {
|
||||
vtxo_pubkey: PublicKey,
|
||||
vtxo_signatures: Vec<musig::MusigPartialSignature>,
|
||||
forfeit: HashMap<VtxoId, (Vec<musig::MusigPubNonce>, Vec<musig::MusigPartialSignature>)>,
|
||||
VtxoSignatures {
|
||||
pubkey: PublicKey,
|
||||
signatures: 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,
|
||||
// * 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);
|
||||
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.
|
||||
// We need to prepare N nonces for each of N inputs.
|
||||
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);
|
||||
}
|
||||
|
||||
// Send out proposal to signers.
|
||||
let _ = app.round_event_tx.send(RoundEvent::Proposal {
|
||||
// Send out round proposal to signers.
|
||||
let _ = app.round_event_tx.send(RoundEvent::RoundProposal {
|
||||
id: round_id,
|
||||
vtxos_spec: vtxos_spec.clone(),
|
||||
round_tx: round_tx.clone(),
|
||||
vtxos_signers: cosigners.iter().copied().collect(),
|
||||
vtxos_agg_nonces: agg_vtxo_nonces.clone(),
|
||||
vtxos: signed_vtxos.clone(),
|
||||
forfeit_nonces: forfeit_pub_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());
|
||||
let mut forfeit_part_sigs = HashMap::with_capacity(all_inputs.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::Signatures { vtxo_pubkey, vtxo_signatures, forfeit } => {
|
||||
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;
|
||||
}
|
||||
|
||||
RoundInput::ForfeitSignatures { signatures } => {
|
||||
//TODO(stevenroose) validate forfeit txs
|
||||
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() {
|
||||
warn!("User didn't provide enough forfeit sigs for {}", id);
|
||||
ok = false;
|
||||
@@ -386,7 +460,7 @@ pub async fn run_round_scheduler(
|
||||
if ok {
|
||||
//TODO(stevenroose) actually check if the forfeit sigs are
|
||||
//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.
|
||||
@@ -402,11 +476,6 @@ pub async fn run_round_scheduler(
|
||||
}
|
||||
|
||||
//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() {
|
||||
error!("Not enough forfeit partial signatures! ({} != {})",
|
||||
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
|
||||
|
||||
// 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);
|
||||
if let Err(e) = signed_vtxos.validate() {
|
||||
bail!("We created an incorrect vtxo tree: {}", e);
|
||||
}
|
||||
// ****************************************************************
|
||||
// * Finish the round
|
||||
// ****************************************************************
|
||||
|
||||
// And sign the on-chain tx.
|
||||
// Sign the on-chain tx.
|
||||
let finalized = wallet.sign(&mut round_tx_psbt, bdk::SignOptions::default())?;
|
||||
assert!(finalized);
|
||||
let round_tx = round_tx_psbt.extract_tx();
|
||||
@@ -504,7 +543,6 @@ pub async fn run_round_scheduler(
|
||||
round_tx: round_tx.clone(),
|
||||
});
|
||||
|
||||
|
||||
// Store forfeit txs and round info in database.
|
||||
let round_id = round_tx.txid();
|
||||
for vtxo in all_inputs {
|
||||
|
||||
@@ -63,17 +63,30 @@ pub struct ForfeitNonces {
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct RoundProposal {
|
||||
pub struct VtxoProposal {
|
||||
#[prost(uint64, tag = "1")]
|
||||
pub round_id: u64,
|
||||
#[prost(bytes = "vec", tag = "2")]
|
||||
pub vtxos_spec: ::prost::alloc::vec::Vec<u8>,
|
||||
/// / The unsigned round tx.
|
||||
#[prost(bytes = "vec", tag = "3")]
|
||||
pub round_tx: ::prost::alloc::vec::Vec<u8>,
|
||||
#[prost(bytes = "vec", repeated, tag = "4")]
|
||||
pub vtxos_signers: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
|
||||
#[prost(bytes = "vec", repeated, tag = "5")]
|
||||
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")]
|
||||
pub forfeit_nonces: ::prost::alloc::vec::Vec<ForfeitNonces>,
|
||||
}
|
||||
@@ -82,15 +95,17 @@ pub struct RoundProposal {
|
||||
pub struct RoundFinished {
|
||||
#[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 signed round tx.
|
||||
#[prost(bytes = "vec", tag = "3")]
|
||||
pub round_tx: ::prost::alloc::vec::Vec<u8>,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
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>,
|
||||
}
|
||||
/// Nested message and enum types in `RoundEvent`.
|
||||
@@ -101,8 +116,10 @@ pub mod round_event {
|
||||
#[prost(message, tag = "1")]
|
||||
Start(super::RoundStart),
|
||||
#[prost(message, tag = "2")]
|
||||
Proposal(super::RoundProposal),
|
||||
VtxoProposal(super::VtxoProposal),
|
||||
#[prost(message, tag = "3")]
|
||||
RoundProposal(super::RoundProposal),
|
||||
#[prost(message, tag = "4")]
|
||||
Finished(super::RoundFinished),
|
||||
}
|
||||
}
|
||||
@@ -150,7 +167,14 @@ pub struct ForfeitSignatures {
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[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")]
|
||||
pub pubkey: ::prost::alloc::vec::Vec<u8>,
|
||||
#[prost(bytes = "vec", repeated, tag = "2")]
|
||||
@@ -158,14 +182,6 @@ pub struct VtxoSignatures {
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[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 {}
|
||||
/// Generated server implementations.
|
||||
pub mod ark_service_server {
|
||||
@@ -210,9 +226,13 @@ pub mod ark_service_server {
|
||||
&self,
|
||||
request: tonic::Request<super::SubmitPaymentRequest>,
|
||||
) -> std::result::Result<tonic::Response<super::Empty>, tonic::Status>;
|
||||
async fn provide_signatures(
|
||||
async fn provide_vtxo_signatures(
|
||||
&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>;
|
||||
}
|
||||
/// / Public ark service for arkd.
|
||||
@@ -567,13 +587,13 @@ pub mod ark_service_server {
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/arkd.ArkService/ProvideSignatures" => {
|
||||
"/arkd.ArkService/ProvideVtxoSignatures" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct ProvideSignaturesSvc<T: ArkService>(pub Arc<T>);
|
||||
struct ProvideVtxoSignaturesSvc<T: ArkService>(pub Arc<T>);
|
||||
impl<
|
||||
T: ArkService,
|
||||
> tonic::server::UnaryService<super::RoundSignatures>
|
||||
for ProvideSignaturesSvc<T> {
|
||||
> tonic::server::UnaryService<super::VtxoSignaturesRequest>
|
||||
for ProvideVtxoSignaturesSvc<T> {
|
||||
type Response = super::Empty;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
@@ -581,11 +601,12 @@ pub mod ark_service_server {
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::RoundSignatures>,
|
||||
request: tonic::Request<super::VtxoSignaturesRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as ArkService>::provide_signatures(&inner, request).await
|
||||
<T as ArkService>::provide_vtxo_signatures(&inner, request)
|
||||
.await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
@@ -597,7 +618,57 @@ pub mod ark_service_server {
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
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 mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
|
||||
@@ -124,15 +124,22 @@ impl rpc::ArkService for Arc<App> {
|
||||
offboard_feerate_sat_vkb: offboard_feerate.to_sat_per_kwu() * 4,
|
||||
})
|
||||
},
|
||||
RoundEvent::Proposal {
|
||||
id, vtxos_spec, round_tx, vtxos_signers, vtxos_agg_nonces, forfeit_nonces,
|
||||
RoundEvent::VtxoProposal {
|
||||
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,
|
||||
vtxos_spec: vtxos_spec.encode(),
|
||||
round_tx: bitcoin::consensus::serialize(&round_tx),
|
||||
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(),
|
||||
})
|
||||
},
|
||||
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)| {
|
||||
rpc::ForfeitNonces {
|
||||
input_vtxo_id: id.bytes().to_vec(),
|
||||
@@ -206,35 +213,42 @@ impl rpc::ArkService for Arc<App> {
|
||||
Ok(tonic::Response::new(rpc::Empty {}))
|
||||
}
|
||||
|
||||
async fn provide_signatures(
|
||||
async fn provide_vtxo_signatures(
|
||||
&self,
|
||||
req: tonic::Request<rpc::RoundSignatures>,
|
||||
req: tonic::Request<rpc::VtxoSignaturesRequest>,
|
||||
) -> Result<tonic::Response<rpc::Empty>, tonic::Status> {
|
||||
let req = req.into_inner();
|
||||
|
||||
let forfeit = req.forfeit.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>>()?;
|
||||
|
||||
let vtxo = req.vtxo.ok_or_else(|| badarg!("vtxo signatures missing"))?;
|
||||
let inp = RoundInput::Signatures {
|
||||
vtxo_pubkey: PublicKey::from_slice(&vtxo.pubkey)
|
||||
let inp = RoundInput::VtxoSignatures {
|
||||
pubkey: PublicKey::from_slice(&req.pubkey)
|
||||
.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)
|
||||
.map_err(|e| badarg!("invalid signature: {}", e))
|
||||
}).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");
|
||||
Ok(tonic::Response::new(rpc::Empty {}))
|
||||
|
||||
139
noah/src/lib.rs
139
noah/src/lib.rs
@@ -473,13 +473,17 @@ impl Wallet {
|
||||
public_nonces: pub_nonces.iter().map(|n| n.serialize().to_vec()).collect(),
|
||||
}).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?
|
||||
// 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::Proposal(p) => {
|
||||
rpc::round_event::Event::VtxoProposal(p) => {
|
||||
assert_eq!(p.round_id, round_id, "missing messages");
|
||||
let vtxos = VtxoTreeSpec::decode(&p.vtxos_spec)
|
||||
.context("decoding vtxo spec")?;
|
||||
@@ -492,22 +496,7 @@ impl Wallet {
|
||||
musig::MusigAggNonce::from_slice(&k).context("invalid agg nonce")
|
||||
}).collect::<anyhow::Result<Vec<_>>>()?;
|
||||
|
||||
// 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, cosigners, vtxo_nonces, forfeit_nonces);
|
||||
break (vtxos, tx, cosigners, vtxo_nonces);
|
||||
},
|
||||
// If a new round started meanwhile, pick up on that one.
|
||||
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");
|
||||
}
|
||||
|
||||
// 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.
|
||||
let connectors = ConnectorChain::new(
|
||||
forfeit_nonces.values().next().unwrap().len(),
|
||||
@@ -573,39 +637,23 @@ impl Wallet {
|
||||
}).collect::<anyhow::Result<Vec<_>>>()?;
|
||||
Ok((v.id(), sigs))
|
||||
}).collect::<anyhow::Result<HashMap<_, _>>>()?;
|
||||
|
||||
// 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_signatures(rpc::RoundSignatures {
|
||||
forfeit: forfeit_signatures.into_iter().map(|(id, sigs)| {
|
||||
self.asp.provide_forfeit_signatures(rpc::ForfeitSignaturesRequest {
|
||||
signatures: forfeit_signatures.into_iter().map(|(id, sigs)| {
|
||||
rpc::ForfeitSignatures {
|
||||
input_vtxo_id: id.bytes().to_vec(),
|
||||
pub_nonces: sigs.iter().map(|s| s.0.serialize().to_vec()).collect(),
|
||||
signatures: sigs.iter().map(|s| s.1.serialize().to_vec()).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")?;
|
||||
|
||||
// Wait for the finishing of the round.
|
||||
|
||||
// ****************************************************************
|
||||
// * Wait for the finishing of the round.
|
||||
// ****************************************************************
|
||||
|
||||
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) => {
|
||||
if f.round_id != round_id {
|
||||
bail!("Unexpected round ID from round finished event: {} != {}",
|
||||
@@ -627,9 +675,8 @@ impl Wallet {
|
||||
other => panic!("Unexpected message: {:?}", other),
|
||||
};
|
||||
|
||||
// Validate the vtxo tree.
|
||||
if let Err(e) = vtxos.validate() {
|
||||
bail!("Received incorrect signed vtxo tree from asp: {}", e);
|
||||
if vtxos != new_vtxos {
|
||||
bail!("ASP changed the vtxo tree halfway the round");
|
||||
}
|
||||
|
||||
// We also broadcast the tx, just to have it go around faster.
|
||||
|
||||
Reference in New Issue
Block a user