use core::cell::RefCell;
use std::collections::{BTreeSet, HashSet};
use bitcoin::hashes::{hash160, sha256, Hmac};
use bitcoin::secp256k1;
use bitcoin::PubkeyHash;
use client_side_validation::commit_verify::EmbedCommitVerify;
use miniscript::Segwitv0;
use wallet::LockScript;
use super::{Container, Error, KeysetCommitment, Proof, ScriptEncodeData};
use crate::dbc::KeysetContainer;
#[derive(Clone, PartialEq, Eq, Hash, Debug, Display)]
#[display(Debug)]
pub struct LockscriptContainer {
pub script: LockScript,
pub pubkey: secp256k1::PublicKey,
pub tag: sha256::Hash,
pub tweaking_factor: Option<Hmac<sha256::Hash>>,
}
impl Container for LockscriptContainer {
type Supplement = sha256::Hash;
type Host = Option<()>;
fn reconstruct(
proof: &Proof,
supplement: &Self::Supplement,
_: &Self::Host,
) -> Result<Self, Error> {
if let ScriptEncodeData::LockScript(ref script) = proof.source {
Ok(Self {
pubkey: proof.pubkey,
script: script.clone(),
tag: supplement.clone(),
tweaking_factor: None,
})
} else {
Err(Error::InvalidProofStructure)
}
}
#[inline]
fn deconstruct(self) -> (Proof, Self::Supplement) {
(
Proof {
source: ScriptEncodeData::LockScript(self.script),
pubkey: self.pubkey,
},
self.tag,
)
}
#[inline]
fn to_proof(&self) -> Proof {
Proof {
source: ScriptEncodeData::LockScript(self.script.clone()),
pubkey: self.pubkey.clone(),
}
}
#[inline]
fn into_proof(self) -> Proof {
Proof {
source: ScriptEncodeData::LockScript(self.script),
pubkey: self.pubkey,
}
}
}
#[derive(
Wrapper,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Default,
Debug,
Display,
From,
)]
#[display(inner)]
#[wrapper(LowerHex, UpperHex)]
pub struct LockscriptCommitment(LockScript);
impl<MSG> EmbedCommitVerify<MSG> for LockscriptCommitment
where
MSG: AsRef<[u8]>,
{
type Container = LockscriptContainer;
type Error = Error;
fn embed_commit(
container: &mut Self::Container,
msg: &MSG,
) -> Result<Self, Self::Error> {
let original_hash = bitcoin::PublicKey {
compressed: true,
key: container.pubkey,
}
.pubkey_hash();
let (keys, hashes) =
container.script.extract_pubkey_hash_set::<Segwitv0>()?;
if keys.is_empty() && hashes.is_empty() {
Err(Error::LockscriptContainsNoKeys)?;
}
let mut key_hashes: HashSet<PubkeyHash> =
keys.iter().map(bitcoin::PublicKey::pubkey_hash).collect();
key_hashes.insert(original_hash);
let keys: BTreeSet<_> = keys.into_iter().map(|pk| pk.key).collect();
if hashes.is_empty() {
keys.get(&container.pubkey)
.ok_or(Error::LockscriptKeyNotFound)?;
} else if hashes
.into_iter()
.find(|hash| !key_hashes.contains(hash))
.is_some()
{
Err(Error::LockscriptContainsUnknownHashes)?;
}
let mut keyset_container = KeysetContainer {
pubkey: container.pubkey,
keyset: keys,
tag: container.tag,
tweaking_factor: None,
};
let tweaked_pubkey =
KeysetCommitment::embed_commit(&mut keyset_container, msg)?;
container.tweaking_factor = keyset_container.tweaking_factor;
let tweaked_hash = bitcoin::PublicKey {
key: *tweaked_pubkey,
compressed: true,
}
.pubkey_hash();
let found = RefCell::new(0);
let lockscript = container
.script
.replace_pubkeys_and_hashes::<Segwitv0, _, _>(
|pubkey: &bitcoin::PublicKey| match pubkey.key
== container.pubkey
{
true => {
*found.borrow_mut() += 1;
bitcoin::PublicKey {
compressed: true,
key: *tweaked_pubkey,
}
}
false => *pubkey,
},
|hash: &hash160::Hash| match *hash == original_hash.as_hash() {
true => {
*found.borrow_mut() += 1;
tweaked_hash.as_hash()
}
false => *hash,
},
)?;
Ok(lockscript.into())
}
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use bitcoin::hashes::{hash160, sha256, Hash};
use bitcoin::secp256k1;
use miniscript::{Miniscript, Segwitv0};
use wallet::SECP256K1;
use super::*;
use crate::dbc::Error;
macro_rules! ms_str {
($($arg:tt)*) => (Miniscript::<bitcoin::PublicKey, Segwitv0>::from_str_insane(&format!($($arg)*)).unwrap())
}
macro_rules! policy_str {
($($arg:tt)*) => (miniscript::policy::Concrete::<bitcoin::PublicKey>::from_str(&format!($($arg)*)).unwrap())
}
fn pubkeys(n: usize) -> Vec<bitcoin::PublicKey> {
let mut ret = Vec::with_capacity(n);
let mut sk = [0; 32];
for i in 1..n + 1 {
sk[0] = i as u8;
sk[1] = (i >> 8) as u8;
sk[2] = (i >> 16) as u8;
let pk = bitcoin::PublicKey {
key: secp256k1::PublicKey::from_secret_key(
&SECP256K1,
&secp256k1::SecretKey::from_slice(&sk[..])
.expect("secret key"),
),
compressed: true,
};
ret.push(pk);
}
ret
}
fn gen_test_data(
) -> (Vec<bitcoin::PublicKey>, Vec<PubkeyHash>, Vec<hash160::Hash>) {
let keys = pubkeys(13);
let key_hashes =
keys.iter().map(bitcoin::PublicKey::pubkey_hash).collect();
let dummy_hashes = (1..13)
.map(|i| hash160::Hash::from_inner([i; 20]))
.collect();
(keys, key_hashes, dummy_hashes)
}
#[test]
fn test_no_keys_and_hashes() {
let tag = sha256::Hash::hash(b"TEST_TAG");
let (keys, _, dummy_hashes) = gen_test_data();
let sha_hash = sha256::Hash::hash(&"(nearly)random string".as_bytes());
let ms = vec![
ms_str!("older(921)"),
ms_str!("sha256({})", sha_hash),
ms_str!("hash256({})", sha_hash),
ms_str!("hash160({})", dummy_hashes[0]),
ms_str!("ripemd160({})", dummy_hashes[1]),
ms_str!("hash160({})", dummy_hashes[2]),
];
ms.into_iter()
.map(|ms: Miniscript<_, _>| LockScript::from(ms.encode()))
.for_each(|ls| {
assert_eq!(
LockscriptCommitment::embed_commit(
&mut LockscriptContainer {
script: ls,
pubkey: keys[0].key,
tag,
tweaking_factor: None
},
&"Test message"
)
.err(),
Some(Error::LockscriptContainsNoKeys)
);
});
}
#[test]
fn test_unknown_key() {
let tag = sha256::Hash::hash(b"TEST_TAG");
let (keys, _, _) = gen_test_data();
let mut uncompressed = keys[5];
uncompressed.compressed = false;
let ms = vec![
ms_str!("c:pk_k({})", keys[1]),
ms_str!("c:pk_k({})", keys[2]),
ms_str!("c:pk_k({})", keys[3]),
ms_str!("c:pk_k({})", keys[4]),
];
ms.into_iter()
.map(|ms| LockScript::from(ms.encode()))
.for_each(|ls| {
assert_eq!(
LockscriptCommitment::embed_commit(
&mut LockscriptContainer {
script: ls,
pubkey: keys[0].key,
tag,
tweaking_factor: None
},
&"Test message"
)
.err(),
Some(Error::LockscriptKeyNotFound)
);
});
}
#[test]
fn test_unknown_hash() {
let tag = sha256::Hash::hash(b"TEST_TAG");
let (keys, _, _) = gen_test_data();
let ms = vec![
ms_str!("c:pk_h({})", keys[1].pubkey_hash()),
ms_str!("c:pk_h({})", keys[2].pubkey_hash()),
ms_str!("c:pk_h({})", keys[3].pubkey_hash()),
ms_str!("c:pk_h({})", keys[4].pubkey_hash()),
];
ms.into_iter()
.map(|ms| LockScript::from(ms.encode()))
.for_each(|ls| {
assert_eq!(
LockscriptCommitment::embed_commit(
&mut LockscriptContainer {
script: ls,
pubkey: keys[0].key,
tag,
tweaking_factor: None
},
&"Test message"
)
.err(),
Some(Error::LockscriptContainsUnknownHashes)
);
});
}
#[test]
fn test_known_key() {
let tag = sha256::Hash::hash(b"TEST_TAG");
let (keys, _, _) = gen_test_data();
let mut uncompressed = keys[5];
uncompressed.compressed = false;
let ms = vec![
ms_str!("c:pk_k({})", keys[0]),
ms_str!("c:pk_k({})", keys[1]),
ms_str!("c:pk_k({})", keys[2]),
ms_str!("c:pk_k({})", keys[3]),
];
ms.into_iter()
.map(|ms| LockScript::from(ms.encode()))
.enumerate()
.for_each(|(idx, ls)| {
let container = LockscriptContainer {
script: ls,
pubkey: keys[idx].key,
tag,
tweaking_factor: None,
};
let msg = "Test message";
let commitment = LockscriptCommitment::embed_commit(
&mut container.clone(),
&msg,
)
.unwrap();
assert!(commitment.verify(&container, &msg).unwrap());
});
}
#[test]
fn test_known_hash() {
let tag = sha256::Hash::hash(b"TEST_TAG");
let (keys, _, _) = gen_test_data();
let ms = vec![
ms_str!("c:pk_h({})", keys[0].pubkey_hash()),
ms_str!("c:pk_h({})", keys[1].pubkey_hash()),
ms_str!("c:pk_h({})", keys[2].pubkey_hash()),
ms_str!("c:pk_h({})", keys[3].pubkey_hash()),
];
ms.into_iter()
.map(|ms| LockScript::from(ms.encode()))
.enumerate()
.for_each(|(idx, ls)| {
let container = LockscriptContainer {
script: ls,
pubkey: keys[idx].key,
tag,
tweaking_factor: None,
};
let msg = "Test message";
let commitment = LockscriptCommitment::embed_commit(
&mut container.clone(),
&msg,
)
.unwrap();
assert!(commitment.verify(&container, &msg).unwrap())
});
}
#[test]
fn test_multisig() {
let tag = sha256::Hash::hash(b"TEST_TAG");
let (keys, _, _) = gen_test_data();
let ms: Vec<Miniscript<_, Segwitv0>> = vec![
policy_str!("thresh(2,pk({}),pk({}))", keys[0], keys[1],),
policy_str!(
"thresh(3,pk({}),pk({}),pk({}),pk({}),pk({}))",
keys[0],
keys[1],
keys[2],
keys[3],
keys[4]
),
]
.into_iter()
.map(|p| p.compile().unwrap())
.collect();
ms.into_iter()
.map(|ms| LockScript::from(ms.encode()))
.for_each(|ls| {
let container = LockscriptContainer {
script: ls,
pubkey: keys[1].key,
tag,
tweaking_factor: None,
};
let msg = "Test message";
let commitment = LockscriptCommitment::embed_commit(
&mut container.clone(),
&msg,
)
.unwrap();
assert!(commitment.verify(&container, &msg).unwrap())
});
}
#[test]
fn test_complex_scripts_unique_key() {
let tag = sha256::Hash::hash(b"TEST_TAG");
let (keys, _, _) = gen_test_data();
let ms = policy_str!(
"or(thresh(3,pk({}),pk({}),pk({})),and(thresh(2,pk({}),pk({})),older(10000)))",
keys[0],
keys[1],
keys[2],
keys[3],
keys[4],
)
.compile::<Segwitv0>()
.unwrap();
let container = LockscriptContainer {
script: LockScript::from(ms.encode()),
pubkey: keys[1].key,
tag,
tweaking_factor: None,
};
let msg = "Test message";
let commitment =
LockscriptCommitment::embed_commit(&mut container.clone(), &msg)
.unwrap();
assert!(commitment.verify(&container, &msg).unwrap())
}
}