Proving Your Age, Without Giving Away Your Age

If we were to start our computing and data architectures again, we certainly wouldn’t build them as we have. Overall, we have a legacy data…

Proving Your Age, Without Giving Away Your Age

If we were to start our computing and data architectures again, we certainly wouldn’t build them as we have. Overall, we have a legacy data architecture, and where we often fail to protect sensitive and private information. The usage of encryption, for example, is often seen as a barrier to accessing the data rather than a positive aspect of protecting sensitive data. But, a company that does not store private information — but proofs of knowledge — will be much less likely to have a data breach than one that stores the raw data on users.

An example of this is with age range checking, where a user needs to provide proof of their date of birth every time they need to prove that their age is in a given range. But why can’t I create a trusted proof that someone is within a certain age range and then get that signed by a trusted entity? This could then be stored as a proof in a digital wallet and prove it whenever required. Then, there would be no need for someone to give away their actual date of birth to anyone checking for a given age range.

For this, we need a cryptographic range proof, and one of the best around is the Bulletproof method [here][1]:

They were named Bulletproofs, as they are short like bullets, but give bulletproof security assumptions. One of the core applications of bulletproofs is towards anonymised cryptocurrency transactions, and where Bob could prove to Alice that he has enough cryptocurrency to pay her, and can provide a proof that his funds are in a given range. This could be likened to a salary check for a mortgage application, and where Bob can prove he has the funds to cover his loan without revealing his salary.

Before Bulletproofs, most of the methods involved in proving a range of values involved a trusted setup. With Bulletproofs, we could now combine multiple transactions together and show that the current balance is within a given range, and have been adopted into the Monero cryptocurrency to provide anonymous transactions. In fact, Monero further advanced Bulletproofs, with Bulletproof+ [here][2]:

The two basic assumptions around the security of Bulletproofs is the discrete logarithm problem, and which makes it difficult to compute an input value from an output value, and with the Fiat-Shamir heuristic, and which creates a non-interactive zero knowledge proof. Being non-interactive, the Prover (Peggy) can produce her own proof without being prompted by the Verifier (Victor). They also use the Pedersen commitment, and which blinds transactions, but where we can tell if the sum of the inputs is greater than the sum of the outputs, in a privacy preserving way.

Age range verification

The maths are rather complicated, so, instead, let’s take an example of an implementation which proves that someone’s age is within a given range. We will use the fork of the ing-bank code at https://github.com/0xdecaf/zkrp to implement [here]:

package main

import (
"encoding/json"
"fmt"
"math/big"
"os"
"strconv"

"github.com/0xdecaf/zkrp/bulletproofs"
)

func main() {

age := 30
ageupper := 50
agelower := 20

argCount := len(os.Args[1:])

if argCount > 0 {
age, _ = strconv.Atoi(os.Args[1])
}
if argCount > 1 {
agelower, _ = strconv.Atoi(os.Args[2])
}
if argCount > 2 {
ageupper, _ = strconv.Atoi(os.Args[3])
}

params, _ := bulletproofs.SetupGeneric(int64(agelower), int64(ageupper))

bigSecret := new(big.Int).SetInt64(int64(age))

proof, _ := bulletproofs.ProveGeneric(bigSecret, params)

jsonEncoded, _ := json.Marshal(proof)

var decodedProof bulletproofs.ProofBPRP
_ = json.Unmarshal(jsonEncoded, &decodedProof)

rtn, _ := decodedProof.Verify()

fmt.Printf("Secret age: %d\n\n", age)
if rtn {
fmt.Printf("Age verified to be between %d ad %d\n\n", agelower, ageupper)
} else {
fmt.Printf("Age NOT verified to be between %d ad %d\n\n", agelower, ageupper)
}
fmt.Printf("Proof P1 A: %s\n\n", proof.P1.A)
fmt.Printf("Proof P1 Mu: %s\n\n", proof.P1.Mu)
fmt.Printf("Proof P1 S: %s\n\n", proof.P1.S)
fmt.Printf("Proof P1 T1: %s\n\n", proof.P1.T1)
fmt.Printf("Proof P1 T2: %s\n\n", proof.P1.T2)
fmt.Printf("Proof P1 Taux: %s\n\n", proof.P1.Taux)
fmt.Printf("Proof P1 Tprime: %s\n\n", proof.P1.Tprime)
fmt.Printf("Proof P1 V: %s\n\n", proof.P1.V)
fmt.Printf("Proof P1 A.X: %s\n\n", proof.P1.A.X)
fmt.Printf("Proof P1 V.X: %s\n\n", proof.P1.V.X)
fmt.Printf("Proof P1 V.Y: %s\n\n", proof.P1.V.Y)

fmt.Printf("Proof (showing first 400 characters): %s\n\n", jsonEncoded[:400])
}

The result is two proof values (P1 and P2). A sample run for the proof of the secret age of 50 between a range of 45 and 55 is [here]:

Secret age: 50

Age verified to be between 45 and 55

Proof P1 A: P256(112590415159915442325542577090046217072579791599205019206107100125825882566911,75092096974762129574834678888933623452343913756380584230349468641709089916110)

Proof P1 Mu: 89113826683569621766868094362724155972681442818934237781388006481735800318931

Proof P1 S: P256(32230650061109896541475943778235026753363072738912395670476014740523947688185,94627466719475644534365915190159312521208797013902295554091553763686240640678)

Proof P1 T1: P256(68480467553037728612115637452758031452784850901590124933547410266253398203950,113484901617165126374155453439654004610507453467782619954265502689801322154423)

Proof P1 T2: P256(83804692232972686041297937836219126957405233138502172888117096586272801916591,43607374112103348215744873858257477430449372928999532947520284695729664340062)

Proof P1 Taux: 8297917194055974191750040500111750184150570346072232765221949146158884140750

Proof P1 Tprime: 38129448874067796648246936192305136050354261235559641399390044426779305791047

Proof P1 V: P256(45951076488224712477665663962136398765012798493805494863776547651504173136629,31660199037105196447929244379061214555924032943966142646279654301568307680224)

Proof P1 A.X: 112590415159915442325542577090046217072579791599205019206107100125825882566911

Proof P1 V.X: 45951076488224712477665663962136398765012798493805494863776547651504173136629

Proof P1 V.Y: 31660199037105196447929244379061214555924032943966142646279654301568307680224

Proof (showing first 400 characters): {"P1":{"V":{"X":45951076488224712477665663962136398765012798493805494863776547651504173136629,"Y":31660199037105196447929244379061214555924032943966142646279654301568307680224},"A":{"X":112590415159915442325542577090046217072579791599205019206107100125825882566911,"Y":75092096974762129574834678888933623452343913756380584230349468641709089916110},"S":{"X":3223065006110989654147594377823502675336307

Proof size: 74493 bytes

This shows that the proof is 74,493 bytes (in a JSON format). If we try for a age that is not in the range we get [here]:

Secret age: 30

Age NOT verified to be between 45 and 55

Proof P1 A: P256(107674935833028786050950483401080890580461775654591187379760994418138582905515,29302741224754676887700881285174432529606838906812698878137081312842063670864)

Proof P1 Mu: 70743958195056025730311973257891298214846932327473432101386337579503425603611

Proof P1 S: P256(104167637927924477482189228834785523673371426081794363743937284762339348729492,84577972405285577987848216751664327556762292561991396283706723793680654747085)

Proof P1 T1: P256(91054865889944162264268897143985690071356764526242115517958274954022400660776,52677617561805446801955046827670068458172435646632299737932713469760455079156)

Proof P1 T2: P256(69320379767262679682549071004307497205441360692947644886650624655893660109257,76779099319629283439970276252921366628740544600963914196742776149484390251270)

Proof P1 Taux: 72116349907595771253574614363563385484373575683977577098549421418866301379080

Proof P1 Tprime: 68051421956881412007919993382044431634358454504754201277270023405664951757586

Proof P1 V: P256(32268198112753408863476280626167361282839319922135918081817761840071256186494,27727110602279761035757124167943389777848946938451589116827383210893329418422)

Proof P1 A.X: 107674935833028786050950483401080890580461775654591187379760994418138582905515

Proof P1 V.X: 32268198112753408863476280626167361282839319922135918081817761840071256186494

Proof P1 V.Y: 27727110602279761035757124167943389777848946938451589116827383210893329418422

Proof (showing first 400 characters): {"P1":{"V":{"X":32268198112753408863476280626167361282839319922135918081817761840071256186494,"Y":27727110602279761035757124167943389777848946938451589116827383210893329418422},"A":{"X":107674935833028786050950483401080890580461775654591187379760994418138582905515,"Y":29302741224754676887700881285174432529606838906812698878137081312842063670864},"S":{"X":1041676379279244774821892288347855236733714

Proof size: 74487 bytes

Conclusions

Unforunately, most of the systems we have created, have little respect for privacy, but where we now need to design them with privacy at their core. You can learn more about Bulletproofs and Rangeproofs here:

https://asecuritysite.com/bulletproof

References

[1] Bünz, B., Bootle, J., Boneh, D., Poelstra, A., Wuille, P., & Maxwell, G. (2018, May). Bulletproofs: Short proofs for confidential transactions and more. In 2018 IEEE symposium on security and privacy (SP) (pp. 315–334). IEEE.

[2] Chung, H., Han, K., Ju, C., Kim, M., & Seo, J. H. (2022). Bulletproofs+: Shorter proofs for a privacy-enhanced distributed ledger. IEEE Access, 10, 42081–42096.