feat[pallas-crypto]: Add Nonce Capabilities
This commit is contained in:
parent
07b74515a4
commit
15b424f4fc
7 changed files with 359 additions and 3 deletions
|
|
@ -8,7 +8,10 @@ homepage = "https://github.com/txpipe/pallas"
|
|||
documentation = "https://docs.rs/pallas-crypto"
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
authors = ["Nicolas Di Prima <nicolas@primetype.co.uk>"]
|
||||
authors = [
|
||||
"Nicolas Di Prima <nicolas@primetype.co.uk>",
|
||||
"Andrew Westberg <andrewwestberg@gmail.com>",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
hex = "0.4"
|
||||
|
|
@ -18,7 +21,13 @@ rand_core = "0.6"
|
|||
pallas-codec = { version = "=0.30.2", path = "../pallas-codec" }
|
||||
serde = "1.0.143"
|
||||
|
||||
# FIXME: This needs to be a properly deployed crate from the input-output-hk/vrf repository after my PR is merged
|
||||
# The vrf crate has not been fully tested in production environments and still has several upstream issues that
|
||||
# are open PRs but not merged yet.
|
||||
vrf_dalek = { git = "https://github.com/AndrewWestberg/vrf", rev = "6fc1440b197098feb6d75e2b71517019b8e2e9c2" }
|
||||
|
||||
[dev-dependencies]
|
||||
itertools = "0.13"
|
||||
quickcheck = "1.0"
|
||||
quickcheck_macros = "1.0"
|
||||
rand = "0.8"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
pub mod hash;
|
||||
pub mod key;
|
||||
pub mod memsec;
|
||||
pub mod nonce;
|
||||
pub mod vrf;
|
||||
|
|
|
|||
86
pallas-crypto/src/nonce/epoch_nonce.rs
Normal file
86
pallas-crypto/src/nonce/epoch_nonce.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
use crate::hash::{Hash, Hasher};
|
||||
use crate::nonce::{Error, NonceGenerator};
|
||||
|
||||
/// A nonce generator that calculates an epoch nonce from the eta_v value (nc) of the block right before
|
||||
/// the stability window and the block hash of the first block from the previous epoch (nh).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EpochNonceGenerator {
|
||||
pub nonce: Hash<32>,
|
||||
}
|
||||
|
||||
impl EpochNonceGenerator {
|
||||
/// Create a new [`EpochNonceGenerator`] generator.
|
||||
/// params:
|
||||
/// - nc: the eta_v value of the block right before the stability window.
|
||||
/// - nh: the block hash of the first block from the previous epoch.
|
||||
/// - extra_entropy: optional extra entropy to be used in the nonce calculation.
|
||||
pub fn new(nc: Hash<32>, nh: Hash<32>, extra_entropy: Option<&[u8]>) -> Self {
|
||||
let mut hasher = Hasher::<256>::new();
|
||||
hasher.input(nc.as_ref());
|
||||
hasher.input(nh.as_ref());
|
||||
let epoch_nonce = hasher.finalize();
|
||||
if let Some(extra_entropy) = extra_entropy {
|
||||
let mut hasher = Hasher::<256>::new();
|
||||
hasher.input(epoch_nonce.as_ref());
|
||||
hasher.input(extra_entropy);
|
||||
let extra_nonce = hasher.finalize();
|
||||
Self { nonce: extra_nonce }
|
||||
} else {
|
||||
Self { nonce: epoch_nonce }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NonceGenerator for EpochNonceGenerator {
|
||||
fn finalize(&mut self) -> Result<Hash<32>, Error> {
|
||||
Ok(self.nonce)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use itertools::izip;
|
||||
|
||||
use crate::hash::Hash;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_epoch_nonce() {
|
||||
let nc_values = vec![
|
||||
hex::decode("e86e133bd48ff5e79bec43af1ac3e348b539172f33e502d2c96735e8c51bd04d")
|
||||
.unwrap(),
|
||||
hex::decode("d1340a9c1491f0face38d41fd5c82953d0eb48320d65e952414a0c5ebaf87587")
|
||||
.unwrap(),
|
||||
];
|
||||
let nh_values = vec![
|
||||
hex::decode("d7a1ff2a365abed59c9ae346cba842b6d3df06d055dba79a113e0704b44cc3e9")
|
||||
.unwrap(),
|
||||
hex::decode("ee91d679b0a6ce3015b894c575c799e971efac35c7a8cbdc2b3f579005e69abd")
|
||||
.unwrap(),
|
||||
];
|
||||
let ee = hex::decode("d982e06fd33e7440b43cefad529b7ecafbaa255e38178ad4189a37e4ce9bf1fa")
|
||||
.unwrap();
|
||||
let extra_entropy_values: Vec<Option<&[u8]>> = vec![None, Some(&ee)];
|
||||
let expected_epoch_nonces = vec![
|
||||
hex::decode("e536a0081ddd6d19786e9d708a85819a5c3492c0da7349f59c8ad3e17e4acd98")
|
||||
.unwrap(),
|
||||
hex::decode("0022cfa563a5328c4fb5c8017121329e964c26ade5d167b1bd9b2ec967772b60")
|
||||
.unwrap(),
|
||||
];
|
||||
|
||||
for (nc_value, nh_value, extra_entropy_value, expected_epoch_nonce) in izip!(
|
||||
nc_values.iter(),
|
||||
nh_values.iter(),
|
||||
extra_entropy_values.iter(),
|
||||
expected_epoch_nonces.iter()
|
||||
) {
|
||||
let nc: Hash<32> = Hash::from(nc_value.as_slice());
|
||||
let nh: Hash<32> = Hash::from(nh_value.as_slice());
|
||||
let extra_entropy = *extra_entropy_value;
|
||||
let mut epoch_nonce = EpochNonceGenerator::new(nc, nh, extra_entropy);
|
||||
let nonce = epoch_nonce.finalize().unwrap();
|
||||
assert_eq!(nonce.as_ref(), expected_epoch_nonce.as_slice());
|
||||
}
|
||||
}
|
||||
}
|
||||
17
pallas-crypto/src/nonce/mod.rs
Normal file
17
pallas-crypto/src/nonce/mod.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
use thiserror::Error;
|
||||
|
||||
use crate::hash::Hash;
|
||||
|
||||
pub mod epoch_nonce;
|
||||
pub mod rolling_nonce;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Nonce error: {0}")]
|
||||
Nonce(String),
|
||||
}
|
||||
|
||||
/// A trait for generating nonces.
|
||||
pub trait NonceGenerator: Sized {
|
||||
fn finalize(&mut self) -> Result<Hash<32>, Error>;
|
||||
}
|
||||
168
pallas-crypto/src/nonce/rolling_nonce.rs
Normal file
168
pallas-crypto/src/nonce/rolling_nonce.rs
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
use crate::hash::{Hash, Hasher};
|
||||
use crate::nonce::{Error, NonceGenerator};
|
||||
|
||||
/// A nonce generator that calculates a rolling nonce by applying each cardano block in
|
||||
/// the shelley era and beyond. These rolling nonce values are used to help calculate the epoch
|
||||
/// nonce values used in consensus for the Ouroboros protocols (tpraos, praos, cpraos).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RollingNonceGenerator {
|
||||
pub nonce: Hash<32>,
|
||||
block_eta_v: Option<Hash<32>>,
|
||||
}
|
||||
|
||||
impl RollingNonceGenerator {
|
||||
pub fn new(nonce: Hash<32>) -> Self {
|
||||
Self {
|
||||
nonce,
|
||||
block_eta_v: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_block(&mut self, eta_vrf_0: &[u8]) -> Result<(), Error> {
|
||||
let len = eta_vrf_0.len();
|
||||
if len != 64 && len != 32 {
|
||||
return Err(Error::Nonce(format!(
|
||||
"Invalid eta_vrf_0 length: {}, expected 32 or 64",
|
||||
eta_vrf_0.len()
|
||||
)));
|
||||
}
|
||||
self.block_eta_v = Some(Hasher::<256>::hash(eta_vrf_0));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl NonceGenerator for RollingNonceGenerator {
|
||||
fn finalize(&mut self) -> Result<Hash<32>, Error> {
|
||||
if self.block_eta_v.is_none() {
|
||||
return Err(Error::Nonce(
|
||||
"Must call apply_block before finalize!".to_string(),
|
||||
));
|
||||
}
|
||||
let mut hasher = Hasher::<256>::new();
|
||||
hasher.input(self.nonce.as_ref());
|
||||
hasher.input(self.block_eta_v.unwrap().as_ref());
|
||||
Ok(hasher.finalize())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_rolling_nonce() {
|
||||
let shelley_genesis_hash =
|
||||
hex::decode("1a3be38bcbb7911969283716ad7aa550250226b76a61fc51cc9a9a35d9276d81")
|
||||
.unwrap();
|
||||
|
||||
let eta_vrf_0_values = vec![
|
||||
hex::decode("36ec5378d1f5041a59eb8d96e61de96f0950fb41b49ff511f7bc7fd109d4383e1d24be7034e6749c6612700dd5ceb0c66577b88a19ae286b1321d15bce1ab736").unwrap(),
|
||||
hex::decode("e0bf34a6b73481302f22987cde4c12807cbc2c3fea3f7fcb77261385a50e8ccdda3226db3efff73e9fb15eecf841bbc85ce37550de0435ebcdcb205e0ed08467").unwrap(),
|
||||
hex::decode("7107ef8c16058b09f4489715297e55d145a45fc0df75dfb419cab079cd28992854a034ad9dc4c764544fb70badd30a9611a942a03523c6f3d8967cf680c4ca6b").unwrap(),
|
||||
hex::decode("6f561aad83884ee0d7b19fd3d757c6af096bfd085465d1290b13a9dfc817dfcdfb0b59ca06300206c64d1ba75fd222a88ea03c54fbbd5d320b4fbcf1c228ba4e").unwrap(),
|
||||
hex::decode("3d3ba80724db0a028783afa56a85d684ee778ae45b9aa9af3120f5e1847be1983bd4868caf97fcfd82d5a3b0b7c1a6d53491d75440a75198014eb4e707785cad").unwrap(),
|
||||
hex::decode("0b07976bc04321c2e7ba0f1acb3c61bd92b5fc780a855632e30e6746ab4ac4081490d816928762debd3e512d22ad512a558612adc569718df1784261f5c26aff").unwrap(),
|
||||
hex::decode("5e9e001fb1e2ddb0dc7ff40af917ecf4ba9892491d4bcbf2c81db2efc57627d40d7aac509c9bcf5070d4966faaeb84fd76bb285af2e51af21a8c024089f598c1").unwrap(),
|
||||
hex::decode("182e83f8c67ad2e6bddead128e7108499ebcbc272b50c42783ef08f035aa688fecc7d15be15a90dbfe7fe5d7cd9926987b6ec12b05f2eadfe0eb6cad5130aca4").unwrap(),
|
||||
hex::decode("275e7404b2385a9d606d67d0e29f5516fb84c1c14aaaf91afa9a9b3dcdfe09075efdadbaf158cfa1e9f250cc7c691ed2db4a29288d2426bd74a371a2a4b91b57").unwrap(),
|
||||
hex::decode("0f35c7217792f8b0cbb721ae4ae5c9ae7f2869df49a3db256aacc10d23997a09e0273261b44ebbcecd6bf916f2c1cd79cf25b0c2851645d75dd0747a8f6f92f5").unwrap(),
|
||||
hex::decode("14c28bf9b10421e9f90ffc9ab05df0dc8c8a07ffac1c51725fba7e2b7972d0769baea248f93ed0f2067d11d719c2858c62fc1d8d59927b41d4c0fbc68d805b32").unwrap(),
|
||||
hex::decode("e4ce96fee9deb9378a107db48587438cddf8e20a69e21e5e4fbd35ef0c56530df77eba666cb152812111ba66bbd333ed44f627c727115f8f4f15b31726049a19").unwrap(),
|
||||
hex::decode("b38f315e3ce369ea2551bf4f44e723dd15c7d67ba4b3763997909f65e46267d6540b9b00a7a65ae3d1f3a3316e57a821aeaac33e4e42ded415205073134cd185").unwrap(),
|
||||
hex::decode("4bcbf774af9c8ff24d4d96099001ec06a24802c88fea81680ea2411392d32dbd9b9828a690a462954b894708d511124a2db34ec4179841e07a897169f0f1ac0e").unwrap(),
|
||||
hex::decode("65247ace6355f978a12235265410c44f3ded02849ec8f8e6db2ac705c3f57d322ea073c13cf698e15d7e1d7f2bc95e7b3533be0dee26f58864f1664df0c1ebba").unwrap(),
|
||||
hex::decode("d0c2bb451d0a3465a7fef7770718e5e49bf092a85dbf5af66ea26ec9c1b359026905fc1457e2b98b01ede7ba42aedcc525301f747a0ed9a9b61c37f27f9d8812").unwrap(),
|
||||
hex::decode("250d9ec7ebec73e885798ae9427e1ea47b5ae66059b465b7c0fd132d17a9c2dcae29ba72863c1861cfb776d342812c4e9000981c4a40819430d0e84aa8bfeb0d").unwrap(),
|
||||
hex::decode("0549cc0a5e5b9920796b88784c49b7d9a04cf2e86ab18d5af7b00780e60fb0fb5a7129945f4f918201dbad5348d4ccface4370f266540f8e072cdb46d3705930").unwrap(),
|
||||
hex::decode("e543a26031dbdc8597b1beeba48a4f1cf6ab90c0e5b9343936b6e948a791198fc4fa22928e21edec812a04d0c9629772bf78e475d91a323cd8a8a6e005f92b4d").unwrap(),
|
||||
hex::decode("4e4be69ad170fb8b3b17835913391ee537098d49e4452844a71ab2147ac55e45871c8943271806034ee9450b31c9486db9d26942946f48040ece7eea81424af1").unwrap(),
|
||||
hex::decode("cb8a528288f902349250f9e8015e8334b0e24c2eeb9bb7d75e73c39024685804577565e62aca35948d2686ea38e9f8de97837ea30d2fb08347768394416e4a38").unwrap(),
|
||||
hex::decode("fce94c47196a56a5cb94d5151ca429daf1c563ae889d0a42c2d03cfe43c94a636221c7e21b0668de9e5b6b32ee1e78b2c9aabc16537bf79c7b85eb956f433ac7").unwrap(),
|
||||
hex::decode("fc8a125c9e2418c87907db4437a0ad6a378bba728ac8e0ce0e64f2a2f4b8201315e1b08d7983ce597cb68be2a2400d6d0d59b7359fe3dc9daca73d468da48972").unwrap(),
|
||||
hex::decode("49290417311420d67f029a80b013b754150dd0097aa64de1c14a2467ab2e26cc2724071c04cb90cb0cf6c6353cf31f63235af7849d6ba023fd0fc0bc79d32f0b").unwrap(),
|
||||
hex::decode("45c65effdc8007c9f2fc9057af986e94eb5c12b755465058d4b933ee37638452c5eeca4b43b8cbddabc60f29cbe5676b0bc55c0da88f8d0c36068e7d17ee603a").unwrap(),
|
||||
hex::decode("a51e4e0f28aee3024207d87a5a1965313bdba4df44c6b845f7ca3408e5dabfe873df6b6ba26000e841f83f69e1de7857122ba538b42f255da2d013208af806ba").unwrap(),
|
||||
hex::decode("5dbd891bf3bcfd5d054274759c13552aeaa187949875d81ee62ed394253ae25182e78b3a4a1976a7674e425bab860931d57f8a1d4fdc81fa4c3e8e8bf9016d5d").unwrap(),
|
||||
hex::decode("3b5b044026e9066d62ce2f5a1fb01052a8cfe200dea28d421fc70f42c4d2b890b90ffef5675de1e47e4a20c9ca8700ceea23a61338ac759a098d167fa71642cb").unwrap(),
|
||||
hex::decode("bb4017880cfa1e37f256dfe2a9cdb1349ed5dea8f69de75dc5933540dcf49e69afc33c837ba8a791857e16fad8581c4e9046778c49ca1ecd1fb675983be6d721").unwrap(),
|
||||
hex::decode("517bbdb6e9e5f4702193064543204e780f5d33a866d0dcd65ada19f05715dea60ca81b842de5dca8f6b84a9cf469c8fb81991369dba21571476cc9c8d4ff2136").unwrap(),
|
||||
];
|
||||
|
||||
let expected_eta_v_values = vec![
|
||||
hex::decode("2af15f57076a8ff225746624882a77c8d2736fe41d3db70154a22b50af851246")
|
||||
.unwrap(),
|
||||
hex::decode("a815ff978369b57df09b0072485c26920dc0ec8e924a852a42f0715981cf0042")
|
||||
.unwrap(),
|
||||
hex::decode("f112d91435b911b6b5acaf27198762905b1cdec8c5a7b712f925ce3c5c76bb5f")
|
||||
.unwrap(),
|
||||
hex::decode("5450d95d9be4194a0ded40fbb4036b48d1f1d6da796e933fefd2c5c888794b4b")
|
||||
.unwrap(),
|
||||
hex::decode("c5c0f406cb522ad3fead4ecc60bce9c31e80879bc17eb1bb9acaa9b998cdf8bf")
|
||||
.unwrap(),
|
||||
hex::decode("5857048c728580549de645e087ba20ef20bb7c51cc84b5bc89df6b8b0ed98c41")
|
||||
.unwrap(),
|
||||
hex::decode("d6f40ef403687115db061b2cb9b1ab4ddeb98222075d5a3e03c8d217d4d7c40e")
|
||||
.unwrap(),
|
||||
hex::decode("5489d75a9f4971c1824462b5e2338609a91f121241f21fee09811bd5772ae0a8")
|
||||
.unwrap(),
|
||||
hex::decode("04716326833ecdb595153adac9566a4b39e5c16e8d02526cb4166e4099a00b1a")
|
||||
.unwrap(),
|
||||
hex::decode("39db709f50c8a279f0a94adcefb9360dbda6cdce168aed4288329a9cd53492b6")
|
||||
.unwrap(),
|
||||
hex::decode("c784b8c8678e0a04748a3ad851dd7c34ed67141cd9dc0c50ceaff4df804699a7")
|
||||
.unwrap(),
|
||||
hex::decode("cc1a5861358c075de93a26a91c5a951d5e71190d569aa2dc786d4ca8fc80cc38")
|
||||
.unwrap(),
|
||||
hex::decode("514979c89313c49e8f59fb8445113fa7623e99375cc4917fe79df54f8d4bdfce")
|
||||
.unwrap(),
|
||||
hex::decode("6a783e04481b9e04e8f3498a3b74c90c06a1031fb663b6793ce592a6c26f56f4")
|
||||
.unwrap(),
|
||||
hex::decode("1190f5254599dcee4f3cf1afdf4181085c36a6db6c30f334bfe6e6f320a6ed91")
|
||||
.unwrap(),
|
||||
hex::decode("91c777d6db066fe58edd67cd751fc7240268869b365393f6910e0e8f0fa58af3")
|
||||
.unwrap(),
|
||||
hex::decode("c545d83926c011b5c68a72de9a4e2f9da402703f4aab1b967456eae73d9f89b3")
|
||||
.unwrap(),
|
||||
hex::decode("ec31d2348bf543482842843a61d5b32691dedf801f198d68126c423ddf391e8b")
|
||||
.unwrap(),
|
||||
hex::decode("de223867d5c972895dd99ac0280a3e02947a7fb018ed42ed048266f913d2dfc2")
|
||||
.unwrap(),
|
||||
hex::decode("4dd9801752aade9c6e06bf03e9d2ec8a30ef7c6f30106790a23a9599e90ee08a")
|
||||
.unwrap(),
|
||||
hex::decode("fcb183abd512271f40408a5872827ce79cc2dda685a986a7dbdc61d842495a91")
|
||||
.unwrap(),
|
||||
hex::decode("e834d8ffd6dd042167b13e38512c62afdaf4d635d5b1ab0d513e08e9bef0ef63")
|
||||
.unwrap(),
|
||||
hex::decode("270a78257a958cd5fdb26f0b9ab302df2d2196fd04989f7ca1bb703e4dd904f0")
|
||||
.unwrap(),
|
||||
hex::decode("7e324f67af787dfddee10354128c60c60bf601bd8147c867d2471749a7b0f334")
|
||||
.unwrap(),
|
||||
hex::decode("54521ed42e0e782b5268ec55f80cff582162bc23fdcee5cdaa0f1a2ce7fa1f02")
|
||||
.unwrap(),
|
||||
hex::decode("557c296a71d8c9cb3fe7dcd95fbf4d70f6a3974d93c71b450d62a41b9a85d5a1")
|
||||
.unwrap(),
|
||||
hex::decode("20e078301ca282857378bbf10ac40965445c4c9fa73a160e0a116b4cf808b4b4")
|
||||
.unwrap(),
|
||||
hex::decode("b5a741dd3ff6a5a3d27b4d046dfb7a3901aacd37df7e931ba05e1320ad155c1c")
|
||||
.unwrap(),
|
||||
hex::decode("8b445f35f4a7b76e5d279d71fa9e05376a7c4533ca8b2b98fd2dbaf814d3bf8f")
|
||||
.unwrap(),
|
||||
hex::decode("08e7b5277abc139deb50f61264375fa091c580f8a85f259be78a002f7023c31f")
|
||||
.unwrap(),
|
||||
];
|
||||
|
||||
let mut rolling_nonce_generator =
|
||||
RollingNonceGenerator::new(Hash::from(shelley_genesis_hash.as_slice()));
|
||||
|
||||
for (eta_vrf_0, expected_eta_v) in eta_vrf_0_values.iter().zip(expected_eta_v_values.iter())
|
||||
{
|
||||
rolling_nonce_generator.apply_block(eta_vrf_0).unwrap();
|
||||
rolling_nonce_generator =
|
||||
RollingNonceGenerator::new(rolling_nonce_generator.finalize().unwrap());
|
||||
assert_eq!(
|
||||
rolling_nonce_generator.nonce.as_ref(),
|
||||
expected_eta_v.as_slice()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
pallas-crypto/src/vrf/mod.rs
Normal file
74
pallas-crypto/src/vrf/mod.rs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
use thiserror::Error;
|
||||
use vrf_dalek::vrf03::{PublicKey03, SecretKey03, VrfProof03};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("TryFromSlice {0}")]
|
||||
TryFromSlice(#[from] std::array::TryFromSliceError),
|
||||
|
||||
#[error("VrfError {0}")]
|
||||
VrfError(#[from] vrf_dalek::errors::VrfError),
|
||||
}
|
||||
|
||||
/// Sign a seed value with a vrf secret key and produce a proof signature
|
||||
pub fn vrf_prove(secret_key: &[u8], seed: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
let sk = SecretKey03::from_bytes(secret_key[..32].try_into()?);
|
||||
let pk = PublicKey03::from(&sk);
|
||||
let proof = VrfProof03::generate(&pk, &sk, seed);
|
||||
Ok(proof.to_bytes().to_vec())
|
||||
}
|
||||
|
||||
/// Convert a proof signature to a hash
|
||||
pub fn vrf_proof_to_hash(proof: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
let proof = VrfProof03::from_bytes(proof[..80].try_into()?)?;
|
||||
Ok(proof.proof_to_hash().to_vec())
|
||||
}
|
||||
|
||||
/// Verify a proof signature with a vrf public key. This will return a hash to compare with the original
|
||||
/// signature hash, but any non-error result is considered a successful verification without needing
|
||||
/// to do the extra comparison check.
|
||||
pub fn vrf_verify(public_key: &[u8], signature: &[u8], seed: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
let pk = PublicKey03::from_bytes(public_key.try_into()?);
|
||||
let proof = VrfProof03::from_bytes(signature.try_into()?)?;
|
||||
Ok(proof.verify(&pk, seed)?.to_vec())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
#[test]
|
||||
fn 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 = vrf_prove(&vrf_skey, &seed).unwrap();
|
||||
let proof_hash = vrf_proof_to_hash(&proof_signature).unwrap();
|
||||
|
||||
// verify the proof signature with the public vrf public key
|
||||
let verified_hash = vrf_verify(&vrf_vkey, &proof_signature, &seed).unwrap();
|
||||
assert_eq!(proof_hash, verified_hash);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue