Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "dlccryptlib"
version = "1.9.1"
version = "1.9.5"
edition = "2021"
description = "Library for working with DLC's with adaptor signatures (Discrete Log Contracts), by Cadena Bitcoin"
license = "MIT"
Expand Down
51 changes: 44 additions & 7 deletions lib-py/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use dlccryptlib;

use pyo3::prelude::*;
use pyo3::exceptions::PyException;
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;


// ##### Facade functions for easy Python invocations (pyo3/maturin)

/// Initialize the library, load secret from encrypted file. Return the XPUB.
Expand Down Expand Up @@ -51,12 +50,18 @@ pub fn get_address(index4: u32, index5: u32) -> PyResult<String> {
/// Verify a child public key.
#[pyfunction]
pub fn verify_public_key(index4: u32, index5: u32, pubkey: String) -> PyResult<bool> {
dlccryptlib::verify_public_key(index4, index5, &pubkey).map_err(|e| PyErr::new::<PyException, _>(e))
dlccryptlib::verify_public_key(index4, index5, &pubkey)
.map_err(|e| PyErr::new::<PyException, _>(e))
}

/// Sign a hash with a child private key (specified by index).
#[pyfunction]
pub fn sign_hash_ecdsa(hash: String, signer_index4: u32, signer_index5: u32, signer_pubkey: String) -> PyResult<String> {
pub fn sign_hash_ecdsa(
hash: String,
signer_index4: u32,
signer_index5: u32,
signer_pubkey: String,
) -> PyResult<String> {
dlccryptlib::sign_hash_ecdsa(&hash, signer_index4, signer_index5, &signer_pubkey)
.map_err(|e| PyErr::new::<PyException, _>(e))
}
Expand All @@ -73,14 +78,24 @@ pub fn create_deterministic_nonce(

/// Sign a message using Schnorr, with a nonce, using a child key
#[pyfunction]
pub fn sign_schnorr_with_nonce(msg: String, nonce_sec_hex: String, index4: u32, index5: u32) -> PyResult<String> {
pub fn sign_schnorr_with_nonce(
msg: String,
nonce_sec_hex: String,
index4: u32,
index5: u32,
) -> PyResult<String> {
dlccryptlib::sign_schnorr_with_nonce(&msg, &nonce_sec_hex, index4, index5)
.map_err(|e| PyErr::new::<PyException, _>(e))
}

/// Verify a Schnorr signature over a message, using a child key
#[pyfunction]
pub fn verify_schnorr(msg: String, signature_hex: String, index4: u32, index5: u32) -> PyResult<bool> {
pub fn verify_schnorr(
msg: String,
signature_hex: String,
index4: u32,
index5: u32,
) -> PyResult<bool> {
dlccryptlib::verify_schnorr(&msg, &signature_hex, index4, index5)
.map_err(|e| PyErr::new::<PyException, _>(e))
}
Expand Down Expand Up @@ -181,6 +196,28 @@ pub fn create_final_cet_sigs(
.map_err(|e| PyErr::new::<PyException, _>(e))
}

/// Perform final signing of a CET, decrypt a signature when outcome signatures are available.
/// Return the decrypted signature.
#[pyfunction]
pub fn create_final_cet_sig(
pubkey: String,
num_digits: u8,
oracle_signatures: String,
cet_value_wildcard: String,
cet_sighash: String,
adaptor_signature: String,
) -> PyResult<String> {
dlccryptlib::create_final_cet_sig(
&pubkey,
num_digits,
&oracle_signatures,
&cet_value_wildcard,
&cet_sighash,
&adaptor_signature,
)
.map_err(|e| PyErr::new::<PyException, _>(e))
}

#[pymodule]
fn dlccryptlib_py(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(init, m)?)?;
Expand All @@ -199,6 +236,6 @@ fn dlccryptlib_py(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(create_cet_adaptor_sigs, m)?)?;
m.add_function(wrap_pyfunction!(verify_cet_adaptor_sigs, m)?)?;
m.add_function(wrap_pyfunction!(create_final_cet_sigs, m)?)?;
m.add_function(wrap_pyfunction!(create_final_cet_sig, m)?)?;
Ok(())
}

55 changes: 55 additions & 0 deletions src/adaptor_signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,3 +498,58 @@ pub fn create_final_cet_signatures<S: Signing>(

Ok((adapted_sig, my_sig))
}

/// Decrypt a signature on a CET when outcome signatures are available.
/// Return the decrypted signature.
pub fn create_final_cet_signature(
pubkey: &PublicKey,
num_digits: u8,
oracle_signatures: &Vec<SchnorrSignature>,
cet_value_wildcard: &str,
cet_sighash: &[u8; 32],
adaptor_signature: &EcdsaAdaptorSignature,
) -> Result<Vec<u8>, String> {
// Decompose oracle signatures
if oracle_signatures.len() != num_digits as usize {
return Err(format!(
"Wrong number of oracle signatures {} {}",
oracle_signatures.len(),
num_digits
));
}
debug_assert_eq!(oracle_signatures.len(), num_digits as usize);
let wildcard = cet_value_wildcard.as_bytes();
let mut adaptor_secret_vec = Vec::new();
for d in 0..num_digits {
let ch = wildcard[d as usize];
if ch >= 48 && ch <= 57 {
let (_nonce, secret_value) = schnorrsig_decompose(&oracle_signatures[d as usize])
.map_err(|e| format!("Error decomposing Schnorr signature {}", e.to_string()))?;
let adaptor_secret = SecretKey::from_slice(secret_value).map_err(|e| {
format!(
"Error retrieving adaptor secret from signature {}",
e.to_string()
)
})?;
adaptor_secret_vec.push(adaptor_secret);
}
}
let adaptor_secret_aggregate = aggregate_secret_values(&adaptor_secret_vec)?;

// Adaptor signature, from the other
let mut adapted_sig = adaptor_signature
.decrypt(&adaptor_secret_aggregate)
.map_err(|e| format!("Error in adaptor signature decryption {}", e.to_string()))?
.serialize_der()
.to_vec();
adapted_sig.push(EcdsaSighashType::All as u8);
// verify signature
let _res = verify_ecdsa_signature(cet_sighash, &adapted_sig, &pubkey, true).map_err(|e| {
format!(
"Adaptor-derived signature verification failed {}",
e.to_string()
)
})?;

Ok(adapted_sig)
}
48 changes: 48 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,54 @@ pub fn create_final_cet_sigs(
Ok(sigs)
}

pub fn create_final_cet_sig(
pubkey_str: &str,
num_digits: u8,
oracle_signatures_str: &str,
cet_value_wildcard: &str,
cet_sighash_str: &str,
adaptor_signature_str: &str,
) -> Result<String, String> {
let pubkey =
pubkey_from_hex(pubkey_str).map_err(|e| format!("Failed to parse other pubkey {}", e))?;

let sigs_split: Vec<_> = oracle_signatures_str.split(" ").collect();
let mut sigs = Vec::<SchnorrSignature>::with_capacity(sigs_split.len());
for i in 0..sigs_split.len() {
let sig_hex = sigs_split[i].trim();
if sig_hex.len() > 0 {
let sig = schnorr_sig_from_hex(&sig_hex)
.map_err(|e| format!("Failed to parse element {} {}", i, e))?;
sigs.push(sig);
}
}
if sigs.len() != num_digits as usize {
return Err(format!(
"Wrong number of signatures {} {}",
sigs.len(),
num_digits
));
}

let cet_sighash =
hash_from_hex(cet_sighash_str).map_err(|e| format!("Failed to parse sighash {}", e))?;

let adaptor_signature_bin = Vec::from_hex(adaptor_signature_str)
.map_err(|e| format!("Failed to parse adaptor sig {}", e))?;
let adaptor_signature = EcdsaAdaptorSignature::from_slice(&adaptor_signature_bin)
.map_err(|e| format!("Failed to parse adaptor sig {}", e))?;
let sig = global_lib().read().unwrap().create_final_cet_sig(
&pubkey,
num_digits,
&sigs,
cet_value_wildcard,
&cet_sighash,
&adaptor_signature,
)?;

Ok(sig.to_lower_hex_string())
}

// ##### Facade functions for C-style-interface invocations

/// Initialize the library, provide the secret as parameter. Return the XPUB.
Expand Down
26 changes: 24 additions & 2 deletions src/lib_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

use crate::adaptor_signature::{
create_cet_adaptor_signatures, create_final_cet_signatures, sign_hash_ecdsa_with_key,
sign_schnorr_with_nonce_sec, verify_cet_adaptor_signatures, verify_schnorr,
create_cet_adaptor_signatures, create_final_cet_signature, create_final_cet_signatures,
sign_hash_ecdsa_with_key, sign_schnorr_with_nonce_sec, verify_cet_adaptor_signatures,
verify_schnorr,
};
use crate::hd_wallet_storage::HDWalletStorage;

Expand Down Expand Up @@ -282,6 +283,27 @@ impl Lib {
other_adaptor_signature,
)
}

/// Decrypt a signature on a CET when outcome signatures are available.
/// Return the decrypted signature.
pub fn create_final_cet_sig(
&self,
pubkey: &PublicKey,
num_digits: u8,
oracle_signatures: &Vec<SchnorrSignature>,
cet_value_wildcard: &str,
cet_sighash: &[u8; 32],
adaptor_signature: &EcdsaAdaptorSignature,
) -> Result<Vec<u8>, String> {
create_final_cet_signature(
pubkey,
num_digits,
oracle_signatures,
cet_value_wildcard,
cet_sighash,
adaptor_signature,
)
}
}

pub(crate) fn global_lib() -> &'static RwLock<Lib> {
Expand Down
120 changes: 119 additions & 1 deletion src/test_lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
init_with_entropy, keypair_from_sec_key_hex, sign_schnorr_with_nonce, verify_public_key,
verify_schnorr, Lib,
};
use bitcoin::hex::FromHex;
use bitcoin::hex::{DisplayHex, FromHex};
use bitcoin::secp256k1::PublicKey;
use secp256k1_zkp::schnorr::Signature as SchnorrSignature;
use secp256k1_zkp::EcdsaAdaptorSignature;
Expand Down Expand Up @@ -533,3 +533,121 @@ fn test_create_final_cet_sigs() {
let verif_res2 = verify_ecdsa_signature(&sighash, &my_sig, &my_pubkey, true).unwrap();
assert!(verif_res2);
}

#[test]
fn test_create_final_cet_sig_full() {
let event_id = "btcusd1741474920";
let digits_template_string = "Outcome:btcusd1741474920:{digit_index}:{digit_outcome}";
let digits = 4 as u8;
let final_cet_wildcard = "9534";
let sighash = dummy_bytes32(7);

// First preparation: create oracle signatures
let mut lib_ora = Lib::new_empty();
let _xpub = lib_ora.init_with_entropy(&dummy_bytes32(3).to_vec(), DEFAULT_NETWORK);
let oracle_pubkey = lib_ora.get_child_public_key(0, 0).unwrap();
assert_eq!(
oracle_pubkey.to_string(),
"020a5e571a47cc259d3cc0454a8b7e58bba16e01156bb72d0ce490823f51117cce"
);

// Prepare nonces
let mut nonces_sec_vec = Vec::new();
let mut nonces_pub_vec = Vec::new();
let mut nonces = String::new();
for i in 0..(digits as usize) {
let (nsec, npub) = lib_ora
.create_deterministic_nonce(event_id, i as u32)
.unwrap();
nonces_sec_vec.push(<[u8; 32]>::from_hex(&nsec).unwrap());
nonces_pub_vec.push(npub.clone());
nonces += &format!("{} ", npub);
}
assert_eq!(nonces, "03829589a7db8530b521577ce5b9560e31cb29b943927b417c580ec3b6e57317a9 0278d6b6808d5370da62c9304f66415c1f9f408a2ee9d95a9dc836512218a7b04f 027c14675c2bd2e728e5760d5017d4ae2b22a4a33193689654e5eb13111ab7f491 02e7657c7d006d27b248642974875348b41299690a6415bc019ac71e6988434daa ");

let mut oracle_signatures = Vec::new();
for i in 0..(digits as usize) {
let digit_value = final_cet_wildcard.chars().collect::<Vec<_>>()[i as usize];
let digit_string = digits_template_string
.replace("{digit_index}", &format!("{}", i))
.replace("{digit_outcome}", &format!("{}", digit_value));
let sig = lib_ora
.sign_schnorr_with_nonce(&digit_string, &nonces_sec_vec[i], 0, 0)
.unwrap();
oracle_signatures.push(sig);
}

let mut lib1 = Lib::new_empty();
let _xpub = lib1
.init_with_entropy(&dummy_bytes32(1).to_vec(), DEFAULT_NETWORK)
.unwrap();
let my_pubkey = lib1.get_child_public_key(0, 0).unwrap();
assert_eq!(
my_pubkey.to_string(),
"035bcac7323e9971268213a188d8268277abcd962cdf096e68e2b58c228216f104"
);

// Create adaptor sig
// only use one CET
let adaptor_sigs_vec = lib1
.create_cet_adaptor_sigs(
digits,
1, // num_cets
digits_template_string,
&oracle_pubkey,
0,
0,
&my_pubkey,
&nonces_pub_vec,
&vec![final_cet_wildcard.to_string()], // interval_wildcards
&vec![sighash],
)
.unwrap();
let adaptor_sig = adaptor_sigs_vec[0];
// adator sig is variable, cannot assert
assert_eq!(adaptor_sig.to_string().len(), 324);

// And now decode the CET signature
let my_final_sig = lib1
.create_final_cet_sig(
&my_pubkey,
digits,
&oracle_signatures,
final_cet_wildcard,
&sighash,
&adaptor_sig,
)
.unwrap();
// Result is variable, cannot assert
assert!(my_final_sig.len() >= 70 && my_final_sig.len() <= 72);
}

#[test]
fn test_create_final_cet_sig() {
let digits = 4 as u8;
let final_cet_wildcard = "9534";
let sighash = dummy_bytes32(7);
let oracle_signatures = vec![
SchnorrSignature::from_str("829589a7db8530b521577ce5b9560e31cb29b943927b417c580ec3b6e57317a91e36a673186742bd75a85f6751473fd10155f44fec71a62e95ca6e2f6a436de6").unwrap(),
SchnorrSignature::from_str("78d6b6808d5370da62c9304f66415c1f9f408a2ee9d95a9dc836512218a7b04fd67ccbe7eefd9cd96c396a64b5ddc8e6a42d82e8e753ced18e162d238787da50").unwrap(),
SchnorrSignature::from_str("7c14675c2bd2e728e5760d5017d4ae2b22a4a33193689654e5eb13111ab7f491f5889b410410014ddc5bef26d55b3cfd6f3c0bec338fc8c29edc323da3e301e1").unwrap(),
SchnorrSignature::from_str("e7657c7d006d27b248642974875348b41299690a6415bc019ac71e6988434daaf722c5e3e015c26180d7a5e5543f6668297d188536e98b9de6313df4c5ca5f60").unwrap(),
];
let my_pubkey =
PublicKey::from_str("035bcac7323e9971268213a188d8268277abcd962cdf096e68e2b58c228216f104")
.unwrap();
let adaptor_sig = EcdsaAdaptorSignature::from_str("02b916af8ad219724712ec349ed2a0cb200efe228b8ad4bcbe048aa41d687b07c5039325102a25ead4dd1ce4cccd0fe49d444c041a2f230ee0469eb3f3cf34efe41deb736f677a9fbd248e8fcd6afae74c418fc8f791075f62d9ec66f03a761be15b7e803a66e5b57db2920cfaf5343bd68027ca01689967aa316c2af9ca75c35e68338436fb24963572af2cc762b8666653645282569b16997324cf884375ca6684").unwrap();

let lib = Lib::new_empty();
let final_sig = lib
.create_final_cet_sig(
&my_pubkey,
digits,
&oracle_signatures,
final_cet_wildcard,
&sighash,
&adaptor_sig,
)
.unwrap();
assert_eq!(final_sig.to_lower_hex_string(), "3045022100b916af8ad219724712ec349ed2a0cb200efe228b8ad4bcbe048aa41d687b07c502202fe73c65c04abd4394375f4225ae11e0dfad77362898497e4d79b0df0b2b8fdf01");
}
Loading