Skip to content

Commit

Permalink
first release
Browse files Browse the repository at this point in the history
  • Loading branch information
eliastor committed Aug 25, 2023
1 parent df65a0b commit 8296496
Show file tree
Hide file tree
Showing 7 changed files with 747 additions and 0 deletions.
395 changes: 395 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

147 changes: 147 additions & 0 deletions ed25519.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package nizk

import (
"crypto/rand"
"crypto/sha256"
"fmt"
"io"

"filippo.io/edwards25519"
"golang.org/x/crypto/sha3"
)

const (
SecretSize = 32
ProofSize = 64

// this prefix is needed to eliminate accidental generation of the same hash outside the proof
magicPrefix = "Fiat-Shamir NIZK proof made by https://github.com/eliastor?d3eac842bf90905fc958c42422132e984676521f EOL"
)

var (
_ Nizk = &ed25519Nizk{}
)

type ed25519Nizk struct {
hashSum func(data []byte) [SecretSize]byte
}

func (n *ed25519Nizk) fingerprintPointScalar(sum []byte) (*edwards25519.Point, *edwards25519.Scalar) {
sc, _ := edwards25519.NewScalar().SetBytesWithClamping(sum)
point := edwards25519.NewIdentityPoint().ScalarBaseMult(sc)
return point, sc
}

func (n *ed25519Nizk) messagePointID(message []byte) (*edwards25519.Point, *edwards25519.Scalar) {
sum := n.hashSum(append([]byte(magicPrefix), message...))
s := make([]byte, SecretSize)
copy(s, sum[:])
X, x := n.fingerprintPointScalar(s)
return X, x
}

func (n *ed25519Nizk) Fingerprint(message []byte) (id []byte) {
point, _ := n.messagePointID(message)
id = point.Bytes()
return
}

func (n *ed25519Nizk) calcGYTPHashScalar(fingerprint, pT, stamp []byte) (*edwards25519.Scalar, error) {
gytp := []byte{}
gytp = append(gytp, edwards25519.NewGeneratorPoint().Bytes()...)
gytp = append(gytp, fingerprint...)
gytp = append(gytp, pT...)
gytp = append(gytp, stamp...)
sum := n.hashSum(gytp)
c, err := edwards25519.NewScalar().SetBytesWithClamping(sum[:])
if err != nil {
return nil, fmt.Errorf("can't create c scalar: %w", err)
}
return c, nil
}

func (n *ed25519Nizk) Proove(msg []byte, stamp []byte) (Proof, []byte, error) {
vb := [SecretSize]byte{}
_, err := io.ReadFull(rand.Reader, vb[:])
if err != nil {
return nil, nil, err
}
v, err := edwards25519.NewScalar().SetBytesWithClamping(vb[:])
if err != nil {
return nil, nil, fmt.Errorf("can't create r scalar: %w", err)
}

T := edwards25519.NewIdentityPoint().ScalarBaseMult(v)

Tb := T.Bytes()

X, x := n.messagePointID(msg)
fingerprint := X.Bytes()

c, err := n.calcGYTPHashScalar(fingerprint, Tb, stamp) // h = hash(G||id||vG||stamp)
if err != nil {
return nil, nil, fmt.Errorf("can't calculate GYTP hash: %w", err)
}

c.Negate(c)
r := edwards25519.NewScalar().MultiplyAdd(c, x, v) // (-h) * x + v

var proof [ProofSize]byte
copy(proof[:SecretSize], Tb)
copy(proof[SecretSize:], r.Bytes())

return proof[:], fingerprint, nil
}

func (n *ed25519Nizk) ProofSize() int {
return ProofSize
}

func (n *ed25519Nizk) FingerprintSize() int {
return SecretSize
}

func (n *ed25519Nizk) Verify(id []byte, stamp []byte, proof Proof) (bool, error) {
if len(proof) != n.ProofSize() {
return false, fmt.Errorf("wrong size of proof, expected %d", n.ProofSize())
}

Tb := proof[:SecretSize]
rb := proof[SecretSize:]

r, err := edwards25519.NewScalar().SetCanonicalBytes(rb)
if err != nil {
return false, fmt.Errorf("can't read r scalar: %w", err)
}

X, err := edwards25519.NewIdentityPoint().SetBytes(id)
if err != nil {
return false, fmt.Errorf("can't read X point: %w", err)
}

c, err := n.calcGYTPHashScalar(id, Tb, stamp)
if err != nil {
return false, fmt.Errorf("can't calculate GYTP hash: %w", err)
}

T, err := edwards25519.NewIdentityPoint().SetBytes(Tb)
if err != nil {
return false, fmt.Errorf("can't read T point: %w", err)
}

calculatedT := edwards25519.NewIdentityPoint().VarTimeDoubleScalarBaseMult(c, X, r)

return T.Equal(calculatedT) == 1, nil
}

func NewEd25519Sha3() Nizk {
return &ed25519Nizk{
hashSum: sha3.Sum256,
}
}

func NewEd25519Sha256() Nizk {
return &ed25519Nizk{
hashSum: sha256.Sum256,
}
}
48 changes: 48 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package nizk_test

import (
"bytes"
"fmt"

"github.com/eliastor/go-nizk"
)

func Example() {

// Alice
AliceZK := nizk.NewEd25519Sha3()

msg := []byte("Arbitrary message which knowledge you want to prove.")
stamp := []byte("any other information you want to include to the proof: public keys, salts, symmetric keys, etc.")

AliceProof, AliceFingerprint, err := AliceZK.Proove(msg, stamp)
if err != nil {
fmt.Println(err)
return
}

// Alice publishes fingerprint and proof.
// Bob who knows the same message can generate fingerprint of the message
// and can easily verify that Alice also knows the message:

// Bob
msg = msg // Bob knows the message
stamp = stamp // stamp must be the same, it's offline agreement of the protocol or Alice and Bob

BobZK := nizk.NewEd25519Sha3()
BobFingerprint := BobZK.Fingerprint(msg)

if !bytes.Equal(BobFingerprint, AliceFingerprint) {
fmt.Println("fingerprints are not equal, so Bob's and Alice's messages are not equal, so nothing to check")
return
}

valid, err := BobZK.Verify(AliceFingerprint, stamp, AliceProof)
if err != nil {
fmt.Println(err)
return
}
if !valid {
fmt.Println("Alice's proof is not valid")
}
}
14 changes: 14 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module github.com/eliastor/go-nizk

go 1.20

require github.com/stretchr/testify v1.8.4

require (
filippo.io/edwards25519 v1.0.1-0.20220803165937-8c58ed0e3550 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/sys v0.11.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
17 changes: 17 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
filippo.io/edwards25519 v1.0.1-0.20220803165937-8c58ed0e3550 h1:Mqu6Q2e//30TWeP5bM9Th5KEzWdFAFd80Y2ZXN9fmeE=
filippo.io/edwards25519 v1.0.1-0.20220803165937-8c58ed0e3550/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
13 changes: 13 additions & 0 deletions nizk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package nizk

type Suite interface{}

type Proof []byte

type Nizk interface {
Proove(msg []byte, stamp []byte) (proof Proof, fingerprint []byte, err error)
ProofSize() int
FingerprintSize() int
Fingerprint(msg []byte) []byte
Verify(fingerprint []byte, stamp []byte, proof Proof) (valid bool, err error)
}
113 changes: 113 additions & 0 deletions nizk_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package nizk_test

import (
"crypto/rand"
"io"
"testing"

"github.com/eliastor/go-nizk"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func makeMsgStamp() (msg, stamp []byte) {
msg = make([]byte, 4096)
io.ReadFull(rand.Reader, msg)
stamp = make([]byte, 1024)
io.ReadFull(rand.Reader, stamp)
return msg, stamp
}

func makeProove(t *testing.T, msg, stamp []byte, zk nizk.Nizk) ([]byte, []byte) {
var err error
fingerprint1 := zk.Fingerprint(msg)
proof, fingerprint2, err := zk.Proove(msg, stamp)
require.NoError(t, err)
assert.Equal(t, fingerprint1, fingerprint2)
return proof, fingerprint2
}

func testSuccessFlow(t *testing.T, id, stamp, proof []byte, zk nizk.Nizk) {
t.Run("SuccessFlow", func(t *testing.T) {
valid, err := zk.Verify(id, stamp, proof)
require.NoError(t, err)
assert.True(t, valid)
})
}

func testFailFlow(t *testing.T, id, stamp []byte, proof []byte, zk nizk.Nizk) {
t.Run("FailFlow", func(t *testing.T) {
proof[0] ^= 0xFF
valid, _ := zk.Verify(id, stamp, proof)
// require.NoError(t, err) there can be the error with invalid point encoding
assert.False(t, valid)
})
}

func testFlows(t *testing.T, zk nizk.Nizk) {
msg, stamp := makeMsgStamp()
proof, id := makeProove(t, msg, stamp, zk)
testSuccessFlow(t, id, stamp, proof, zk)
testFailFlow(t, id, stamp, proof, zk)
}

func TestAllSuites(t *testing.T) {
suites := []struct {
Name string
Creator func() nizk.Nizk
}{
{"ed25519_sha3", nizk.NewEd25519Sha3},
{"ed25519_sha256", nizk.NewEd25519Sha256},
}

for _, suite := range suites {
name := suite.Name
zk := suite.Creator()
t.Run(name, func(t *testing.T) {
testFlows(t, zk)
})
}
}

func benchmarkSuite(b *testing.B, zk nizk.Nizk) {
msg, stamp := makeMsgStamp()
id := zk.Fingerprint(msg)
var proof nizk.Proof
var err error
b.Run("Proove", func(b *testing.B) {
for i := 0; i < b.N; i++ {
proof, _, err = zk.Proove(msg, stamp)
if err != nil {
b.Fatal(err)
}
}
})
b.Run("Verify", func(b *testing.B) {
for i := 0; i < b.N; i++ {
valid, err := zk.Verify(id, stamp, proof)
if err != nil {
b.Fatal(err)
}
if !valid {
b.Fatal("failed verification")
}
}
})
}

func BenchmarkAllSuites(b *testing.B) {
suites := []struct {
Name string
Creator func() nizk.Nizk
}{
{"ed25519_sha3", nizk.NewEd25519Sha3},
{"ed25519_sha256", nizk.NewEd25519Sha256},
}
for _, suite := range suites {
name := suite.Name
zk := suite.Creator()
b.Run(name, func(b *testing.B) {
benchmarkSuite(b, zk)
})
}
}

0 comments on commit 8296496

Please sign in to comment.