diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 976bab9..17373ce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # -name: "kms-go ci" +name: "bbs-signature-go ci" on: push: @@ -21,3 +21,44 @@ jobs: - uses: amannn/action-semantic-pull-request@v4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + Checks: + runs-on: ubuntu-22.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: ${{ env.GO_VERSION }} + - name: Run checks + run: | + echo $PATH + go env + echo ${{ github.workspace }} + make checks + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + UnitTest: + runs-on: ubuntu-22.04 + timeout-minutes: 10 + steps: + + - name: Setup Go 1.21 + uses: actions/setup-go@v2 + with: + go-version: '1.21' + id: go + + - uses: actions/checkout@v3 + + - name: Run unit test + timeout-minutes: 15 + run: make unit-test + + - name: Upload coverage to Codecov + timeout-minutes: 10 + uses: codecov/codecov-action@v2.1.0 + with: + file: ./coverage.out \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8136a33 --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +# Copyright Gen Digital Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + + +.PHONY: all +all: clean checks unit-test + +.PHONY: checks +checks: license lint + +.PHONY: lint +lint: + @scripts/check_lint.sh + +.PHONY: license +license: + @scripts/check_license.sh + +.PHONY: unit-test +unit-test: + @scripts/check_unit.sh + +.PHONY: clean +clean: + @rm -rf ./.build + @rm -rf coverage*.out \ No newline at end of file diff --git a/bbs12381g2pub/bbs12381g2pub.go b/bbs12381g2pub/bbs12381g2pub.go new file mode 100644 index 0000000..258be69 --- /dev/null +++ b/bbs12381g2pub/bbs12381g2pub.go @@ -0,0 +1,307 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +// Package bbs12381g2pub contains BBS+ signing primitives and keys. Although it can be used directly, it is recommended +// to use BBS+ keys created by the kms along with the framework's Crypto service. +package bbs12381g2pub + +import ( + "errors" + "fmt" + "sort" + + ml "github.com/IBM/mathlib" +) + +// nolint:gochecknoglobals +var curve = ml.Curves[ml.BLS12_381_BBS] + +// BBSG2Pub defines BBS+ signature scheme where public key is a point in the field of G2. +// BBS+ signature scheme (as defined in https://eprint.iacr.org/2016/663.pdf, section 4.3). +type BBSG2Pub struct{} + +// New creates a new BBSG2Pub. +func New() *BBSG2Pub { + return &BBSG2Pub{} +} + +// Number of bytes in scalar compressed form. +const frCompressedSize = 32 + +var ( + // nolint:gochecknoglobals + // Signature length. + bls12381SignatureLen = curve.CompressedG1ByteSize + 2*frCompressedSize + + // nolint:gochecknoglobals + // Default BLS 12-381 public key length in G2 field. + bls12381G2PublicKeyLen = curve.CompressedG2ByteSize + + // nolint:gochecknoglobals + // Number of bytes in G1 X coordinate. + g1CompressedSize = curve.CompressedG1ByteSize + + // nolint:gochecknoglobals + // Number of bytes in G1 X and Y coordinates. + g1UncompressedSize = curve.G1ByteSize + + // nolint:gochecknoglobals + // Number of bytes in G2 X(a, b) and Y(a, b) coordinates. + g2UncompressedSize = curve.G2ByteSize + + // nolint:gochecknoglobals + // Number of bytes in scalar uncompressed form. + frUncompressedSize = curve.ScalarByteSize +) + +// Verify makes BLS BBS12-381 signature verification. +func (bbs *BBSG2Pub) Verify(messages [][]byte, sigBytes, pubKeyBytes []byte) error { + signature, err := ParseSignature(sigBytes) + if err != nil { + return fmt.Errorf("parse signature: %w", err) + } + + pubKey, err := UnmarshalPublicKey(pubKeyBytes) + if err != nil { + return fmt.Errorf("parse public key: %w", err) + } + + messagesCount := len(messages) + + publicKeyWithGenerators, err := pubKey.ToPublicKeyWithGenerators(messagesCount) + if err != nil { + return fmt.Errorf("build generators from public key: %w", err) + } + + messagesFr := messagesToFr(messages) + + return signature.Verify(messagesFr, publicKeyWithGenerators) +} + +// Sign signs the one or more messages using private key in compressed form. +func (bbs *BBSG2Pub) Sign(messages [][]byte, privKeyBytes []byte) ([]byte, error) { + privKey, err := UnmarshalPrivateKey(privKeyBytes) + if err != nil { + return nil, fmt.Errorf("unmarshal private key: %w", err) + } + + if len(messages) == 0 { + return nil, errors.New("messages are not defined") + } + + return bbs.SignWithKey(messages, privKey) +} + +// VerifyProof verifies BBS+ signature proof for one ore more revealed messages. +func (bbs *BBSG2Pub) VerifyProof(messagesBytes [][]byte, proof, nonce, pubKeyBytes []byte) error { + payload, err := parsePoKPayload(proof) + if err != nil { + return fmt.Errorf("parse signature proof: %w", err) + } + + signatureProof, err := ParseSignatureProof(proof[payload.lenInBytes():]) + if err != nil { + return fmt.Errorf("parse signature proof: %w", err) + } + + messages := messagesToFr(messagesBytes) + + pubKey, err := UnmarshalPublicKey(pubKeyBytes) + if err != nil { + return fmt.Errorf("parse public key: %w", err) + } + + publicKeyWithGenerators, err := pubKey.ToPublicKeyWithGenerators(payload.messagesCount) + if err != nil { + return fmt.Errorf("build generators from public key: %w", err) + } + + if len(payload.revealed) > len(messages) { + return fmt.Errorf("payload revealed bigger from messages") + } + + revealedMessages := make(map[int]*SignatureMessage) + for i := range payload.revealed { + revealedMessages[payload.revealed[i]] = messages[i] + } + + challengeBytes := signatureProof.GetBytesForChallenge(revealedMessages, publicKeyWithGenerators) + proofNonce := ParseProofNonce(nonce) + proofNonceBytes := proofNonce.ToBytes() + challengeBytes = append(challengeBytes, proofNonceBytes...) + proofChallenge := frFromOKM(challengeBytes) + + return signatureProof.Verify(proofChallenge, publicKeyWithGenerators, revealedMessages, messages) +} + +// DeriveProof derives a proof of BBS+ signature with some messages disclosed. +func (bbs *BBSG2Pub) DeriveProof(messages [][]byte, sigBytes, nonce, pubKeyBytes []byte, + revealedIndexes []int) ([]byte, error) { + if len(revealedIndexes) == 0 { + return nil, errors.New("no message to reveal") + } + + sort.Ints(revealedIndexes) + + messagesCount := len(messages) + + messagesFr := messagesToFr(messages) + + pubKey, err := UnmarshalPublicKey(pubKeyBytes) + if err != nil { + return nil, fmt.Errorf("parse public key: %w", err) + } + + publicKeyWithGenerators, err := pubKey.ToPublicKeyWithGenerators(messagesCount) + if err != nil { + return nil, fmt.Errorf("build generators from public key: %w", err) + } + + signature, err := ParseSignature(sigBytes) + if err != nil { + return nil, fmt.Errorf("parse signature: %w", err) + } + + pokSignature, err := NewPoKOfSignature(signature, messagesFr, revealedIndexes, publicKeyWithGenerators) + if err != nil { + return nil, fmt.Errorf("init proof of knowledge signature: %w", err) + } + + challengeBytes := pokSignature.ToBytes() + + proofNonce := ParseProofNonce(nonce) + proofNonceBytes := proofNonce.ToBytes() + challengeBytes = append(challengeBytes, proofNonceBytes...) + + proofChallenge := frFromOKM(challengeBytes) + + proof := pokSignature.GenerateProof(proofChallenge) + + payload := newPoKPayload(messagesCount, revealedIndexes) + + payloadBytes, err := payload.toBytes() + if err != nil { + return nil, fmt.Errorf("derive proof: paylod to bytes: %w", err) + } + + signatureProofBytes := append(payloadBytes, proof.ToBytes()...) + + return signatureProofBytes, nil +} + +// SignWithKey signs the one or more messages using BBS+ key pair. +func (bbs *BBSG2Pub) SignWithKey(messages [][]byte, privKey *PrivateKey) ([]byte, error) { + var err error + + pubKey := privKey.PublicKey() + messagesCount := len(messages) + + pubKeyWithGenerators, err := pubKey.ToPublicKeyWithGenerators(messagesCount) + if err != nil { + return nil, fmt.Errorf("build generators from public key: %w", err) + } + + messagesFr := make([]*SignatureMessage, len(messages)) + for i := range messages { + messagesFr[i] = ParseSignatureMessage(messages[i]) + } + + e, s := createRandSignatureFr(), createRandSignatureFr() + exp := privKey.FR.Copy() + exp = exp.Plus(e) + exp.InvModP(curve.GroupOrder) + + b := computeB(s, messagesFr, pubKeyWithGenerators) + + sig := b.Mul(frToRepr(exp)) + + signature := &Signature{ + A: sig, + E: e, + S: s, + } + + return signature.ToBytes() +} + +func computeB(s *ml.Zr, messages []*SignatureMessage, key *PublicKeyWithGenerators) *ml.G1 { + const basesOffset = 2 + + cb := newCommitmentBuilder(len(messages) + basesOffset) + + cb.add(curve.GenG1, curve.NewZrFromInt(1)) + cb.add(key.h0, s) + + for i := 0; i < len(messages); i++ { + cb.add(key.h[i], messages[i].FR) + } + + return cb.build() +} + +type commitmentBuilder struct { + bases []*ml.G1 + scalars []*ml.Zr +} + +func newCommitmentBuilder(expectedSize int) *commitmentBuilder { + return &commitmentBuilder{ + bases: make([]*ml.G1, 0, expectedSize), + scalars: make([]*ml.Zr, 0, expectedSize), + } +} + +func (cb *commitmentBuilder) add(base *ml.G1, scalar *ml.Zr) { + cb.bases = append(cb.bases, base) + cb.scalars = append(cb.scalars, scalar) +} + +func (cb *commitmentBuilder) build() *ml.G1 { + return sumOfG1Products(cb.bases, cb.scalars) +} + +func sumOfG1Products(bases []*ml.G1, scalars []*ml.Zr) *ml.G1 { + var res *ml.G1 + + for i := 0; i < len(bases); i++ { + b := bases[i] + s := scalars[i] + + g := b.Mul(frToRepr(s)) + if res == nil { + res = g + } else { + res.Add(g) + } + } + + return res +} + +func compareTwoPairings(p1 *ml.G1, q1 *ml.G2, + p2 *ml.G1, q2 *ml.G2) bool { + p := curve.Pairing2(q1, p1, q2, p2) + p = curve.FExp(p) + + return p.IsUnity() +} + +// ProofNonce is a nonce for Proof of Knowledge proof. +type ProofNonce struct { + fr *ml.Zr +} + +// ParseProofNonce creates a new ProofNonce from bytes. +func ParseProofNonce(proofNonceBytes []byte) *ProofNonce { + return &ProofNonce{ + frFromOKM(proofNonceBytes), + } +} + +// ToBytes converts ProofNonce into bytes. +func (pn *ProofNonce) ToBytes() []byte { + return frToRepr(pn.fr).Bytes() +} diff --git a/bbs12381g2pub/bbs_test.go b/bbs12381g2pub/bbs_test.go new file mode 100644 index 0000000..d86e40b --- /dev/null +++ b/bbs12381g2pub/bbs_test.go @@ -0,0 +1,265 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package bbs12381g2pub_test + +import ( + "crypto/rand" + "encoding/base64" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/trustbloc/bbs-signature-go/bbs12381g2pub" +) + +//nolint:lll +func TestBlsG2Pub_Verify(t *testing.T) { + pkBase64 := "lOpN7uGZWivVIjs0325N/V0dAhoPomrgfXVpg7pZNdRWwFwJDVxoE7TvRyOx/Qr7GMtShNuS2Px/oScD+SMf08t8eAO78QRNErPzwNpfkP4ppcSTShStFDfFbsv9L9yb" + pkBytes, err := base64.RawStdEncoding.DecodeString(pkBase64) + require.NoError(t, err) + + sigBase64 := "hPbLkeMZZ6KKzkjWoTVHeMeuLJfYWjmdAU1Vg5fZ/VZnIXxxeXBB+q0/EL8XQmWkOMMwEGA/D2dCb4MDuntKZpvHEHlvaFR6l1A4bYj0t2Jd6bYwGwCwirNbmSeIoEmJeRzJ1cSvsL+jxvLixdDPnw==" + sigBytes, err := base64.StdEncoding.DecodeString(sigBase64) + require.NoError(t, err) + + messagesBytes := [][]byte{[]byte("message1"), []byte("message2")} + + bls := bbs12381g2pub.New() + + t.Run("valid signature", func(t *testing.T) { + err = bls.Verify(messagesBytes, sigBytes, pkBytes) + require.NoError(t, err) + }) + + t.Run("invalid signature", func(t *testing.T) { + // swap messages order + invalidMessagesBytes := [][]byte{[]byte("message2"), []byte("message1")} + + err = bls.Verify(invalidMessagesBytes, sigBytes, pkBytes) + require.Error(t, err) + require.EqualError(t, err, "invalid BLS12-381 signature") + }) + + t.Run("invalid input public key", func(t *testing.T) { + err = bls.Verify(messagesBytes, sigBytes, []byte("invalid")) + require.Error(t, err) + require.EqualError(t, err, "parse public key: invalid size of public key") + + pkBytesInvalid := make([]byte, len(pkBytes)) + + _, err = rand.Read(pkBytesInvalid) + require.NoError(t, err) + + err = bls.Verify(messagesBytes, sigBytes, pkBytesInvalid) + require.Error(t, err) + require.Contains(t, err.Error(), "parse public key: deserialize public key") + }) + + t.Run("invalid input signature", func(t *testing.T) { + err = bls.Verify(messagesBytes, []byte("invalid"), pkBytes) + require.Error(t, err) + require.EqualError(t, err, "parse signature: invalid size of signature") + + sigBytesInvalid := make([]byte, len(sigBytes)) + + _, err = rand.Read(sigBytesInvalid) + require.NoError(t, err) + + err = bls.Verify(messagesBytes, sigBytesInvalid, pkBytes) + require.Error(t, err) + require.Contains(t, err.Error(), "parse signature: deserialize G1 compressed signature") + }) +} + +func TestBBSG2Pub_SignWithKeyPair(t *testing.T) { + pubKey, privKey, err := generateKeyPairRandom() + require.NoError(t, err) + + bls := bbs12381g2pub.New() + + messagesBytes := [][]byte{[]byte("message1"), []byte("message2")} + + signatureBytes, err := bls.SignWithKey(messagesBytes, privKey) + require.NoError(t, err) + require.NotEmpty(t, signatureBytes) + require.Len(t, signatureBytes, 112) + + pubKeyBytes, err := pubKey.Marshal() + require.NoError(t, err) + + require.NoError(t, bls.Verify(messagesBytes, signatureBytes, pubKeyBytes)) +} + +func TestBBSG2Pub_Sign(t *testing.T) { + pubKey, privKey, err := generateKeyPairRandom() + require.NoError(t, err) + + bls := bbs12381g2pub.New() + + messagesBytes := [][]byte{[]byte("message1"), []byte("message2")} + + privKeyBytes, err := privKey.Marshal() + require.NoError(t, err) + + signatureBytes, err := bls.Sign(messagesBytes, privKeyBytes) + require.NoError(t, err) + require.NotEmpty(t, signatureBytes) + require.Len(t, signatureBytes, 112) + + pubKeyBytes, err := pubKey.Marshal() + require.NoError(t, err) + + require.NoError(t, bls.Verify(messagesBytes, signatureBytes, pubKeyBytes)) + + // invalid private key bytes + signatureBytes, err = bls.Sign(messagesBytes, []byte("invalid")) + require.Error(t, err) + require.EqualError(t, err, "unmarshal private key: invalid size of private key") + require.Nil(t, signatureBytes) + + // at least one message must be passed + signatureBytes, err = bls.Sign([][]byte{}, privKeyBytes) + require.Error(t, err) + require.EqualError(t, err, "messages are not defined") + require.Nil(t, signatureBytes) +} + +//nolint:lll +func TestBBSG2Pub_VerifyProof(t *testing.T) { + pkBase64 := "sVEbbh9jDPGSBK/oT/EeXQwFvNuC+47rgq9cxXKrwo6G7k4JOY/vEcfgZw9Vf/TpArbIdIAJCFMDyTd7l2atS5zExAKX0B/9Z3E/mgIZeQJ81iZ/1HUnUCT2Om239KFx" + pkBytes, err := base64.RawStdEncoding.DecodeString(pkBase64) + require.NoError(t, err) + + proofBase64 := "AAIBiN4EL9psRsIUlwQah7a5VROD369PPt09Z+jfzamP+/114a5RfWVMju3NCUl2Yv6ahyIdHGdEfxhC985ShlGQrRPLa+crFRiu2pfnAk+L6QMNooVMQhzJc2yYgktHen4QhsKV3IGoRRUs42zqPTP3BdqIPQeLgjDVi1d1LXEnP+WFQGEQmTKWTja4u1MsERdmAAAAdIb6HuFznhE3OByXN0Xp3E4hWQlocCdpExyNlSLh3LxK5duCI/WMM7ETTNS0Ozxe3gAAAAIuALkiwplgKW6YmvrEcllWSkG3H+uHEZzZGL6wq6Ac0SuktQ4n84tZPtMtR9vC1Rsu8f7Kwtbq1Kv4v02ct9cvj7LGcitzg3u/ZO516qLz+iitKeGeJhtFB8ggALcJOEsebPFl12cYwkieBbIHCBt4AAAAAxgEHt3iqKIyIQbTYJvtrMjGjT4zuimiZbtE3VXnqFmGaxVTeR7dh89PbPtsBI8LLMrCvFFpks9D/oTzxnw13RBmMgMlc1bcfQOmE9DZBGB7NCdwOnT7q4TVKhswOITKTQ==" + proofBytes, err := base64.StdEncoding.DecodeString(proofBase64) + require.NoError(t, err) + + nonce := []byte("nonce") + + messagesBytes := [][]byte{[]byte("message1"), []byte("message2")} + revealedMessagesBytes := messagesBytes[:1] + + bls := bbs12381g2pub.New() + + t.Run("valid signature proof", func(t *testing.T) { + err = bls.VerifyProof(revealedMessagesBytes, proofBytes, nonce, pkBytes) + require.NoError(t, err) + }) + + t.Run("test payload revealed bigger from messages", func(t *testing.T) { + wrongProofBytes, errDecode := base64.StdEncoding.DecodeString(`AAwP/4nFun/RtaXtUVTppUimMRTcEROs3gbjh9iqjGQAsvD+ne2uzME26gY4zNBcMKpvyLD4I6UGm8ATKLQI4OUiBXHNCQZI4YEM5hWI7AzhFXLEEVDFL0Gzr4S04PvcJsmV74BqST8iI1HUO2TCjdT1LkhgPabP/Zy8IpnbWUtLZO1t76NFwCV8+R1YpOozTNKRQQAAAHSpyGry6Rx3PRuOZUeqk4iGFq67iHSiBybjo6muud7aUyCxd9AW3onTlV2Nxz8AJD0AAAACB3FmuAUcklAj5cdSdw7VY57y7p4VmfPCKaEp1SSJTJRZXiE2xUqDntend+tkq+jjHhLCk56zk5GoZzr280IeuLne4WgpB2kNN7n5dqRpy4+UkS5+kiorLtKiJuWhk+OFTiB8jFlTbm0dH3O3tm5CzQAAAAIhY6I8vQ96tdSoyGy09wEMCdWzB06GElVHeQhWVw8fukq1dUAwWRXmZKT8kxDNAlp2NS7fXpEGXZ9fF7+c1IJp`) + require.NoError(t, errDecode) + err = bls.VerifyProof(revealedMessagesBytes, wrongProofBytes, nonce, pkBytes) + require.Error(t, err) + require.EqualError(t, err, "payload revealed bigger from messages") + }) + + t.Run("invalid size of signature proof payload", func(t *testing.T) { + err = bls.VerifyProof(revealedMessagesBytes, []byte("?"), nonce, pkBytes) + require.Error(t, err) + require.EqualError(t, err, "parse signature proof: invalid size of PoK payload") + }) + + t.Run("invalid size of signature proof", func(t *testing.T) { + proofBytesCopy := make([]byte, 5) + + copy(proofBytesCopy, proofBytes) + + err = bls.VerifyProof(revealedMessagesBytes, proofBytesCopy, nonce, pkBytes) + require.Error(t, err) + require.EqualError(t, err, "parse signature proof: invalid size of signature proof") + }) + + t.Run("invalid proof", func(t *testing.T) { + proofBytesCopy := make([]byte, len(proofBytes)) + + copy(proofBytesCopy, proofBytes) + proofBytesCopy[21] = 255 - proofBytesCopy[21] + + err = bls.VerifyProof(revealedMessagesBytes, proofBytesCopy, nonce, pkBytes) + require.Error(t, err) + require.ErrorContains(t, err, "parse signature proof: parse G1 point: failure [set bytes failed") + }) + + t.Run("invalid input public key", func(t *testing.T) { + err = bls.VerifyProof(revealedMessagesBytes, proofBytes, nonce, []byte("invalid public key")) + require.Error(t, err) + require.EqualError(t, err, "parse public key: invalid size of public key") + }) +} + +//nolint:lll +func TestBBSG2Pub_VerifyProof_SeveralDisclosedMessages(t *testing.T) { + pkBase64 := "l0Wtf3gy5f140G5vCoCJw2420hwk6Xw65/DX3ycv1W7/eMky8DyExw+o1s2bmq3sEIJatkiN8f5D4k0766x0UvfbupFX+vVkeqnlOvT6o2cag2osQdMFbBQqAybOM4Gm" + pkBytes, err := base64.RawStdEncoding.DecodeString(pkBase64) + require.NoError(t, err) + + proofBase64 := "AAQFpAE2VALtmriOzSMk/oqid4uJhPQRUVUuyenL/L4w4ykdyh0jCX64EFqCdLP+n8VrkOKXhHPKPoCOdHBOMv96aM15NFg867/MToMeNN0IFzZkzhs37qk1vWWFKReMF+cRsCAmkHO6An1goNHdY/4XquSV3LwykezraWt8+8bLvVn6ciaXBVxVcYkbIXRsVjqbAAAAdIl/C/W5G1pDbLMrUrBAYdpvzGHG25gktAuUFZb/SkIyy0uhtWJk2v6A+D3zkoEBsgAAAAJY/jfJR9kpGbSY5pfz+qPkqyNOTJbs6OEpfBwYGsyC7hspvBGUOYyvuKlS8SvKAXW7hVawAhYJbvnRwzeiP6P9kbZKtLQZIkRQB+mxRSbMk/0JgE1jApHOlPtgbqI9yIouhK9xT2wVZl79qTAwifonAAAABDTDo5VtXR2gloy+au7ai0wcnnzjMJ6ztQHRI1ApV5VuOQ19TYL7SW+C90p3QSZFQ5gtl90PHaUuEAHIb+7ZgbJvh5sc1DjKfThwPx0Ao0w8+xTbLhNlxvo6VE1cfbiuME+miCAibLgHjksQ8ctl322qnblYJLXiS4lvx/jtGvA3" + proofBytes, err := base64.StdEncoding.DecodeString(proofBase64) + require.NoError(t, err) + + nonce := []byte("nonce") + + messagesBytes := [][]byte{ + []byte("message1"), + []byte("message2"), + []byte("message3"), + []byte("message4"), + } + revealedMessagesBytes := [][]byte{messagesBytes[0], messagesBytes[2]} + + bls := bbs12381g2pub.New() + + t.Run("valid signature", func(t *testing.T) { + err = bls.VerifyProof(revealedMessagesBytes, proofBytes, nonce, pkBytes) + require.NoError(t, err) + }) +} + +func TestBBSG2Pub_DeriveProof(t *testing.T) { + pubKey, privKey, err := generateKeyPairRandom() + require.NoError(t, err) + + privKeyBytes, err := privKey.Marshal() + require.NoError(t, err) + + messagesBytes := [][]byte{ + []byte("message1"), + []byte("message2"), + []byte("message3"), + []byte("message4"), + } + bls := bbs12381g2pub.New() + + signatureBytes, err := bls.Sign(messagesBytes, privKeyBytes) + require.NoError(t, err) + + pubKeyBytes, err := pubKey.Marshal() + require.NoError(t, err) + + require.NoError(t, bls.Verify(messagesBytes, signatureBytes, pubKeyBytes)) + + nonce := []byte("nonce") + revealedIndexes := []int{0, 2} + proofBytes, err := bls.DeriveProof(messagesBytes, signatureBytes, nonce, pubKeyBytes, revealedIndexes) + require.NoError(t, err) + require.NotEmpty(t, proofBytes) + + revealedMessages := make([][]byte, len(revealedIndexes)) + for i, ind := range revealedIndexes { + revealedMessages[i] = messagesBytes[ind] + } + + require.NoError(t, bls.VerifyProof(revealedMessages, proofBytes, nonce, pubKeyBytes)) + + t.Run("DeriveProof with revealedIndexes larger than revealedMessages count", func(t *testing.T) { + revealedIndexes = []int{0, 2, 4, 7, 9, 11} + _, err = bls.DeriveProof(messagesBytes, signatureBytes, nonce, pubKeyBytes, revealedIndexes) + require.EqualError(t, err, "init proof of knowledge signature: invalid size: 6 revealed indexes is "+ + "larger than 4 messages") + }) +} diff --git a/bbs12381g2pub/fr.go b/bbs12381g2pub/fr.go new file mode 100644 index 0000000..47d6116 --- /dev/null +++ b/bbs12381g2pub/fr.go @@ -0,0 +1,71 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package bbs12381g2pub + +import ( + "crypto/rand" + + ml "github.com/IBM/mathlib" + "golang.org/x/crypto/blake2b" +) + +func parseFr(data []byte) *ml.Zr { + return curve.NewZrFromBytes(data) +} + +// nolint:gochecknoglobals +var f2192Bytes = []byte{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +} + +func f2192() *ml.Zr { + return curve.NewZrFromBytes(f2192Bytes) +} + +func frFromOKM(message []byte) *ml.Zr { + const ( + eightBytes = 8 + okmMiddle = 24 + ) + + // We pass a null key so error is impossible here. + h, _ := blake2b.New384(nil) //nolint:errcheck + + // blake2b.digest() does not return an error. + _, _ = h.Write(message) + okm := h.Sum(nil) + emptyEightBytes := make([]byte, eightBytes) + + elm := curve.NewZrFromBytes(append(emptyEightBytes, okm[:okmMiddle]...)) + elm = elm.Mul(f2192()) + + fr := curve.NewZrFromBytes(append(emptyEightBytes, okm[okmMiddle:]...)) + elm = elm.Plus(fr) + + return elm +} + +func frToRepr(fr *ml.Zr) *ml.Zr { + return fr.Copy() +} + +func messagesToFr(messages [][]byte) []*SignatureMessage { + messagesFr := make([]*SignatureMessage, len(messages)) + + for i := range messages { + messagesFr[i] = ParseSignatureMessage(messages[i]) + } + + return messagesFr +} + +func createRandSignatureFr() *ml.Zr { + return curve.NewRandomZr(rand.Reader) +} diff --git a/bbs12381g2pub/keys.go b/bbs12381g2pub/keys.go new file mode 100644 index 0000000..e685078 --- /dev/null +++ b/bbs12381g2pub/keys.go @@ -0,0 +1,196 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package bbs12381g2pub + +import ( + "crypto/rand" + "errors" + "fmt" + "hash" + "io" + + ml "github.com/IBM/mathlib" + "golang.org/x/crypto/hkdf" +) + +var ( + // nolint:gochecknoglobals + seedSize = frCompressedSize + + // nolint:gochecknoglobals + generateKeySalt = "BBS-SIG-KEYGEN-SALT-" +) + +// PublicKey defines BLS Public Key. +type PublicKey struct { + PointG2 *ml.G2 +} + +// PrivateKey defines BLS Public Key. +type PrivateKey struct { + FR *ml.Zr +} + +// PublicKeyWithGenerators extends PublicKey with a blinding generator h0, a commitment to the secret key w, +// and a generator for each message h. +type PublicKeyWithGenerators struct { + h0 *ml.G1 + h []*ml.G1 + + w *ml.G2 + + messagesCount int +} + +// ToPublicKeyWithGenerators creates PublicKeyWithGenerators from the PublicKey. +func (pk *PublicKey) ToPublicKeyWithGenerators(messagesCount int) (*PublicKeyWithGenerators, error) { + offset := g2UncompressedSize + 1 + + data := calcData(pk, messagesCount) + + h0 := hashToG1(data) + + h := make([]*ml.G1, messagesCount) + + for i := 1; i <= messagesCount; i++ { + dataCopy := make([]byte, len(data)) + copy(dataCopy, data) + + iBytes := uint32ToBytes(uint32(i)) + + for j := 0; j < len(iBytes); j++ { + dataCopy[j+offset] = iBytes[j] + } + + h[i-1] = hashToG1(dataCopy) + } + + return &PublicKeyWithGenerators{ + h0: h0, + h: h, + w: pk.PointG2, + messagesCount: messagesCount, + }, nil +} + +func calcData(key *PublicKey, messagesCount int) []byte { + data := key.PointG2.Bytes() + + data = append(data, 0, 0, 0, 0, 0, 0) + + mcBytes := uint32ToBytes(uint32(messagesCount)) + + data = append(data, mcBytes...) + + return data +} + +func hashToG1(data []byte) *ml.G1 { + var dstG1 = []byte("BLS12381G1_XMD:BLAKE2B_SSWU_RO_BBS+_SIGNATURES:1_0_0") + + return curve.HashToG1WithDomain(data, dstG1) +} + +// UnmarshalPrivateKey unmarshals PrivateKey. +func UnmarshalPrivateKey(privKeyBytes []byte) (*PrivateKey, error) { + if len(privKeyBytes) != frCompressedSize { + return nil, errors.New("invalid size of private key") + } + + fr := parseFr(privKeyBytes) + + return &PrivateKey{ + FR: fr, + }, nil +} + +// Marshal marshals PrivateKey. +func (k *PrivateKey) Marshal() ([]byte, error) { + bytes := k.FR.Bytes() + return bytes, nil +} + +// PublicKey returns a Public Key as G2 point generated from the Private Key. +func (k *PrivateKey) PublicKey() *PublicKey { + pointG2 := curve.GenG2.Mul(frToRepr(k.FR)) + + return &PublicKey{pointG2} +} + +// UnmarshalPublicKey parses a PublicKey from bytes. +func UnmarshalPublicKey(pubKeyBytes []byte) (*PublicKey, error) { + if len(pubKeyBytes) != bls12381G2PublicKeyLen { + return nil, errors.New("invalid size of public key") + } + + pointG2, err := curve.NewG2FromCompressed(pubKeyBytes) + if err != nil { + return nil, fmt.Errorf("deserialize public key: %w", err) + } + + return &PublicKey{ + PointG2: pointG2, + }, nil +} + +// Marshal marshals PublicKey. +func (pk *PublicKey) Marshal() ([]byte, error) { + pkBytes := pk.PointG2.Compressed() + + return pkBytes, nil +} + +// GenerateKeyPair generates BBS+ PublicKey and PrivateKey pair. +func GenerateKeyPair(h func() hash.Hash, seed []byte) (*PublicKey, *PrivateKey, error) { + if len(seed) != 0 && len(seed) != seedSize { + return nil, nil, errors.New("invalid size of seed") + } + + okm, err := generateOKM(seed, h) + if err != nil { + return nil, nil, err + } + + privKeyFr := frFromOKM(okm) + + privKey := &PrivateKey{privKeyFr} + pubKey := privKey.PublicKey() + + return pubKey, privKey, nil +} + +func generateOKM(ikm []byte, h func() hash.Hash) ([]byte, error) { + salt := []byte(generateKeySalt) + info := make([]byte, 2) + + if ikm != nil { + ikm = append(ikm, 0) + } else { + ikm = make([]byte, seedSize+1) + + _, err := rand.Read(ikm) + if err != nil { + return nil, err + } + + ikm[seedSize] = 0 + } + + return newHKDF(h, ikm, salt, info, frUncompressedSize) +} + +func newHKDF(h func() hash.Hash, ikm, salt, info []byte, length int) ([]byte, error) { + reader := hkdf.New(h, ikm, salt, info) + result := make([]byte, length) + + _, err := io.ReadFull(reader, result) + if err != nil { + return nil, err + } + + return result, nil +} diff --git a/bbs12381g2pub/keys_test.go b/bbs12381g2pub/keys_test.go new file mode 100644 index 0000000..bb7e055 --- /dev/null +++ b/bbs12381g2pub/keys_test.go @@ -0,0 +1,103 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package bbs12381g2pub_test + +import ( + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/btcsuite/btcutil/base58" + "github.com/stretchr/testify/require" + + bbs "github.com/trustbloc/bbs-signature-go/bbs12381g2pub" +) + +func TestGenerateKeyPair(t *testing.T) { + h := sha256.New + + seed := make([]byte, 32) + + pubKey, privKey, err := bbs.GenerateKeyPair(h, seed) + require.NoError(t, err) + require.NotNil(t, pubKey) + require.NotNil(t, privKey) + + // use random seed + pubKey, privKey, err = bbs.GenerateKeyPair(h, nil) + require.NoError(t, err) + require.NotNil(t, pubKey) + require.NotNil(t, privKey) + + // invalid size of seed + pubKey, privKey, err = bbs.GenerateKeyPair(h, make([]byte, 31)) + require.Error(t, err) + require.EqualError(t, err, "invalid size of seed") + require.Nil(t, pubKey) + require.Nil(t, privKey) +} + +func TestPrivateKey_Marshal(t *testing.T) { + _, privKey, err := generateKeyPairRandom() + require.NoError(t, err) + + privKeyBytes, err := privKey.Marshal() + require.NoError(t, err) + require.NotNil(t, privKeyBytes) + + privKeyUnmarshalled, err := bbs.UnmarshalPrivateKey(privKeyBytes) + require.NoError(t, err) + require.NotNil(t, privKeyUnmarshalled) + require.Equal(t, privKey, privKeyUnmarshalled) +} + +func TestPrivateKey_PublicKey(t *testing.T) { + pubKey, privKey, err := generateKeyPairRandom() + require.NoError(t, err) + + require.Equal(t, pubKey, privKey.PublicKey()) +} + +func TestPublicKey_Marshal(t *testing.T) { + pubKey, _, err := generateKeyPairRandom() + require.NoError(t, err) + + pubKeyBytes, err := pubKey.Marshal() + require.NoError(t, err) + require.NotNil(t, pubKeyBytes) + + pubKeyUnmarshalled, err := bbs.UnmarshalPublicKey(pubKeyBytes) + require.NoError(t, err) + require.NotNil(t, pubKeyUnmarshalled) + require.Equal(t, pubKey, pubKeyUnmarshalled) +} + +func TestParseMattrKeys(t *testing.T) { + privKeyB58 := "5D6Pa8dSwApdnfg7EZR8WnGfvLDCZPZGsZ5Y1ELL9VDj" + privKeyBytes := base58.Decode(privKeyB58) + + pubKeyB58 := "oqpWYKaZD9M1Kbe94BVXpr8WTdFBNZyKv48cziTiQUeuhm7sBhCABMyYG4kcMrseC68YTFFgyhiNeBKjzdKk9MiRWuLv5H4FFujQsQK2KTAtzU8qTBiZqBHMmnLF4PL7Ytu" //nolint:lll + pubKeyBytes := base58.Decode(pubKeyB58) + + messagesBytes := [][]byte{[]byte("message1"), []byte("message2")} + signatureBytes, err := bbs.New().Sign(messagesBytes, privKeyBytes) + require.NoError(t, err) + + err = bbs.New().Verify(messagesBytes, signatureBytes, pubKeyBytes) + require.NoError(t, err) +} + +func generateKeyPairRandom() (*bbs.PublicKey, *bbs.PrivateKey, error) { + seed := make([]byte, 32) + + _, err := rand.Read(seed) + if err != nil { + panic(err) + } + + return bbs.GenerateKeyPair(sha256.New, seed) +} diff --git a/bbs12381g2pub/proof_of_knowledge.go b/bbs12381g2pub/proof_of_knowledge.go new file mode 100644 index 0000000..359b972 --- /dev/null +++ b/bbs12381g2pub/proof_of_knowledge.go @@ -0,0 +1,230 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package bbs12381g2pub + +import ( + "fmt" + + ml "github.com/IBM/mathlib" +) + +// PoKOfSignature is Proof of Knowledge of a Signature that is used by the prover to construct PoKOfSignatureProof. +type PoKOfSignature struct { + aPrime *ml.G1 + aBar *ml.G1 + d *ml.G1 + + pokVC1 *ProverCommittedG1 + secrets1 []*ml.Zr + + pokVC2 *ProverCommittedG1 + secrets2 []*ml.Zr + + revealedMessages map[int]*SignatureMessage +} + +// NewPoKOfSignature creates a new PoKOfSignature. +func NewPoKOfSignature(signature *Signature, messages []*SignatureMessage, revealedIndexes []int, + pubKey *PublicKeyWithGenerators) (*PoKOfSignature, error) { + err := signature.Verify(messages, pubKey) + if err != nil { + return nil, fmt.Errorf("verify input signature: %w", err) + } + + r1, r2 := createRandSignatureFr(), createRandSignatureFr() + b := computeB(signature.S, messages, pubKey) + aPrime := signature.A.Mul(frToRepr(r1)) + + aBarDenom := aPrime.Mul(frToRepr(signature.E)) + + aBar := b.Mul(frToRepr(r1)) + aBar.Sub(aBarDenom) + + r2D := r2.Copy() + r2D.Neg() + + commitmentBasesCount := 2 + cb := newCommitmentBuilder(commitmentBasesCount) + cb.add(b, r1) + cb.add(pubKey.h0, r2D) + + d := cb.build() + r3 := r1.Copy() + r3.InvModP(curve.GroupOrder) + + sPrime := r2.Mul(r3) + sPrime.Neg() + sPrime = sPrime.Plus(signature.S) + + pokVC1, secrets1 := newVC1Signature(aPrime, pubKey.h0, signature.E, r2) + + revealedMessages := make(map[int]*SignatureMessage, len(revealedIndexes)) + + if len(messages) < len(revealedIndexes) { + return nil, fmt.Errorf("invalid size: %d revealed indexes is larger than %d messages", len(revealedIndexes), + len(messages)) + } + + for _, ind := range revealedIndexes { + revealedMessages[ind] = messages[ind] + } + + pokVC2, secrets2 := newVC2Signature(d, r3, pubKey, sPrime, messages, revealedMessages) + + return &PoKOfSignature{ + aPrime: aPrime, + aBar: aBar, + d: d, + pokVC1: pokVC1, + secrets1: secrets1, + pokVC2: pokVC2, + secrets2: secrets2, + revealedMessages: revealedMessages, + }, nil +} + +func newVC1Signature(aPrime *ml.G1, h0 *ml.G1, + e, r2 *ml.Zr) (*ProverCommittedG1, []*ml.Zr) { + committing1 := NewProverCommittingG1() + secrets1 := make([]*ml.Zr, 2) + + committing1.Commit(aPrime) + + sigE := e.Copy() + sigE.Neg() + secrets1[0] = sigE + + committing1.Commit(h0) + + secrets1[1] = r2 + pokVC1 := committing1.Finish() + + return pokVC1, secrets1 +} + +func newVC2Signature(d *ml.G1, r3 *ml.Zr, pubKey *PublicKeyWithGenerators, sPrime *ml.Zr, + messages []*SignatureMessage, revealedMessages map[int]*SignatureMessage) (*ProverCommittedG1, []*ml.Zr) { + messagesCount := len(messages) + committing2 := NewProverCommittingG1() + baseSecretsCount := 2 + secrets2 := make([]*ml.Zr, 0, baseSecretsCount+messagesCount) + + committing2.Commit(d) + + r3D := r3.Copy() + r3D.Neg() + + secrets2 = append(secrets2, r3D) + + committing2.Commit(pubKey.h0) + + secrets2 = append(secrets2, sPrime) + + for i := 0; i < messagesCount; i++ { + if _, ok := revealedMessages[i]; ok { + continue + } + + committing2.Commit(pubKey.h[i]) + + sourceFR := messages[i].FR + hiddenFRCopy := sourceFR.Copy() + + secrets2 = append(secrets2, hiddenFRCopy) + } + + pokVC2 := committing2.Finish() + + return pokVC2, secrets2 +} + +// ToBytes converts PoKOfSignature to bytes. +func (pos *PoKOfSignature) ToBytes() []byte { + challengeBytes := pos.aBar.Bytes() + challengeBytes = append(challengeBytes, pos.pokVC1.ToBytes()...) + challengeBytes = append(challengeBytes, pos.pokVC2.ToBytes()...) + + return challengeBytes +} + +// GenerateProof generates PoKOfSignatureProof proof from PoKOfSignature signature. +func (pos *PoKOfSignature) GenerateProof(challengeHash *ml.Zr) *PoKOfSignatureProof { + return &PoKOfSignatureProof{ + aPrime: pos.aPrime, + aBar: pos.aBar, + d: pos.d, + proofVC1: pos.pokVC1.GenerateProof(challengeHash, pos.secrets1), + proofVC2: pos.pokVC2.GenerateProof(challengeHash, pos.secrets2), + } +} + +// ProverCommittedG1 helps to generate a ProofG1. +type ProverCommittedG1 struct { + bases []*ml.G1 + blindingFactors []*ml.Zr + commitment *ml.G1 +} + +// ToBytes converts ProverCommittedG1 to bytes. +func (g *ProverCommittedG1) ToBytes() []byte { + bytes := make([]byte, 0) + + for _, base := range g.bases { + bytes = append(bytes, base.Bytes()...) + } + + return append(bytes, g.commitment.Bytes()...) +} + +// GenerateProof generates proof ProofG1 for all secrets. +func (g *ProverCommittedG1) GenerateProof(challenge *ml.Zr, secrets []*ml.Zr) *ProofG1 { + responses := make([]*ml.Zr, len(g.bases)) + + for i := range g.blindingFactors { + c := challenge.Mul(secrets[i]) + + s := g.blindingFactors[i].Minus(c) + responses[i] = s + } + + return &ProofG1{ + commitment: g.commitment, + responses: responses, + } +} + +// ProverCommittingG1 is a proof of knowledge of messages in a vector commitment. +type ProverCommittingG1 struct { + bases []*ml.G1 + blindingFactors []*ml.Zr +} + +// NewProverCommittingG1 creates a new ProverCommittingG1. +func NewProverCommittingG1() *ProverCommittingG1 { + return &ProverCommittingG1{ + bases: make([]*ml.G1, 0), + blindingFactors: make([]*ml.Zr, 0), + } +} + +// Commit append a base point and randomly generated blinding factor. +func (pc *ProverCommittingG1) Commit(base *ml.G1) { + pc.bases = append(pc.bases, base) + r := createRandSignatureFr() + pc.blindingFactors = append(pc.blindingFactors, r) +} + +// Finish helps to generate ProverCommittedG1 after commitment of all base points. +func (pc *ProverCommittingG1) Finish() *ProverCommittedG1 { + commitment := sumOfG1Products(pc.bases, pc.blindingFactors) + + return &ProverCommittedG1{ + bases: pc.bases, + blindingFactors: pc.blindingFactors, + commitment: commitment, + } +} diff --git a/bbs12381g2pub/signature.go b/bbs12381g2pub/signature.go new file mode 100644 index 0000000..3491e48 --- /dev/null +++ b/bbs12381g2pub/signature.go @@ -0,0 +1,70 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package bbs12381g2pub + +import ( + "errors" + "fmt" + + ml "github.com/IBM/mathlib" +) + +// Signature defines BLS signature. +type Signature struct { + A *ml.G1 + E *ml.Zr + S *ml.Zr +} + +// ParseSignature parses a Signature from bytes. +func ParseSignature(sigBytes []byte) (*Signature, error) { + if len(sigBytes) != bls12381SignatureLen { + return nil, errors.New("invalid size of signature") + } + + pointG1, err := curve.NewG1FromCompressed(sigBytes[:g1CompressedSize]) + if err != nil { + return nil, fmt.Errorf("deserialize G1 compressed signature: %w", err) + } + + e := parseFr(sigBytes[g1CompressedSize : g1CompressedSize+frCompressedSize]) + s := parseFr(sigBytes[g1CompressedSize+frCompressedSize:]) + + return &Signature{ + A: pointG1, + E: e, + S: s, + }, nil +} + +// ToBytes converts signature to bytes using compression of G1 point and E, S FR points. +func (s *Signature) ToBytes() ([]byte, error) { + bytes := make([]byte, bls12381SignatureLen) + + copy(bytes, s.A.Compressed()) + copy(bytes[g1CompressedSize:g1CompressedSize+frCompressedSize], s.E.Bytes()) + copy(bytes[g1CompressedSize+frCompressedSize:], s.S.Bytes()) + + return bytes, nil +} + +// Verify is used for signature verification. +func (s *Signature) Verify(messages []*SignatureMessage, pubKey *PublicKeyWithGenerators) error { + p1 := s.A + + q1 := curve.GenG2.Mul(frToRepr(s.E)) + q1.Add(pubKey.w) + + p2 := computeB(s.S, messages, pubKey) + p2.Neg() + + if compareTwoPairings(p1, q1, p2, curve.GenG2) { + return nil + } + + return errors.New("invalid BLS12-381 signature") +} diff --git a/bbs12381g2pub/signature_message.go b/bbs12381g2pub/signature_message.go new file mode 100644 index 0000000..33e39ba --- /dev/null +++ b/bbs12381g2pub/signature_message.go @@ -0,0 +1,25 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package bbs12381g2pub + +import ( + ml "github.com/IBM/mathlib" +) + +// SignatureMessage defines a message to be used for a signature check. +type SignatureMessage struct { + FR *ml.Zr +} + +// ParseSignatureMessage parses SignatureMessage from bytes. +func ParseSignatureMessage(message []byte) *SignatureMessage { + elm := frFromOKM(message) + + return &SignatureMessage{ + FR: elm, + } +} diff --git a/bbs12381g2pub/signature_proof.go b/bbs12381g2pub/signature_proof.go new file mode 100644 index 0000000..4f1fc3a --- /dev/null +++ b/bbs12381g2pub/signature_proof.go @@ -0,0 +1,276 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package bbs12381g2pub + +import ( + "encoding/binary" + "errors" + "fmt" + + ml "github.com/IBM/mathlib" +) + +// PoKOfSignatureProof defines BLS signature proof. +// It is the actual proof that is sent from prover to verifier. +type PoKOfSignatureProof struct { + aPrime *ml.G1 + aBar *ml.G1 + d *ml.G1 + + proofVC1 *ProofG1 + proofVC2 *ProofG1 +} + +// GetBytesForChallenge creates bytes for proof challenge. +func (sp *PoKOfSignatureProof) GetBytesForChallenge(revealedMessages map[int]*SignatureMessage, + pubKey *PublicKeyWithGenerators) []byte { + hiddenCount := pubKey.messagesCount - len(revealedMessages) + + bytesLen := (7 + hiddenCount) * g1UncompressedSize //nolint:gomnd + bytes := make([]byte, 0, bytesLen) + + bytes = append(bytes, sp.aBar.Bytes()...) + bytes = append(bytes, sp.aPrime.Bytes()...) + bytes = append(bytes, pubKey.h0.Bytes()...) + bytes = append(bytes, sp.proofVC1.commitment.Bytes()...) + bytes = append(bytes, sp.d.Bytes()...) + bytes = append(bytes, pubKey.h0.Bytes()...) + + for i := range pubKey.h { + if _, ok := revealedMessages[i]; !ok { + bytes = append(bytes, pubKey.h[i].Bytes()...) + } + } + + bytes = append(bytes, sp.proofVC2.commitment.Bytes()...) + + return bytes +} + +// Verify verifies PoKOfSignatureProof. +func (sp *PoKOfSignatureProof) Verify(challenge *ml.Zr, pubKey *PublicKeyWithGenerators, + revealedMessages map[int]*SignatureMessage, messages []*SignatureMessage) error { + aBar := sp.aBar.Copy() + aBar.Neg() + + ok := compareTwoPairings(sp.aPrime, pubKey.w, aBar, curve.GenG2) + if !ok { + return errors.New("bad signature") + } + + err := sp.verifyVC1Proof(challenge, pubKey) + if err != nil { + return err + } + + return sp.verifyVC2Proof(challenge, pubKey, revealedMessages, messages) +} + +func (sp *PoKOfSignatureProof) verifyVC1Proof(challenge *ml.Zr, pubKey *PublicKeyWithGenerators) error { + basesVC1 := []*ml.G1{sp.aPrime, pubKey.h0} + aBarD := sp.aBar.Copy() + aBarD.Sub(sp.d) + + err := sp.proofVC1.Verify(basesVC1, aBarD, challenge) + if err != nil { + return errors.New("bad signature") + } + + return nil +} + +func (sp *PoKOfSignatureProof) verifyVC2Proof(challenge *ml.Zr, pubKey *PublicKeyWithGenerators, + revealedMessages map[int]*SignatureMessage, messages []*SignatureMessage) error { + revealedMessagesCount := len(revealedMessages) + + basesVC2 := make([]*ml.G1, 0, 2+pubKey.messagesCount-revealedMessagesCount) + basesVC2 = append(basesVC2, sp.d, pubKey.h0) + + basesDisclosed := make([]*ml.G1, 0, 1+revealedMessagesCount) + exponents := make([]*ml.Zr, 0, 1+revealedMessagesCount) + + basesDisclosed = append(basesDisclosed, curve.GenG1) + exponents = append(exponents, curve.NewZrFromInt(1)) + + revealedMessagesInd := 0 + + for i := range pubKey.h { + if _, ok := revealedMessages[i]; ok { + basesDisclosed = append(basesDisclosed, pubKey.h[i]) + exponents = append(exponents, messages[revealedMessagesInd].FR) + revealedMessagesInd++ + } else { + basesVC2 = append(basesVC2, pubKey.h[i]) + } + } + + // TODO: expose 0 + pr := curve.GenG1.Copy() + pr.Sub(curve.GenG1) + + for i := 0; i < len(basesDisclosed); i++ { + b := basesDisclosed[i] + s := exponents[i] + + g := b.Mul(frToRepr(s)) + pr.Add(g) + } + + pr.Neg() + + err := sp.proofVC2.Verify(basesVC2, pr, challenge) + if err != nil { + return errors.New("bad signature") + } + + return nil +} + +// ToBytes converts PoKOfSignatureProof to bytes. +func (sp *PoKOfSignatureProof) ToBytes() []byte { + bytes := make([]byte, 0) + + bytes = append(bytes, sp.aPrime.Compressed()...) + bytes = append(bytes, sp.aBar.Compressed()...) + bytes = append(bytes, sp.d.Compressed()...) + + proof1Bytes := sp.proofVC1.ToBytes() + lenBytes := make([]byte, 4) + binary.BigEndian.PutUint32(lenBytes, uint32(len(proof1Bytes))) + bytes = append(bytes, lenBytes...) + bytes = append(bytes, proof1Bytes...) + + bytes = append(bytes, sp.proofVC2.ToBytes()...) + + return bytes +} + +// ProofG1 is a proof of knowledge of a signature and hidden messages. +type ProofG1 struct { + commitment *ml.G1 + responses []*ml.Zr +} + +// NewProofG1 creates a new ProofG1. +func NewProofG1(commitment *ml.G1, responses []*ml.Zr) *ProofG1 { + return &ProofG1{ + commitment: commitment, + responses: responses, + } +} + +// Verify verifies the ProofG1. +func (pg1 *ProofG1) Verify(bases []*ml.G1, commitment *ml.G1, challenge *ml.Zr) error { + contribution := pg1.getChallengeContribution(bases, commitment, challenge) + contribution.Sub(pg1.commitment) + + if !contribution.IsInfinity() { + return errors.New("contribution is not zero") + } + + return nil +} + +func (pg1 *ProofG1) getChallengeContribution(bases []*ml.G1, commitment *ml.G1, + challenge *ml.Zr) *ml.G1 { + points := append(bases, commitment) + scalars := append(pg1.responses, challenge) + + return sumOfG1Products(points, scalars) +} + +// ToBytes converts ProofG1 to bytes. +func (pg1 *ProofG1) ToBytes() []byte { + bytes := make([]byte, 0) + + commitmentBytes := pg1.commitment.Compressed() + bytes = append(bytes, commitmentBytes...) + + lenBytes := make([]byte, 4) + binary.BigEndian.PutUint32(lenBytes, uint32(len(pg1.responses))) + bytes = append(bytes, lenBytes...) + + for i := range pg1.responses { + responseBytes := frToRepr(pg1.responses[i]).Bytes() + bytes = append(bytes, responseBytes...) + } + + return bytes +} + +// ParseSignatureProof parses a signature proof. +func ParseSignatureProof(sigProofBytes []byte) (*PoKOfSignatureProof, error) { + if len(sigProofBytes) < g1CompressedSize*3 { + return nil, errors.New("invalid size of signature proof") + } + + g1Points := make([]*ml.G1, 3) + offset := 0 + + for i := range g1Points { + g1Point, err := curve.NewG1FromCompressed(sigProofBytes[offset : offset+g1CompressedSize]) + if err != nil { + return nil, fmt.Errorf("parse G1 point: %w", err) + } + + g1Points[i] = g1Point + offset += g1CompressedSize + } + + proof1BytesLen := int(uint32FromBytes(sigProofBytes[offset : offset+4])) + offset += 4 + + proofVc1, err := ParseProofG1(sigProofBytes[offset : offset+proof1BytesLen]) + if err != nil { + return nil, fmt.Errorf("parse G1 proof: %w", err) + } + + offset += proof1BytesLen + + proofVc2, err := ParseProofG1(sigProofBytes[offset:]) + if err != nil { + return nil, fmt.Errorf("parse G1 proof: %w", err) + } + + return &PoKOfSignatureProof{ + aPrime: g1Points[0], + aBar: g1Points[1], + d: g1Points[2], + proofVC1: proofVc1, + proofVC2: proofVc2, + }, nil +} + +// ParseProofG1 parses ProofG1 from bytes. +func ParseProofG1(bytes []byte) (*ProofG1, error) { + if len(bytes) < g1CompressedSize+4 { + return nil, errors.New("invalid size of G1 signature proof") + } + + offset := 0 + + commitment, err := curve.NewG1FromCompressed(bytes[:g1CompressedSize]) + if err != nil { + return nil, fmt.Errorf("parse G1 point: %w", err) + } + + offset += g1CompressedSize + length := int(uint32FromBytes(bytes[offset : offset+4])) + offset += 4 + + if len(bytes) < g1CompressedSize+4+length*frCompressedSize { + return nil, errors.New("invalid size of G1 signature proof") + } + + responses := make([]*ml.Zr, length) + for i := 0; i < length; i++ { + responses[i] = parseFr(bytes[offset : offset+frCompressedSize]) + offset += frCompressedSize + } + + return NewProofG1(commitment, responses), nil +} diff --git a/bbs12381g2pub/signature_test.go b/bbs12381g2pub/signature_test.go new file mode 100644 index 0000000..3fd0ec9 --- /dev/null +++ b/bbs12381g2pub/signature_test.go @@ -0,0 +1,33 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package bbs12381g2pub_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + bbs "github.com/trustbloc/bbs-signature-go/bbs12381g2pub" +) + +func TestParseSignature(t *testing.T) { + sigBytes := []byte{179, 22, 156, 110, 6, 135, 216, 0, 253, 221, 34, 23, 84, 99, 206, 177, 70, 39, 227, 170, 31, 198, 153, 146, 254, 80, 87, 165, 43, 147, 216, 60, 240, 196, 31, 200, 191, 85, 46, 230, 229, 198, 52, 94, 39, 178, 132, 7, 20, 151, 53, 123, 253, 84, 174, 230, 112, 210, 136, 122, 249, 50, 146, 214, 210, 252, 142, 158, 39, 0, 128, 216, 193, 210, 12, 195, 20, 250, 40, 251, 3, 48, 32, 63, 3, 72, 128, 226, 173, 209, 93, 73, 253, 95, 122, 81, 60, 8, 9, 70, 136, 171, 193, 249, 190, 245, 171, 187, 253, 25, 107, 201} //nolint:lll + + signature, err := bbs.ParseSignature(sigBytes) + require.NoError(t, err) + + sigBytes2, err := signature.ToBytes() + require.NoError(t, err) + require.Equal(t, sigBytes, sigBytes2) + + // invalid G1 signature part + invalidSigBytes := make([]byte, len(sigBytes)) + signature, err = bbs.ParseSignature(invalidSigBytes) + require.Error(t, err) + require.Contains(t, err.Error(), "deserialize G1 compressed signature") + require.Nil(t, signature) +} diff --git a/bbs12381g2pub/utils.go b/bbs12381g2pub/utils.go new file mode 100644 index 0000000..2b9d812 --- /dev/null +++ b/bbs12381g2pub/utils.go @@ -0,0 +1,125 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package bbs12381g2pub + +import ( + "encoding/binary" + "errors" +) + +func uint32ToBytes(value uint32) []byte { + bytes := make([]byte, 4) + + binary.BigEndian.PutUint32(bytes, value) + + return bytes +} + +func uint16FromBytes(bytes []byte) uint16 { + return binary.BigEndian.Uint16(bytes) +} + +func uint32FromBytes(bytes []byte) uint32 { + return binary.BigEndian.Uint32(bytes) +} + +func bitvectorToIndexes(data []byte) []int { + revealedIndexes := make([]int, 0) + scalar := 0 + + for _, v := range data { + remaining := 8 + + for v > 0 { + revealed := v & 1 + if revealed == 1 { + revealedIndexes = append(revealedIndexes, scalar) + } + + v >>= 1 + scalar++ + remaining-- + } + + scalar += remaining + } + + return revealedIndexes +} + +type pokPayload struct { + messagesCount int + revealed []int +} + +// nolint:gomnd +func parsePoKPayload(bytes []byte) (*pokPayload, error) { + if len(bytes) < 2 { + return nil, errors.New("invalid size of PoK payload") + } + + messagesCount := int(uint16FromBytes(bytes[0:2])) + offset := lenInBytes(messagesCount) + + if len(bytes) < offset { + return nil, errors.New("invalid size of PoK payload") + } + + revealed := bitvectorToIndexes(reverseBytes(bytes[2:offset])) + + return &pokPayload{ + messagesCount: messagesCount, + revealed: revealed, + }, nil +} + +// nolint:gomnd +func (p *pokPayload) toBytes() ([]byte, error) { + bytes := make([]byte, p.lenInBytes()) + + binary.BigEndian.PutUint16(bytes, uint16(p.messagesCount)) + + bitvector := bytes[2:] + + for _, r := range p.revealed { + idx := r / 8 + bit := r % 8 + + if len(bitvector) <= idx { + return nil, errors.New("invalid size of PoK payload") + } + + bitvector[idx] |= 1 << bit + } + + reverseBytes(bitvector) + + return bytes, nil +} + +func (p *pokPayload) lenInBytes() int { + return lenInBytes(p.messagesCount) +} + +func lenInBytes(messagesCount int) int { + return 2 + (messagesCount / 8) + 1 //nolint:gomnd +} + +func newPoKPayload(messagesCount int, revealed []int) *pokPayload { + return &pokPayload{ + messagesCount: messagesCount, + revealed: revealed, + } +} + +func reverseBytes(s []byte) []byte { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } + + return s +} diff --git a/bbs12381g2pub/utils_test.go b/bbs12381g2pub/utils_test.go new file mode 100644 index 0000000..4fac61a --- /dev/null +++ b/bbs12381g2pub/utils_test.go @@ -0,0 +1,43 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package bbs12381g2pub + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_pokPayload(t *testing.T) { + payload := newPoKPayload(4, []int{0, 2}) + require.Equal(t, 3, payload.lenInBytes()) + + bytes, err := payload.toBytes() + require.NoError(t, err) + require.Len(t, bytes, 3) + + payloadParsed, err := parsePoKPayload(bytes) + require.NoError(t, err) + require.Equal(t, payload, payloadParsed) + + payloadParsed, err = parsePoKPayload([]byte{}) + require.Error(t, err) + require.Nil(t, payloadParsed) +} + +func Test_pokPayloadFail(t *testing.T) { + payload := newPoKPayload(1, []int{0, 2, 4, 5, 9}) + require.Equal(t, 3, payload.lenInBytes()) + + _, err := payload.toBytes() + require.EqualError(t, err, "invalid size of PoK payload") + + bytes := []byte{9, 0} + payloadParsed, err := parsePoKPayload(bytes) + require.EqualError(t, err, "invalid size of PoK payload") + require.Nil(t, payloadParsed) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c133d97 --- /dev/null +++ b/go.mod @@ -0,0 +1,31 @@ +// Copyright Gen Digital Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +module github.com/trustbloc/bbs-signature-go + +go 1.21 + +require ( + github.com/IBM/mathlib v0.0.3-0.20230605104224-932ab92f2ce0 + github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce + github.com/stretchr/testify v1.8.1 + golang.org/x/crypto v0.1.0 +) + +require ( + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.9.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 // indirect + github.com/kilic/bls12-381 v0.1.0 // indirect + github.com/kr/pretty v0.2.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.2.0 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + rsc.io/tmplfunc v0.0.3 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..6ef9103 --- /dev/null +++ b/go.sum @@ -0,0 +1,84 @@ +github.com/IBM/mathlib v0.0.3-0.20230605104224-932ab92f2ce0 h1:V3ElfC3Xs8bxJyc7VPcBQ9th6vyBBX8u/5bIUOXljk4= +github.com/IBM/mathlib v0.0.3-0.20230605104224-932ab92f2ce0/go.mod h1:k0NBSWMYVgaZ2keDuI8DSwdIEhUNhp8XnlVmm6Xwyuk= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.9.1 h1:mru55qKdWl3E035hAoh1jj9d7hVnYY5pfb6tmovSmII= +github.com/consensys/gnark-crypto v0.9.1/go.mod h1:a2DQL4+5ywF6safEeZFEPGRiiGbjzGFRUN2sg06VuU4= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 h1:B1Nt8hKb//KvgGRprk0h1t4lCnwhE9/ryb1WqfZbV+M= +github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2/go.mod h1:X+DIyUsaTmalOpmpQfIvFZjKHQedrURQ5t4YqquX7lE= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4= +github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/scripts/check_license.sh b/scripts/check_license.sh new file mode 100755 index 0000000..79d96ba --- /dev/null +++ b/scripts/check_license.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# +# Copyright IBM Corp, SecureKey Technologies Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +echo "Running $0" + +function filterExcludedFiles { + CHECK=`echo "$CHECK" | grep -v .png$ | grep -v .rst$ | grep -v ^.git/ \ + | grep -v .pem$ | grep -v .block$ | grep -v .tx$ | grep -v ^LICENSE$ | grep -v _sk$ \ + | grep -v .key$ | grep -v .crt$ | grep -v \\.gen.go$ | grep -v \\.json$ | grep -v Gopkg.lock$ \ + | grep -v .md$ | grep -v ^vendor/ | grep -v ^build/ | grep -v .pb.go$ | grep -v ci.properties$ \ + | grep -v go.sum$ | grep -v gomocks | grep -v \\.jsonld$ | grep -v testdata/ | grep -v third_party/ | sort -u` +} + +CHECK=$(git diff --name-only --diff-filter=ACMRTUXB HEAD) +REMOTE_REF=$(git log -1 --pretty=format:"%d" | grep '[(].*\/' | wc -l) + +# If CHECK is empty then there is no working directory changes: fallback to last two commits. +# Else if REMOTE_REF=0 then working copy commits are even with remote: only use the working copy changes. +# Otherwise assume that the change is amending the previous commit: use both last two commit and working copy changes. +if [[ -z "${CHECK}" ]] || [[ "${REMOTE_REF}" -eq 0 ]]; then + if [[ ! -z "${CHECK}" ]]; then + echo "Examining last commit and working directory changes" + CHECK+=$'\n' + else + echo "Examining last commit changes" + fi + + LAST_COMMITS=($(git log -2 --pretty=format:"%h")) + CHECK+=$(git diff-tree --no-commit-id --name-only --diff-filter=ACMRTUXB -r ${LAST_COMMITS[1]} ${LAST_COMMITS[0]}) +else + echo "Examining working directory changes" +fi + +filterExcludedFiles + +if [[ -z "$CHECK" ]]; then + echo "All files are excluded from having license headers" + exit 0 +fi + +missing=`echo "$CHECK" | xargs ls -d 2>/dev/null | xargs grep -L "SPDX-License-Identifier"` +if [[ -z "$missing" ]]; then + echo "All files have SPDX-License-Identifier headers" + exit 0 +fi +echo "The following files are missing SPDX-License-Identifier headers:" +echo "$missing" +echo +echo "Please replace the Apache license header comment text with:" +echo "SPDX-License-Identifier: Apache-2.0" + +echo +echo "Checking committed files for traditional Apache License headers ..." +missing=`echo "$missing" | xargs ls -d 2>/dev/null | xargs grep -L "http://www.apache.org/licenses/LICENSE-2.0"` +if [[ -z "$missing" ]]; then + echo "All remaining files have Apache 2.0 headers" + exit 0 +fi +echo "The following files are missing traditional Apache 2.0 headers:" +echo "$missing" +echo "Fatal Error - All files must have a license header" +exit 1 \ No newline at end of file diff --git a/scripts/check_lint.sh b/scripts/check_lint.sh new file mode 100755 index 0000000..dea9233 --- /dev/null +++ b/scripts/check_lint.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# +# Copyright Gen Digital Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +set -e + +echo "Running $0" + +DOCKER_CMD=${DOCKER_CMD:-docker} +GOLANGCI_LINT_IMAGE="golangci/golangci-lint:v1.53.3" +SHARED_OPTS="--rm --security-opt seccomp=unconfined -e GOPROXY=${GOPROXY} -v $(pwd):/opt/workspace" + +if [ ! $(command -v ${DOCKER_CMD}) ]; then + exit 0 +fi + +echo "linting root directory.." +${DOCKER_CMD} run ${SHARED_OPTS} -w /opt/workspace ${GOLANGCI_LINT_IMAGE} golangci-lint run +echo "done linting root directory" + +echo "Done Running $0" diff --git a/scripts/check_unit.sh b/scripts/check_unit.sh new file mode 100755 index 0000000..b560e06 --- /dev/null +++ b/scripts/check_unit.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# +# Copyright Gen Digital Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +set -e + +echo "Running $0" + +pwd=`pwd` +touch "$pwd"/coverage.out + +amend_coverage_file () { +if [ -f profile.out ]; then + cat profile.out | grep -v ".gen.go" >> "$pwd"/coverage.out + rm profile.out +fi +} + +# Running wallet-sdk unit tests +PKGS=`go list github.com/trustbloc/bbs-signature-go/... 2> /dev/null | \ + grep -v /mocks` +go test $PKGS -count=1 -race -coverprofile=profile.out -covermode=atomic -timeout=10m +amend_coverage_file + + +cd "$pwd" \ No newline at end of file