Skip to main content

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 attestations;
12pub mod board;
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 message;
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, 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	/// The number of blocks a VTXO lives before it expires
67	pub vtxo_lifetime: 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	/// The number of blocks a VTXO lives before it expires.
83	///
84	/// Deprecated in favour of [ArkInfo::vtxo_lifetime]. This field is still
85	/// populated with the same value for backwards compatibility with older
86	/// clients.
87	#[deprecated(note = "renamed to vtxo_lifetime")]
88	pub vtxo_expiry_delta: BlockDelta,
89
90	/// The feerate for offboard transactions.
91	///
92	/// Deprecated in favour of the dedicated `GetOffboardFeeRate` RPC.
93	/// This field is still populated for backwards compatibility with
94	/// older clients but may be stale; prefer
95	/// `ServerConnection::offboard_feerate` which calls the dedicated
96	/// endpoint.
97	#[deprecated(since = "0.1.5", note = "use ServerConnection::offboard_feerate instead")]
98	pub offboard_feerate: FeeRate,
99
100	/// The maximum number of inputs for an offboard
101	pub max_offboard_inputs: usize,
102
103	/// Indicates whether the Ark server requires clients to either
104	/// provide a VTXO ownership proof, or a lightning receive token
105	/// when preparing a lightning claim.
106	pub ln_receive_anti_dos_required: bool,
107
108	/// Fee schedule for all Ark operations
109	pub fees: FeeSchedule,
110
111	/// Maximum exit depth (genesis chain length) allowed for a VTXO.
112	/// Once a VTXO's exit depth reaches this value the server will refuse to
113	/// cosign further OOR transactions spending it. Clients should refresh
114	/// their VTXOs into a round before this limit is reached.
115	pub max_vtxo_exit_depth: u16,
116
117	/// Link to the server's terms of service, if any.
118	pub tos_link: Option<String>,
119}
120
121/// Request for the creation of an vtxo.
122#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
123pub struct VtxoRequest {
124	pub amount: Amount,
125	#[serde(with = "crate::encode::serde")]
126	pub policy: VtxoPolicy,
127}
128
129impl AsRef<VtxoRequest> for VtxoRequest {
130	fn as_ref(&self) -> &VtxoRequest {
131	    self
132	}
133}
134
135/// Request for the creation of an vtxo in a signed round
136#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
137pub struct SignedVtxoRequest {
138	/// The actual VTXO request.
139	pub vtxo: VtxoRequest,
140	/// The public key used by the client to cosign the transaction tree
141	/// The client SHOULD forget this key after signing it
142	pub cosign_pubkey: PublicKey,
143	/// The public cosign nonces for the cosign pubkey
144	pub nonces: Vec<musig::PublicNonce>,
145}
146
147impl AsRef<VtxoRequest> for SignedVtxoRequest {
148	fn as_ref(&self) -> &VtxoRequest {
149	    &self.vtxo
150	}
151}
152
153pub mod scripts {
154	use bitcoin::{opcodes, ScriptBuf, TapSighash, TapTweakHash, Transaction};
155	use bitcoin::hashes::{sha256, ripemd160, Hash};
156	use bitcoin::secp256k1::{schnorr, PublicKey, XOnlyPublicKey};
157
158	use bitcoin_ext::{BlockDelta, BlockHeight, TAPROOT_KEYSPEND_WEIGHT};
159
160	use crate::musig;
161
162	/// Create a tapscript that is a checksig and a relative timelock.
163	pub fn delayed_sign(delay_blocks: BlockDelta, pubkey: XOnlyPublicKey) -> ScriptBuf {
164		let csv = bitcoin::Sequence::from_height(delay_blocks);
165		bitcoin::Script::builder()
166			.push_int(csv.to_consensus_u32() as i64)
167			.push_opcode(opcodes::all::OP_CSV)
168			.push_opcode(opcodes::all::OP_DROP)
169			.push_x_only_key(&pubkey)
170			.push_opcode(opcodes::all::OP_CHECKSIG)
171			.into_script()
172	}
173
174	/// Create a tapscript that is a checksig and an absolute timelock.
175	pub fn timelock_sign(timelock_height: BlockHeight, pubkey: XOnlyPublicKey) -> ScriptBuf {
176		let lt = bitcoin::absolute::LockTime::from_height(timelock_height).unwrap();
177		bitcoin::Script::builder()
178			.push_int(lt.to_consensus_u32() as i64)
179			.push_opcode(opcodes::all::OP_CLTV)
180			.push_opcode(opcodes::all::OP_DROP)
181			.push_x_only_key(&pubkey)
182			.push_opcode(opcodes::all::OP_CHECKSIG)
183			.into_script()
184	}
185
186	/// Create a tapscript
187	pub fn delay_timelock_sign(
188		delay_blocks: BlockDelta,
189		timelock_height: BlockHeight,
190		pubkey: XOnlyPublicKey,
191	) -> ScriptBuf {
192		let csv = bitcoin::Sequence::from_height(delay_blocks);
193		let lt = bitcoin::absolute::LockTime::from_height(timelock_height).unwrap();
194		bitcoin::Script::builder()
195			.push_int(lt.to_consensus_u32().try_into().unwrap())
196			.push_opcode(opcodes::all::OP_CLTV)
197			.push_opcode(opcodes::all::OP_DROP)
198			.push_int(csv.to_consensus_u32().try_into().unwrap())
199			.push_opcode(opcodes::all::OP_CSV)
200			.push_opcode(opcodes::all::OP_DROP)
201			.push_x_only_key(&pubkey)
202			.push_opcode(opcodes::all::OP_CHECKSIG)
203			.into_script()
204	}
205
206	/// Contract that requires revealing the preimage to the given hash
207	/// and a signature using the given (aggregate) pubkey
208	///
209	/// The expected spending script witness is the preimage followed by
210	/// the signature.
211	pub fn hash_and_sign(hash: sha256::Hash, pubkey: XOnlyPublicKey) -> ScriptBuf {
212		let hash_160 = ripemd160::Hash::hash(&hash[..]);
213
214		bitcoin::Script::builder()
215			.push_opcode(opcodes::all::OP_SIZE)
216			.push_int(32)
217			.push_opcode(opcodes::all::OP_EQUALVERIFY)
218			.push_opcode(opcodes::all::OP_HASH160)
219			.push_slice(hash_160.as_byte_array())
220			.push_opcode(opcodes::all::OP_EQUALVERIFY)
221			.push_x_only_key(&pubkey)
222			.push_opcode(opcodes::all::OP_CHECKSIG)
223			.into_script()
224	}
225
226	/// Contract that requires revealing the preimage to the given hash
227	/// and a signature using the given (aggregate) pubkey
228	///
229	/// The expected spending script witness is the preimage followed by
230	/// the signature.
231	pub fn hash_and_sign_v0(hash: sha256::Hash, pubkey: XOnlyPublicKey) -> ScriptBuf {
232		let hash_160 = ripemd160::Hash::hash(&hash[..]);
233
234		bitcoin::Script::builder()
235			.push_opcode(opcodes::all::OP_HASH160)
236			.push_slice(hash_160.as_byte_array())
237			.push_opcode(opcodes::all::OP_EQUALVERIFY)
238			.push_x_only_key(&pubkey)
239			.push_opcode(opcodes::all::OP_CHECKSIG)
240			.into_script()
241	}
242
243	pub fn hash_delay_sign(
244		hash: sha256::Hash,
245		delay_blocks: BlockDelta,
246		pubkey: XOnlyPublicKey,
247	) -> ScriptBuf {
248		let hash_160 = ripemd160::Hash::hash(&hash[..]);
249		let csv = bitcoin::Sequence::from_height(delay_blocks);
250
251		bitcoin::Script::builder()
252			.push_int(csv.to_consensus_u32().try_into().unwrap())
253			.push_opcode(opcodes::all::OP_CSV)
254			.push_opcode(opcodes::all::OP_DROP)
255			.push_opcode(opcodes::all::OP_SIZE)
256			.push_int(32)
257			.push_opcode(opcodes::all::OP_EQUALVERIFY)
258			.push_opcode(opcodes::all::OP_HASH160)
259			.push_slice(hash_160.as_byte_array())
260			.push_opcode(opcodes::all::OP_EQUALVERIFY)
261			.push_x_only_key(&pubkey)
262			.push_opcode(opcodes::all::OP_CHECKSIG)
263			.into_script()
264	}
265
266	pub fn hash_delay_sign_v0(
267		hash: sha256::Hash,
268		delay_blocks: BlockDelta,
269		pubkey: XOnlyPublicKey,
270	) -> ScriptBuf {
271		let hash_160 = ripemd160::Hash::hash(&hash[..]);
272		let csv = bitcoin::Sequence::from_height(delay_blocks);
273
274		bitcoin::Script::builder()
275			.push_int(csv.to_consensus_u32().try_into().unwrap())
276			.push_opcode(opcodes::all::OP_CSV)
277			.push_opcode(opcodes::all::OP_DROP)
278			.push_opcode(opcodes::all::OP_HASH160)
279			.push_slice(hash_160.as_byte_array())
280			.push_opcode(opcodes::all::OP_EQUALVERIFY)
281			.push_x_only_key(&pubkey)
282			.push_opcode(opcodes::all::OP_CHECKSIG)
283			.into_script()
284	}
285
286	/// Fill in the signatures into the unsigned transaction.
287	///
288	/// Panics if the nb of inputs and signatures doesn't match or if some input
289	/// witnesses are not empty.
290	pub fn fill_taproot_sigs(tx: &mut Transaction, sigs: &[schnorr::Signature]) {
291		assert_eq!(tx.input.len(), sigs.len());
292		for (input, sig) in tx.input.iter_mut().zip(sigs.iter()) {
293			assert!(input.witness.is_empty());
294			input.witness.push(&sig[..]);
295			debug_assert_eq!(TAPROOT_KEYSPEND_WEIGHT.to_wu(), input.witness.size() as u64);
296		}
297	}
298
299	/// Verify a partial signature from either of the two parties cosigning a tx.
300	pub fn verify_partial_sig(
301		sighash: TapSighash,
302		tweak: TapTweakHash,
303		signer: (PublicKey, &musig::PublicNonce),
304		other: (PublicKey, &musig::PublicNonce),
305		partial_signature: &musig::PartialSignature,
306	) -> bool {
307		let agg_nonce = musig::nonce_agg(&[&signer.1, &other.1]);
308		let agg_pk = musig::tweaked_key_agg([signer.0, other.0], tweak.to_byte_array()).0;
309
310		let session = musig::Session::new(&agg_pk, agg_nonce, &sighash.to_byte_array());
311		session.partial_verify(
312			&agg_pk, partial_signature, signer.1, musig::pubkey_to(signer.0),
313		)
314	}
315}