Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 54 additions & 10 deletions pkg/zkproofs/range.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,27 @@ type RangeProof struct {
UpperBound int
}

type VerifierFactory interface {
getVerifier(upperBound int) (*bulletproof.RangeVerifier, error)
}

// Caches the verifiers created for each upper bound
type CachedRangeVerifierFactory struct {
verifiers map[int]*bulletproof.RangeVerifier
delegator VerifierFactory
}

type Ed25519RangeVerifierFactory struct{}

func NewCachedRangeVerifierFactory(delegator VerifierFactory) *CachedRangeVerifierFactory {
mapping := make(map[int]*bulletproof.RangeVerifier)

return &CachedRangeVerifierFactory{
verifiers: mapping,
delegator: delegator,
}
}

// NewRangeProof generates a range proof for some ciphertext that proves the value is between 0 and 2^upperBound
// Parameters:
// - upperBound The upper bound of the range we want to prove the value lies within, calculated as 2^upperBound
Expand All @@ -47,7 +68,7 @@ func NewRangeProof(upperBound int, value *big.Int, randomness curves.Scalar) (*R
if err != nil {
return nil, err
}

proof, err := prover.Prove(vScalar, randomness, upperBound, proofGenerators, transcript)
if err != nil {
return nil, err
Expand All @@ -64,7 +85,7 @@ func NewRangeProof(upperBound int, value *big.Int, randomness curves.Scalar) (*R
// - proof: The range proof to verify
// - ciphertext: The ciphertext for which we are verifying the range proof
// - upperBound: The upper bound of the range we want to prove the value lies within, calculated as 2^upperBound
func VerifyRangeProof(proof *RangeProof, ciphertext *elgamal.Ciphertext, upperBound int) (bool, error) {
func VerifyRangeProof(proof *RangeProof, ciphertext *elgamal.Ciphertext, upperBound int, verifierFactory VerifierFactory) (bool, error) {
// Validate input
if proof == nil || proof.Proof == nil || proof.Randomness == nil {
return false, errors.New("invalid proof")
Expand All @@ -74,25 +95,48 @@ func VerifyRangeProof(proof *RangeProof, ciphertext *elgamal.Ciphertext, upperBo
return false, errors.New("invalid ciphertext")
}

curve := curves.ED25519()
// Verifier gets the proof, the commitment, the generators to verify the value is within the range
verifier, err := bulletproof.NewRangeVerifier(upperBound, getRangeDomain(), getIppDomain(), *curve)
if err != nil {
return false, err
}

eg := elgamal.NewTwistedElgamal()
g := eg.GetG()
h := eg.GetH()
proofGenerators := bulletproof.NewRangeProofGenerators(g, h, proof.Randomness)
verified, err := verifier.Verify(proof.Proof, ciphertext.C, proofGenerators, proof.UpperBound, getTranscript())

// Get the verifier from the cache, then verify.
verifier, err := verifierFactory.getVerifier(upperBound)
if err != nil {
return false, err
}
verified, err := verifier.Verify(proof.Proof, ciphertext.C, proofGenerators, upperBound, getTranscript())
if err != nil {
return false, err
}

return verified, nil
}

func (c *CachedRangeVerifierFactory) getVerifier(upperBound int) (*bulletproof.RangeVerifier, error) {
verifier, exists := c.verifiers[upperBound]
if exists {
return verifier, nil
}

verifier, err := c.delegator.getVerifier(upperBound)
if err != nil {
return nil, err
}

c.verifiers[upperBound] = verifier
return verifier, nil
}

func (e *Ed25519RangeVerifierFactory) getVerifier(upperBound int) (*bulletproof.RangeVerifier, error) {
curve := curves.ED25519()
verifier, err := bulletproof.NewRangeVerifier(upperBound, getRangeDomain(), getIppDomain(), *curve)
if err != nil {
return nil, err
}
return verifier, nil
}

func getRangeDomain() []byte {
return []byte(rangeDomain)
}
Expand Down
101 changes: 84 additions & 17 deletions pkg/zkproofs/range_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ import (
"github.com/stretchr/testify/require"
)

type TestVerifier struct {
actualVerifier VerifierFactory
counter int
}

func (t *TestVerifier) getVerifier(upperBound int) (*bulletproof.RangeVerifier, error) {
t.counter++
return t.actualVerifier.getVerifier(upperBound)
}

// Coinbase Kryptology's bulletproof package is used to generate range proofs
func TestValueIsInRange(t *testing.T) {
curve := curves.ED25519()
Expand Down Expand Up @@ -164,15 +174,17 @@ func TestRangeProofs(t *testing.T) {
proof, err := NewRangeProof(n, value, gamma)
require.Nil(t, err)

verified, err := VerifyRangeProof(proof, ciphertext, n)
ed25519factory := Ed25519RangeVerifierFactory{}
verifierFactory := NewCachedRangeVerifierFactory(&ed25519factory)
verified, err := VerifyRangeProof(proof, ciphertext, n, verifierFactory)
require.NoError(t, err)
require.True(t, verified)

// Check that a ciphertext with a different value cannot use the same proof to verify as true, even if it meets the requirements.
ciphertext101, _, err := eg.Encrypt(keyPair.PublicKey, big.NewInt(101))
require.Nil(t, err)

verified, err = VerifyRangeProof(proof, ciphertext101, n)
verified, err = VerifyRangeProof(proof, ciphertext101, n, verifierFactory)
require.Error(t, err)
require.False(t, verified)
}
Expand All @@ -193,15 +205,18 @@ func TestRangeProofsLargeN(t *testing.T) {
proof, err := NewRangeProof(n, value, gamma)
require.Nil(t, err)

verified, err := VerifyRangeProof(proof, ciphertext, n)
ed25519factory := Ed25519RangeVerifierFactory{}
verifierFactory := NewCachedRangeVerifierFactory(&ed25519factory)

verified, err := VerifyRangeProof(proof, ciphertext, n, verifierFactory)
require.NoError(t, err)
require.True(t, verified)

// Check that a ciphertext with a different value cannot use the same proof to verify as true, even if it meets the requirements.
ciphertext101, _, err := eg.Encrypt(keyPair.PublicKey, big.NewInt(101))
require.Nil(t, err)

verified, err = VerifyRangeProof(proof, ciphertext101, n)
verified, err = VerifyRangeProof(proof, ciphertext101, n, verifierFactory)
require.Error(t, err)
require.False(t, verified)
}
Expand Down Expand Up @@ -233,7 +248,10 @@ func TestRangeProofsWithMarshaling(t *testing.T) {
err = json.Unmarshal(marshaledProof, &unmarshaled)
require.NoError(t, err, "Unmarshaling should not produce an error")

verified, err := VerifyRangeProof(&unmarshaled, ciphertext, n)
ed25519factory := Ed25519RangeVerifierFactory{}
verifierFactory := NewCachedRangeVerifierFactory(&ed25519factory)

verified, err := VerifyRangeProof(&unmarshaled, ciphertext, n, verifierFactory)
require.NoError(t, err)
require.True(t, verified)
}
Expand Down Expand Up @@ -275,37 +293,41 @@ func TestVerifyRangeProof_InvalidInput(t *testing.T) {
proof, err := NewRangeProof(64, value, gamma)
require.NoError(t, err)

ed25519factory := Ed25519RangeVerifierFactory{}
verifierFactory := NewCachedRangeVerifierFactory(&ed25519factory)

t.Run("Nil proof", func(t *testing.T) {
// Proof is nil
valid, err := VerifyRangeProof(nil, ciphertext, 64)
valid, err := VerifyRangeProof(nil, ciphertext, 64, verifierFactory)
require.EqualError(t, err, "invalid proof")
require.False(t, valid)
})

t.Run("Proof with nil fields", func(t *testing.T) {
valid, err := VerifyRangeProof(&RangeProof{}, ciphertext, 64)
valid, err := VerifyRangeProof(&RangeProof{}, ciphertext, 64, verifierFactory)
require.EqualError(t, err, "invalid proof")
require.False(t, valid)
})

t.Run("nil ciphertext", func(t *testing.T) {
// Proof is nil
valid, err := VerifyRangeProof(proof, nil, 64)
valid, err := VerifyRangeProof(proof, nil, 64, verifierFactory)
require.EqualError(t, err, "invalid ciphertext")
require.False(t, valid)
})

t.Run("Ciphertext with nil fields", func(t *testing.T) {
// Proof is nil
valid, err := VerifyRangeProof(proof, &elgamal.Ciphertext{}, 64)
valid, err := VerifyRangeProof(proof, &elgamal.Ciphertext{}, 64, verifierFactory)
require.EqualError(t, err, "invalid ciphertext")
require.False(t, valid)
})
}

func TestRangeProofsPerformance(t *testing.T) {
// Test that it is fine to reuse verifiers.
func TestRangeProofVerifierReuse(t *testing.T) {
value := big.NewInt(10)
n := 32 // the range is [0, 2^128]
n := 128 // the range is [0, 2^128]

privateKey, err := testutils.GenerateKey()
require.Nil(t, err, "Error generating private key")
Expand All @@ -316,12 +338,57 @@ func TestRangeProofsPerformance(t *testing.T) {

ciphertext, gamma, _ := eg.Encrypt(keyPair.PublicKey, value)

for i := 0; i < 100; i++ {
proof, err := NewRangeProof(n, value, gamma)
require.Nil(t, err)
proof, err := NewRangeProof(n, value, gamma)
require.Nil(t, err)

// Create a verifier with len 128 vector
ed25519factory := Ed25519RangeVerifierFactory{}
verifierFactory := NewCachedRangeVerifierFactory(&ed25519factory)

// Verify that this works normally for verifying a proof with the same upper bound
verified, err := VerifyRangeProof(proof, ciphertext, n, verifierFactory)
require.NoError(t, err)
require.True(t, verified)

// Verify that this still works on a different proof
proof, err = NewRangeProof(n, value, gamma)
require.Nil(t, err)

verified, err = VerifyRangeProof(proof, ciphertext, n, verifierFactory)
require.NoError(t, err)
require.True(t, verified)

// Verify that this still works on a proof on a different value
newValue := big.NewInt(10238032)
newCiphertext, gamma, _ := eg.Encrypt(keyPair.PublicKey, newValue)
proof, err = NewRangeProof(n, newValue, gamma)
require.Nil(t, err)

verified, err = VerifyRangeProof(proof, newCiphertext, n, verifierFactory)
require.NoError(t, err)
require.True(t, verified)
}

func TestRangeVerifierIsLazyLoadedFromCache(t *testing.T) {
upperBound := 64
curve := curves.ED25519()
expectedVerifier, err := bulletproof.NewRangeVerifier(upperBound, getRangeDomain(), getIppDomain(), *curve)
require.NoError(t, err)

verified, err := VerifyRangeProof(proof, ciphertext, n)
require.NoError(t, err)
require.True(t, verified)
testVerifier := &TestVerifier{
actualVerifier: &Ed25519RangeVerifierFactory{},
}
cachedVerifier := NewCachedRangeVerifierFactory(testVerifier)

// First invocation should call the actual verifier
actualVerifier, err := cachedVerifier.getVerifier(upperBound)
require.NoError(t, err)
require.Equal(t, expectedVerifier, actualVerifier)
require.Equal(t, 1, testVerifier.counter)

// Second invocation should return the cached verifier, so the counter should not increment
actualVerifier, err = cachedVerifier.getVerifier(upperBound)
require.NoError(t, err)
require.Equal(t, expectedVerifier, actualVerifier)
require.Equal(t, 1, testVerifier.counter)
}
Loading