examples/aqua-examples/vrfun
renovate[bot] 929ff6b40d
chore(deps): update rust crate marine-rs-sdk-test to 0.8.2 (#428)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-21 21:03:51 -06:00
..
aqua update aqua 2022-09-22 09:25:55 -05:00
artifacts add artifacts 2022-09-22 09:25:55 -05:00
configs init vrf demo 2022-09-22 09:25:55 -05:00
data update and extend demo data 2022-09-22 09:25:55 -05:00
scripts init vrf demo 2022-09-22 09:25:55 -05:00
src init vrf demo 2022-09-22 09:25:55 -05:00
.gitignore update key reference 2022-09-22 09:25:55 -05:00
Cargo.lock chore(deps): update rust crate marine-rs-sdk-test to 0.8.2 (#428) 2023-02-21 21:03:51 -06:00
Cargo.toml chore(deps): update rust crate marine-rs-sdk-test to 0.8.2 (#428) 2023-02-21 21:03:51 -06:00
LICENSE init vrf demo 2022-09-22 09:25:55 -05:00
README.md fix reference 2022-09-22 09:25:55 -05:00

Verifiable Random Function With Marine And Aqua

Overview

A Verifiable Random Function (VRF) is a pseudorandom function that provides proofs that its outputs were constructed in a verifiable manner. While the proof is constructed with both public and private data, i.e., private key, the verification only requires the public data. Not surprisingly, VRFs play an important role in trustless systems, such as blockchain protocols and, of course, the Fluence network. We are implementing ECVRF, which uses ED25519, as a Marine Wasm off-chain service callable from Aqua.

Note:

  • the ECVRF crate is provided under a GPL-3.0 License. Hence, this repo is also made available under a GPL-3.0 license.
  • neither the ECVRF crate nor this repo have not undergone a security audit. Tread With Care!

Getting to Services

Implementing a Wasm wrapper around the ECVRF library is pretty straight forward. Aside from the proof generation and verification functions, we added a few convenience features you should only use in the intended context of experimentation. The complete code is in the src/main.rs file. Compile with:

./scripts/build.sh

And test with:

cargo +nightly test --release

Which should result in:

running 4 tests
test tests::t_key_gen ... ok
test tests::test_proof_module ... ok
test tests::verify_proof_module_no_sk ... ok
test tests::verify_proof_module_with_sk ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s

Service Deployment

While the Wasm module handles both proof generation and verification, it seems prudent to deploy the service to different peers to separate the proof generation from the verification process.

aqua remote deploy_service \
    --addr /dns4/kras-05.fluence.dev/tcp/19001/wss/p2p/12D3KooWCMr9mU894i8JXAFqpgoFtx6qnV1LFPSfVc3Y34N4h4LS \
    --config-path configs/deployment_cfg.json \
    --service vrfun \
    --sk <YOUR-SECRET_KEY>

Blueprint id:
98b3cca51ffdef00bd591da569bd606bf7a1099dd30761af3147e84ea0b3bf79
And your service id is:
"751c137d-37ea-4e57-ad22-518f6ee1900b"

And

aqua remote deploy_service \
    --addr /dns4/kras-07.fluence.dev/tcp/19001/wss/p2p/12D3KooWEFFCZnar1cUJQ3rMWjvPQg6yMV2aXWs2DkJNSRbduBWn \
    --config-path configs/deployment_cfg.json \
    --service vrfun \
    --sk <YOUR-SECRET_KEY>

Blueprint id:
98b3cca51ffdef00bd591da569bd606bf7a1099dd30761af3147e84ea0b3bf79
And your service id is:
"132b1b5d-295d-402c-af23-678367c53c33"

See run_data.json for a sample file populated with the different argument settings for running the Aqua functions.

VRF With Aqua

Let's have a look at our Aqua functions for both creating and verifying a proof:

-- aqua/vrf.aqua
func vrf_proof(service: FuncAddr, payload: []u8, sk:[]u8) -> ProofResult:
  on service.peer_id:
    Vrfun service.service_id
    proof_result <- Vrfun.vrf_proof(payload, sk)
  <- proof_result

func vrf_verify(service: FuncAddr, payload: []u8, proof:ProofResult) -> VerificationResult:
    on service.peer_id:
        Vrfun service.service_id
        verification <- Vrfun.verify_vrf(proof.pk, payload, proof.output, proof.proof)
    <- verification

In order to create a verifiable pseudorandom proof, you need to provide the following inputs to the VRF:

  • some (public) input data as bytes
  • your private (ed25519) key as bytes

which results in:

where the output the is the VRF hash.

For some input data provided by the user, e.g., [222, 173, 190, 239], we call the vrf_proof function like so:

aqua run \
    --input aqua/vrf.aqua \
    --addr /dns4/kras-08.fluence.dev/tcp/19001/wss/p2p/12D3KooWFtf3rfCDAfWwt6oLZYZbDfn9Vn7bv7g6QjjQxUUEFVBt \
    -f 'vrf_proof(proof_arg, payload_arg, sk_arg)' \
    --data-path data/run_data.json

Whic returns the proof and output as well as the pk derived from the sk, just in case:

{
  "output": [
    115, 194, 116, 211, 189, 96, 252, 211, 160, 128, 67, 1, 35, 98, 3, 203, 39,
    2, 139, 239, 75, 123, 218, 2, 248, 152, 124, 164, 186, 213, 67, 15
  ],
  "pk": [
    234, 192, 114, 199, 44, 198, 65, 29, 15, 47, 10, 159, 184, 125, 1, 26, 221,
    188, 125, 60, 189, 98, 137, 165, 60, 199, 97, 92, 85, 224, 29, 47
  ],
  "proof": [
    30, 242, 203, 248, 93, 221, 218, 224, 68, 11, 90, 29, 193, 184, 139, 17, 88,
    211, 141, 198, 98, 252, 112, 102, 166, 17, 182, 244, 154, 158, 89, 34, 40,
    122, 95, 121, 49, 35, 96, 85, 36, 27, 171, 242, 190, 63, 54, 210, 229, 210,
    160, 161, 226, 147, 139, 40, 75, 163, 74, 111, 121, 49, 62, 188, 95, 185,
    159, 185, 94, 162, 148, 198, 115, 237, 15, 61, 30, 122, 106, 74, 171, 240,
    74, 71, 217, 51, 200, 195, 161, 233, 162, 50, 233, 176, 100, 13
  ],
  "stderr": ""
}

We can know utilize these parameters to verify the (pseudo) randomness of the result using the prepared proof_data.json file with the above results parameters:

aqua run \
    --input aqua/vrf.aqua \
    --addr /dns4/kras-08.fluence.dev/tcp/19001/wss/p2p/12D3KooWFtf3rfCDAfWwt6oLZYZbDfn9Vn7bv7g6QjjQxUUEFVBt \
    -f 'vrf_verify(verify_arg, payload_arg, proof_result)' \
    --data-path data/proof_data.json

which gives us the expected but nevertheless reassuring:

{
  "stderr": "",
  "verified": true
}

If you change any of the verification parameters, such as the input parameter, the verification fails. For example, change "payload_arg": [222, 173, 190, 239] to "bad_payload_arg": [222, 173, 190, 240]:

 aqua run \
    --input aqua/vrf.aqua \
    --addr /dns4/kras-08.fluence.dev/tcp/19001/wss/p2p/12D3KooWFtf3rfCDAfWwt6oLZYZbDfn9Vn7bv7g6QjjQxUUEFVBt \
    -f 'vrf_verify(verify_arg, bad_payload_arg, proof_result)' \
    --data-path data/proof_data.json

and the verification fails:

{
  "stderr": "",
  "verified": false
}

In addition to the core proof and verify functions, a vrf_rountrip function for a test run is available. Moreover, we also provide a (ed25519) key generation function for convenience purposes. In general, you should not rely on a (untrusted) peer to generate or otherwise handle your secret key and instead use some client side tool, e.g., openssl, to create and manage your ED25519 keypair(s). Moreover, since the private key is required to create the VRF (and proof), even with a trusted peer, it might be best to work with ephemeral key pairs.

// src/main.rs
#[marine]
// #[derive(Default)]
pub fn gen_keys() -> KeyPair {
    let (sk, pk) = keygen();

    KeyPair {
        pk: pk.to_bytes().to_vec(),
        sk: sk.to_bytes().to_vec(),
    }
}

The corresponding Aqua implementation is:

-- aqua/vrf.aqua
func get_keys(service: FuncAddr) -> KeyPair:
  on service.peer_id:
    Vrfun service.service_id
    res <- Vrfun.gen_keys()
  <- res

And you can get a keypair with Aqua and the run_data.json file like so:

aqua run \
    --input aqua/vrf.aqua \
    --addr /dns4/kras-08.fluence.dev/tcp/19001/wss/p2p/12D3KooWFtf3rfCDAfWwt6oLZYZbDfn9Vn7bv7g6QjjQxUUEFVBt \
    -f 'get_keys(proof_arg)' \
    --data-path data/run_data.json

Which provides a ED25519 keypair in bytes:


  "pk": [
    190, 224, 92, 34, 161, 20, 20, 60, 246, 240, 179, 93, 130, 228, 148, 215,
    41, 140, 155, 132, 237, 188, 114, 150, 186, 95, 51, 129, 169, 173, 154, 119
  ],
  "sk": [
    230, 18, 194, 149, 33, 251, 226, 223, 244, 115, 82, 210, 61, 198, 68, 233,
    13, 47, 188, 209, 82, 99, 234, 34, 40, 61, 54, 54, 108, 238, 114, 13
  ]
}

Summary

We provide a Marine Wasm wrapper and corresponding Aqua implementation around the ECVRF ED25519 Rust project. As a low-cost, off-chain source of pseudorandomness, Fluence users, and especially Marine Wasm developers, do not need to rely on (trustless) peers to provide randomness. Moreover, developers may anchor the provided randomness and proof on-chain for immutable references and off-chain, or even on-chain, verification. In the case of Fluence WASM, it may make sense to use VRF to provable pseudorandom generate seeds for PRNG initiations.