From ea4bcfa22bf241f5350a06da41c818cf12d1dfe7 Mon Sep 17 00:00:00 2001 From: Andrew Westberg Date: Wed, 4 Sep 2024 16:44:14 +0000 Subject: [PATCH] Implement libsodium vrf signature verification --- .github/workflows/validate.yml | 23 +++++- .gitmodules | 3 + pallas-crypto/Cargo.toml | 11 ++- pallas-crypto/README.md | 2 +- pallas-crypto/build.rs | 53 +++++++++++++ pallas-crypto/contrib/libsodium | 1 + pallas-crypto/src/lib.rs | 1 + pallas-crypto/src/vrf/mod.rs | 129 ++++++++++++++++++++++++++++++++ 8 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 .gitmodules create mode 100644 pallas-crypto/build.rs create mode 160000 pallas-crypto/contrib/libsodium create mode 100644 pallas-crypto/src/vrf/mod.rs diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index dd24b439..dea91845 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -20,6 +20,19 @@ jobs: - name: Checkout sources uses: actions/checkout@v2 + - name: Install build dependencies macOS + if: matrix.os == 'macOS-latest' + run: brew install autoconf automake libtool + + - name: Install msys2 Windows + if: matrix.os == 'windows-latest' + uses: msys2/setup-msys2@v2 + + - name: Install build dependencies Windows + shell: cmd + if: matrix.os == 'windows-latest' + run: msys2 -c 'pacman -S --noconfirm base-devel autoconf automake libtool' + - name: Install stable toolchain uses: dtolnay/rust-toolchain@stable with: @@ -27,7 +40,8 @@ jobs: - name: Run cargo check Windows if: matrix.os == 'windows-latest' - run: cargo check --no-default-features --features num + shell: cmd + run: msys2 -c 'cargo check --no-default-features --features num' - name: Run cargo check if: matrix.os != 'windows-latest' @@ -55,6 +69,13 @@ jobs: - name: Checkout sources uses: actions/checkout@v2 + - name: Install msys2 Windows + uses: msys2/setup-msys2@v2 + + - name: Install build dependencies Windows + shell: cmd + run: msys2 -c 'pacman -S --noconfirm base-devel autoconf automake libtool' + - name: Install stable toolchain uses: dtolnay/rust-toolchain@stable with: diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..721ec7cf --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "pallas-crypto/contrib/libsodium"] + path = pallas-crypto/contrib/libsodium + url = https://github.com/input-output-hk/libsodium diff --git a/pallas-crypto/Cargo.toml b/pallas-crypto/Cargo.toml index e0a0b7b7..0d249ff8 100644 --- a/pallas-crypto/Cargo.toml +++ b/pallas-crypto/Cargo.toml @@ -8,7 +8,12 @@ homepage = "https://github.com/txpipe/pallas" documentation = "https://docs.rs/pallas-crypto" license = "Apache-2.0" readme = "README.md" -authors = ["Nicolas Di Prima "] +authors = [ + "Nicolas Di Prima ", + "Andrew Westberg ", +] +build = "build.rs" +links = "libsodium" [dependencies] hex = "0.4" @@ -24,3 +29,7 @@ quickcheck = "1.0" quickcheck_macros = "1.0" rand = "0.8" serde_test = "1.0.143" + +[build-dependencies] +autotools = "0.2" +pkg-config = "0.3" \ No newline at end of file diff --git a/pallas-crypto/README.md b/pallas-crypto/README.md index 367eaa30..e2ab6e17 100644 --- a/pallas-crypto/README.md +++ b/pallas-crypto/README.md @@ -8,7 +8,7 @@ Crate with all the cryptographic material to support Cardano protocol: - [x] Ed25519 Extended asymmetric key pair - [ ] Bip32-Ed25519 key derivation - [ ] BIP39 mnemonics -- [ ] VRF +- [x] VRF - [ ] KES - [ ] SECP256k1 - [x] Nonce calculations diff --git a/pallas-crypto/build.rs b/pallas-crypto/build.rs new file mode 100644 index 00000000..a185e705 --- /dev/null +++ b/pallas-crypto/build.rs @@ -0,0 +1,53 @@ +use std::process::Command; + +macro_rules! ok (($expression:expr) => ($expression.unwrap())); +macro_rules! log { + ($fmt:expr) => (println!(concat!("pallas-crypto/build.rs:{}: ", $fmt), line!())); + ($fmt:expr, $($arg:tt)*) => (println!(concat!("pallas-crypto/build.rs:{}: ", $fmt), + line!(), $($arg)*)); +} + +fn main() { + // Build and link libsodium + run("git", |command| { + command + .arg("submodule") + .arg("update") + .arg("--init") + .arg("--recursive") + .arg("--force") + }); + + // #[cfg(target_os = "windows")] + // { + // run("contrib/libsodium/dist-build/msys2-win64.sh", |command| {}); + // } + // #[cfg(not(target_os = "windows"))] + // { + // Build libsodium automatically (as part of rust build) + let libsodium = autotools::Config::new("contrib/libsodium/") + .reconf("-vfi") + .enable_static() + .disable_shared() + .build(); + println!( + "cargo:rustc-link-search=native={}", + libsodium.join("lib").display() + ); + // } + println!("cargo:rustc-link-lib=static=sodium"); + println!("cargo:rerun-if-changed=build.rs"); +} + +fn run(name: &str, mut configure: F) +where + F: FnMut(&mut Command) -> &mut Command, +{ + let mut command = Command::new(name); + let configured = configure(&mut command); + log!("Executing {:?}", configured); + if !ok!(configured.status()).success() { + panic!("failed to execute {:?}", configured); + } + log!("Command {:?} finished successfully", configured); +} diff --git a/pallas-crypto/contrib/libsodium b/pallas-crypto/contrib/libsodium new file mode 160000 index 00000000..dbb48cce --- /dev/null +++ b/pallas-crypto/contrib/libsodium @@ -0,0 +1 @@ +Subproject commit dbb48cce5429cb6585c9034f002568964f1ce567 diff --git a/pallas-crypto/src/lib.rs b/pallas-crypto/src/lib.rs index 127037eb..0a4a5105 100644 --- a/pallas-crypto/src/lib.rs +++ b/pallas-crypto/src/lib.rs @@ -2,3 +2,4 @@ pub mod hash; pub mod key; pub mod memsec; pub mod nonce; +pub mod vrf; diff --git a/pallas-crypto/src/vrf/mod.rs b/pallas-crypto/src/vrf/mod.rs new file mode 100644 index 00000000..615bf71d --- /dev/null +++ b/pallas-crypto/src/vrf/mod.rs @@ -0,0 +1,129 @@ +use thiserror::Error; + +#[link(name = "sodium", kind = "static")] +extern "C" { + // int crypto_vrf_ietfdraft03_prove(unsigned char *proof, const unsigned char *sk, const unsigned char *m, unsigned long long mlen); + fn crypto_vrf_ietfdraft03_prove(proof: *mut u8, sk: *const u8, m: *const u8, mlen: u64) -> i32; + + // int crypto_vrf_ietfdraft03_proof_to_hash(unsigned char *hash, const unsigned char *proof); + fn crypto_vrf_ietfdraft03_proof_to_hash(hash: *mut u8, proof: *const u8) -> i32; + + // int crypto_vrf_ietfdraft03_verify(unsigned char *output, const unsigned char *pk, const unsigned char *proof, const unsigned char *m, unsigned long long mlen) + fn crypto_vrf_ietfdraft03_verify( + output: *mut u8, + pk: *const u8, + proof: *const u8, + m: *const u8, + mlen: u64, + ) -> i32; +} + +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}")] + Libsodium(String), +} + +/// Sign a seed value with a vrf secret key and produce a proof signature +pub fn sodium_crypto_vrf_prove(secret_key: &[u8], seed: &[u8]) -> Result, Error> { + let mut proof: Vec = Vec::with_capacity(80); + unsafe { + let rc = crypto_vrf_ietfdraft03_prove( + proof.as_mut_ptr(), + secret_key.as_ptr(), + seed.as_ptr(), + seed.len() as u64, + ); + if rc != 0 { + Err(Error::Libsodium(format!( + "libsodium crypto_vrf_ietfdraft03_prove() failed, returned {rc}, expected 0" + ))) + } else { + proof.set_len(80); + Ok(proof) + } + } +} + +/// Convert a proof signature to a hash +pub fn sodium_crypto_vrf_proof_to_hash(proof: &[u8]) -> Result, Error> { + let mut hash: Vec = Vec::with_capacity(64); + unsafe { + let rc = crypto_vrf_ietfdraft03_proof_to_hash(hash.as_mut_ptr(), proof.as_ptr()); + if rc != 0 { + Err(Error::Libsodium(format!( + "libsodium crypto_vrf_ietfdraft03_proof_to_hash() failed, returned {rc}, expected 0" + ))) + } else { + hash.set_len(64); + Ok(hash) + } + } +} + +/// Verify a proof signature with a vrf public key. This will return a hash to compare with the original +/// signature hash. +pub fn sodium_crypto_vrf_verify( + public_key: &[u8], + signature: &[u8], + seed: &[u8], +) -> Result, Error> { + let mut verification: Vec = Vec::with_capacity(64); + unsafe { + let rc = crypto_vrf_ietfdraft03_verify( + verification.as_mut_ptr(), + public_key.as_ptr(), + signature.as_ptr(), + seed.as_ptr(), + seed.len() as u64, + ); + if rc != 0 { + Err(Error::Libsodium(format!( + "libsodium crypto_vrf_ietfdraft03_verify() failed, returned {rc}, expected 0" + ))) + } else { + verification.set_len(64); + Ok(verification) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::{thread_rng, Rng}; + + #[test] + fn sodium_crypto_vrf_prove_and_verify() { + // Node operational VRF-Verification-Key: pool.vrf.vkey + // { + // "type": "VrfVerificationKey_PraosVRF", + // "description": "VRF Verification Key", + // "cborHex": "5820e0ff2371508ac339431b50af7d69cde0f120d952bb876806d3136f9a7fda4381" + // } + // + // Node operational VRF-Signing-Key: pool.vrf.skey + // { + // "type": "VrfSigningKey_PraosVRF", + // "description": "VRF Signing Key", + // "cborHex": "5840adb9c97bec60189aa90d01d113e3ef405f03477d82a94f81da926c90cd46a374e0ff2371508ac339431b50af7d69cde0f120d952bb876806d3136f9a7fda4381" + // } + + let vrf_skey = hex::decode("adb9c97bec60189aa90d01d113e3ef405f03477d82a94f81da926c90cd46a374e0ff2371508ac339431b50af7d69cde0f120d952bb876806d3136f9a7fda4381").unwrap(); + let vrf_vkey = + hex::decode("e0ff2371508ac339431b50af7d69cde0f120d952bb876806d3136f9a7fda4381") + .unwrap(); + + // random seed to sign with vrf_skey + let mut seed = [0u8; 64]; + thread_rng().fill(&mut seed); + + // create a proof signature and hash of the seed + let proof_signature = sodium_crypto_vrf_prove(&vrf_skey, &seed).unwrap(); + let proof_hash = sodium_crypto_vrf_proof_to_hash(&proof_signature).unwrap(); + + // verify the proof signature with the public vrf public key + let verified_hash = sodium_crypto_vrf_verify(&vrf_vkey, &proof_signature, &seed).unwrap(); + assert_eq!(proof_hash, verified_hash); + } +}