1use std::collections::HashMap;
2use std::borrow::Borrow;
3use std::iter;
4
5use bitcoin::{Amount, Transaction};
6use bitcoin::secp256k1::{Keypair, PublicKey};
7use bitcoin_ext::P2TR_DUST;
8
9use crate::{Vtxo, VtxoRequest, VtxoId, VtxoPolicy, musig, error::IncorrectSigningKeyError};
10use super::{ArkoorError, ArkoorBuilder, ArkoorCosignResponse};
11use super::{build_arkoor_vtxos, unsigned_arkoor_tx};
12
13
14pub struct ArkoorPackageBuilder<'a, T: Clone> {
30 pub arkoors: Vec<ArkoorBuilder<'a, T>>,
32 spending_tx_by_input: HashMap<VtxoId, Transaction>,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
36pub enum ArkoorPackageError {
37 #[error("Payment has non-null change amount but no change pubkey provided")]
38 MissingChangePk,
39 #[error("Invalid length of cosignature response")]
40 InvalidLength,
41 #[error("No vtxo created")]
42 MissingVtxo,
43 #[error("Invalid spk for revocation")]
44 InvalidRevocationSpk,
45 #[error("Invalid length of user nonces")]
46 InvalidUserNoncesLength,
47 #[error("Htlc amount does not match invoice amount")]
48 InvalidHtlcAmount,
49 #[error("An error occurred while building arkoor: {0}")]
50 ArkoorError(ArkoorError),
51 #[error("Too many outputs")]
52 TooManyOutputs,
53 #[error("incorrect signing key provided")]
54 Signing(#[from] IncorrectSigningKeyError),
55}
56
57impl<'a> ArkoorPackageBuilder<'a, VtxoRequest> {
58 pub fn new(
59 inputs: impl IntoIterator<Item = &'a Vtxo>,
60 user_nonces: &'a [musig::PublicNonce],
61 vtxo_request: VtxoRequest,
62 change_pubkey: Option<PublicKey>,
63 ) -> Result<Self, ArkoorPackageError> {
64 let mut remaining_amount = vtxo_request.amount;
65 let mut arkoors = vec![];
66 let mut spending_tx_by_input = HashMap::new();
67
68 for (idx, input) in inputs.into_iter().enumerate() {
69 let user_nonce = user_nonces.get(idx).ok_or(ArkoorPackageError::InvalidUserNoncesLength)?;
70
71 let change_amount = input.amount().checked_sub(remaining_amount);
72 let (output_amount, change) = if let Some(change_amount) = change_amount {
73 let change = if change_amount < P2TR_DUST {
75 None
76 } else {
77 Some(VtxoRequest {
78 amount: change_amount,
79 policy: VtxoPolicy::new_pubkey(change_pubkey.ok_or(ArkoorPackageError::MissingChangePk)?),
80 })
81 };
82
83 (remaining_amount, change)
84 } else {
85 (input.amount(), None)
86 };
87
88 let output = VtxoRequest {
89 amount: output_amount,
90 policy: vtxo_request.policy.clone(),
91 };
92
93 let pay_reqs = iter::once(output.clone()).chain(change).collect::<Vec<_>>();
94
95 let arkoor = ArkoorBuilder::new(&input, user_nonce, pay_reqs)
96 .map_err(ArkoorPackageError::ArkoorError)?;
97
98 spending_tx_by_input.insert(input.id(), arkoor.unsigned_transaction());
99 arkoors.push(arkoor);
100
101 remaining_amount = remaining_amount - output_amount;
102 if remaining_amount == Amount::ZERO {
103 break;
104 }
105 }
106
107 Ok(Self {
108 arkoors,
109 spending_tx_by_input,
110 })
111 }
112
113 pub fn new_htlc_revocation(
114 htlc_vtxos: &'a [Vtxo],
115 user_nonces: &'a [musig::PublicNonce],
116 ) -> Result<Self, ArkoorPackageError> {
117 let arkoors = htlc_vtxos.iter().zip(user_nonces).map(|(v, u)| {
118 if !matches!(v.policy(), VtxoPolicy::ServerHtlcSend { .. }) {
119 return Err(ArkoorPackageError::InvalidRevocationSpk);
120 }
121
122 let refund = VtxoRequest {
123 amount: v.amount(),
124 policy: VtxoPolicy::new_pubkey(v.user_pubkey()),
125 };
126 ArkoorBuilder::new(v, u, vec![refund])
127 .map_err(ArkoorPackageError::ArkoorError)
128 }).collect::<Result<Vec<_>, ArkoorPackageError>>()?;
129
130 Self::from_arkoors(arkoors)
131 }
132
133 pub fn from_arkoors(
134 arkoors: Vec<ArkoorBuilder<'a, VtxoRequest>>,
135 ) -> Result<Self, ArkoorPackageError> {
136 let mut spending_tx_by_input = HashMap::new();
137
138 for arkoor in arkoors.iter() {
139 spending_tx_by_input.insert(arkoor.input.id(), arkoor.unsigned_transaction());
140 }
141
142 Ok(Self {
143 arkoors,
144 spending_tx_by_input,
145 })
146 }
147
148 pub fn inputs(&self) -> Vec<&'a Vtxo> {
149 self.arkoors.iter().map(|a| a.input).collect::<Vec<_>>()
150 }
151
152 pub fn spending_tx(&self, input_id: VtxoId) -> Option<&Transaction> {
153 self.spending_tx_by_input.get(&input_id)
154 }
155
156 pub fn build_vtxos<'b>(
157 self,
158 sigs: impl IntoIterator<Item = &'a ArkoorCosignResponse>,
159 keypairs: impl IntoIterator<Item = &'a Keypair>,
160 sec_nonces: impl IntoIterator<Item = musig::SecretNonce>,
161 ) -> Result<(Vec<Vtxo>, Option<Vtxo>), ArkoorPackageError> {
162 let mut sent_vtxos = vec![];
163 let mut change_vtxo = None;
164
165 let expected_len = self.arkoors.len();
166
167 let iter = self.arkoors.into_iter().zip(sigs).zip(keypairs).zip(sec_nonces);
168 for (((arkoor, cosign), keypair), sec_nonce) in iter {
169 let vtxos = arkoor.build_vtxos(sec_nonce, keypair, cosign)?;
170
171 let mut vtxo_iter = vtxos.into_iter();
173 let user_vtxo = vtxo_iter.next().ok_or(ArkoorPackageError::MissingVtxo)?;
174 sent_vtxos.push(user_vtxo);
175
176 if let Some(vtxo) = vtxo_iter.next() {
177 assert!(change_vtxo.replace(vtxo).is_none(), "change vtxo already set");
178 }
179 }
180
181 if sent_vtxos.len() != expected_len {
182 return Err(ArkoorPackageError::InvalidLength);
183 }
184
185 Ok((sent_vtxos, change_vtxo))
186 }
187
188 pub fn new_vtxos(&self) -> Vec<Vec<Vtxo>> {
189 self.arkoors.iter().map(|arkoor| {
190 let txouts = arkoor.txouts();
191 let tx = unsigned_arkoor_tx(&arkoor.input, &txouts);
192 build_arkoor_vtxos(&arkoor.input, &arkoor.outputs, &txouts, tx.compute_txid(), None) }).collect::<Vec<Vec<_>>>()
194 }
195
196 pub fn server_cosign(&self, keypair: &Keypair) -> Vec<ArkoorCosignResponse> {
198 let mut cosign = vec![];
199
200 for arkoor in self.arkoors.iter() {
201 cosign.push(arkoor.server_cosign(keypair));
202 }
203
204 cosign
205 }
206
207 pub fn verify_cosign_response<T: Borrow<ArkoorCosignResponse>>(
208 &self,
209 server_cosign: &[T],
210 ) -> bool {
211 for (idx, builder) in self.arkoors.iter().enumerate() {
212 if let Some(cosign) = server_cosign.get(idx) {
213 if !builder.verify_cosign_response(cosign.borrow()) {
214 return false;
215 }
216 } else {
217 return false;
218 }
219 }
220 true
221 }
222}
223