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
30 changes: 30 additions & 0 deletions .github/workflows/build_python.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Rust-Python

on:
push:
branches: [ "main", "develop", "staging" ]
pull_request:
branches: [ "main", "develop", "staging" ]

env:
CARGO_TERM_COLOR: always

jobs:
build:

runs-on: ubuntu-latest

steps:
- name: Set up Rust
run: sudo apt-get install cargo && rustup override set 1.81
- name: Set up Python
run: sudo apt-get install python3 python3-dev
- uses: actions/checkout@v4
- name: Set up a Python virtual environment (needed for maturin)
run: python3 -m venv ./venv
- name: Install Python deps
run: ./venv/bin/pip3 install -r ./lib-py/requirements.txt
- name: Build the Rust library, install it as a Python module
run: cd lib-py && VIRTUAL_ENV='../venv' ../venv/bin/maturin develop && cd ..
- name: Run the Python sample app
run: cd samples/python && ../../venv/bin/python main.py && cd ../..
2 changes: 0 additions & 2 deletions .github/workflows/build_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ jobs:
- uses: actions/checkout@v4
- name: Build the library
run: cargo build
- name: Build with pyo3
run: cargo build --features with-pyo3
- name: Run unit tests
run: cargo test
- name: Build and run sample/rust
Expand Down
5 changes: 1 addition & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "dlccryptlib"
version = "0.3.2"
version = "0.9.0"
edition = "2021"
description = "Library for working with DLC's with adaptor signatures (Discrete Log Contracts), by Cadena Bitcoin"
license = "MIT"
Expand All @@ -13,12 +13,9 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
bip39 = "2.1.0"
bitcoin = "0.32.4"
pyo3 = { version = "0.23.1", optional = true }
secp256k1-sys = "0.10.1"
secp256k1-zkp = "0.11.0"

[features]
default = ["std"]
std = ["bitcoin/std", "secp256k1-zkp/rand-std"]
# To compile with Python interfacing methods
with-pyo3 = ["pyo3"]
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ To build and test in Rust:
cargo build && cargo test
```

Publish the library:

```
cargo publish
```

To build the Python-wrapper library:

```
cd lib-py
./venv/bin/pip install -r requirements.txt
VIRTUAL_ENV="./venv" ./venv/bin/maturin develop
```

## Samples

```
Expand Down
14 changes: 14 additions & 0 deletions lib-py/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "dlccryptlib-py"
version = "0.9.0"
edition = "2021"
description = "Python-wrapped library for working with DLC's with adaptor signatures (Discrete Log Contracts), by Cadena Bitcoin"
license = "MIT"

[lib]
name = "dlccryptlib_py"

[dependencies]
#dlccryptlib = "0.9.0"
dlccryptlib = { path = "../" }
pyo3 = { version = "0.23.1" }
2 changes: 2 additions & 0 deletions lib-py/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# for Rust
maturin
192 changes: 192 additions & 0 deletions lib-py/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
use dlccryptlib;

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


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

/// Initialize the library, load secret from encrypted file. Return the XPUB.
#[pyfunction]
pub fn init(path_for_secret_file: String, encryption_password: String) -> PyResult<String> {
dlccryptlib::init_intern(&path_for_secret_file, &encryption_password, false)
.map_err(|e| PyErr::new::<PyException, _>(e))
}

#[pyfunction]
pub fn reinit_for_testing(
path_for_secret_file: String,
encryption_password: String,
) -> PyResult<String> {
dlccryptlib::init_intern(&path_for_secret_file, &encryption_password, true)
.map_err(|e| PyErr::new::<PyException, _>(e))
}

/// network: "bitcoin", or "signet".
// #[cfg(test)]
#[pyfunction]
pub fn init_with_entropy(entropy: String, network: String) -> PyResult<String> {
dlccryptlib::init_with_entropy_intern(&entropy, &network).map_err(|e| PyErr::new::<PyException, _>(e))
}

/// Return the XPUB
#[pyfunction]
pub fn get_xpub() -> PyResult<String> {
dlccryptlib::get_xpub_intern().map_err(|e| PyErr::new::<PyException, _>(e))
}

/// Return a child public key (specified by its index).
#[pyfunction]
pub fn get_public_key(index: u32) -> PyResult<String> {
dlccryptlib::get_public_key_intern(index).map_err(|e| PyErr::new::<PyException, _>(e))
}

/// Return a child address (specified by index).
#[pyfunction]
pub fn get_address(index: u32) -> PyResult<String> {
dlccryptlib::get_address_intern(index).map_err(|e| PyErr::new::<PyException, _>(e))
}

/// Verify a child public key.
#[pyfunction]
pub fn verify_public_key(index: u32, pubkey: String) -> PyResult<bool> {
dlccryptlib::verify_public_key_intern(index, &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_index: u32, signer_pubkey: String) -> PyResult<String> {
dlccryptlib::sign_hash_ecdsa_intern(&hash, signer_index, &signer_pubkey)
.map_err(|e| PyErr::new::<PyException, _>(e))
}

/// Create a nonce value deterministically
#[pyfunction]
pub fn create_deterministic_nonce(
event_id: String,
nonce_index: u32,
) -> PyResult<(String, String)> {
dlccryptlib::create_deterministic_nonce_intern(&event_id, nonce_index)
.map_err(|e| PyErr::new::<PyException, _>(e))
}

/// 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, index: u32) -> PyResult<String> {
dlccryptlib::sign_schnorr_with_nonce_intern(&msg, &nonce_sec_hex, index)
.map_err(|e| PyErr::new::<PyException, _>(e))
}

/// Combine a number of public keys into one
#[pyfunction]
pub fn combine_pubkeys(keys_hex: String) -> PyResult<String> {
dlccryptlib::combine_pubkeys_intern(&keys_hex).map_err(|e| PyErr::new::<PyException, _>(e))
}

/// Combine a number of secret keys into one.
/// Warning: Handle secret keys with caution!
#[pyfunction]
pub fn combine_seckeys(keys_hex: String) -> PyResult<String> {
dlccryptlib::combine_seckeys_intern(&keys_hex).map_err(|e| PyErr::new::<PyException, _>(e))
}

/// Create adaptor signatures for a number of CETs
#[pyfunction]
pub fn create_cet_adaptor_sigs(
num_digits: u8,
num_cets: u64,
digit_string_template: String,
oracle_pubkey: String,
signing_key_index: u32,
signing_pubkey: String,
nonces: String,
interval_wildcards: String,
sighashes: String,
) -> PyResult<String> {
dlccryptlib::create_cet_adaptor_sigs_intern(
num_digits,
num_cets,
&digit_string_template,
&oracle_pubkey,
signing_key_index,
&signing_pubkey,
&nonces,
&interval_wildcards,
&sighashes,
)
.map_err(|e| PyErr::new::<PyException, _>(e))
}

/// Verify adaptor signatures for a number of CETs
#[pyfunction]
pub fn verify_cet_adaptor_sigs(
num_digits: u8,
num_cets: u64,
digit_string_template: String,
oracle_pubkey: String,
signing_pubkey: String,
nonces: String,
interval_wildcards: String,
sighashes: String,
signatures: String,
) -> PyResult<bool> {
dlccryptlib::verify_cet_adaptor_sigs_intern(
num_digits,
num_cets,
&digit_string_template,
&oracle_pubkey,
&signing_pubkey,
&nonces,
&interval_wildcards,
&sighashes,
&signatures,
)
.map_err(|e| PyErr::new::<PyException, _>(e))
}

/// Perform final signing of a CET
#[pyfunction]
pub fn create_final_cet_sigs(
signing_key_index: u32,
signing_pubkey: String,
other_pubkey: String,
num_digits: u8,
oracle_signatures: String,
cet_value_wildcard: String,
cet_sighash: String,
other_adaptor_signature: String,
) -> PyResult<String> {
dlccryptlib::create_final_cet_sigs_intern(
signing_key_index,
&signing_pubkey,
&other_pubkey,
num_digits,
&oracle_signatures,
&cet_value_wildcard,
&cet_sighash,
&other_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)?)?;
m.add_function(wrap_pyfunction!(reinit_for_testing, m)?)?;
m.add_function(wrap_pyfunction!(init_with_entropy, m)?)?;
m.add_function(wrap_pyfunction!(get_xpub, m)?)?;
m.add_function(wrap_pyfunction!(get_public_key, m)?)?;
m.add_function(wrap_pyfunction!(get_address, m)?)?;
m.add_function(wrap_pyfunction!(verify_public_key, m)?)?;
m.add_function(wrap_pyfunction!(sign_hash_ecdsa, m)?)?;
m.add_function(wrap_pyfunction!(create_deterministic_nonce, m)?)?;
m.add_function(wrap_pyfunction!(sign_schnorr_with_nonce, m)?)?;
m.add_function(wrap_pyfunction!(combine_pubkeys, m)?)?;
m.add_function(wrap_pyfunction!(combine_seckeys, m)?)?;
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)?)?;
Ok(())
}

21 changes: 21 additions & 0 deletions samples/python/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import dlccryptlib_py

def sample():
print("Dlccryptlib sample app, Python")

entropy_hex = "99d33a674ce99d33a674ce99d33a674c" # oil x 12
network = "signet";

xpub = dlccryptlib_py.init_with_entropy(entropy_hex, network)
print(f"Library initialized, xpub {xpub}")

pubkey0 = dlccryptlib_py.get_public_key(0)
print(f"pubkey 0: {pubkey0}")

hash = "0001020300000000000000000000000000000000000000000000000000010203"
sig = dlccryptlib_py.sign_hash_ecdsa(hash, 0, pubkey0)
print(f"signature: {sig}")

if __name__ == "__main__":
sample()

2 changes: 2 additions & 0 deletions samples/python/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dlccryptlib-py

2 changes: 1 addition & 1 deletion samples/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ version = "0.1.0"
edition = "2021"

[dependencies]
#dlccryptlib = "0.3.2"
#dlccryptlib = "0.9.0"
dlccryptlib = { path = "../../" }
Loading