Skip to content

Commit

Permalink
feat: BBS+ selective disclosure verifier
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitriy Kinoshenko <[email protected]>

hyperledger-archives#2295
  • Loading branch information
kdimak committed Nov 30, 2020
1 parent 5fac400 commit 3f7251a
Show file tree
Hide file tree
Showing 6 changed files with 440 additions and 31 deletions.
7 changes: 6 additions & 1 deletion pkg/doc/bbs/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ package bbs
// BBS defines BBS+ signature scheme (https://eprint.iacr.org/2016/663.pdf, section 4.3).
type BBS interface {

// Verify will verify an aggregated signature of one or more messages against a public key
// Verify will verify an aggregated signature of one or more messages against a public key.
// returns:
// error in case of errors or nil if signature verification was successful
Verify(messages [][]byte, signature, pubKey []byte) error
Expand All @@ -20,4 +20,9 @@ type BBS interface {
// signature in []byte
// error in case of errors
Sign(messages [][]byte, privKey []byte) ([]byte, error)

// VerifyProof will verify a BBS+ proof (generated e.g. by Sign()) with a BLS12-381 public key.
// returns:
// error in case of errors or nil if signature proof verification was successful
VerifyProof(messages [][]byte, proof, nonce, pubKey []byte) error
}
134 changes: 104 additions & 30 deletions pkg/doc/bbs/bbs12381g2pub/bbs.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ package bbs12381g2pub

import (
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"hash"
Expand Down Expand Up @@ -41,6 +40,9 @@ const (
// Number of bytes in G1 X coordinate.
g1CompressedSize = 48

// Number of bytes in G1 X and Y coordinates
g1UncompressedSize = 96

// Number of bytes in G2 X(a, b) and Y(a, b) coordinates.
g2UncompressedSize = 192

Expand All @@ -63,12 +65,9 @@ func (bbs *BBSG2Pub) Verify(messages [][]byte, sigBytes, pubKeyBytes []byte) err
return fmt.Errorf("parse public key: %w", err)
}

messagesFr := make([]*SignatureMessage, len(messages))
for i := range messages {
messagesFr[i], err = ParseSignatureMessage(messages[i])
if err != nil {
return fmt.Errorf("parse signature message %d: %w", i+1, err)
}
messagesFr, err := messagesToFr(messages)
if err != nil {
return fmt.Errorf("parse signature messages: %w", err)
}

p1 := signature.A
Expand All @@ -82,13 +81,28 @@ func (bbs *BBSG2Pub) Verify(messages [][]byte, sigBytes, pubKeyBytes []byte) err
return fmt.Errorf("get B point: %w", err)
}

if compareTwoPairingsKilic(p1, q1, p2, bbs.g2.One()) {
if compareTwoPairings(p1, q1, p2, bbs.g2.One()) {
return nil
}

return errors.New("BLS12-381: invalid signature")
}

func messagesToFr(messages [][]byte) ([]*SignatureMessage, error) {
var err error

messagesFr := make([]*SignatureMessage, len(messages))

for i := range messages {
messagesFr[i], err = ParseSignatureMessage(messages[i])
if err != nil {
return nil, fmt.Errorf("parse signature message %d: %w", i+1, err)
}
}

return messagesFr, nil
}

// 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)
Expand All @@ -103,6 +117,64 @@ func (bbs *BBSG2Pub) Sign(messages [][]byte, privKeyBytes []byte) ([]byte, error
return bbs.SignWithKey(messages, privKey)
}

func (bbs *BBSG2Pub) VerifyProof(messages [][]byte, proof, nonce, pubKeyBytes []byte) error {
messagesCount := int(uint16FromBytes(proof[0:2]))

fmt.Printf("messages count: %d\n", messagesCount)

bitvectorLen := (messagesCount / 8) + 1
offset := 2 + bitvectorLen

fmt.Printf("bitvectorLen = %d, offset = %d\n",
bitvectorLen, offset)

revealed := bitvectorToIndexes(proof[2:offset])
fmt.Printf("revealed: %v\n", revealed)

signatureProof, err := ParseSignatureProof(proof[offset:])
if err != nil {
return fmt.Errorf("parse compressed signature proof: %w", err)
}
fmt.Printf("signatureProof = %v\n", signatureProof)

messagesFr, err := messagesToFr(messages)
if err != nil {
return fmt.Errorf("parse signature messages: %w", err)
}
fmt.Printf("messagesFr = %v\n", messagesFr)

publicKey, err := UnmarshalPublicKey(pubKeyBytes)
if err != nil {
return fmt.Errorf("parse public key: %w", err)
}
fmt.Printf("publicKey = %v\n", publicKey)

proofNonce, err := ParseProofNonce(nonce)
if err != nil {
return fmt.Errorf("parse nonce: %w", err)
}
fmt.Printf("proofNonce = %v\n", proofNonce)

revealedMessages := make(map[int]*SignatureMessage)
for i := range revealed {
revealedMessages[revealed[i]] = messagesFr[i]
}

h0, h, err := bbs.calcH(publicKey, messagesCount)
if err != nil {
return err
}

challengeBytes := signatureProof.GetBytesForChallenge(revealed, h0, h)
proofNonceBytes := frToRepr(proofNonce.fr).ToBytes()
challengeBytes = append(challengeBytes, proofNonceBytes...)

proofChallenge := frFromOKM(challengeBytes)

return signatureProof.verify(proofChallenge, publicKey, h0, h, revealedMessages, messagesFr)
//return errors.New("not implemented")
}

func createRandSignatureFr() (*bls12381.Fr, error) {
fr, err := bls12381.NewFr().Rand(rand.Reader)
if err != nil {
Expand Down Expand Up @@ -157,24 +229,15 @@ func (bbs *BBSG2Pub) SignWithKey(messages [][]byte, privKey *PrivateKey) ([]byte
return signature.ToBytes()
}

func (bbs *BBSG2Pub) computeB(s *bls12381.Fr, messages []*SignatureMessage, key *PublicKey) (*bls12381.PointG1, error) {
const basesOffset = 2

messagesCount := len(messages)

bases := make([]*bls12381.PointG1, messagesCount+basesOffset)
scalars := make([]*bls12381.Fr, messagesCount+basesOffset)

bases[0] = bbs.g1.One()
scalars[0] = bls12381.NewFr().RedOne()

// todo introduce a separate class?
func (bbs *BBSG2Pub) calcH(key *PublicKey, messagesCount int) (*bls12381.PointG1, []*bls12381.PointG1, error) {
offset := g2UncompressedSize + 1

data := bbs.calcData(key, messagesCount)

h0, err := bbs.hashToG1(data)
if err != nil {
return nil, fmt.Errorf("create G1 point from hash")
return nil, nil, fmt.Errorf("create G1 point from hash")
}

h := make([]*bls12381.PointG1, messagesCount)
Expand All @@ -191,10 +254,29 @@ func (bbs *BBSG2Pub) computeB(s *bls12381.Fr, messages []*SignatureMessage, key

h[i-1], err = bbs.hashToG1(dataCopy)
if err != nil {
return nil, fmt.Errorf("create G1 point from hash: %w", err)
return nil, nil, fmt.Errorf("create G1 point from hash: %w", err)
}
}

return h0, h, nil
}

func (bbs *BBSG2Pub) computeB(s *bls12381.Fr, messages []*SignatureMessage, key *PublicKey) (*bls12381.PointG1, error) {
const basesOffset = 2

messagesCount := len(messages)

bases := make([]*bls12381.PointG1, messagesCount+basesOffset)
scalars := make([]*bls12381.Fr, messagesCount+basesOffset)

bases[0] = bbs.g1.One()
scalars[0] = bls12381.NewFr().RedOne()

h0, h, err := bbs.calcH(key, messagesCount)
if err != nil {
return nil, err
}

bases[1] = h0
scalars[1] = s

Expand Down Expand Up @@ -241,14 +323,6 @@ func (bbs *BBSG2Pub) calcData(key *PublicKey, messagesCount int) []byte {
return data
}

func uint32ToBytes(value uint32) []byte {
bytes := make([]byte, 4)

binary.BigEndian.PutUint32(bytes, value)

return bytes
}

func (bbs *BBSG2Pub) hashToG1(data []byte) (*bls12381.PointG1, error) {
dstG1 := []byte("BLS12381G1_XMD:BLAKE2B_SSWU_RO_BBS+_SIGNATURES:1_0_0")

Expand All @@ -261,7 +335,7 @@ func (bbs *BBSG2Pub) hashToG1(data []byte) (*bls12381.PointG1, error) {
return bbs.g1.HashToCurve(newBlake2b, data, dstG1)
}

func compareTwoPairingsKilic(p1 *bls12381.PointG1, q1 *bls12381.PointG2,
func compareTwoPairings(p1 *bls12381.PointG1, q1 *bls12381.PointG2,
p2 *bls12381.PointG1, q2 *bls12381.PointG2) bool {
engine := bls12381.NewEngine()

Expand Down
22 changes: 22 additions & 0 deletions pkg/doc/bbs/bbs12381g2pub/bbs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,25 @@ func TestBBSG2Pub_Sign(t *testing.T) {
require.EqualError(t, err, "messages are not defined")
require.Nil(t, signatureBytes)
}

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", func(t *testing.T) {
err = bls.VerifyProof(revealedMessagesBytes, proofBytes, nonce, pkBytes)
require.NoError(t, err)
})
}
19 changes: 19 additions & 0 deletions pkg/doc/bbs/bbs12381g2pub/proof_nonce.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package bbs12381g2pub

import bls12381 "github.com/kilic/bls12-381"

type ProofNonce struct {
fr *bls12381.Fr
}

func ParseProofNonce(proofNonceBytes []byte) (*ProofNonce, error) {
return &ProofNonce{
frFromOKM(proofNonceBytes),
}, nil
}
Loading

0 comments on commit 3f7251a

Please sign in to comment.