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