Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

Add support for BLS keys and BBS+ signatures #288

Merged
merged 59 commits into from
Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
5d3e9cf
simple bbs key usage
Feb 15, 2023
47d5a63
some progress
Feb 17, 2023
ff80ef6
progress
Feb 17, 2023
ba641de
Merge branch 'main' into bbs-plus
decentralgabe Feb 17, 2023
425adbd
switch to any
Feb 17, 2023
f28b789
sign verify working
Feb 18, 2023
50e56c6
create derive proof
Feb 18, 2023
24c1d20
implemented but untested
Feb 18, 2023
835c3e3
some bugs
Feb 18, 2023
f783cc0
fix test issue
Feb 20, 2023
42cc8e2
not passing test vector yet
Feb 20, 2023
7eeb7b3
problem with bls verify
Feb 20, 2023
1402b90
fix test vector
Feb 21, 2023
e477161
problem with signing and derive verify
Feb 21, 2023
d6e597d
fix signing with suite
Feb 21, 2023
39c8c84
it works
Feb 21, 2023
4e34014
tests pass
Feb 21, 2023
384f5e4
not there yet
Feb 22, 2023
2d8630e
progress with nonces
Feb 22, 2023
c196a03
one bug fixed
Feb 22, 2023
340d947
same input different output
Feb 22, 2023
c72ce22
add compaction
Feb 22, 2023
23f5e49
not working fully
Feb 22, 2023
77be15e
bbs+ working
Feb 22, 2023
2319afb
Merge branch 'main' into bbs-plus
decentralgabe Feb 22, 2023
7e4c63f
fix lint
Feb 22, 2023
e2cb937
revert map any replacement
Feb 22, 2023
70bba0a
remove more any
Feb 22, 2023
1650236
more replacements
Feb 22, 2023
90b1106
more replacements
Feb 22, 2023
28d9e70
more more
Feb 22, 2023
c6d2e8d
fix lint
Feb 22, 2023
cdb2cba
more lints
Feb 22, 2023
a90fa5a
final ones
Feb 22, 2023
8b317af
Update crypto/bbs_test.go
decentralgabe Feb 23, 2023
38119e6
Update cryptosuite/bbsplussignaturesuite.go
decentralgabe Feb 23, 2023
2ca6a48
pr comments
Feb 23, 2023
54cda26
more comments
Feb 23, 2023
b6ae137
better err messages
Feb 23, 2023
eb8fdf3
Update cryptosuite/bbsplussignaturesuite.go
decentralgabe Feb 23, 2023
310b499
pr comments
Feb 23, 2023
e7883da
Merge remote-tracking branch 'origin/bbs-plus' into bbs-plus
Feb 23, 2023
8f98524
remove provable from cvh
Feb 23, 2023
805f1a2
pr comments
Feb 23, 2023
b84cfdb
move generic provable
Feb 23, 2023
425b0c6
pr comments
Feb 23, 2023
2b73629
Merge branch 'main' into bbs-plus
decentralgabe Feb 28, 2023
a981ef8
Update cryptosuite/bbsplussignaturesuite.go
decentralgabe Mar 21, 2023
3e816a5
pr comments
Mar 21, 2023
0d36a93
Update cryptosuite/bbsplussignatureproofsuite.go
decentralgabe Mar 21, 2023
7d968e2
Update cryptosuite/bbsplussignatureproofsuite.go
decentralgabe Mar 21, 2023
247339f
pr comments
Mar 21, 2023
b172d0a
Merge remote-tracking branch 'origin/main' into bbs-plus
Mar 21, 2023
625fe9c
merge
Mar 21, 2023
5562aa0
simpler proof handling
Mar 21, 2023
66c7a3f
update re:pr comments
Mar 22, 2023
f1fee95
Merge remote-tracking branch 'origin/main' into bbs-plus
Mar 22, 2023
4f1c00b
merge
Mar 22, 2023
fc73c86
pr comment
Mar 23, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/idea-submission.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Idea Submission
about: Suggest an idea for this project
title: "[Idea] <Idea Title Here>"
labels: enhancement
assignees: decentralgabe, nitro-neal
assignees: decentralgabe, nitro-neal, andresuribe87

---

Expand Down
193 changes: 193 additions & 0 deletions crypto/bbs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package crypto
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting stuff here pt1


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

bbsg2 "github.com/hyperledger/aries-framework-go/pkg/crypto/primitive/bbs12381g2pub"
)

// GenerateBBSKeyPair https://w3c-ccg.github.io/ldp-bbs2020
func GenerateBBSKeyPair() (*bbsg2.PublicKey, *bbsg2.PrivateKey, error) {
seed := make([]byte, 32)
if _, err := rand.Read(seed); err != nil {
return nil, nil, err
}
return bbsg2.GenerateKeyPair(sha256.New, seed)
}

type BBSPlusSigner struct {
kid string
*bbsg2.PrivateKey
*bbsg2.PublicKey
*BBSPlusVerifier
}

func NewBBSPlusSigner(kid string, privKey *bbsg2.PrivateKey) *BBSPlusSigner {
pubKey := privKey.PublicKey()
return &BBSPlusSigner{
kid: kid,
PrivateKey: privKey,
PublicKey: pubKey,
BBSPlusVerifier: &BBSPlusVerifier{
KID: kid,
PublicKey: pubKey,
},
}
}

func (s *BBSPlusSigner) GetKeyID() string {
return s.kid
}

func (s *BBSPlusSigner) Sign(message []byte) ([]byte, error) {
bls := bbsg2.New()
return bls.SignWithKey(prepareBBSMessage(message), s.PrivateKey)
}

func (s *BBSPlusSigner) SignMultiple(messages ...[]byte) ([]byte, error) {
bls := bbsg2.New()
return bls.SignWithKey(messages, s.PrivateKey)
}

func (s *BBSPlusSigner) GetVerifier() *BBSPlusVerifier {
return s.BBSPlusVerifier
}

type BBSPlusVerifier struct {
KID string
*bbsg2.PublicKey
}

func NewBBSPlusVerifier(kid string, pubKey *bbsg2.PublicKey) *BBSPlusVerifier {
return &BBSPlusVerifier{
KID: kid,
PublicKey: pubKey,
}
}

func (v *BBSPlusVerifier) GetKeyID() string {
return v.KID
}

func (v *BBSPlusVerifier) Verify(message, signature []byte) error {
bls := bbsg2.New()
pubKeyBytes, err := v.PublicKey.Marshal()
if err != nil {
return err
}
return bls.Verify(prepareBBSMessage(message), signature, pubKeyBytes)
}

// VerifyDerived verifies a derived proof, or a selective disclosure proof that has been derived from a
// BBSPlusSignature signed object.
func (v *BBSPlusVerifier) VerifyDerived(message, signature, nonce []byte) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some pointers to what Derived is would be useful.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will add comments

bls := bbsg2.New()
pubKeyBytes, err := v.PublicKey.Marshal()
if err != nil {
return err
}
return bls.VerifyProof(prepareBBSDerivedMessage(message), signature, nonce, pubKeyBytes)
}

func (v *BBSPlusVerifier) VerifyMultiple(signature []byte, messages ...[]byte) error {
bls := bbsg2.New()
pubKeyBytes, err := v.PublicKey.Marshal()
if err != nil {
return err
}
return bls.Verify(messages, signature, pubKeyBytes)
}

func (v *BBSPlusVerifier) DeriveProof(messages [][]byte, sigBytes, nonce []byte, revealedIndexes []int) ([]byte, error) {
bls := bbsg2.New()
pubKeyBytes, err := v.PublicKey.Marshal()
if err != nil {
return nil, err
}
return bls.DeriveProof(messages, sigBytes, nonce, pubKeyBytes, revealedIndexes)
}

// Utility methods to be used without a signer

func SignBBSMessage(privKey *bbsg2.PrivateKey, messages ...[]byte) ([]byte, error) {
signer := BBSPlusSigner{
PrivateKey: privKey,
}
return signer.SignMultiple(messages...)
}

func VerifyBBSMessage(pubKey *bbsg2.PublicKey, signature, message []byte) error {
verifier := BBSPlusVerifier{
PublicKey: pubKey,
}
return verifier.Verify(message, signature)
}

func VerifyDerivedBBSMessage(pubKey *bbsg2.PublicKey, signature, message, nonce []byte) error {
verifier := BBSPlusVerifier{
PublicKey: pubKey,
}
return verifier.VerifyDerived(message, signature, nonce)
}

// helpers

// prepareBBSMessage transforms a message into a message that can be used to verify a BBS+ signature
// as per https://w3c-ccg.github.io/ldp-bbs2020/#create-verify-data-algorithm
func prepareBBSMessage(msg []byte) [][]byte {
rows := strings.Split(string(msg), "\n")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lolwut, why?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks - weird... the link seems to be broken for me. Something must be up :/

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like the link was moved to this new repo 3 days ago https://w3c-ccg.github.io/vc-di-bbs/

msgs := make([][]byte, 0, len(rows))
for _, row := range rows {
if strings.TrimSpace(row) == "" {
continue
}
msgs = append(msgs, []byte(row))
}
return msgs
}

// prepareBBSDerivedMessage transforms a message from a derived proof into a message that can be used
// to verify the derived proof as per https://w3c-ccg.github.io/ldp-bbs2020/#create-verify-data-algorithm
func prepareBBSDerivedMessage(msg []byte) [][]byte {
rows := strings.Split(string(msg), "\n")
msgs := make([][]byte, 0, len(rows))
for _, row := range rows {
if strings.TrimSpace(row) == "" {
continue
}
transformedRow := transformFromBlankNode(row)
msgs = append(msgs, []byte(transformedRow))
}
return msgs
}

// necessary to patch this version of the go JSON-LD library; taken from:
// https://github.com/hyperledger/aries-framework-go/blob/02f80847168a99c8eb3baeaafcba8d0367bd9551/pkg/doc/signature/jsonld/processor.go#L453
func transformFromBlankNode(row string) string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what this function is doing.... is it replacing ajskdlfjas <urn:bnid:_:c14n89340284093> jaskdjflasdf with ajskdlfjas 89340284093 jaskdjflasdf?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before
ajskdlfjas <urn:bnid:_:c14n89340284093> jaskdjflasdf

after
ajskdlfjas <c14n89340284093> jaskdjflasdf

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a comment

// transform from "urn:bnid:_:c14n0" to "_:c14n0"
const (
emptyNodePlaceholder = "<urn:bnid:_:c14n"
emptyNodePrefixLen = 10
)

prefixIndex := strings.Index(row, emptyNodePlaceholder)
if prefixIndex < 0 {
return row
}

sepIndex := strings.Index(row[prefixIndex:], ">")
if sepIndex < 0 {
return row
}

sepIndex += prefixIndex

prefix := row[:prefixIndex]
blankNode := row[prefixIndex+emptyNodePrefixLen : sepIndex]
suffix := row[sepIndex+1:]

return fmt.Sprintf("%s%s%s", prefix, blankNode, suffix)
}
102 changes: 102 additions & 0 deletions crypto/bbs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package crypto

import (
"encoding/base64"
"testing"

bbs "github.com/hyperledger/aries-framework-go/pkg/crypto/primitive/bbs12381g2pub"
"github.com/mr-tron/base58"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestGenerateBBSKeyPair(t *testing.T) {
t.Run("generate key pair", func(tt *testing.T) {
pubKey, privKey, err := GenerateBBSKeyPair()
assert.NotEmpty(tt, pubKey)
assert.NotEmpty(tt, privKey)
assert.NoError(tt, err)
})

t.Run("sign and verify message", func(tt *testing.T) {
pubKey, privKey, err := GenerateBBSKeyPair()
assert.NoError(tt, err)

msg := []byte("hello world")
signature, err := SignBBSMessage(privKey, msg)
assert.NoError(tt, err)
assert.NotEmpty(tt, signature)

err = VerifyBBSMessage(pubKey, signature, msg)
assert.NoError(tt, err)
})

// This test aims to verify implementation compatibility with the aries-framework-go, taken from here:
// https://github.com/hyperledger/aries-framework-go/blob/02f80847168a99c8eb3baeaafcba8d0367bd9551/pkg/doc/signature/verifier/public_key_verifier_test.go#L452
t.Run("verify test vector", func(tt *testing.T) {
// pkBase58 from did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2
pubKeyBase58 := "nEP2DEdbRaQ2r5Azeatui9MG6cj7JUHa8GD7khub4egHJREEuvj4Y8YG8w51LnhPEXxVV1ka93HpSLkVzeQuuPE1mH9oCMrqoHXAKGBsuDT1yJvj9cKgxxLCXiRRirCycki"
pubKeyBytes, err := base58.Decode(pubKeyBase58)
assert.NoError(tt, err)

bbsPubKey, err := bbs.UnmarshalPublicKey(pubKeyBytes)
assert.NoError(tt, err)

signatureB64 := `qPrB+1BLsVSeOo1ci8dMF+iR6aa5Q6iwV/VzXo2dw94ctgnQGxaUgwb8Hd68IiYTVabQXR+ZPuwJA//GOv1OwXRHkHqXg9xPsl8HcaXaoWERanxYClgHCfy4j76Vudr14U5AhT3v8k8f0oZD+zBIUQ==`
signatureBytes, err := base64.StdEncoding.DecodeString(signatureB64)
require.NoError(tt, err)

// Case 16 (https://github.com/w3c-ccg/vc-http-api/pull/128)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to include more cases from the PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to hold off until I can find the current test vectors. I wanted to add a few to prove basic compatibility with the aries code; but the http-api repo removed the test vectors and I can't seem to find where they're at today.

msg := `
Comment on lines +49 to +50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure where this msg came from. The referenced PR only includes a VC. Is this the canonicalized representation of the VC? If so, what canonicalization algo was used?

Either way, can you add docs please?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used the aries sdk to generate a canonicalized output. Will add a doc.

_:c14n0 <http://purl.org/dc/terms/created> "2021-02-23T19:31:12Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
_:c14n0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/security#BbsBlsSignature2020> .
_:c14n0 <https://w3id.org/security#proofPurpose> <https://w3id.org/security#assertionMethod> .
_:c14n0 <https://w3id.org/security#verificationMethod> <did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2#zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2> .
<did:example:b34ca6cd37bbf23> <http://schema.org/birthDate> "1958-07-17"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
<did:example:b34ca6cd37bbf23> <http://schema.org/familyName> "SMITH" .
<did:example:b34ca6cd37bbf23> <http://schema.org/gender> "Male" .
<did:example:b34ca6cd37bbf23> <http://schema.org/givenName> "JOHN" .
<did:example:b34ca6cd37bbf23> <http://schema.org/image> <data:image/png;base64,iVBORw0KGgo...kJggg==> .
<did:example:b34ca6cd37bbf23> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
<did:example:b34ca6cd37bbf23> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/citizenship#PermanentResident> .
<did:example:b34ca6cd37bbf23> <https://w3id.org/citizenship#birthCountry> "Bahamas" .
<did:example:b34ca6cd37bbf23> <https://w3id.org/citizenship#commuterClassification> "C1" .
<did:example:b34ca6cd37bbf23> <https://w3id.org/citizenship#lprCategory> "C09" .
<did:example:b34ca6cd37bbf23> <https://w3id.org/citizenship#lprNumber> "999-999-999" .
<did:example:b34ca6cd37bbf23> <https://w3id.org/citizenship#residentSince> "2015-01-01"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
<https://issuer.oidp.uscis.gov/credentials/83627465> <http://schema.org/description> "Government of Example Permanent Resident Card." .
<https://issuer.oidp.uscis.gov/credentials/83627465> <http://schema.org/name> "Permanent Resident Card" .
<https://issuer.oidp.uscis.gov/credentials/83627465> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/citizenship#PermanentResidentCard> .
<https://issuer.oidp.uscis.gov/credentials/83627465> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://www.w3.org/2018/credentials#VerifiableCredential> .
<https://issuer.oidp.uscis.gov/credentials/83627465> <https://www.w3.org/2018/credentials#credentialSubject> <did:example:b34ca6cd37bbf23> .
<https://issuer.oidp.uscis.gov/credentials/83627465> <https://www.w3.org/2018/credentials#expirationDate> "2029-12-03T12:19:52Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
<https://issuer.oidp.uscis.gov/credentials/83627465> <https://www.w3.org/2018/credentials#issuanceDate> "2019-12-03T12:19:52Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
<https://issuer.oidp.uscis.gov/credentials/83627465> <https://www.w3.org/2018/credentials#issuer> <did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2> .
`
err = VerifyBBSMessage(bbsPubKey, signatureBytes, []byte(msg))
assert.NoError(tt, err)
})
}

func TestBBSSignatureEncoding(t *testing.T) {
pubKey, privKey, err := GenerateBBSKeyPair()
assert.NotNil(t, pubKey)
assert.NotNil(t, privKey)
assert.NoError(t, err)

signature, err := SignBBSMessage(privKey, []byte("hello world"))
assert.NoError(t, err)
assert.NotEmpty(t, signature)

encoded := base64.RawStdEncoding.EncodeToString(signature)
assert.NotEmpty(t, encoded)

decoded, err := base64.RawStdEncoding.DecodeString(encoded)
assert.NoError(t, err)
assert.NotEmpty(t, decoded)

assert.Equal(t, signature, decoded)

err = VerifyBBSMessage(pubKey, decoded, []byte("hello world"))
assert.NoError(t, err)
}
6 changes: 3 additions & 3 deletions crypto/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,12 @@ func jwtSignerVerifier(kid string, key any) (jwk.Key, *jwa.SignatureAlgorithm, e
if err != nil {
return nil, nil, errors.Wrap(err, "could not get verification alg from jwk")
}
// TODO(gabe) distinguish between issuer and kid
// TODO(gabe) distinguish between issuer and KID
if err = parsedKey.Set(jwt.IssuerKey, kid); err != nil {
return nil, nil, fmt.Errorf("could not set kid with provided value: %s", kid)
return nil, nil, fmt.Errorf("could not set KID with provided value: %s", kid)
}
if err = parsedKey.Set(jwk.KeyIDKey, kid); err != nil {
return nil, nil, fmt.Errorf("could not set kid with provided value: %s", kid)
return nil, nil, fmt.Errorf("could not set KID with provided value: %s", kid)
}
if err = parsedKey.Set(jwk.AlgorithmKey, alg); err != nil {
return nil, nil, fmt.Errorf("could not set alg with value: %s", alg)
Expand Down
Loading