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