Skip to content

Commit

Permalink
Implement libsodium vrf signature verification
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewWestberg committed Sep 4, 2024
1 parent 0f13f1c commit 176d957
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 2 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,31 @@ 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
with:
update: true
install: >-
base-devel
autoconf
automake
libtool
pacboy: >-
toolchain:p
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}

- name: Run cargo check Windows
if: matrix.os == 'windows-latest'
shell: msys2 {0}
run: cargo check --no-default-features --features num

- name: Run cargo check
Expand Down Expand Up @@ -55,12 +73,25 @@ jobs:
- name: Checkout sources
uses: actions/checkout@v2

- name: Install msys2 Windows
uses: msys2/setup-msys2@v2
with:
update: true
install: >-
base-devel
autoconf
automake
libtool
pacboy: >-
toolchain:p
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable

- name: Run cargo test
shell: msys2 {0}
run: cargo test --no-default-features --features num

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
11 changes: 10 additions & 1 deletion pallas-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>"]
authors = [
"Nicolas Di Prima <[email protected]>",
"Andrew Westberg <[email protected]>",
]
build = "build.rs"
links = "libsodium"

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

// 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 176d957

Please sign in to comment.