-
Notifications
You must be signed in to change notification settings - Fork 18
/
htlc.rs
392 lines (337 loc) · 13.9 KB
/
htlc.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
//! This module contains `Verifier` implementations related to Hash Time Lock Contracts.
//! It contains a simple hash lock, a simple time lock, and a hash time lock.
//!
//! These could be used as the base of an atomic swap protocol with a similarly expressive
//! utxo chain like Bitcoin. For atomic swaps with less expressive counter party chains,
//! such as Monero, see the Farcaster protocol.
use super::Verifier;
use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
use sp_core::{
sr25519::{Public, Signature},
H256,
};
use sp_runtime::traits::{BlakeTwo256, Hash};
use sp_std::vec::Vec;
/// Allows UTXOs to be spent after a certain block height has been reached.
/// This is useful for locking up tokens as a future investment. Timelocking
/// also form the basis of timeout paths in swapping protocols.
///
/// This verifier is unlike many others because it requires some environmental information,
/// namely the current block number. So there is a decision to be made:
/// * Allow the verifier to take come config and grab that info by calling a function given in the config.
/// This is what we do with constraint checker.
/// * Modify the `Verifier` trait to pass along the block number.
///
/// On the one hand the block number seems like a pretty fundamental and basic thing to add. On the other
/// hand there could be many more things to pass. For example, the timestamp.
/// However any more complex information would require coupling with Constraint Checkers and it is not
/// easy to red state like in accounts.
///
/// Decision: I will add block number to the signature. And remain open to adding more blockchain-level
/// fundamental things. Especially if they are available in bitcoin script.
///
/// Regarding the verifier constraint checker separation, perhaps the right line to be drawn is
/// that verifiers are useful in a lot of places, but perhaps not expressive enough in others.
/// When they are not expressive enough, just use `UpForGrabs` and rely on the constraint checker,
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
pub struct TimeLock {
pub unlock_block_height: u32,
}
impl Verifier for TimeLock {
type Redeemer = ();
fn verify(&self, _: &[u8], block_height: u32, _: &()) -> bool {
block_height >= self.unlock_block_height
}
}
/// Allows UTXOs to be spent when a preimage to a recorded hash is provided.
/// This could be used as a puzzle (although a partial preimage search would be better)
/// or a means of sharing a password, or as part of a simple atomic swapping protocol.
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
pub struct BlakeTwoHashLock {
pub hash_lock: H256,
}
impl BlakeTwoHashLock {
pub fn new_from_secret(secret: Vec<u8>) -> Self {
Self {
hash_lock: BlakeTwo256::hash(&secret),
}
}
}
impl Verifier for BlakeTwoHashLock {
type Redeemer = Vec<u8>;
fn verify(&self, _: &[u8], _: u32, secret: &Self::Redeemer) -> bool {
BlakeTwo256::hash(secret) == self.hash_lock
}
fn new_unspendable() -> Option<Self> {
Some(BlakeTwoHashLock {
hash_lock: H256::zero(),
})
}
}
/// Allows a UTXO to be spent, and therefore acknowledged by an intended recipient by revealing
/// a hash preimage. After an initial claim period elapses on chain, the UTXO can also be spent
/// by the refunder. In practice, the refunder is often the same address initially funded the HTLC.
///
/// The receiver and refunder are specified as a simple public keys for simplicity. It would be
/// interesting to use public key hash, or better yet, simply abstract this over some opaque
/// inner verifier for maximum composability.
///
/// After the time refund path opens, the happy path remains open. This is for compatibility with
/// bitcoin, but may not be desired in all cases.
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
pub struct HashTimeLockContract {
/// The hash whose preimage must be revealed (along with the recipient's signature) to spend the UTXO.
pub hash_lock: H256,
/// The pubkey that is intended to receive and acknowledge receipt of the funds.
pub recipient_pubkey: Public,
/// The time (as a block height) when the refund path opens up.
pub claim_period_end: u32,
/// The address who can spend the coins without revealing the preimage after the claim period has ended.
pub refunder_pubkey: Public,
}
/// This is the redeemer information needed to spend a `HashTimeLockContract` verifier.
///
/// The `HashTimeLockContract` has two spend paths, and therefore this enum has two variants.
/// The variant selects the spend path and contains the corresponding witness data.
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
pub enum HtlcSpendPath {
/// The primary spend path is for the recipient to claim the UTXO by revealing the
/// hash preimage as well as a signature.
Claim {
secret: Vec<u8>,
signature: Signature,
},
/// The secondary spend path is for the original owner to refund their money to their private
/// ownership. This path is not enabled until the enough time has elapsed. Once the time has
/// elapsed, only the refunder's signature is required.
Refund { signature: Signature },
}
impl Verifier for HashTimeLockContract {
type Redeemer = HtlcSpendPath;
fn verify(&self, simplified_tx: &[u8], block_height: u32, spend_path: &HtlcSpendPath) -> bool {
match spend_path {
HtlcSpendPath::Claim { secret, signature } => {
// Claims are valid as long as the secret is correct and the receiver signature is correct.
BlakeTwo256::hash(secret) == self.hash_lock
&& sp_io::crypto::sr25519_verify(
signature,
simplified_tx,
&self.recipient_pubkey,
)
}
HtlcSpendPath::Refund { signature } => {
// Check that the time has elapsed
block_height >= self.claim_period_end
&&
// Check that the refunder has signed properly
sp_io::crypto::sr25519_verify(
signature,
simplified_tx,
&self.refunder_pubkey,
)
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use sp_core::{sr25519::Pair, ByteArray, Pair as _};
fn bad_sig() -> Signature {
Signature::from_slice(
b"bogus_signature_bogus_signature_bogus_signature_bogus_signature!".as_slice(),
)
.expect("Should be able to create a bogus signature.")
}
#[test]
fn time_lock_too_soon() {
let time_lock = TimeLock {
unlock_block_height: 100,
};
assert!(!time_lock.verify(&[], 10, &()));
}
#[test]
fn time_lock_exactly_on_time() {
let time_lock = TimeLock {
unlock_block_height: 100,
};
assert!(time_lock.verify(&[], 100, &()));
}
#[test]
fn time_lock_past_threshold() {
let time_lock = TimeLock {
unlock_block_height: 100,
};
assert!(time_lock.verify(&[], 200, &()));
}
#[test]
fn hash_lock_correct_secret() {
let secret = "htlc ftw";
let hash_lock = BlakeTwoHashLock::new_from_secret(secret.encode());
assert!(hash_lock.verify(&[], 0, &secret.encode()));
}
#[test]
fn hash_lock_wrong_secret() {
let secret = "htlc ftw";
let incorrect = "there is no second best";
let hash_lock = BlakeTwoHashLock::new_from_secret(secret.encode());
assert!(!hash_lock.verify(&[], 0, &incorrect.encode()));
}
#[test]
fn htlc_claim_success() {
const THRESHOLD: u32 = 100;
let secret = "htlc ftw".encode();
let recipient_pair = Pair::from_seed(&[0u8; 32]);
let refunder_pair = Pair::from_seed(&[1u8; 32]);
let htlc = HashTimeLockContract {
hash_lock: BlakeTwo256::hash(&secret),
recipient_pubkey: recipient_pair.public(),
claim_period_end: THRESHOLD,
refunder_pubkey: refunder_pair.public(),
};
let simplified_tx = b"hello world".as_slice();
let recipient_sig = recipient_pair.sign(simplified_tx);
let redeemer = HtlcSpendPath::Claim {
secret,
signature: recipient_sig,
};
assert!(htlc.verify(simplified_tx, 0, &redeemer));
}
#[test]
fn htlc_claim_wrong_secret() {
const THRESHOLD: u32 = 100;
let secret = "htlc ftw".encode();
let recipient_pair = Pair::from_seed(&[0u8; 32]);
let refunder_pair = Pair::from_seed(&[1u8; 32]);
let htlc = HashTimeLockContract {
hash_lock: BlakeTwo256::hash(&secret),
recipient_pubkey: recipient_pair.public(),
claim_period_end: THRESHOLD,
refunder_pubkey: refunder_pair.public(),
};
let incorrect_secret = "there is no second best".encode();
let simplified_tx = b"hello world".as_slice();
let recipient_sig = recipient_pair.sign(simplified_tx);
let redeemer = HtlcSpendPath::Claim {
secret: incorrect_secret,
signature: recipient_sig,
};
assert!(!htlc.verify(simplified_tx, 0, &redeemer));
}
#[test]
fn htlc_claim_bogus_signature() {
const THRESHOLD: u32 = 100;
let secret = "htlc ftw".encode();
let recipient_pair = Pair::from_seed(&[0u8; 32]);
let refunder_pair = Pair::from_seed(&[1u8; 32]);
let htlc = HashTimeLockContract {
hash_lock: BlakeTwo256::hash(&secret),
recipient_pubkey: recipient_pair.public(),
claim_period_end: THRESHOLD,
refunder_pubkey: refunder_pair.public(),
};
let simplified_tx = b"hello world".as_slice();
let redeemer = HtlcSpendPath::Claim {
secret,
signature: bad_sig(),
};
assert!(!htlc.verify(simplified_tx, 0, &redeemer));
}
#[test]
fn htlc_claim_fails_when_signature_is_from_refunder() {
const THRESHOLD: u32 = 100;
let secret = "htlc ftw".encode();
let recipient_pair = Pair::from_seed(&[0u8; 32]);
let refunder_pair = Pair::from_seed(&[1u8; 32]);
let htlc = HashTimeLockContract {
hash_lock: BlakeTwo256::hash(&secret),
recipient_pubkey: recipient_pair.public(),
claim_period_end: THRESHOLD,
refunder_pubkey: refunder_pair.public(),
};
let simplified_tx = b"hello world".as_slice();
let refunder_sig = refunder_pair.sign(simplified_tx);
let redeemer = HtlcSpendPath::Claim {
secret,
signature: refunder_sig,
};
assert!(!htlc.verify(simplified_tx, 0, &redeemer));
}
#[test]
fn htlc_refund_success() {
const THRESHOLD: u32 = 100;
let secret = "htlc ftw".encode();
let recipient_pair = Pair::from_seed(&[0u8; 32]);
let refunder_pair = Pair::from_seed(&[1u8; 32]);
let htlc = HashTimeLockContract {
hash_lock: BlakeTwo256::hash(&secret),
recipient_pubkey: recipient_pair.public(),
claim_period_end: THRESHOLD,
refunder_pubkey: refunder_pair.public(),
};
let simplified_tx = b"hello world".as_slice();
let refunder_sig = refunder_pair.sign(simplified_tx);
let redeemer = HtlcSpendPath::Refund {
signature: refunder_sig,
};
assert!(htlc.verify(simplified_tx, 2 * THRESHOLD, &redeemer));
}
#[test]
fn htlc_refund_too_early() {
const THRESHOLD: u32 = 100;
let secret = "htlc ftw".encode();
let recipient_pair = Pair::from_seed(&[0u8; 32]);
let refunder_pair = Pair::from_seed(&[1u8; 32]);
let htlc = HashTimeLockContract {
hash_lock: BlakeTwo256::hash(&secret),
recipient_pubkey: recipient_pair.public(),
claim_period_end: THRESHOLD,
refunder_pubkey: refunder_pair.public(),
};
let simplified_tx = b"hello world".as_slice();
let refunder_sig = refunder_pair.sign(simplified_tx);
let redeemer = HtlcSpendPath::Refund {
signature: refunder_sig,
};
assert!(!htlc.verify(simplified_tx, 0, &redeemer));
}
#[test]
fn htlc_refund_bogus_sig() {
const THRESHOLD: u32 = 100;
let secret = "htlc ftw".encode();
let recipient_pair = Pair::from_seed(&[0u8; 32]);
let refunder_pair = Pair::from_seed(&[1u8; 32]);
let htlc = HashTimeLockContract {
hash_lock: BlakeTwo256::hash(&secret),
recipient_pubkey: recipient_pair.public(),
claim_period_end: THRESHOLD,
refunder_pubkey: refunder_pair.public(),
};
let simplified_tx = b"hello world".as_slice();
let redeemer = HtlcSpendPath::Refund {
signature: bad_sig(),
};
assert!(!htlc.verify(simplified_tx, 2 * THRESHOLD, &redeemer));
}
#[test]
fn htlc_refund_fails_when_signature_is_from_recipient() {
const THRESHOLD: u32 = 100;
let secret = "htlc ftw".encode();
let recipient_pair = Pair::from_seed(&[0u8; 32]);
let refunder_pair = Pair::from_seed(&[1u8; 32]);
let htlc = HashTimeLockContract {
hash_lock: BlakeTwo256::hash(&secret),
recipient_pubkey: recipient_pair.public(),
claim_period_end: THRESHOLD,
refunder_pubkey: refunder_pair.public(),
};
let simplified_tx = b"hello world".as_slice();
let recipient_sig = recipient_pair.sign(simplified_tx);
let redeemer = HtlcSpendPath::Refund {
signature: recipient_sig,
};
assert!(!htlc.verify(simplified_tx, 2 * THRESHOLD, &redeemer));
}
}