Skip to content

Commit

Permalink
feat[pallas-crypto]: Implement libsodium vrf signature verification
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewWestberg committed Sep 6, 2024
1 parent 48f26db commit 19d1043
Show file tree
Hide file tree
Showing 8 changed files with 275 additions and 3 deletions.
13 changes: 12 additions & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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@v2

- name: Install stable toolchain
uses: dtolnay/rust-toolchain@stable
with:
Expand Down Expand Up @@ -55,13 +63,16 @@ jobs:
- name: Checkout sources
uses: actions/checkout@v2

- name: Set up MSBuild
uses: microsoft/setup-msbuild@v2

- name: Install stable toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable

- name: Run cargo test
run: cargo test --no-default-features --features num
run: cargo test --verbose --no-default-features --features num

lints:
name: Lints
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "pallas-crypto/contrib/libsodium"]
path = pallas-crypto/contrib/libsodium
url = https://github.com/input-output-hk/libsodium
12 changes: 11 additions & 1 deletion pallas-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>"]
authors = [
"Nicolas Di Prima <[email protected]>",
"Andrew Westberg <[email protected]>",
]
build = "build.rs"

[dependencies]
hex = "0.4"
Expand All @@ -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"
2 changes: 1 addition & 1 deletion pallas-crypto/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
117 changes: 117 additions & 0 deletions pallas-crypto/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
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=StaticRelease")
.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")
});

// // debugging: find the path to libsodium.lib
// run("where", |command| {
// command
// .current_dir("contrib/libsodium")
// .arg("/r")
// .arg(".")
// .arg("libsodium.lib")
// });

// // debugging: list all files in the directory
// run("dir", |command| {
// command.current_dir("contrib/libsodium/bin/x64/Release/v142/static")
// });

// ensure the path to libsodium.lib is correct
assert!(std::path::Path::new(
"contrib/libsodium/bin/x64/Release/v142/static/libsodium.lib"
)
.exists());

// copy libsodium.lib to sodium.lib
run("cmd", |command| {
command
.current_dir("contrib/libsodium/bin/x64/Release/v142/static")
.arg("/c")
.arg("copy libsodium.lib sodium.lib")
});

// copy libsodium.pdb to sodium.pdb
run("cmd", |command| {
command
.current_dir("contrib/libsodium/bin/x64/Release/v142/static")
.arg("/c")
.arg("copy libsodium.pdb sodium.pdb")
});

// get the current dir
let current_dir = std::env::current_dir().unwrap();

println!("cargo::rustc-link-search=native={}\\contrib\\libsodium\\bin\\x64\\Release\\v142\\static", current_dir.display());
println!("cargo::rustc-link-lib=static=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");
}

fn run<F>(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);
}
1 change: 1 addition & 0 deletions pallas-crypto/contrib/libsodium
Submodule libsodium added at dbb48c
1 change: 1 addition & 0 deletions pallas-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub mod hash;
pub mod key;
pub mod memsec;
pub mod nonce;
pub mod vrf;
129 changes: 129 additions & 0 deletions pallas-crypto/src/vrf/mod.rs
Original file line number Diff line number Diff line change
@@ -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<Vec<u8>, Error> {
let mut proof: Vec<u8> = 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<Vec<u8>, Error> {
let mut hash: Vec<u8> = 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<Vec<u8>, Error> {
let mut verification: Vec<u8> = 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);
}
}

0 comments on commit 19d1043

Please sign in to comment.