use bincode::{Decode, Encode};
#[cfg(feature = "json")]
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use crate::Result;
use crate::error::ConditionError;
pub mod ed25519;
pub mod hashlock;
pub mod secp256k1;
pub mod threshold;
use ed25519::Ed25519;
use hashlock::Hashlock;
use secp256k1::Secp256k1;
use threshold::Threshold;
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "json",
serde(tag = "condition", content = "fulfillment", rename_all = "lowercase")
)]
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
pub enum Condition {
Hashlock(Hashlock),
Ed25519(Ed25519),
Secp256k1(Secp256k1),
Threshold(Threshold),
}
impl Condition {
#[inline]
pub fn verify(&self) -> Result<()> {
match self {
Self::Hashlock(hashlock) => hashlock.verify().map_err(ConditionError::Hashlock)?,
Self::Ed25519(ed25519) => ed25519.verify().map_err(ConditionError::Ed25519)?,
Self::Secp256k1(secp256k1) => secp256k1.verify().map_err(ConditionError::Secp256k1)?,
Self::Threshold(threshold) => threshold.verify().map_err(ConditionError::Threshold)?,
}
Ok(())
}
pub fn hashlock(hash: [u8; 32], preimage: Vec<u8>) -> Self {
Self::Hashlock(Hashlock { hash, preimage })
}
pub fn ed25519(public_key: [u8; 32], message: Vec<u8>, signature: Vec<u8>) -> Self {
Self::Ed25519(Ed25519 {
public_key,
signature,
message,
})
}
pub fn secp256k1(public_key: Vec<u8>, message: Vec<u8>, signature: Vec<u8>) -> Self {
Self::Secp256k1(Secp256k1 {
public_key,
signature,
message,
})
}
pub fn threshold(threshold: usize, subconditions: Vec<Self>) -> Self {
Self::Threshold(Threshold {
threshold,
subconditions,
})
}
pub fn commitment(&self) -> [u8; 32] {
let mut hasher = Sha256::new();
match self {
Self::Hashlock(hashlock) => {
hasher.update(b"ZESCROW_COND_HASHLOCK_V1");
hasher.update(hashlock.hash);
}
Self::Ed25519(ed25519) => {
hasher.update(b"ZESCROW_COND_ED25519_V1");
hasher.update(ed25519.public_key);
hasher.update(&ed25519.message);
}
Self::Secp256k1(secp256k1) => {
hasher.update(b"ZESCROW_COND_SECP256K1_V1");
hasher.update(&secp256k1.public_key);
hasher.update(&secp256k1.message);
}
Self::Threshold(threshold) => {
hasher.update(b"ZESCROW_COND_THRESHOLD_V1");
hasher.update(saturating_u32(threshold.threshold).to_be_bytes());
hasher.update(saturating_u32(threshold.subconditions.len()).to_be_bytes());
threshold
.subconditions
.iter()
.for_each(|sub| hasher.update(sub.commitment()));
}
}
hasher.finalize().into()
}
}
fn saturating_u32(value: usize) -> u32 {
u32::try_from(value).unwrap_or(u32::MAX)
}
#[cfg(feature = "json")]
impl std::fmt::Display for Condition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let json = serde_json::to_string(self).map_err(|_| std::fmt::Error)?;
write!(f, "{json}")
}
}
#[cfg(test)]
mod tests {
use sha2::{Digest, Sha256};
use super::*;
#[test]
fn preimage() {
let preimage = b"secret".to_vec();
let hash = Sha256::digest(&preimage).into();
let cond = Condition::hashlock(hash, preimage);
assert!(cond.verify().is_ok());
let cond = Condition::hashlock(hash, b"wrong-secret".to_vec());
assert!(cond.verify().is_err());
}
#[test]
fn ed25519() {
use ed25519_dalek::ed25519::signature::rand_core::OsRng;
use ed25519_dalek::{Signer, SigningKey};
let mut csprng = OsRng;
let sk: SigningKey = SigningKey::generate(&mut csprng);
let message = b"zkEscrow".to_vec();
let signature = sk.sign(&message).to_bytes().to_vec();
let public_key = sk.verifying_key().to_bytes();
let cond = Condition::ed25519(public_key, message.clone(), signature.clone());
assert!(cond.verify().is_ok());
let mut signature = signature;
signature[0] ^= 0xFF;
let cond = Condition::ed25519(public_key, message, signature);
assert!(cond.verify().is_err());
}
#[test]
fn secp256k1() {
use k256::ecdsa::signature::Signer;
use k256::ecdsa::{Signature, SigningKey};
use k256::elliptic_curve::rand_core::OsRng;
let sk = SigningKey::random(&mut OsRng);
let vk = sk.verifying_key();
let message = b"zkEscrow".to_vec();
let signature: Signature = sk.sign(&message);
let sig_bytes = signature.to_der().as_bytes().to_vec();
let pk_bytes = vk.to_encoded_point(false).as_bytes().to_vec();
let cond = Condition::secp256k1(pk_bytes.clone(), message, sig_bytes.clone());
assert!(cond.verify().is_ok());
let cond = Condition::secp256k1(pk_bytes, b"tampered".to_vec(), sig_bytes);
assert!(cond.verify().is_err());
}
#[test]
fn nonzero_threshold() {
let hash = Sha256::digest(b"zkEscrow").into();
let correct = Condition::hashlock(hash, b"zkEscrow".to_vec());
let wrong = Condition::hashlock(hash, b"wrong-preimage".to_vec());
let cond = Condition::threshold(1, vec![correct.clone(), wrong.clone()]);
assert!(cond.verify().is_ok());
let cond = Condition::threshold(2, vec![correct, wrong]);
assert!(cond.verify().is_err());
let cond = Condition::threshold(1, vec![]);
assert!(cond.verify().is_err());
}
#[test]
fn zero_threshold_rejected() {
let cond = Condition::threshold(0, vec![]);
assert!(cond.verify().is_err());
let preimage = b"zkEscrow".to_vec();
let hash = Sha256::digest(&preimage).into();
let subcond = Condition::hashlock(hash, preimage);
let cond = Condition::threshold(0, vec![subcond]);
assert!(cond.verify().is_err());
}
#[test]
fn threshold_exceeding_subconditions_rejected() {
let preimage = b"zkEscrow".to_vec();
let hash = Sha256::digest(&preimage).into();
let subcond = Condition::hashlock(hash, preimage);
let cond = Condition::threshold(2, vec![subcond]);
assert!(cond.verify().is_err());
}
#[test]
fn nested_thresholds() {
let preimage = b"zkEscrow".to_vec();
let hash = Sha256::digest(&preimage).into();
let leaf = Condition::hashlock(hash, preimage);
let inner = Condition::threshold(1, vec![leaf.clone()]);
let outer = Condition::threshold(1, vec![inner]);
assert!(outer.verify().is_ok());
let wrong_leaf = Condition::hashlock(hash, b"wrong-preimage".to_vec());
let inner2 = Condition::threshold(1, vec![wrong_leaf]);
let outer2 = Condition::threshold(1, vec![inner2]);
assert!(outer2.verify().is_err());
}
#[cfg(feature = "json")]
#[test]
fn json_roundtrip_hashlock() {
let preimage = b"secret".to_vec();
let hash = Sha256::digest(&preimage).into();
let cond = Condition::hashlock(hash, preimage);
let json = serde_json::to_string(&cond).unwrap();
let decoded: Condition = serde_json::from_str(&json).unwrap();
assert_eq!(decoded, cond);
}
#[cfg(feature = "json")]
#[test]
fn json_roundtrip_threshold() {
let preimage = b"nested".to_vec();
let hash = Sha256::digest(&preimage).into();
let inner = Condition::hashlock(hash, preimage);
let cond = Condition::threshold(1, vec![inner]);
let json = serde_json::to_string(&cond).unwrap();
let decoded: Condition = serde_json::from_str(&json).unwrap();
assert_eq!(decoded, cond);
}
}