# Bandersnatch Thin VRF Implementatoin Notes Crates: - [bandersnatch_vrfs](https://github.com/w3f/ring-vrf/tree/master/bandersnatch_vrfs) - [dleq_vrfs](https://github.com/w3f/ring-vrf/tree/master/dleq_vrf) RFC 9381 Notes: https://hackmd.io/@davxy/B1EB_Yr0p ## VRF Input (H) (aka `VrfInput`) Currently we're hashing the user domain and input data `饾浖` using `ark-transcript`. (I'm not sure what hashing technique `ark-transcript` is using internally). https://github.com/w3f/ring-vrf/blob/e9782f938629c90f3adb3fff2358bc8d1386af3e/bandersnatch_vrfs/src/lib.rs#L67-L81 鈿狅笍 Replace with Elligator2 (ref 5.4.1 of RFC) as soon as ark-ec v0.4.3 is released (shoud include https://github.com/arkworks-rs/algebra/pull/758) ## VRF Pre-Output (P) (aka `VrfPreOutput`) Computed as per RFC 9381 (`P = x路H`) ## VRF Nonce Random bytes from xof. Even though it is not one of the ones specified by the RFC, IMHO this is fine and interoperability is ensured regardless of this. Code refs: https://github.com/w3f/ring-vrf/blob/e9782f938629c90f3adb3fff2358bc8d1386af3e/dleq_vrf/src/thin.rs#L80 https://github.com/w3f/ring-vrf/blob/e9782f938629c90f3adb3fff2358bc8d1386af3e/dleq_vrf/src/keys.rs#L212 ## VRF Proof (aka `VrfSignature`) `VrfInput` and `VrfPreOut` are kept within as a single structure (`VrfInOut`). Compared to RFC 9381 `bandersnatch_vrfs` allows to: - have a sequence of `VrfInOut`. - sign additional data (as an `ark_transcript::Transcript`) which doesn't influence the VRF output. Parameters: - `x`: secret key scalar - `Y`: public key (`x路B`) - `ad`: additional data to be signed (`Transcript`) - `饾浖`: VRF input (can be a sequence) Function: - `thin_vrf_merge`: merge user io, public key, additional data - `challenge`: Fiat-Sharmir transform - `nonce`: random nonce Steps: ``` H = encode_to_curve(饾浖) // We need to do this using Elligator2 t = Transcript(ad) P = x路H // NOTE: in the impl, this is a sequence of inputs io = (H, P) t = thin_vrf_merge(t, Y, (H, P)) (H', P') = vrf_delinearize(t, io) k = nonce(t) R = k路H' // impl: fn new_thin_witness t.append(R) // Append serialized (compressed) R to the transcript. c = t.challenge() // Fiat-Shamir(ad, Y, 饾浖, R) s = k + x路c 饾湅 = (P, R, s) ``` The signature is mostly a *classic* Schnorr signature `(R, s)` where: - `R` is computed using `H'` instead of the fixed base point `B`. - Fiat-Shamir transform also takes `R` as input Code refs: Signing: https://github.com/w3f/ring-vrf/blob/e9782f938629c90f3adb3fff2358bc8d1386af3e/dleq_vrf/src/thin.rs#L103 Transcript to vrf in/out: https://github.com/w3f/ring-vrf/blob/e9782f938629c90f3adb3fff2358bc8d1386af3e/dleq_vrf/src/vrf.rs#L270 Internally, from the user additional data `t`, public key `Y` and a list of `VrfInOut` a single `VrfInOut` is derived: `io = thin_vrf_merge(t, Y, ios)` Code refs: https://github.com/w3f/ring-vrf/blob/e9782f938629c90f3adb3fff2358bc8d1386af3e/dleq_vrf/src/thin.rs#L93 ## VRF Verify Parameters: - `Y`: public key - `ad`: user additional data to be signed (`Transcript`) - `饾浖`: VRF input (can be a sequence) - `饾湅`: VRF signature Steps: ``` (P, R, s) = 饾湅 H = encode_to_curve(饾浖) // We need to do this using Elligator2 t = Transcript(ad) io = (H, P) t = thin_vrf_merge(t, Y, (H, P)) (H', P') = vrf_delinearize(t, io) t.append(R) // Append witness R to the transcript hash. c = t.challenge() z = R + P'路c - H'路s check if z路cofactor == 0 ``` Final step expansion (under correct signature): ``` z = R + P'路c - H'路s = k路H' + P'路c - H'路k - H'路x路c = k路H' + H'路x路c - H'路k - H'路x路c = 0 ``` In practice is a Schnorr signature where the `R` is computed using vrf-input point instead of the group generator. And vrf-output point is used in place of pub key. ``` vrf_in路s = vrf_in路k + vrf_in路x路c = R + vrf_out路c ``` ## Spec Notes - Usage of `Transcript` to accumulate and hash various components