diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index dd24b439..dbc9a99f 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -20,6 +20,14 @@ 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: Set up MSBuild + if: matrix.os == 'windows-latest' + uses: microsoft/setup-msbuild@v1.0.2 + - name: Install stable toolchain uses: dtolnay/rust-toolchain@stable with: @@ -55,6 +63,9 @@ jobs: - name: Checkout sources uses: actions/checkout@v2 + - name: Set up MSBuild + uses: microsoft/setup-msbuild@v1.0.2 + - 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..835f4437 100644 --- a/pallas-crypto/Cargo.toml +++ b/pallas-crypto/Cargo.toml @@ -8,7 +8,11 @@ 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" [dependencies] hex = "0.4" @@ -24,3 +28,9 @@ quickcheck = "1.0" quickcheck_macros = "1.0" rand = "0.8" serde_test = "1.0.143" + +[build-dependencies] +autotools = "0.2" +pkg-config = "0.3" +cc = "1.1" +regex = "1.10" \ 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..592cdeee --- /dev/null +++ b/pallas-crypto/build.rs @@ -0,0 +1,84 @@ +#[cfg(target_os = "windows")] +use std::fs; + +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") + }); + + // if windows + #[cfg(target_os = "windows")] + { + // Build libsodium with MSBuild + run("msbuild", |command| { + command + .current_dir("contrib/libsodium/builds/msvc/vs2019") + .arg("libsodium.sln") + .arg("/p:Configuration=ReleaseLIB") + .arg("/p:Platform=x64") + .arg("/t:Rebuild") + .arg("/m") + .arg("/v:m") + .arg("/nologo") + .arg("/clp:NoSummary;NoItemAndPropertyList;ErrorsOnly") + .arg("/nr:false") + .arg("/fl") + .arg("/flp:NoSummary;NoItemAndPropertyList;ErrorsOnly") + .arg("/p:PlatformToolset=v142") + .arg("/p:WindowsTargetPlatformVersion=10.0") + .arg("/p:PreferredToolArchitecture=x64") + .arg("/p:DefineConstants=SODIUM_STATIC") + }); + // Copy the static libsodium.lib to the target directory so it can be linked + fs::copy( + "contrib/libsodium/builds/msvc/vs2019/x64/Release/v142/static/libsodium.lib", + "target/libsodium.lib", + ) + .expect("Failed to copy libsodium.lib"); + } + + // if not windows + #[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); + } +}