ark/
lib.rs

1
2pub extern crate bitcoin;
3
4#[macro_use] extern crate serde;
5#[macro_use] extern crate lazy_static;
6
7#[macro_use] pub mod util;
8
9pub mod address;
10pub mod arkoor;
11pub mod board;
12pub mod challenges;
13pub mod connectors;
14pub mod encode;
15pub mod error;
16pub mod fees;
17pub mod forfeit;
18pub mod lightning;
19pub mod mailbox;
20pub mod musig;
21pub mod offboard;
22pub mod rounds;
23pub mod time;
24pub mod tree;
25pub mod vtxo;
26pub mod integration;
27
28pub use crate::address::Address;
29pub use crate::encode::{ProtocolEncoding, WriteExt, ReadExt, ProtocolDecodingError};
30pub use crate::vtxo::{Vtxo, VtxoId, VtxoPolicy, ServerVtxoPolicy, ServerVtxo};
31
32#[cfg(test)]
33mod napkin;
34#[cfg(any(test, feature = "test-util"))]
35pub mod test_util;
36
37
38use std::time::Duration;
39
40use bitcoin::{Amount, FeeRate, Network};
41use bitcoin::secp256k1::{self, schnorr, PublicKey};
42
43use bitcoin_ext::BlockDelta;
44
45use crate::fees::FeeSchedule;
46
47lazy_static! {
48	/// Global secp context.
49	pub static ref SECP: secp256k1::Secp256k1<secp256k1::All> = secp256k1::Secp256k1::new();
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct ArkInfo {
54	/// The bitcoin network the server operates on
55	pub network: Network,
56	/// The Ark server pubkey
57	pub server_pubkey: PublicKey,
58	/// The pubkey used for blinding unified mailbox IDs
59	pub mailbox_pubkey: PublicKey,
60	/// The interval between each round
61	pub round_interval: Duration,
62	/// Number of nonces per round
63	pub nb_round_nonces: usize,
64	/// Delta between exit confirmation and coins becoming spendable
65	pub vtxo_exit_delta: BlockDelta,
66	/// Expiration delta of the VTXO
67	pub vtxo_expiry_delta: BlockDelta,
68	/// The number of blocks after which an HTLC-send VTXO expires once granted.
69	pub htlc_send_expiry_delta: BlockDelta,
70	/// The number of blocks to keep between Lightning and Ark HTLCs expiries
71	pub htlc_expiry_delta: BlockDelta,
72	/// Maximum amount of a VTXO
73	pub max_vtxo_amount: Option<Amount>,
74	/// The number of confirmations required to register a board vtxo
75	pub required_board_confirmations: usize,
76	/// Maximum CLTV delta server will allow clients to request an
77	/// invoice generation with.
78	pub max_user_invoice_cltv_delta: u16,
79	/// Minimum amount for a board the server will cosign
80	pub min_board_amount: Amount,
81
82	//TODO(stevenroose) move elsewhere eith other temp fields
83
84	/// The feerate for offboards
85	pub offboard_feerate: FeeRate,
86
87	/// Indicates whether the Ark server requires clients to either
88	/// provide a VTXO ownership proof, or a lightning receive token
89	/// when preparing a lightning claim.
90	pub ln_receive_anti_dos_required: bool,
91
92	/// Fee schedule for all Ark operations
93	pub fees: FeeSchedule,
94}
95
96/// Input of a round
97#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
98pub struct VtxoIdInput {
99	pub vtxo_id: VtxoId,
100	/// A schnorr signature over a message containing a static prefix,
101	/// a random challenge generated by the server and the VTXO's id.
102	/// See [`challenges::RoundAttemptChallenge`].
103	///
104	/// Should be produced using VTXO's private key
105	pub ownership_proof: schnorr::Signature,
106}
107
108/// Request for the creation of an vtxo.
109#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
110pub struct VtxoRequest {
111	pub amount: Amount,
112	#[serde(with = "crate::encode::serde")]
113	pub policy: VtxoPolicy,
114}
115
116impl AsRef<VtxoRequest> for VtxoRequest {
117	fn as_ref(&self) -> &VtxoRequest {
118	    self
119	}
120}
121
122/// Request for the creation of an vtxo in a signed round
123#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
124pub struct SignedVtxoRequest {
125	/// The actual VTXO request.
126	pub vtxo: VtxoRequest,
127	/// The public key used by the client to cosign the transaction tree
128	/// The client SHOULD forget this key after signing it
129	pub cosign_pubkey: PublicKey,
130	/// The public cosign nonces for the cosign pubkey
131	pub nonces: Vec<musig::PublicNonce>,
132}
133
134impl AsRef<VtxoRequest> for SignedVtxoRequest {
135	fn as_ref(&self) -> &VtxoRequest {
136	    &self.vtxo
137	}
138}
139
140pub mod scripts {
141	use bitcoin::{opcodes, ScriptBuf, TapSighash, TapTweakHash, Transaction};
142	use bitcoin::hashes::{sha256, ripemd160, Hash};
143	use bitcoin::secp256k1::{schnorr, PublicKey, XOnlyPublicKey};
144
145	use bitcoin_ext::{BlockDelta, BlockHeight, TAPROOT_KEYSPEND_WEIGHT};
146
147	use crate::musig;
148
149	/// Create a tapscript that is a checksig and a relative timelock.
150	pub fn delayed_sign(delay_blocks: BlockDelta, pubkey: XOnlyPublicKey) -> ScriptBuf {
151		let csv = bitcoin::Sequence::from_height(delay_blocks);
152		bitcoin::Script::builder()
153			.push_int(csv.to_consensus_u32() as i64)
154			.push_opcode(opcodes::all::OP_CSV)
155			.push_opcode(opcodes::all::OP_DROP)
156			.push_x_only_key(&pubkey)
157			.push_opcode(opcodes::all::OP_CHECKSIG)
158			.into_script()
159	}
160
161	/// Create a tapscript that is a checksig and an absolute timelock.
162	pub fn timelock_sign(timelock_height: BlockHeight, pubkey: XOnlyPublicKey) -> ScriptBuf {
163		let lt = bitcoin::absolute::LockTime::from_height(timelock_height).unwrap();
164		bitcoin::Script::builder()
165			.push_int(lt.to_consensus_u32() as i64)
166			.push_opcode(opcodes::all::OP_CLTV)
167			.push_opcode(opcodes::all::OP_DROP)
168			.push_x_only_key(&pubkey)
169			.push_opcode(opcodes::all::OP_CHECKSIG)
170			.into_script()
171	}
172
173	/// Create a tapscript
174	pub fn delay_timelock_sign(
175		delay_blocks: BlockDelta,
176		timelock_height: BlockHeight,
177		pubkey: XOnlyPublicKey,
178	) -> ScriptBuf {
179		let csv = bitcoin::Sequence::from_height(delay_blocks);
180		let lt = bitcoin::absolute::LockTime::from_height(timelock_height).unwrap();
181		bitcoin::Script::builder()
182			.push_int(lt.to_consensus_u32().try_into().unwrap())
183			.push_opcode(opcodes::all::OP_CLTV)
184			.push_opcode(opcodes::all::OP_DROP)
185			.push_int(csv.to_consensus_u32().try_into().unwrap())
186			.push_opcode(opcodes::all::OP_CSV)
187			.push_opcode(opcodes::all::OP_DROP)
188			.push_x_only_key(&pubkey)
189			.push_opcode(opcodes::all::OP_CHECKSIG)
190			.into_script()
191	}
192
193	/// Contract that requires revealing the preimage to the given hash
194	/// and a signature using the given (aggregate) pubkey
195	///
196	/// The expected spending script witness is the preimage followed by
197	/// the signature.
198	pub fn hash_and_sign(hash: sha256::Hash, pubkey: XOnlyPublicKey) -> ScriptBuf {
199		let hash_160 = ripemd160::Hash::hash(&hash[..]);
200
201		bitcoin::Script::builder()
202			.push_opcode(opcodes::all::OP_HASH160)
203			.push_slice(hash_160.as_byte_array())
204			.push_opcode(opcodes::all::OP_EQUALVERIFY)
205			.push_x_only_key(&pubkey)
206			.push_opcode(opcodes::all::OP_CHECKSIG)
207			.into_script()
208	}
209
210	pub fn hash_delay_sign(
211		hash: sha256::Hash,
212		delay_blocks: BlockDelta,
213		pubkey: XOnlyPublicKey,
214	) -> ScriptBuf {
215		let hash_160 = ripemd160::Hash::hash(&hash[..]);
216		let csv = bitcoin::Sequence::from_height(delay_blocks);
217
218		bitcoin::Script::builder()
219			.push_int(csv.to_consensus_u32().try_into().unwrap())
220			.push_opcode(opcodes::all::OP_CSV)
221			.push_opcode(opcodes::all::OP_DROP)
222			.push_opcode(opcodes::all::OP_HASH160)
223			.push_slice(hash_160.as_byte_array())
224			.push_opcode(opcodes::all::OP_EQUALVERIFY)
225			.push_x_only_key(&pubkey)
226			.push_opcode(opcodes::all::OP_CHECKSIG)
227			.into_script()
228	}
229
230	/// Fill in the signatures into the unsigned transaction.
231	///
232	/// Panics if the nb of inputs and signatures doesn't match or if some input
233	/// witnesses are not empty.
234	pub fn fill_taproot_sigs(tx: &mut Transaction, sigs: &[schnorr::Signature]) {
235		assert_eq!(tx.input.len(), sigs.len());
236		for (input, sig) in tx.input.iter_mut().zip(sigs.iter()) {
237			assert!(input.witness.is_empty());
238			input.witness.push(&sig[..]);
239			debug_assert_eq!(TAPROOT_KEYSPEND_WEIGHT, input.witness.size());
240		}
241	}
242
243	/// Verify a partial signature from either of the two parties cosigning a tx.
244	pub fn verify_partial_sig(
245		sighash: TapSighash,
246		tweak: TapTweakHash,
247		signer: (PublicKey, &musig::PublicNonce),
248		other: (PublicKey, &musig::PublicNonce),
249		partial_signature: &musig::PartialSignature,
250	) -> bool {
251		let agg_nonce = musig::nonce_agg(&[&signer.1, &other.1]);
252		let agg_pk = musig::tweaked_key_agg([signer.0, other.0], tweak.to_byte_array()).0;
253
254		let session = musig::Session::new(&agg_pk, agg_nonce, &sighash.to_byte_array());
255		session.partial_verify(
256			&agg_pk, partial_signature, signer.1, musig::pubkey_to(signer.0),
257		)
258	}
259}