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;
#[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
}
}
#[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(),
})
}
}
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
pub struct HashTimeLockContract {
pub hash_lock: H256,
pub recipient_pubkey: Public,
pub claim_period_end: u32,
pub refunder_pubkey: Public,
}
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
pub enum HtlcSpendPath {
Claim {
secret: Vec<u8>,
signature: Signature,
},
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 } => {
BlakeTwo256::hash(secret) == self.hash_lock
&& sp_io::crypto::sr25519_verify(
signature,
simplified_tx,
&self.recipient_pubkey,
)
}
HtlcSpendPath::Refund { signature } => {
block_height >= self.claim_period_end
&&
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));
}
}