diff --git a/.github/workflows/go.yaml b/.github/workflows/go.yaml new file mode 100644 index 0000000..5c9a012 --- /dev/null +++ b/.github/workflows/go.yaml @@ -0,0 +1,31 @@ +name: Golang SDK + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +jobs: + build: + env: + TEST_ARGS: -v + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.22 + + - name: Build and Test + run: cd zkp/golang && make + + - uses: codecov/codecov-action@v4 + with: + codecov_yml_path: ./codecov.yml + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/zkp/golang/Makefile b/zkp/golang/Makefile new file mode 100644 index 0000000..323f3c5 --- /dev/null +++ b/zkp/golang/Makefile @@ -0,0 +1,32 @@ +VGO=go +GOFILES := $(shell find ./internal ./pkg -name '*.go' -print) +GOBIN := $(shell $(VGO) env GOPATH)/bin +LINT := $(GOBIN)/golangci-lint + +# Expect that Zeto compiles with CGO disabled +CGO_ENABLED=0 +GOGC=30 + +.DELETE_ON_ERROR: + +all: test go-mod-tidy +test: deps lint + $(VGO) test ./internal/... ./pkg/... -cover -coverprofile=coverage.txt -covermode=atomic -timeout=30s ${TEST_ARGS} +coverage.html: + $(VGO) tool cover -html=coverage.txt +coverage: test coverage.html +lint: ${LINT} + GOGC=20 $(LINT) run -v --timeout 5m +${LINT}: + $(VGO) install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.0 + +go-mod-tidy: .ALWAYS + $(VGO) mod tidy +e2e: test + $(VGO) test ./integration-test +.ALWAYS: ; +clean: + $(VGO) clean + rm -f *.so +deps: + $(VGO) get -u ./... diff --git a/zkp/golang/go.mod b/zkp/golang/go.mod index cce741b..234adf1 100644 --- a/zkp/golang/go.mod +++ b/zkp/golang/go.mod @@ -7,7 +7,20 @@ require ( github.com/stretchr/testify v1.9.0 ) -require github.com/iden3/wasmer-go v0.0.1 // indirect +require ( + github.com/iden3/wasmer-go v0.0.1 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.5.5 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/text v0.16.0 // indirect +) require ( github.com/davecgh/go-spew v1.1.1 // indirect @@ -17,7 +30,10 @@ require ( github.com/iden3/go-rapidsnark/types v0.0.2 // indirect github.com/iden3/go-rapidsnark/witness/v2 v2.0.0 github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/sys v0.22.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/postgres v1.5.9 + gorm.io/driver/sqlite v1.5.6 + gorm.io/gorm v1.25.11 ) diff --git a/zkp/golang/go.sum b/zkp/golang/go.sum index 5e55229..cf4e8db 100644 --- a/zkp/golang/go.sum +++ b/zkp/golang/go.sum @@ -1,3 +1,5 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/dchest/blake512 v1.0.0 h1:oDFEQFIqFSeuA34xLtXZ/rWxCXdSjirjzPhey5EUvmA= @@ -14,17 +16,52 @@ github.com/iden3/go-rapidsnark/witness/wasmer v0.0.0-20230524142950-0986cf057d4e github.com/iden3/go-rapidsnark/witness/wasmer v0.0.0-20230524142950-0986cf057d4e/go.mod h1:WUtPVKXrhfZHJXavwId2+8J/fKMHQ92N0MZDxt8sfEA= github.com/iden3/wasmer-go v0.0.1 h1:TZKh8Se8B/73PvWrcu+FTU9L1k5XYAmtFbioj7l0Uog= github.com/iden3/wasmer-go v0.0.1/go.mod h1:ZnZBAO012M7o+Q1INXLRIxKQgEcH2FuwL0Iga8A4ufg= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 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/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8= +gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE= +gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= +gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg= +gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= diff --git a/zkp/golang/integration-test/e2e_test.go b/zkp/golang/integration-test/e2e_test.go index 2f2e046..1ec5f39 100644 --- a/zkp/golang/integration-test/e2e_test.go +++ b/zkp/golang/integration-test/e2e_test.go @@ -19,23 +19,31 @@ package integration_test import ( "fmt" "math/big" + "math/rand" "os" "path" "testing" "time" "github.com/hyperledger-labs/zeto/internal/testutils" + "github.com/hyperledger-labs/zeto/pkg/core" "github.com/hyperledger-labs/zeto/pkg/node" "github.com/hyperledger-labs/zeto/pkg/smt" "github.com/hyperledger-labs/zeto/pkg/storage" "github.com/hyperledger-labs/zeto/pkg/utxo" + "github.com/iden3/go-iden3-crypto/babyjub" "github.com/iden3/go-iden3-crypto/poseidon" "github.com/iden3/go-rapidsnark/prover" "github.com/iden3/go-rapidsnark/witness/v2" "github.com/iden3/go-rapidsnark/witness/wasmer" "github.com/stretchr/testify/assert" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/gorm" ) +const MAX_HEIGHT = 64 + func LoadCircuit(circuitName string) (witness.Calculator, []byte, error) { circuitRoot, exists := os.LookupEnv("CIRCUITS_ROOT") if !exists { @@ -183,6 +191,92 @@ func TestZeto_2_SuccessfulProving(t *testing.T) { } func TestZeto_3_SuccessfulProving(t *testing.T) { + calc, provingKey, err := LoadCircuit("anon_nullifier") + assert.NoError(t, err) + assert.NotNil(t, calc) + + sender := testutils.NewKeypair() + receiver := testutils.NewKeypair() + + inputValues := []*big.Int{big.NewInt(30), big.NewInt(40)} + outputValues := []*big.Int{big.NewInt(32), big.NewInt(38)} + + salt1 := testutils.NewSalt() + input1, _ := poseidon.Hash([]*big.Int{inputValues[0], salt1, sender.PublicKey.X, sender.PublicKey.Y}) + salt2 := testutils.NewSalt() + input2, _ := poseidon.Hash([]*big.Int{inputValues[1], salt2, sender.PublicKey.X, sender.PublicKey.Y}) + inputCommitments := []*big.Int{input1, input2} + + nullifier1, _ := poseidon.Hash([]*big.Int{inputValues[0], salt1, sender.PrivateKeyBigInt}) + nullifier2, _ := poseidon.Hash([]*big.Int{inputValues[1], salt2, sender.PrivateKeyBigInt}) + nullifiers := []*big.Int{nullifier1, nullifier2} + + mt, err := smt.NewMerkleTree(storage.NewMemoryStorage(), MAX_HEIGHT) + assert.NoError(t, err) + utxo1 := utxo.NewFungible(inputValues[0], sender.PublicKey, salt1) + n1, err := node.NewLeafNode(utxo1) + assert.NoError(t, err) + err = mt.AddLeaf(n1) + assert.NoError(t, err) + utxo2 := utxo.NewFungible(inputValues[1], sender.PublicKey, salt2) + n2, err := node.NewLeafNode(utxo2) + assert.NoError(t, err) + err = mt.AddLeaf(n2) + assert.NoError(t, err) + proof1, _, err := mt.GenerateProof(input1, nil) + assert.NoError(t, err) + circomProof1, err := proof1.ToCircomVerifierProof(input1, input1, mt.Root(), MAX_HEIGHT) + assert.NoError(t, err) + proof2, _, err := mt.GenerateProof(input2, nil) + assert.NoError(t, err) + circomProof2, err := proof2.ToCircomVerifierProof(input2, input2, mt.Root(), MAX_HEIGHT) + assert.NoError(t, err) + + salt3 := testutils.NewSalt() + output1, _ := poseidon.Hash([]*big.Int{outputValues[0], salt3, receiver.PublicKey.X, receiver.PublicKey.Y}) + salt4 := testutils.NewSalt() + output2, _ := poseidon.Hash([]*big.Int{outputValues[1], salt4, sender.PublicKey.X, sender.PublicKey.Y}) + outputCommitments := []*big.Int{output1, output2} + + proof1Siblings := make([]*big.Int, len(circomProof1.Siblings)-1) + for i, s := range circomProof1.Siblings[0 : len(circomProof1.Siblings)-1] { + proof1Siblings[i] = s.BigInt() + } + proof2Siblings := make([]*big.Int, len(circomProof2.Siblings)-1) + for i, s := range circomProof2.Siblings[0 : len(circomProof2.Siblings)-1] { + proof2Siblings[i] = s.BigInt() + } + witnessInputs := map[string]interface{}{ + "nullifiers": nullifiers, + "inputCommitments": inputCommitments, + "inputValues": inputValues, + "inputSalts": []*big.Int{salt1, salt2}, + "inputOwnerPrivateKey": sender.PrivateKeyBigInt, + "root": mt.Root().BigInt(), + "merkleProof": [][]*big.Int{proof1Siblings, proof2Siblings}, + "enabled": []*big.Int{big.NewInt(1), big.NewInt(1)}, + "outputCommitments": outputCommitments, + "outputValues": outputValues, + "outputSalts": []*big.Int{salt3, salt4}, + "outputOwnerPublicKeys": [][]*big.Int{{receiver.PublicKey.X, receiver.PublicKey.Y}, {sender.PublicKey.X, sender.PublicKey.Y}}, + } + + startTime := time.Now() + witnessBin, err := calc.CalculateWTNSBin(witnessInputs, true) + assert.NoError(t, err) + assert.NotNil(t, witnessBin) + + proof, err := prover.Groth16Prover(provingKey, witnessBin) + elapsedTime := time.Since(startTime) + fmt.Printf("Proving time: %s\n", elapsedTime) + assert.NoError(t, err) + assert.Equal(t, 3, len(proof.Proof.A)) + assert.Equal(t, 3, len(proof.Proof.B)) + assert.Equal(t, 3, len(proof.Proof.C)) + assert.Equal(t, 7, len(proof.PubSignals)) +} + +func TestZeto_4_SuccessfulProving(t *testing.T) { calc, provingKey, err := LoadCircuit("anon_enc_nullifier") assert.NoError(t, err) assert.NotNil(t, calc) @@ -203,7 +297,6 @@ func TestZeto_3_SuccessfulProving(t *testing.T) { nullifier2, _ := poseidon.Hash([]*big.Int{inputValues[1], salt2, sender.PrivateKeyBigInt}) nullifiers := []*big.Int{nullifier1, nullifier2} - MAX_HEIGHT := 64 mt, err := smt.NewMerkleTree(storage.NewMemoryStorage(), MAX_HEIGHT) assert.NoError(t, err) utxo1 := utxo.NewFungible(inputValues[0], sender.PublicKey, salt1) @@ -272,7 +365,7 @@ func TestZeto_3_SuccessfulProving(t *testing.T) { assert.Equal(t, 10, len(proof.PubSignals)) } -func TestZeto_4_SuccessfulProving(t *testing.T) { +func TestZeto_5_SuccessfulProving(t *testing.T) { calc, provingKey, err := LoadCircuit("nf_anon") assert.NoError(t, err) assert.NotNil(t, calc) @@ -331,7 +424,7 @@ func TestZeto_4_SuccessfulProving(t *testing.T) { assert.Equal(t, 2, len(proof.PubSignals)) } -func TestZeto_5_SuccessfulProving(t *testing.T) { +func TestZeto_6_SuccessfulProving(t *testing.T) { calc, provingKey, err := LoadCircuit("nf_anon_nullifier") assert.NoError(t, err) assert.NotNil(t, calc) @@ -349,7 +442,6 @@ func TestZeto_5_SuccessfulProving(t *testing.T) { nullifier1, _ := poseidon.Hash([]*big.Int{tokenId, tokenUri, salt1, sender.PrivateKeyBigInt}) - MAX_HEIGHT := 64 mt, err := smt.NewMerkleTree(storage.NewMemoryStorage(), MAX_HEIGHT) assert.NoError(t, err) utxo1 := utxo.NewNonFungible(tokenId, tokenUri, sender.PublicKey, salt1) @@ -407,3 +499,155 @@ func TestZeto_5_SuccessfulProving(t *testing.T) { assert.Equal(t, 3, len(proof.Proof.C)) assert.Equal(t, 3, len(proof.PubSignals)) } + +func TestConcurrentLeafnodesInsertion(t *testing.T) { + x, _ := new(big.Int).SetString("9198063289874244593808956064764348354864043212453245695133881114917754098693", 10) + y, _ := new(big.Int).SetString("3600411115173311692823743444460566395943576560299970643507632418781961416843", 10) + alice := &babyjub.PublicKey{ + X: x, + Y: y, + } + + values := []int{10, 20, 30, 40} + salts := []string{ + "43c49e8ba68a9b8a6bb5c230a734d8271a83d2f63722e7651272ebeef5446e", + "19b965f7629e4f0c4bd0b8f9c87f17580f18a32a31b4641550071ee4916bbbfc", + "9b0b93df975547e430eabff085a77831b8fcb6b5396e6bb815fda8d14125370", + "194ec10ec96a507c7c9b60df133d13679b874b0bd6ab89920135508f55b3f064", + } + + // run the test 10 times + for i := 0; i < 100; i++ { + // shuffle the utxos for this run + for i := range values { + j := rand.Intn(i + 1) + values[i], values[j] = values[j], values[i] + salts[i], salts[j] = salts[j], salts[i] + } + + testConcurrentInsertion(t, alice, values, salts) + } +} + +func testConcurrentInsertion(t *testing.T, alice *babyjub.PublicKey, values []int, salts []string) { + mt, err := smt.NewMerkleTree(storage.NewMemoryStorage(), MAX_HEIGHT) + assert.NoError(t, err) + done := make(chan bool, len(values)) + + for i, v := range values { + go func(i, v int) { + salt, _ := new(big.Int).SetString(salts[i], 16) + utxo := utxo.NewFungible(big.NewInt(int64(v)), alice, salt) + n, err := node.NewLeafNode(utxo) + assert.NoError(t, err) + err = mt.AddLeaf(n) + assert.NoError(t, err) + done <- true + }(i, v) + } + + for i := 0; i < len(values); i++ { + <-done + } + + assert.Equal(t, "abacf46f5217552ee28fe50b8fd7ca6aa46daeb9acf9f60928654c3b1a472f23", mt.Root().Hex()) +} + +type testSqlProvider struct { + db *gorm.DB +} + +func (s *testSqlProvider) DB() *gorm.DB { + return s.db +} + +func (s *testSqlProvider) Close() {} + +func TestSqliteStorage(t *testing.T) { + dbfile, err := os.CreateTemp("", "gorm.db") + assert.NoError(t, err) + defer func() { + os.Remove(dbfile.Name()) + }() + db, err := gorm.Open(sqlite.Open(dbfile.Name()), &gorm.Config{}) + assert.NoError(t, err) + err = db.Table(core.TreeRootsTable).AutoMigrate(&core.SMTRoot{}) + assert.NoError(t, err) + err = db.Table(core.NodesTablePrefix + "test_1").AutoMigrate(&core.SMTNode{}) + assert.NoError(t, err) + + provider := &testSqlProvider{db: db} + s, err := storage.NewSqlStorage(provider, "test_1") + assert.NoError(t, err) + + mt, err := smt.NewMerkleTree(s, MAX_HEIGHT) + assert.NoError(t, err) + + tokenId := big.NewInt(1001) + tokenUri, err := utxo.HashTokenUri("https://example.com/token/1001") + assert.NoError(t, err) + sender := testutils.NewKeypair() + salt1 := testutils.NewSalt() + + utxo1 := utxo.NewNonFungible(tokenId, tokenUri, sender.PublicKey, salt1) + n1, err := node.NewLeafNode(utxo1) + assert.NoError(t, err) + err = mt.AddLeaf(n1) + assert.NoError(t, err) + + root := mt.Root() + dbRoot := core.SMTRoot{Name: "test_1"} + err = db.Table(core.TreeRootsTable).First(&dbRoot).Error + assert.NoError(t, err) + assert.Equal(t, root.Hex(), dbRoot.RootIndex) + + dbNode := core.SMTNode{RefKey: n1.Ref().Hex()} + err = db.Table(core.NodesTablePrefix + "test_1").First(&dbNode).Error + assert.NoError(t, err) + assert.Equal(t, n1.Ref().Hex(), dbNode.RefKey) +} + +func TestPostgresStorage(t *testing.T) { + dsn := "host=localhost user=postgres password=my-secret dbname=postgres port=5432 sslmode=disable" + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + assert.NoError(t, err) + err = db.Table(core.TreeRootsTable).AutoMigrate(&core.SMTRoot{}) + assert.NoError(t, err) + err = db.Table(core.NodesTablePrefix + "test_1").AutoMigrate(&core.SMTNode{}) + assert.NoError(t, err) + + defer func() { + db.Exec("DROP TABLE " + core.TreeRootsTable) + db.Exec("DROP TABLE " + core.NodesTablePrefix + "test_1") + }() + + provider := &testSqlProvider{db: db} + s, err := storage.NewSqlStorage(provider, "test_1") + assert.NoError(t, err) + + mt, err := smt.NewMerkleTree(s, MAX_HEIGHT) + assert.NoError(t, err) + + tokenId := big.NewInt(1001) + tokenUri, err := utxo.HashTokenUri("https://example.com/token/1001") + assert.NoError(t, err) + sender := testutils.NewKeypair() + salt1 := testutils.NewSalt() + + utxo1 := utxo.NewNonFungible(tokenId, tokenUri, sender.PublicKey, salt1) + n1, err := node.NewLeafNode(utxo1) + assert.NoError(t, err) + err = mt.AddLeaf(n1) + assert.NoError(t, err) + + root := mt.Root() + dbRoot := core.SMTRoot{Name: "test_1"} + err = db.Table(core.TreeRootsTable).First(&dbRoot).Error + assert.NoError(t, err) + assert.Equal(t, root.Hex(), dbRoot.RootIndex) + + dbNode := core.SMTNode{RefKey: n1.Ref().Hex()} + err = db.Table(core.NodesTablePrefix + "test_1").First(&dbNode).Error + assert.NoError(t, err) + assert.Equal(t, n1.Ref().Hex(), dbNode.RefKey) +} diff --git a/zkp/golang/internal/smt/merkletree.go b/zkp/golang/internal/smt/merkletree.go index 37600ef..fa39d00 100644 --- a/zkp/golang/internal/smt/merkletree.go +++ b/zkp/golang/internal/smt/merkletree.go @@ -22,7 +22,7 @@ import ( "github.com/hyperledger-labs/zeto/internal/node" "github.com/hyperledger-labs/zeto/internal/storage" - "github.com/hyperledger-labs/zeto/internal/utxo" + "github.com/hyperledger-labs/zeto/internal/utils" "github.com/hyperledger-labs/zeto/pkg/core" ) @@ -59,6 +59,8 @@ func NewMerkleTree(db core.Storage, maxLevels int) (core.SparseMerkleTree, error } func (mt *sparseMerkleTree) Root() core.NodeIndex { + mt.RLock() + defer mt.RUnlock() return mt.rootKey } @@ -70,12 +72,19 @@ func (mt *sparseMerkleTree) AddLeaf(node core.Node) error { idx := node.Index() path := idx.ToPath(mt.maxLevels) + // find the lowest level of the tree that can accommodate this new leaf node + // by its unique index. This is done by traversing the tree from the root node + // down through the levels until a unique path is found. It doesn't necessarily + // use up all the bits in the index's path. As soon as a unique path is found, + // which may be only the first few bits of the index, the new leaf node is added. + // One or more branch nodes may be created to accommodate the new leaf node. newRootKey, err := mt.addLeaf(node, mt.rootKey, 0, path) if err != nil { return err } mt.rootKey = newRootKey + // update the root node index in the storage err = mt.db.UpsertRootNodeIndex(mt.rootKey) if err != nil { return err @@ -87,20 +96,18 @@ func (mt *sparseMerkleTree) AddLeaf(node core.Node) error { // GetNode gets a node by key from the merkle tree. Empty nodes are not stored in the // tree: they are all the same and assumed to always exist. func (mt *sparseMerkleTree) GetNode(key core.NodeIndex) (core.Node, error) { - if key.IsZero() { - return node.NewEmptyNode(), nil - } - node, err := mt.db.GetNode(key) - if err != nil { - return nil, err - } - return node, nil + mt.RLock() + defer mt.RUnlock() + return mt.getNode(key) } // GenerateProof generates the proof of existence (or non-existence) of a leaf node // for a Merkle Tree given the root. It uses the node's index to represent the node. // If the rootKey is nil, the current merkletree root is used func (mt *sparseMerkleTree) GenerateProof(k *big.Int, rootKey core.NodeIndex) (core.Proof, *big.Int, error) { + mt.RLock() + defer mt.RUnlock() + p := &proof{} var siblingKey core.NodeIndex @@ -110,11 +117,11 @@ func (mt *sparseMerkleTree) GenerateProof(k *big.Int, rootKey core.NodeIndex) (c } path := kHash.ToPath(mt.maxLevels) if rootKey == nil { - rootKey = mt.Root() + rootKey = mt.rootKey } nextKey := rootKey for p.depth = 0; p.depth < uint(mt.maxLevels); p.depth++ { - n, err := mt.GetNode(nextKey) + n, err := mt.getNode(nextKey) if err != nil { return nil, nil, err } @@ -130,7 +137,7 @@ func (mt *sparseMerkleTree) GenerateProof(k *big.Int, rootKey core.NodeIndex) (c return p, value.BigInt(), nil } // We found a leaf whose entry didn't match the node index - p.existingNode, err = node.NewLeafNode(utxo.NewIndexOnly(idx)) + p.existingNode, err = node.NewLeafNode(utils.NewIndexOnly(idx)) if err != nil { return nil, nil, err } @@ -156,6 +163,19 @@ func (mt *sparseMerkleTree) GenerateProof(k *big.Int, rootKey core.NodeIndex) (c return nil, nil, ErrKeyNotFound } +// must be called from inside a read lock +func (mt *sparseMerkleTree) getNode(key core.NodeIndex) (core.Node, error) { + if key.IsZero() { + return node.NewEmptyNode(), nil + } + node, err := mt.db.GetNode(key) + if err != nil { + return nil, err + } + return node, nil +} + +// must be called from inside a write lock // addLeaf adds a new LeafNode to the MerkleTree. It starts with the current node. // - if the current node is empty, it adds the new leaf node at that location. // - if the current node is a leaf node, it means there's an existing node that shares @@ -174,14 +194,15 @@ func (mt *sparseMerkleTree) addLeaf(newLeaf core.Node, currentNodeIndex core.Nod } var nextKey core.NodeIndex - currentNode, err := mt.GetNode(currentNodeIndex) + currentNode, err := mt.getNode(currentNodeIndex) if err != nil { return nil, err } switch currentNode.Type() { case core.NodeTypeEmpty: - // We have searched to the bottom level and are ensured that - // the node doesn't exist yet. We can add the new leaf node + // We have searched to a level and have found a position in the + // index's path where the tree is empty. This means we have found + // the node that doesn't exist yet. We can add the new leaf node here return mt.addNode(newLeaf) case core.NodeTypeLeaf: nIndex := currentNode.Index() @@ -225,6 +246,7 @@ func (mt *sparseMerkleTree) addLeaf(newLeaf core.Node, currentNodeIndex core.Nod } } +// must be called from inside a write lock // addNode adds a node into the MT. Empty nodes are not stored in the tree; // they are all the same and assumed to always exist. func (mt *sparseMerkleTree) addNode(n core.Node) (core.NodeIndex, error) { @@ -240,6 +262,9 @@ func (mt *sparseMerkleTree) addNode(n core.Node) (core.NodeIndex, error) { return k, err } +// must be called from inside a write lock +// extendPath extends the path of two leaf nodes, which share the same beginnging part of +// their indexes, until their paths diverge, creating ancestor branch nodes as needed. func (mt *sparseMerkleTree) extendPath(newLeaf core.Node, oldLeaf core.Node, level int, pathNewLeaf []bool, pathOldLeaf []bool) (core.NodeIndex, error) { if level > mt.maxLevels-2 { return nil, ErrReachedMaxLevel @@ -289,9 +314,8 @@ func (mt *sparseMerkleTree) extendPath(newLeaf core.Node, oldLeaf core.Node, lev if err != nil { return nil, err } - // finally don't forget to add the new branch node that - // is the parent of the new leaf node to the DB. We also - // return this new branch node's key to allow the caller - // to create branch nodes as needed. + // finally don't forget to add the new branch node that is the parent of + // the new leaf node to the DB. We also return this new branch node's key + // to allow the caller to create branch nodes as needed. return mt.addNode(newBranchNode) } diff --git a/zkp/golang/internal/smt/smt_test.go b/zkp/golang/internal/smt/smt_test.go index 42c844c..7c1c8fa 100644 --- a/zkp/golang/internal/smt/smt_test.go +++ b/zkp/golang/internal/smt/smt_test.go @@ -17,13 +17,16 @@ package smt import ( + "fmt" "math/big" + "math/rand" "testing" "github.com/hyperledger-labs/zeto/internal/node" "github.com/hyperledger-labs/zeto/internal/storage" "github.com/hyperledger-labs/zeto/internal/testutils" "github.com/hyperledger-labs/zeto/internal/utxo" + "github.com/hyperledger-labs/zeto/pkg/core" "github.com/iden3/go-iden3-crypto/babyjub" "github.com/stretchr/testify/assert" ) @@ -144,3 +147,48 @@ func TestGenerateProof(t *testing.T) { assert.NoError(t, err) assert.False(t, proof3.IsOld0) } + +func TestVerifyProof(t *testing.T) { + const levels = 10 + db := storage.NewMemoryStorage() + mt, _ := NewMerkleTree(db, levels) + + alice := testutils.NewKeypair() + values := []int{10, 20, 30, 40, 50} + done := make(chan bool, len(values)) + startProving := make(chan core.Node, len(values)) + for idx, value := range values { + go func(v int, idx int) { + salt := rand.Intn(100000) + utxo := utxo.NewFungible(big.NewInt(int64(v)), alice.PublicKey, big.NewInt(int64(salt))) + node, err := node.NewLeafNode(utxo) + assert.NoError(t, err) + err = mt.AddLeaf(node) + assert.NoError(t, err) + startProving <- node + done <- true + fmt.Printf("Added node %d\n", idx) + }(value, idx) + } + + go func() { + // trigger the proving process after 1 nodes are added + n := <-startProving + fmt.Println("Received node for proving") + + target := n.Index().BigInt() + root := mt.Root() + p, _, err := mt.GenerateProof(target, root) + assert.NoError(t, err) + assert.True(t, p.(*proof).existence) + + valid := VerifyProof(root, p, n) + assert.True(t, valid) + }() + + for i := 0; i < len(values); i++ { + <-done + } + + fmt.Println("All done") +} diff --git a/zkp/golang/internal/storage/postgres.go_ b/zkp/golang/internal/storage/postgres.go_ deleted file mode 100644 index e69de29..0000000 diff --git a/zkp/golang/internal/storage/sql.go b/zkp/golang/internal/storage/sql.go new file mode 100644 index 0000000..0cc3752 --- /dev/null +++ b/zkp/golang/internal/storage/sql.go @@ -0,0 +1,123 @@ +// Copyright © 2024 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package storage + +import ( + "github.com/hyperledger-labs/zeto/internal/node" + "github.com/hyperledger-labs/zeto/internal/utils" + "github.com/hyperledger-labs/zeto/pkg/core" + "gorm.io/gorm" +) + +type sqlStorage struct { + p core.SqlDBProvider + smtName string + nodesTableName string +} + +// NewSqlStorage creates a new sqlStorage instance +// The "smtName" is the name for the tree instance, it must +// be unique within the backend database instance +func NewSqlStorage(p core.SqlDBProvider, smtName string) *sqlStorage { + return &sqlStorage{ + p: p, + smtName: smtName, + nodesTableName: core.NodesTablePrefix + smtName, + } +} + +func (s *sqlStorage) GetRootNodeIndex() (core.NodeIndex, error) { + root := core.SMTRoot{ + Name: s.smtName, + } + err := s.p.DB().Table(core.TreeRootsTable).First(&root).Error + if err == gorm.ErrRecordNotFound { + return nil, ErrNotFound + } else if err != nil { + return nil, err + } + idx, err := node.NewNodeIndexFromHex(root.RootIndex) + return idx, err +} + +func (s *sqlStorage) UpsertRootNodeIndex(root core.NodeIndex) error { + err := s.p.DB().Table(core.TreeRootsTable).Save(&core.SMTRoot{ + RootIndex: root.Hex(), + Name: s.smtName, + }).Error + return err +} + +func (s *sqlStorage) GetNode(ref core.NodeIndex) (core.Node, error) { + // the node's reference key (not the index) is used as the key to + // store the node in the DB + n := core.SMTNode{ + RefKey: ref.Hex(), + } + err := s.p.DB().Table(s.nodesTableName).First(&n).Error + if err == gorm.ErrRecordNotFound { + return nil, ErrNotFound + } else if err != nil { + return nil, err + } + var newNode core.Node + nodeType := core.NodeTypeFromByte(n.Type) + switch nodeType { + case core.NodeTypeLeaf: + idx, err1 := node.NewNodeIndexFromHex(*n.Index) + if err1 != nil { + return nil, err1 + } + v := utils.NewIndexOnly(idx) + newNode, err = node.NewLeafNode(v) + case core.NodeTypeBranch: + leftChild, err1 := node.NewNodeIndexFromHex(*n.LeftChild) + if err1 != nil { + return nil, err1 + } + rightChild, err2 := node.NewNodeIndexFromHex(*n.RightChild) + if err2 != nil { + return nil, err2 + } + newNode, err = node.NewBranchNode(leftChild, rightChild) + } + return newNode, err +} + +func (s *sqlStorage) InsertNode(n core.Node) error { + // we clone the node so that the value properties are not saved + dbNode := &core.SMTNode{ + RefKey: n.Ref().Hex(), + Type: n.Type().ToByte(), + } + if n.Type() == core.NodeTypeBranch { + left := n.LeftChild().Hex() + dbNode.LeftChild = &left + right := n.RightChild().Hex() + dbNode.RightChild = &right + } else if n.Type() == core.NodeTypeLeaf { + idx := n.Index().Hex() + dbNode.Index = &idx + } + + err := s.p.DB().Table(s.nodesTableName).Create(dbNode).Error + return err +} + +func (m *sqlStorage) Close() { + m.p.Close() +} diff --git a/zkp/golang/internal/utxo/indexonly.go b/zkp/golang/internal/utils/indexonly.go similarity index 98% rename from zkp/golang/internal/utxo/indexonly.go rename to zkp/golang/internal/utils/indexonly.go index a634a30..ae9d121 100644 --- a/zkp/golang/internal/utxo/indexonly.go +++ b/zkp/golang/internal/utils/indexonly.go @@ -14,7 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package utxo +package utils import ( "github.com/hyperledger-labs/zeto/pkg/core" diff --git a/zkp/golang/internal/utxo/fungible.go b/zkp/golang/internal/utxo/fungible.go index e3f139e..250d7d4 100644 --- a/zkp/golang/internal/utxo/fungible.go +++ b/zkp/golang/internal/utxo/fungible.go @@ -46,3 +46,28 @@ func (f *Fungible) CalculateIndex() (core.NodeIndex, error) { } return node.NewNodeIndexFromBigInt(hash) } + +// the "Owner" is the private key that must be properly hashed and trimmed to be +// compatible with the BabyJub curve. +// Reference: https://github.com/iden3/circomlib/blob/master/test/babyjub.js#L103 +type FungibleNullifier struct { + Amount *big.Int + Owner *big.Int + Salt *big.Int +} + +func NewFungibleNullifier(amount *big.Int, owner *big.Int, salt *big.Int) *FungibleNullifier { + return &FungibleNullifier{ + Amount: amount, + Owner: owner, + Salt: salt, + } +} + +func (f *FungibleNullifier) CalculateIndex() (core.NodeIndex, error) { + hash, err := poseidon.Hash([]*big.Int{f.Amount, f.Salt, f.Owner}) + if err != nil { + return nil, err + } + return node.NewNodeIndexFromBigInt(hash) +} diff --git a/zkp/golang/internal/utxo/nonfungible.go b/zkp/golang/internal/utxo/nonfungible.go index aaf5a34..219ea93 100644 --- a/zkp/golang/internal/utxo/nonfungible.go +++ b/zkp/golang/internal/utxo/nonfungible.go @@ -51,6 +51,33 @@ func (f *NonFungible) CalculateIndex() (core.NodeIndex, error) { return node.NewNodeIndexFromBigInt(hash) } +// the "Owner" is the private key that must be properly hashed and trimmed to be +// compatible with the BabyJub curve. +// Reference: https://github.com/iden3/circomlib/blob/master/test/babyjub.js#L103 +type NonFungibleNullifier struct { + TokenId *big.Int + TokenUri *big.Int // hash of the token uri string + Owner *big.Int + Salt *big.Int +} + +func NewNonFungibleNullifier(tokenId, tokenUri *big.Int, owner *big.Int, salt *big.Int) *NonFungibleNullifier { + return &NonFungibleNullifier{ + TokenId: tokenId, + TokenUri: tokenUri, + Owner: owner, + Salt: salt, + } +} + +func (f *NonFungibleNullifier) CalculateIndex() (core.NodeIndex, error) { + hash, err := poseidon.Hash([]*big.Int{f.TokenId, f.TokenUri, f.Salt, f.Owner}) + if err != nil { + return nil, err + } + return node.NewNodeIndexFromBigInt(hash) +} + func HashTokenUri(tokenUri string) (*big.Int, error) { hash := sha256.New() _, err := hash.Write([]byte(tokenUri)) diff --git a/zkp/golang/pkg/core/merkletree.go b/zkp/golang/pkg/core/merkletree.go index d05fe7f..de5447a 100644 --- a/zkp/golang/pkg/core/merkletree.go +++ b/zkp/golang/pkg/core/merkletree.go @@ -25,18 +25,20 @@ import "math/big" // // The tree is built from the root node, at level 0, down to the leaf nodes. // -// root level 0 +// root level 0 // / \ -// e f level 1 +// e f level 1 // / \ / \ -// a b c d level 2 -// / \ / \ / \ / \ -// 1 2 3 4 5 - - - level 3 +// a b c d level 2 +// / \ / \ / \ / \ +// 1 2 3 4 5 - - - level 3 type SparseMerkleTree interface { // Root returns the root hash of the tree Root() NodeIndex // AddLeaf adds a key-value pair to the tree AddLeaf(Node) error + // GetNode returns the node at the given reference hash + GetNode(NodeIndex) (Node, error) // GetnerateProof generates a proof of existence (or non-existence) of a leaf node GenerateProof(*big.Int, NodeIndex) (Proof, *big.Int, error) } diff --git a/zkp/golang/pkg/core/node.go b/zkp/golang/pkg/core/node.go index 9ffda8c..e2c7b84 100644 --- a/zkp/golang/pkg/core/node.go +++ b/zkp/golang/pkg/core/node.go @@ -78,3 +78,29 @@ type Node interface { // returns the index of the right child. Only branch nodes have a right child. RightChild() NodeIndex } + +func (t NodeType) ToByte() byte { + switch t { + case NodeTypeEmpty: + return 0 + case NodeTypeBranch: + return 1 + case NodeTypeLeaf: + return 2 + default: + return 3 + } +} + +func NodeTypeFromByte(b byte) NodeType { + switch b { + case 0: + return NodeTypeEmpty + case 1: + return NodeTypeBranch + case 2: + return NodeTypeLeaf + default: + return NodeTypeEmpty + } +} diff --git a/zkp/golang/pkg/core/storage.go b/zkp/golang/pkg/core/storage.go index 004a4db..e2a8802 100644 --- a/zkp/golang/pkg/core/storage.go +++ b/zkp/golang/pkg/core/storage.go @@ -16,6 +16,8 @@ package core +import "gorm.io/gorm" + type Storage interface { // GetRootNodeIndex returns the root node index. // Must return an ErrNotFound error if it does not exist. @@ -31,3 +33,41 @@ type Storage interface { // Close closes the storage resource Close() } + +const ( + // we use a table to store the root node indexes for + // all the merkle trees in the database + TreeRootsTable = "merkelTreeRoots" + // we use a separate table to store the nodes of each + // sparse merkle tree by using the following name as + // the prefix, followed by the name of the tree + NodesTablePrefix = "smtNodes_" +) + +// SqlDBProvider is the interface for providing access to a SQL database to +// the storage layer implementations that are backed by a SQL database. +type SqlDBProvider interface { + DB() *gorm.DB + Close() +} + +// SMTRoot is used to persist tree root in SQL databases via gorm +type SMTRoot struct { + // the name of the merkle tree + Name string `gorm:"primaryKey"` + // this must be the hex bytes of the root index + // following the big-endian encoding + RootIndex string `gorm:"size:64"` +} + +// SMTNode is the structure of a node in the merkle tree. +// It only captures the reference key and the index of the node. +// The value properties of a node are local states that are +// handled outside of the merkle tree library. +type SMTNode struct { + RefKey string `gorm:"primaryKey;size:64"` + Type byte + Index *string `gorm:"size:64"` // only leaf nodes have an index + LeftChild *string `gorm:"size:64"` // only branch nodes have children + RightChild *string `gorm:"size:64"` +} diff --git a/zkp/golang/pkg/storage/memory.go b/zkp/golang/pkg/storage/storage.go similarity index 85% rename from zkp/golang/pkg/storage/memory.go rename to zkp/golang/pkg/storage/storage.go index 1096d6d..cb12d5d 100644 --- a/zkp/golang/pkg/storage/memory.go +++ b/zkp/golang/pkg/storage/storage.go @@ -24,3 +24,7 @@ import ( func NewMemoryStorage() core.Storage { return storage.NewMemoryStorage() } + +func NewSqlStorage(provider core.SqlDBProvider, smtName string) (core.Storage, error) { + return storage.NewSqlStorage(provider, smtName), nil +} diff --git a/zkp/golang/pkg/utxo/utxo.go b/zkp/golang/pkg/utxo/utxo.go index 89c7509..c0c5701 100644 --- a/zkp/golang/pkg/utxo/utxo.go +++ b/zkp/golang/pkg/utxo/utxo.go @@ -19,6 +19,7 @@ package utxo import ( "math/big" + "github.com/hyperledger-labs/zeto/internal/utils" "github.com/hyperledger-labs/zeto/internal/utxo" "github.com/hyperledger-labs/zeto/pkg/core" "github.com/iden3/go-iden3-crypto/babyjub" @@ -32,8 +33,16 @@ func NewNonFungible(tokenId, tokenUri *big.Int, owner *babyjub.PublicKey, salt * return utxo.NewNonFungible(tokenId, tokenUri, owner, salt) } +func NewFungibleNullifier(amount *big.Int, owner *big.Int, salt *big.Int) core.Indexable { + return utxo.NewFungibleNullifier(amount, owner, salt) +} + +func NewNonFungibleNullifier(tokenId *big.Int, tokenUri *big.Int, owner *big.Int, salt *big.Int) core.Indexable { + return utxo.NewNonFungibleNullifier(tokenId, tokenUri, owner, salt) +} + func NewIndexOnly(index core.NodeIndex) core.Indexable { - return utxo.NewIndexOnly(index) + return utils.NewIndexOnly(index) } func HashTokenUri(tokenUri string) (*big.Int, error) {