In our digital world, we give away too many of our secrets, especially where we just have to prove the knowledge of something, rather than actually revealing it. In many of the systems we use, we could just prove things in an oblivious way, and where we could pass a secret but in a blinded form. With this, a server does not discover our password and for us not to discover the identifier that a server holds on us. But, can we also provide a proof back that the right password has been used? Well, with Verifiable Oblivious Pseudorandom Functions (VOPRF), we can generate a random secret based on a key generated on the server (Alice), and which is based on Bob’s secret.
Verifiable Oblivious Pseudorandom Function (VOPF) using CIRCL |
Outline
In our digital world, we give away too many of our secrets, especially where we just have to prove the knowledge of something, rather than actually revealing it. In many of the systems we use, we could just prove things in an oblivious way, and where we could pass a secret but in a blinded form.
With this, a server does not discover our password and for us not to discover the identifier that a server holds on us. But, can we also provide a proof back that the right password has been used? Well, with Verifiable Oblivious Pseudorandom Functions (VOPRF), we can generate a random secret based on a key generated on the server (Alice), and which is based on Bob’s secret:
Initially, Bob generates his secret, and the blinds it. This blind value is then sent to Alice, and then who uses her private key to produce proof values to go back to Bob (\(r\)). Bob then finalises the PRF, by taking his secret value, and the proof. Overall Alice does not learn about Bob’s secret, and Bob does not learn about Alice’s key. We can setup Bob and Alice with:
key, _ := oprf.GenerateKey(id, rand.Reader) alice, _ := oprf.NewVerifiableServer(id, key) bob, _ := oprf.NewVerifiableClient(id, key.Public())
and next generated the blinded value for Bob secret (pass) to send to Alice:
cl, _ := bob.Request([][]byte{[]byte(pass)}) EV, _ := alice.Evaluate(cl.BlindedElements(), nil)
The value of EV is then sent back to Bob, who then generates the PRF:
Bob_token, _ := bob.Finalize(cl, EV, nil)
If Alice needs to prove the PRF, she can take the actual inputs, and prove with:
rtn := alice.VerifyFinalize([]byte(pass), nil, Bob_token) token, _ := alice.FullEvaluate([]byte(pass), nil) // This does a full server side evaluation
A core advantage of VOPRF is that the client (Bob) can check that the server (Alice) has used a committed secret key - and where Alice's public key verifies that private key that has been used - within the Evaluation() method (on Alice's side), and which is checked in the Finalize() method on Bob's side.
The code is:
package main import ( "crypto/rand" "fmt" "os" "github.com/cloudflare/circl/oprf" ) func main() { id := oprf.OPRFP256 // alice -> server, bob -> client pass := "password" idtype := "OPRFP256" argCount := len(os.Args[1:]) if argCount > 0 { idtype = os.Args[1] } if argCount > 1 { pass = os.Args[2] } switch idtype { case "OPRFP256": id = oprf.OPRFP256 case "OPRFP384": id = oprf.OPRFP384 case "OPRFP521": id = oprf.OPRFP521 } key, _ := oprf.GenerateKey(id, rand.Reader) alice, _ := oprf.NewVerifiableServer(id, key) bob, _ := oprf.NewVerifiableClient(id, key.Public()) cl, _ := bob.Request([][]byte{[]byte(pass)}) EV, _ := alice.Evaluate(cl.BlindedElements(), nil) Bob_token, _ := bob.Finalize(cl, EV, nil) fmt.Printf("Bob's secret: %s\n", pass) fmt.Printf("Method: %s\n", idtype) fmt.Printf("\nBob sends blinded requests:\n%x\n", cl.BlindedElements()) fmt.Printf("Alice generates proof (C): %x\n", EV.Proof.C) fmt.Printf("Alice generates proof (S): %x\n", EV.Proof.S) fmt.Printf("Bob gets PRF secret key: %x\n", Bob_token) fmt.Printf("\n\n=== If the server holds inputs and proofs, let's verify ===\n") rtn := alice.VerifyFinalize([]byte(pass), nil, Bob_token[0]) rtn2, _ := alice.FullEvaluate([]byte(pass), nil) // This does a full server side evaluation fmt.Printf("Verified: %v\n", rtn) fmt.Printf("Derived PRF secret key: %x\n", rtn2) }
A sample run:
Bob sends blinded requests: [02174467e4f99b32c42012aa25aa4d88936a16b5eb63ee88898267669c89ca9551] Alice generates proof (C): 5253e4710ad32d805ffed2a56f503358809e5e91aa8bbee85f73224c4432284f Alice generates proof (S): c09e020d232ca9c415ba0d174f2cd2eecd3f9f3a5fc8c2a26c67ffbc85147aeb Bob gets PRF secret key: [dcffd0b47c7bd5975005585e4c282faf6ad7673f35761b5c990ecdc8bf572aa7] === If the server holds inputs and proofs, let's verify === Verified: true Derived PRF secret key: dcffd0b47c7bd5975005585e4c282faf6ad7673f35761b5c990ecdc8bf572aa7