diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index dd24b439..221ffc3b 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -20,6 +20,10 @@ 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 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..3db4f461 --- /dev/null +++ b/pallas-crypto/build.rs @@ -0,0 +1,205 @@ +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 automatically (as part of rust build) + generate_version_h(); + cc::Build::new() + .include("contrib/libsodium/src/libsodium/include") + .include("contrib/libsodium/src/libsodium/include/sodium") + .file("contrib/libsodium/src/libsodium/crypto_auth/hmacsha512/auth_hmacsha512.c") + .file("contrib/libsodium/src/libsodium/crypto_auth/crypto_auth.c") + .file("contrib/libsodium/src/libsodium/crypto_auth/hmacsha512256/auth_hmacsha512256.c") + .file("contrib/libsodium/src/libsodium/crypto_auth/hmacsha256/auth_hmacsha256.c") + .file("contrib/libsodium/src/libsodium/crypto_secretbox/crypto_secretbox.c") + .file("contrib/libsodium/src/libsodium/crypto_secretbox/xchacha20poly1305/secretbox_xchacha20poly1305.c") + .file("contrib/libsodium/src/libsodium/crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305.c") + .file("contrib/libsodium/src/libsodium/crypto_secretbox/crypto_secretbox_easy.c") + .file("contrib/libsodium/src/libsodium/crypto_core/salsa/ref/core_salsa_ref.c") + .file("contrib/libsodium/src/libsodium/crypto_core/hsalsa20/ref2/core_hsalsa20_ref2.c") + .file("contrib/libsodium/src/libsodium/crypto_core/hsalsa20/core_hsalsa20.c") + .file("contrib/libsodium/src/libsodium/crypto_core/hchacha20/core_hchacha20.c") + .file("contrib/libsodium/src/libsodium/crypto_core/ed25519/core_h2c.c") + .file("contrib/libsodium/src/libsodium/crypto_core/ed25519/core_ristretto255.c") + .file("contrib/libsodium/src/libsodium/crypto_core/ed25519/core_ed25519.c") + .file("contrib/libsodium/src/libsodium/crypto_core/ed25519/ref10/ed25519_ref10.c") + .file("contrib/libsodium/src/libsodium/crypto_hash/sha256/hash_sha256.c") + .file("contrib/libsodium/src/libsodium/crypto_hash/sha256/cp/hash_sha256_cp.c") + .file("contrib/libsodium/src/libsodium/crypto_hash/sha512/cp/hash_sha512_cp.c") + .file("contrib/libsodium/src/libsodium/crypto_hash/sha512/hash_sha512.c") + .file("contrib/libsodium/src/libsodium/crypto_hash/crypto_hash.c") + .file("contrib/libsodium/src/libsodium/crypto_onetimeauth/crypto_onetimeauth.c") + .file("contrib/libsodium/src/libsodium/crypto_onetimeauth/poly1305/donna/poly1305_donna.c") + .file("contrib/libsodium/src/libsodium/crypto_onetimeauth/poly1305/sse2/poly1305_sse2.c") + .file("contrib/libsodium/src/libsodium/crypto_onetimeauth/poly1305/onetimeauth_poly1305.c") + .file("contrib/libsodium/src/libsodium/crypto_vrf/crypto_vrf.c") + .file("contrib/libsodium/src/libsodium/crypto_vrf/ietfdraft03/verify.c") + .file("contrib/libsodium/src/libsodium/crypto_vrf/ietfdraft03/prove.c") + .file("contrib/libsodium/src/libsodium/crypto_vrf/ietfdraft03/vrf.c") + .file("contrib/libsodium/src/libsodium/crypto_vrf/ietfdraft13/verify.c") + .file("contrib/libsodium/src/libsodium/crypto_vrf/ietfdraft13/prove.c") + .file("contrib/libsodium/src/libsodium/crypto_vrf/ietfdraft13/vrf.c") + .file("contrib/libsodium/src/libsodium/crypto_scalarmult/curve25519/sandy2x/fe_frombytes_sandy2x.c") + .file("contrib/libsodium/src/libsodium/crypto_scalarmult/curve25519/sandy2x/curve25519_sandy2x.c") + .file("contrib/libsodium/src/libsodium/crypto_scalarmult/curve25519/sandy2x/fe51_invert.c") + .file("contrib/libsodium/src/libsodium/crypto_scalarmult/curve25519/scalarmult_curve25519.c") + .file("contrib/libsodium/src/libsodium/crypto_scalarmult/curve25519/ref10/x25519_ref10.c") + .file("contrib/libsodium/src/libsodium/crypto_scalarmult/ed25519/ref10/scalarmult_ed25519_ref10.c") + .file("contrib/libsodium/src/libsodium/crypto_scalarmult/ristretto255/ref10/scalarmult_ristretto255_ref10.c") + .file("contrib/libsodium/src/libsodium/crypto_scalarmult/crypto_scalarmult.c") + .file("contrib/libsodium/src/libsodium/crypto_sign/crypto_sign.c") + .file("contrib/libsodium/src/libsodium/crypto_sign/ed25519/sign_ed25519.c") + .file("contrib/libsodium/src/libsodium/crypto_sign/ed25519/ref10/obsolete.c") + .file("contrib/libsodium/src/libsodium/crypto_sign/ed25519/ref10/sign.c") + .file("contrib/libsodium/src/libsodium/crypto_sign/ed25519/ref10/open.c") + .file("contrib/libsodium/src/libsodium/crypto_sign/ed25519/ref10/keypair.c") + .file("contrib/libsodium/src/libsodium/crypto_generichash/crypto_generichash.c") + .file("contrib/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-compress-sse41.c") + .file("contrib/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-compress-avx2.c") + .file("contrib/libsodium/src/libsodium/crypto_generichash/blake2b/ref/generichash_blake2b.c") + .file("contrib/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c") + .file("contrib/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-compress-ref.c") + .file("contrib/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-compress-ssse3.c") + .file("contrib/libsodium/src/libsodium/crypto_generichash/blake2b/generichash_blake2.c") + .file("contrib/libsodium/src/libsodium/crypto_secretstream/xchacha20poly1305/secretstream_xchacha20poly1305.c") + .file("contrib/libsodium/src/libsodium/crypto_kdf/blake2b/kdf_blake2b.c") + .file("contrib/libsodium/src/libsodium/crypto_kdf/crypto_kdf.c") + .file("contrib/libsodium/src/libsodium/randombytes/sysrandom/randombytes_sysrandom.c") + .file("contrib/libsodium/src/libsodium/randombytes/randombytes.c") + .file("contrib/libsodium/src/libsodium/randombytes/internal/randombytes_internal_random.c") + .file("contrib/libsodium/src/libsodium/crypto_pwhash/crypto_pwhash.c") + .file("contrib/libsodium/src/libsodium/crypto_pwhash/argon2/argon2-encoding.c") + .file("contrib/libsodium/src/libsodium/crypto_pwhash/argon2/pwhash_argon2i.c") + .file("contrib/libsodium/src/libsodium/crypto_pwhash/argon2/argon2-fill-block-avx2.c") + .file("contrib/libsodium/src/libsodium/crypto_pwhash/argon2/argon2-fill-block-ref.c") + .file("contrib/libsodium/src/libsodium/crypto_pwhash/argon2/argon2-fill-block-avx512f.c") + .file("contrib/libsodium/src/libsodium/crypto_pwhash/argon2/blake2b-long.c") + .file("contrib/libsodium/src/libsodium/crypto_pwhash/argon2/argon2-fill-block-ssse3.c") + .file("contrib/libsodium/src/libsodium/crypto_pwhash/argon2/pwhash_argon2id.c") + .file("contrib/libsodium/src/libsodium/crypto_pwhash/argon2/argon2-core.c") + .file("contrib/libsodium/src/libsodium/crypto_pwhash/argon2/argon2.c") + .file("contrib/libsodium/src/libsodium/crypto_pwhash/scryptsalsa208sha256/scrypt_platform.c") + .file("contrib/libsodium/src/libsodium/crypto_pwhash/scryptsalsa208sha256/sse/pwhash_scryptsalsa208sha256_sse.c") + .file("contrib/libsodium/src/libsodium/crypto_pwhash/scryptsalsa208sha256/crypto_scrypt-common.c") + .file("contrib/libsodium/src/libsodium/crypto_pwhash/scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c") + .file("contrib/libsodium/src/libsodium/crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.c") + .file("contrib/libsodium/src/libsodium/crypto_pwhash/scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c") + .file("contrib/libsodium/src/libsodium/crypto_kx/crypto_kx.c") + .file("contrib/libsodium/src/libsodium/sodium/core.c") + .file("contrib/libsodium/src/libsodium/sodium/utils.c") + .file("contrib/libsodium/src/libsodium/sodium/runtime.c") + .file("contrib/libsodium/src/libsodium/sodium/version.c") + .file("contrib/libsodium/src/libsodium/sodium/codecs.c") + .file("contrib/libsodium/src/libsodium/crypto_aead/xchacha20poly1305/sodium/aead_xchacha20poly1305.c") + .file("contrib/libsodium/src/libsodium/crypto_aead/aes256gcm/aesni/aead_aes256gcm_aesni.c") + .file("contrib/libsodium/src/libsodium/crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c") + .file("contrib/libsodium/src/libsodium/crypto_box/crypto_box.c") + .file("contrib/libsodium/src/libsodium/crypto_box/curve25519xsalsa20poly1305/box_curve25519xsalsa20poly1305.c") + .file("contrib/libsodium/src/libsodium/crypto_box/curve25519xchacha20poly1305/box_curve25519xchacha20poly1305.c") + .file("contrib/libsodium/src/libsodium/crypto_box/curve25519xchacha20poly1305/box_seal_curve25519xchacha20poly1305.c") + .file("contrib/libsodium/src/libsodium/crypto_box/crypto_box_easy.c") + .file("contrib/libsodium/src/libsodium/crypto_box/crypto_box_seal.c") + .file("contrib/libsodium/src/libsodium/crypto_stream/salsa208/ref/stream_salsa208_ref.c") + .file("contrib/libsodium/src/libsodium/crypto_stream/salsa208/stream_salsa208.c") + .file("contrib/libsodium/src/libsodium/crypto_stream/xsalsa20/stream_xsalsa20.c") + .file("contrib/libsodium/src/libsodium/crypto_stream/chacha20/ref/chacha20_ref.c") + .file("contrib/libsodium/src/libsodium/crypto_stream/chacha20/stream_chacha20.c") + .file("contrib/libsodium/src/libsodium/crypto_stream/chacha20/dolbeau/chacha20_dolbeau-ssse3.c") + .file("contrib/libsodium/src/libsodium/crypto_stream/chacha20/dolbeau/chacha20_dolbeau-avx2.c") + .file("contrib/libsodium/src/libsodium/crypto_stream/crypto_stream.c") + .file("contrib/libsodium/src/libsodium/crypto_stream/salsa20/ref/salsa20_ref.c") + .file("contrib/libsodium/src/libsodium/crypto_stream/salsa20/stream_salsa20.c") + .file("contrib/libsodium/src/libsodium/crypto_stream/salsa20/xmm6int/salsa20_xmm6int-avx2.c") + .file("contrib/libsodium/src/libsodium/crypto_stream/salsa20/xmm6int/salsa20_xmm6int-sse2.c") + .file("contrib/libsodium/src/libsodium/crypto_stream/salsa20/xmm6/salsa20_xmm6.c") + .file("contrib/libsodium/src/libsodium/crypto_stream/xchacha20/stream_xchacha20.c") + .file("contrib/libsodium/src/libsodium/crypto_stream/salsa2012/ref/stream_salsa2012_ref.c") + .file("contrib/libsodium/src/libsodium/crypto_stream/salsa2012/stream_salsa2012.c") + .file("contrib/libsodium/src/libsodium/crypto_verify/sodium/verify.c") + .file("contrib/libsodium/src/libsodium/crypto_shorthash/siphash24/ref/shorthash_siphash24_ref.c") + .file("contrib/libsodium/src/libsodium/crypto_shorthash/siphash24/ref/shorthash_siphashx24_ref.c") + .file("contrib/libsodium/src/libsodium/crypto_shorthash/siphash24/shorthash_siphash24.c") + .file("contrib/libsodium/src/libsodium/crypto_shorthash/siphash24/shorthash_siphashx24.c") + .file("contrib/libsodium/src/libsodium/crypto_shorthash/crypto_shorthash.c") + .compile("sodium"); + } + + // 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"); +} + +#[cfg(target_os = "windows")] +fn generate_version_h() { + let configure_ac = "contrib/libsodium/configure.ac"; + let version_h_in = "contrib/libsodium/src/libsodium/include/sodium/version.h.in"; + let version_h = "contrib/libsodium/src/libsodium/include/sodium/version.h"; + + let configure_content = fs::read_to_string(configure_ac).expect("Failed to read configure.ac"); + + let version = extract_version(&configure_content, r"AC_INIT\(\[libsodium\],\[(.*?)\],"); + let major = extract_version(&configure_content, r"SODIUM_LIBRARY_VERSION_MAJOR=(.*?)"); + let minor = extract_version(&configure_content, r"SODIUM_LIBRARY_VERSION_MINOR=(.*?)"); + + let content = fs::read_to_string(version_h_in).expect("Failed to read version.h.in"); + let content = content + .replace("@VERSION@", &version) + .replace("@SODIUM_LIBRARY_VERSION_MAJOR@", &major) + .replace("@SODIUM_LIBRARY_VERSION_MINOR@", &minor) + .replace("@SODIUM_LIBRARY_MINIMAL_DEF@", "#define SODIUM_LIBRARY_MINIMAL 1"); + + fs::write(version_h, content).expect("Failed to write version.h"); +} + +#[cfg(target_os = "windows")] +fn extract_version(content: &str, pattern: &str) -> String { + let re = regex::Regex::new(pattern).expect("Invalid regex pattern"); + re.captures(content) + .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string())) + .expect("Failed to extract version") +} + +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); + } +}