Zero Knowledge Proof (ZKP) in SQL Server

Problem

A zero-knowledge proof is a special way to you, the prover, to show someone else, the verifier, that you know a secret without ever telling them what the secret is. In this article, we look at how you can implement a zero-knowledge proof in SQL Server without using external tools.

Solution

Using a zero-knowledge proof is very useful to preserve information and to retrieve only what you want. The main use case is for private transactions or blockchain.

Examples:

  • Finance: a customer can prove they have enough balance to make a transaction without revealing the full account balance.
  • Healthcare: a patient can prove they are eligible for a medical trial without exposing their medical history.
  • Compliance: a company can prove to a regulator that they processed data according to the rules without sharing any sensitive data.

How It Works

The phase of trusted setup to generate the secret parameters requires security measures to avoid breaches.

The proof is succinct and the verification speed is very fast but recursive proofs are hard and complex.

I will use the Schnorr-style ZKP where everything started with the math-dance between prover and verifier:

  • p: is a large prime number that defines the group size
  • g: is a generator number that can reach all values in the group when raised to powers
  • x: is the prover’s secret private key
  • y: is the prover’s public value, computed as math formula

Now, in the challenge-response part, the proof:

  • r: is a random number the prover picks
  • t: is a commitment mask computed as math formula
  • c: is the challenge sent by the verifier
  • s: is the prover´s response where math formula

This is how it does the work:

  1. Prover: computes t and sends it
  2. Verifier: sends random challenge c
  3. Prover: replies with s
  4. Verifier: check if math formula

If it holds, the proof is valid once the prover must know the value of x, which is never revealed.

In summary, the prover hides their secret x behind math with p and g, uses r and t to disguise it, gets a test c, replies with s, and the verifier checks the equation with only public values.

We need to talk about a 256-bit safe prime which is a prime p that is approximately math formula, about math formula in size, and is called a safe prime due to math formula, where q is also prime. The importance of use this is to make it secure because they ensure the math group is large and has no weak shortcuts for attackers. If p value is small make it trivial to use brute-force attack to guess x.

Visual Studio Code

Once I found that SQL Server bigint has a limitation to deal with numbers greater than math formula, I decided to use as a solution the CLR integration using BigInteger in VB .NET in Visual Studio. You will notice that to deal with big integers I will retrieve them as strings for the purpose to visually follow-up what is going on, but the best format should be varbinary.

Open visual studio:

  1. Get started choosing Create a new project
  2. Choose the template of Class Library (.Net framework) – Visual Basic
  3. I will call the Project SqlMathClr and click Create
  4. Rename the Class1.vb file to SqlMath.vb
  5. Replace the content of SqlMath.vb with the respective code below
  6. Insert a new module called GenSafePrime.vb and replace with the respective code below
  7. Build the solution
  8. Record the path of the created SqlMathClr.dll, in my case “E:\VS Projects\SqlMathClr\SqlMathClr\bin\Release”

SqlMath.vb code:

Imports System.Data.SqlTypes
Imports System.Numerics
Imports System.Security.Cryptography
Imports Microsoft.SqlServer.Server
 
Public Class SqlMath
    Private Shared ReadOnly rnd As New Random()
 
    <SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=True, IsPrecise:=True)>
    Public Shared Function BigAdd(num1 As SqlString, num2 As SqlString) As SqlString
        Dim a As BigInteger = BigInteger.Parse(num1.Value)
        Dim b As BigInteger = BigInteger.Parse(num2.Value)
        Return New SqlString(BigInteger.Add(a, b).ToString())
    End Function
 
    <SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=True, IsPrecise:=True)>
    Public Shared Function BigMod(num As SqlString, modNum As SqlString) As SqlString
        Dim n As BigInteger = BigInteger.Parse(num.Value)
        Dim m As BigInteger = BigInteger.Parse(modNum.Value)
        Return New SqlString(BigInteger.Remainder(n, m).ToString())
    End Function
 
    <SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=True, IsPrecise:=True)>
    Public Shared Function BigModPow(baseNum As SqlString, exponent As SqlString, modNum As SqlString) As SqlString
        Dim b As BigInteger = BigInteger.Parse(baseNum.Value)
        Dim e As BigInteger = BigInteger.Parse(exponent.Value)
        Dim m As BigInteger = BigInteger.Parse(modNum.Value)
        Return New SqlString(BigInteger.ModPow(b, e, m).ToString())
    End Function
 
    <SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=True, IsPrecise:=True)>
    Public Shared Function BigMultiply(num1 As SqlString, num2 As SqlString) As SqlString
        Dim a As BigInteger = BigInteger.Parse(num1.Value)
        Dim b As BigInteger = BigInteger.Parse(num2.Value)
        Return New SqlString(BigInteger.Multiply(a, b).ToString())
    End Function
 
    <SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=True, IsPrecise:=True)>
    Public Shared Function BigSubtract(num1 As SqlString, num2 As SqlString) As SqlString
        Dim a As BigInteger = BigInteger.Parse(num1.Value)
        Dim b As BigInteger = BigInteger.Parse(num2.Value)
        Return New SqlString(BigInteger.Subtract(a, b).ToString())
    End Function
 
    <SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=True, IsPrecise:=True)>
    Public Shared Function GuidToBigInteger(g As Guid) As SqlString
        Dim guidBytes As Byte() = g.ToByteArray()
        Dim bigInt As New BigInteger(guidBytes)
 
        Return New SqlString(bigInt.ToString)
    End Function
 
    <SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=True, IsPrecise:=True)>
    Public Shared Function HashToBigInteger(t As SqlBytes, mStr As SqlString) As SqlString
        Dim maxValue As BigInteger = BigInteger.Parse(mStr.Value)
        Dim hb() As Byte = t.Value
        Dim hashInt As New BigInteger(hb)
 
        If hashInt.Sign < 0 Then
            hashInt = BigInteger.Negate(hashInt)
        End If
 
        Dim result As BigInteger = hashInt Mod maxValue
 
        Return New SqlString(result.ToString())
    End Function
 
    <SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=False, IsPrecise:=True)>
    Public Shared Function CryptoRandomBigInteger(maxStr As SqlString) As SqlString
        Dim max As BigInteger = BigInteger.Parse(maxStr.Value)
 
        Dim bytes(max.ToByteArray().Length - 1) As Byte
        Using rng As RandomNumberGenerator = RandomNumberGenerator.Create()
            rng.GetBytes(bytes)
        End Using
        Dim value As New BigInteger(bytes)
        value = BigInteger.Abs(value) Mod (max - 1) + 1
        Return value.ToString()
    End Function
 
    <SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=False)>
    Public Shared Function GenerateSafePrime(bits As SqlInt32) As SqlString
        Dim p As BigInteger = SafePrimeGen(bits.Value)
        Return New SqlString(p.ToString())
    End Function
 
End Class

If you observe the code I am returning for precaution, in all functions, the values as string due to overflow problems with bigint in SQL.

GenSafePrime.vb code:

Imports System.Numerics
Imports System.Security.Cryptography
 
Module GenSafePrime
   
    Private ReadOnly SmallPrimes As Integer() = {
        3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47
    }
 
    <System.Runtime.CompilerServices.Extension>
    Public Function GetBitLength(value As BigInteger) As Integer
        Dim bytes() As Byte = value.ToByteArray()
        Dim msb As Byte = bytes(bytes.Length - 1)
        Dim bitLen As Integer = (bytes.Length - 1) * 8
 
        While msb <> 0
            bitLen += 1
            msb >>= 1
        End While
 
        Return bitLen
    End Function
 
    <System.Runtime.CompilerServices.Extension()>
    Public Function GetByteCount(value As BigInteger) As Integer
        Return value.ToByteArray().Length
    End Function
 
    Public Function IsProbablePrime(n As BigInteger, Optional k As Integer = 40) As Boolean
        If n < 2 Then Return False
        If n = 2 OrElse n = 3 Then Return True
        If n Mod 2 = 0 Then Return False
 
        Dim d As BigInteger = n - 1
        Dim r As Integer = 0
        While d Mod 2 = 0
            d = BigInteger.Divide(d, 2)
            r += 1
        End While
 
        For i As Integer = 1 To k
            Dim a As BigInteger
            Do
                a = RandomBigInteger(n.GetBitLength())
            Loop While a < 2 OrElse a >= n - 2
 
            Dim x As BigInteger = BigInteger.ModPow(a, d, n)
            If x = 1 OrElse x = n - 1 Then Continue For
 
            Dim passed As Boolean = False
            For j As Integer = 1 To r - 1
                x = BigInteger.ModPow(x, 2, n)
                If x = n - 1 Then
                    passed = True
                    Exit For
                End If
            Next
            If Not passed Then Return False
        Next
 
        Return True
    End Function
 
    Public Function IsPrime(n As BigInteger) As Boolean
        For Each p In SmallPrimes
            If n Mod p = 0 Then
                Return False
            End If
        Next
 
        Return True
    End Function
 
    Public Function SafePrimeGen(bits As Integer) As BigInteger
        Do
            Dim q As BigInteger
            Do
                q = RandomBigInteger(bits - 1) Or 1
            Loop Until IsProbablePrime(q)
 
            Dim p As BigInteger = 2 * q + 1
            If IsProbablePrime(p) AndAlso p.GetBitLength() = bits Then
                Return p
            End If
        Loop
    End Function
 
    Private Function RandomBigInteger(bits As Integer) As BigInteger
        Dim byteLen As Integer = (bits + 7) \ 8
        Dim bytes(byteLen - 1) As Byte
 
        Using rng As RandomNumberGenerator = RandomNumberGenerator.Create()
            rng.GetBytes(bytes)
        End Using
 
        bytes(bytes.Length - 1) = bytes(bytes.Length - 1) Or &H80
 
        Return BigInteger.Abs(New BigInteger(bytes))
    End Function
 
End Module

SQL Server CLR

The first thing that you need to do it to enable CLR if it is not enabled.

-- MSSQLTips (TSQL)
 
EXEC sp_configure 'clr enabled', 1;
RECONFIGURE;
GO

I will create a schema to group in all functions of the CLR.

-- MSSQLTips (TSQL)
 
IF NOT EXISTS 
    (SELECT      1 
        FROM     sys.schemas 
        WHERE    [name] = N'AsmMath')
    EXEC('CREATE SCHEMA [AsmMath]')
GO

It is necessary for security reasons to add the dll as trusted assembly. Change the path to your destination.

-- MSSQLTips (TSQL)
 
DECLARE @hash varbinary(64) = 
    (SELECT HASHBYTES('SHA2_512', BulkColumn)
        FROM OPENROWSET(BULK 'E:\VS Projects\SqlMathClr\SqlMathClr\bin\Release\SqlMathClr.dll', SINGLE_BLOB) AS AssemblyBlob);
 
EXEC sp_add_trusted_assembly 
    @assembly_hash = @hash,
    @description = N'SQL Math CLR assembly';
GO

Now it is necessary to register the assembly, creating or altering it. Change the path to your destination.

-- MSSQLTips (TSQL)
 
-- Register assembly
IF NOT EXISTS 
    (SELECT      1
        FROM     sys.assemblies
        WHERE    [name] = N'SqlMathClr')
    CREATE ASSEMBLY [AsmMath]
        FROM 'E:\VS Projects\SqlMathClr\SqlMathClr\bin\Release\SqlMathClr.dll'
        WITH PERMISSION_SET = SAFE;
ELSE
    ALTER ASSEMBLY [AsmMath]
        FROM 'E:\VS Projects\SqlMathClr\SqlMathClr\bin\Release\SqlMathClr.dll'
        WITH PERMISSION_SET = SAFE;
GO

Now we are going to add all functions to the schema created.

-- MSSQLTips (TSQL)
 
CREATE FUNCTION AsmMath.BigAdd(@a nvarchar(MAX), @b nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigAdd;
GO
 
CREATE FUNCTION AsmMath.BigMod(@a nvarchar(MAX), @m nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigMod;
GO
 
CREATE FUNCTION AsmMath.BigModPow(@a nvarchar(MAX), @exp nvarchar(MAX), @m nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigModPow;
GO
 
CREATE FUNCTION AsmMath.BigMultiply(@a nvarchar(MAX), @b nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigMultiply;
GO
 
CREATE FUNCTION AsmMath.BigSubtract(@a nvarchar(MAX), @b nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigSubtract;
GO
 
CREATE FUNCTION AsmMath.GuidToBigInteger(@g uniqueidentifier)
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].GuidToBigInteger;
GO
 
CREATE FUNCTION AsmMath.zkCryptoRandomBigInteger(@max nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].CryptoRandomBigInteger;
GO
 
CREATE FUNCTION AsmMath.zkGenerateSafePrime(@bits int)
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].GenerateSafePrime;
GO
 
CREATE FUNCTION [AsmMath].[zkHashToBigInteger] (
    @t VARBINARY(MAX),   -- must match SqlBytes
    @mStr NVARCHAR(MAX)  -- must match SqlString
)
RETURNS NVARCHAR(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].HashToBigInteger;
GO

Use this, if you want to verify the assembler details.

-- MSSQLTips (TSQL)
 
SELECT       [name]
            ,[clr_name]
            ,[create_date]
            ,[modify_date]
    FROM     sys.assemblies
    WHERE    [name] = 'AsmMath';
GO

Use this, in case you need to remove the CLR code.

-- MSSQLTips (TSQL)
 
DROP FUNCTION IF EXISTS AsmMath.BigAdd;
DROP FUNCTION IF EXISTS AsmMath.BigMod;
DROP FUNCTION IF EXISTS AsmMath.BigModPow;
DROP FUNCTION IF EXISTS AsmMath.BigMultiply;
DROP FUNCTION IF EXISTS AsmMath.BigSubtract;
DROP FUNCTION IF EXISTS AsmMath.GuidToBigInteger;
DROP FUNCTION IF EXISTS AsmMath.zkCryptoRandomBigInteger;
DROP FUNCTION IF EXISTS AsmMath.zkGenerateSafePrime;
DROP FUNCTION IF EXISTS AsmMath.zkHashToBigInteger;
 
DROP ASSEMBLY IF EXISTS AsmMath;
GO

SQL Server Testing

Here is our first test.

-- MSSQLTips (TSQL)
 
-- Prover: computes t and sends it =======================================================
DECLARE  @p nvarchar(MAX) = AsmMath.zkGenerateSafePrime(256)
        ,@g nvarchar(MAX) = FLOOR(RAND(CHECKSUM(NEWID())) * 8 + 2);
 
DECLARE @p1 nvarchar(MAX) = [AsmMath].[BigSubtract] (@p,1);
 
-- keys private (x) and public (y)
DECLARE @x nvarchar(MAX) = [AsmMath].[zkCryptoRandomBigInteger] (@p1);
DECLARE @y nvarchar(MAX) = [AsmMath].[BigModPow] (@g,@x,@p);
 
DECLARE @r nvarchar(MAX) = [AsmMath].[zkCryptoRandomBigInteger] (@p1);
 
DECLARE @t nvarchar(MAX) = [AsmMath].[BigModPow] (@g,@r,@p);
 
-- Verifier: sends random challenge c ====================================================
 
DECLARE @c nvarchar(MAX) = [AsmMath].[zkHashToBigInteger](HASHBYTES('SHA2_256', @t), @p1);
 
-- Prover: replies with s ================================================================
 
DECLARE @n1 nvarchar(MAX) = [AsmMath].[BigMultiply] (@c ,@x);
DECLARE @n2 nvarchar(MAX) = [AsmMath].[BigAdd] (@r ,@n1);
DECLARE @s nvarchar(MAX) = [AsmMath].[BigMod] (@n2 ,@p1);
 
-- Checking ==============================================================================
 
DECLARE @left nvarchar(MAX) = [AsmMath].[BigModPow] (@g,@s,@p);
 
SET @n1 = [AsmMath].[BigModPow] (@y,@c,@p);
SET @n2 = [AsmMath].[BigMultiply] (@t,@n1);
 
DECLARE @right nvarchar(MAX) = [AsmMath].[BigMod] (@n2 ,@p);
 
IF @left = @right 
    SELECT N'✅ Proof is valid!' AS Chk
ELSE
    SELECT N'✖ Proof is invalid!' AS Chk
 
GO

Resulting in

Proof is valid

If you observe the code, in some cases, to find the safe prime is the part of the code that takes the most time to process. You could change as follows to make it process faster.

--DECLARE  @p nvarchar(MAX) = AsmMath.zkGenerateSafePrime(256)
--        ,@g nvarchar(MAX) = FLOOR(RAND(CHECKSUM(NEWID())) * 8 + 2);
 
DECLARE  @p nvarchar(MAX) = '61472017860777475416208240371337801680959896334481278354353551701346589915127'
        ,@g nvarchar(MAX) = '2';
 
-- the number 614…15127 was obtained running 
-- SELECT AsmMath.zkGenerateSafePrime(256);
GO

Example Use

The premise is to prove a statement is true without revealing the underlying secret.

I will use as example from the ZKProof Community Reference, at page 77, which describes “Mainly, there is an identity, Alice, who wants to prove to some company, Bob Inc. that she is an accredited investor, under the SEC rules, in order to acquire some company shares. Alice is the prover; the IRS, the AML entity and The Bank are all issuers; and Bob Inc. is the verifier.”

Each issuer verified all the constraints before saving its credential.

I intentionally simplified for educational purposes omitting many of the security measures and protocol rules that would be necessary in a real-world zero-knowledge proof implementation. I also fixed the p value to speed up the result since sometimes to find the safe prime value takes a bit of time.

-- MSSQLTips (TSQL)
 
-- ZKProof Community Reference (pg. 77)
-- Credential Aggregation
 
DECLARE         @Credentials
    TABLE    (Issuer nvarchar(10)
            ,CredentialHolder uniqueidentifier
            ,CredentialIssuer nvarchar(MAX));
 
DECLARE         @CredentialsChk
    TABLE    (Issuer nvarchar(10)
            ,CredentialStatus nvarchar(MAX));
 
DECLARE  @c nvarchar(MAX)
        ,@p nvarchar(MAX)
        ,@p1 nvarchar(MAX)
        ,@r nvarchar(MAX)
        ,@t nvarchar(MAX)
        ,@y nvarchar(MAX)
        ,@chk nvarchar(80)
        ,@issuer nvarchar(10);
 
DECLARE @CredentialHolder uniqueidentifier = 'CCBB64E5-62C4-4BFE-B296-FE4AFFD8B57B'; -- SELECT NEWID();
 
DECLARE @Credential nvarchar(MAX) = AsmMath.GuidToBigInteger (@CredentialHolder);
 
-- private info
DECLARE  @x nvarchar(MAX) = @Credential
        ,@g nvarchar(MAX) = '5';
 
-- public info
 
-- SET @p = AsmMath.zkGenerateSafePrime(256);
 
 
/* 
 
-- IRS =====================================================================================
The IRS issues a tax credential, C0, that testifies to the claim “from 1/1/2017 until
1/1/2018, Alice, with identifier X0, owes 0$ to the IRS, with identifier Y ” and holds two
attributes: the net income of Alice, $income, and a bit b such that b = 1 if Alice has
paid her taxes
 
*/
 
SET @p = '61472017860777475416208240371337801680959896334481278354353551701346589915127'; 
SET @y = [AsmMath].[BigModPow] (@g,@x,@p);
SET @p1 = [AsmMath].[BigSubtract] (@p,1);
SET @r  = [AsmMath].[zkCryptoRandomBigInteger] (@p1);
SET @t  = [AsmMath].[BigModPow] (@g,@r,@p);
 
INSERT INTO         @Credentials
    VALUES        ('IRS'
                ,@CredentialHolder
                ,[AsmMath].[zkHashToBigInteger] (HASHBYTES('SHA2_256', CONCAT(@t, @y)), @p1));
 
 
/* 
 
-- AML =====================================================================================
The AML entity issues a KYC credential, C1, that testifies to claim T1:= “Alice, with
identifier X1, has NO relation to a (set of) blacklisted organization(s)”
 
*/
 
 
SET @p = '61472017860777475416208240371337801680959896334481278354353551701346589915127'; 
SET @y = [AsmMath].[BigModPow] (@g,@x,@p);
SET @p1 = [AsmMath].[BigSubtract] (@p,1);
SET @r  = [AsmMath].[zkCryptoRandomBigInteger] (@p1);
SET @t  = [AsmMath].[BigModPow] (@g,@r,@p);
 
INSERT INTO         @Credentials
    VALUES        ('AML'
                ,@CredentialHolder
                ,[AsmMath].[zkHashToBigInteger] (HASHBYTES('SHA2_256', CONCAT(@t, @y)), @p1));
 
 
/* 
 
-- The bank ================================================================================
The Bank issues a net-worth credential, C2, that testifies to claim T2:= “Alice has a net
worth of V Alice”
 
*/
 
 
SET @p = '61472017860777475416208240371337801680959896334481278354353551701346589915127'; 
SET @y = [AsmMath].[BigModPow] (@g,@x,@p);
SET @p1 = [AsmMath].[BigSubtract] (@p,1);
SET @r  = [AsmMath].[zkCryptoRandomBigInteger] (@p1);
SET @t  = [AsmMath].[BigModPow] (@g,@r,@p);
 
INSERT INTO         @Credentials
    VALUES        ('The Bank'
                ,@CredentialHolder
                ,[AsmMath].[zkHashToBigInteger] (HASHBYTES('SHA2_256', CONCAT(@t, @y)), @p1));
 
 
SELECT * FROM @Credentials;
 
-- Bob Inc. ================================================================================
 
 
-- Alice as Prover: replies with s =========================================================
 
DECLARE crsZkProofChk CURSOR FAST_FORWARD READ_ONLY FOR 
    SELECT         Issuer
                ,CredentialIssuer    
        FROM     @Credentials; 
 
OPEN crsZkProofChk
    FETCH NEXT FROM crsZkProofChk INTO @issuer,@c;
 
    WHILE @@FETCH_STATUS = 0 BEGIN
        SET @p = '61472017860777475416208240371337801680959896334481278354353551701346589915127'; 
        SET @y = [AsmMath].[BigModPow] (@g,@x,@p);
        SET @p1 = [AsmMath].[BigSubtract] (@p,1);
        SET @r  = [AsmMath].[zkCryptoRandomBigInteger] (@p1);
        SET @t  = [AsmMath].[BigModPow] (@g,@r,@p);
 
        -- Tampering =============
        --IF @issuer = 'The Bank'
        --    SET @g = '3';
 
        DECLARE @n1 nvarchar(MAX) = [AsmMath].[BigMultiply] (@c ,@x);
        DECLARE @n2 nvarchar(MAX) = [AsmMath].[BigAdd] (@r ,@n1);
        DECLARE @s nvarchar(MAX) =  [AsmMath].[BigMod] (@n2 ,@p1);    
            
        DECLARE @left nvarchar(MAX) = [AsmMath].[BigModPow] (@g,@s,@p);
 
        SET @n1 = [AsmMath].[BigModPow] (@y,@c,@p);
        SET @n2 = [AsmMath].[BigMultiply] (@t,@n1);
 
        DECLARE @right nvarchar(MAX) = [AsmMath].[BigMod] (@n2 ,@p);
 
        IF @left = @right 
            SET @chk = N'✅ Proof is valid!';
        ELSE
            SET @chk = N'✖ Proof is invalid!';            
 
        INSERT INTO @CredentialsChk
            VALUES    (@issuer
                    ,@chk);
 
        FETCH NEXT FROM crsZkProofChk INTO @issuer,@c;
    END
CLOSE crsZkProofChk
DEALLOCATE crsZkProofChk
 
-- Checking ==============================================================================
 
SELECT * FROM @CredentialsChk;
GO

Resulting in

Result proof check

Uncomment the tampering part (shown below) in the above code and run again to check the results.

        -- Tampering =============
        --IF @issuer = 'The Bank'
        --    SET @g = '3';

Final Comments

There are some details to be aware of for a real ZK-Proof implementation:

  • Use proper cryptographic primitives: secure groups, Pedersen commitments, strong hash functions, and cryptographically secure randomness.
  • Ensure the core security properties really hold and that the protocol can be formally simulated without leaking information.
  • Implementation details such as input encoding, proof aggregation, and error handling matter a lot, because real systems exploit these to gain efficiency and safety.
  • Bulletproofs are an improvement to prove that a value is within a range without revealing it.

Next Steps

3 Comments

  1. There are a lot of T-SQL anti-pattern scripts in this article. Please note that developers may use these scripts in production environments and experience poor performance. SQL Server is not a tool for implementing logic. It is a tool for storing and retrieving data.

    Thanks!

  2. Thanks for the tips. Did you examine the execution plans of the queries you published? Also, are you aware of the performance of CLR functions? Their performance is actually quite poor. Additionally, I don’t believe SQL Server is the right tool for writing logic code such as IF…Else.”

    • Hi David, I think the purpose of the article is to show how this could be done, not necessarily that it is the best option. There are lots of other tools that would perform better.

Leave a Reply

Your email address will not be published. Required fields are marked *