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