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

Commit 3d48dfb

Browse files
Add support for BLS keys and BBS+ signatures (#288)
* simple bbs key usage * some progress * progress * switch to any * sign verify working * create derive proof * implemented but untested * some bugs * fix test issue * not passing test vector yet * problem with bls verify * fix test vector * problem with signing and derive verify * fix signing with suite * it works * tests pass * not there yet * progress with nonces * one bug fixed * same input different output * add compaction * not working fully * bbs+ working * fix lint * revert map any replacement * remove more any * more replacements * more replacements * more more * fix lint * more lints * final ones * Update crypto/bbs_test.go Co-authored-by: Andres Uribe <[email protected]> * Update cryptosuite/bbsplussignaturesuite.go Co-authored-by: Andres Uribe <[email protected]> * pr comments * more comments * better err messages * Update cryptosuite/bbsplussignaturesuite.go Co-authored-by: Andres Uribe <[email protected]> * pr comments * remove provable from cvh * pr comments * move generic provable * pr comments * Update cryptosuite/bbsplussignaturesuite.go Co-authored-by: Andres Uribe <[email protected]> * pr comments * Update cryptosuite/bbsplussignatureproofsuite.go Co-authored-by: Andres Uribe <[email protected]> * Update cryptosuite/bbsplussignatureproofsuite.go Co-authored-by: Andres Uribe <[email protected]> * pr comments * merge * simpler proof handling * update re:pr comments * merge * pr comment --------- Co-authored-by: Andres Uribe <[email protected]>
1 parent 1234e62 commit 3d48dfb

31 files changed

+1891
-132
lines changed

.github/ISSUE_TEMPLATE/idea-submission.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: Idea Submission
33
about: Suggest an idea for this project
44
title: "[Idea] <Idea Title Here>"
55
labels: enhancement
6-
assignees: decentralgabe, nitro-neal
6+
assignees: decentralgabe, nitro-neal, andresuribe87
77

88
---
99

crypto/bbs.go

+193
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package crypto
2+
3+
import (
4+
"crypto/rand"
5+
"crypto/sha256"
6+
"fmt"
7+
"strings"
8+
9+
bbsg2 "github.com/hyperledger/aries-framework-go/pkg/crypto/primitive/bbs12381g2pub"
10+
)
11+
12+
// GenerateBBSKeyPair https://w3c-ccg.github.io/ldp-bbs2020
13+
func GenerateBBSKeyPair() (*bbsg2.PublicKey, *bbsg2.PrivateKey, error) {
14+
seed := make([]byte, 32)
15+
if _, err := rand.Read(seed); err != nil {
16+
return nil, nil, err
17+
}
18+
return bbsg2.GenerateKeyPair(sha256.New, seed)
19+
}
20+
21+
type BBSPlusSigner struct {
22+
kid string
23+
*bbsg2.PrivateKey
24+
*bbsg2.PublicKey
25+
*BBSPlusVerifier
26+
}
27+
28+
func NewBBSPlusSigner(kid string, privKey *bbsg2.PrivateKey) *BBSPlusSigner {
29+
pubKey := privKey.PublicKey()
30+
return &BBSPlusSigner{
31+
kid: kid,
32+
PrivateKey: privKey,
33+
PublicKey: pubKey,
34+
BBSPlusVerifier: &BBSPlusVerifier{
35+
KID: kid,
36+
PublicKey: pubKey,
37+
},
38+
}
39+
}
40+
41+
func (s *BBSPlusSigner) GetKeyID() string {
42+
return s.kid
43+
}
44+
45+
func (s *BBSPlusSigner) Sign(message []byte) ([]byte, error) {
46+
bls := bbsg2.New()
47+
return bls.SignWithKey(prepareBBSMessage(message), s.PrivateKey)
48+
}
49+
50+
func (s *BBSPlusSigner) SignMultiple(messages ...[]byte) ([]byte, error) {
51+
bls := bbsg2.New()
52+
return bls.SignWithKey(messages, s.PrivateKey)
53+
}
54+
55+
func (s *BBSPlusSigner) GetVerifier() *BBSPlusVerifier {
56+
return s.BBSPlusVerifier
57+
}
58+
59+
type BBSPlusVerifier struct {
60+
KID string
61+
*bbsg2.PublicKey
62+
}
63+
64+
func NewBBSPlusVerifier(kid string, pubKey *bbsg2.PublicKey) *BBSPlusVerifier {
65+
return &BBSPlusVerifier{
66+
KID: kid,
67+
PublicKey: pubKey,
68+
}
69+
}
70+
71+
func (v *BBSPlusVerifier) GetKeyID() string {
72+
return v.KID
73+
}
74+
75+
func (v *BBSPlusVerifier) Verify(message, signature []byte) error {
76+
bls := bbsg2.New()
77+
pubKeyBytes, err := v.PublicKey.Marshal()
78+
if err != nil {
79+
return err
80+
}
81+
return bls.Verify(prepareBBSMessage(message), signature, pubKeyBytes)
82+
}
83+
84+
// VerifyDerived verifies a derived proof, or a selective disclosure proof that has been derived from a
85+
// BBSPlusSignature signed object.
86+
func (v *BBSPlusVerifier) VerifyDerived(message, signature, nonce []byte) error {
87+
bls := bbsg2.New()
88+
pubKeyBytes, err := v.PublicKey.Marshal()
89+
if err != nil {
90+
return err
91+
}
92+
return bls.VerifyProof(prepareBBSDerivedMessage(message), signature, nonce, pubKeyBytes)
93+
}
94+
95+
func (v *BBSPlusVerifier) VerifyMultiple(signature []byte, messages ...[]byte) error {
96+
bls := bbsg2.New()
97+
pubKeyBytes, err := v.PublicKey.Marshal()
98+
if err != nil {
99+
return err
100+
}
101+
return bls.Verify(messages, signature, pubKeyBytes)
102+
}
103+
104+
func (v *BBSPlusVerifier) DeriveProof(messages [][]byte, sigBytes, nonce []byte, revealedIndexes []int) ([]byte, error) {
105+
bls := bbsg2.New()
106+
pubKeyBytes, err := v.PublicKey.Marshal()
107+
if err != nil {
108+
return nil, err
109+
}
110+
return bls.DeriveProof(messages, sigBytes, nonce, pubKeyBytes, revealedIndexes)
111+
}
112+
113+
// Utility methods to be used without a signer
114+
115+
func SignBBSMessage(privKey *bbsg2.PrivateKey, messages ...[]byte) ([]byte, error) {
116+
signer := BBSPlusSigner{
117+
PrivateKey: privKey,
118+
}
119+
return signer.SignMultiple(messages...)
120+
}
121+
122+
func VerifyBBSMessage(pubKey *bbsg2.PublicKey, signature, message []byte) error {
123+
verifier := BBSPlusVerifier{
124+
PublicKey: pubKey,
125+
}
126+
return verifier.Verify(message, signature)
127+
}
128+
129+
func VerifyDerivedBBSMessage(pubKey *bbsg2.PublicKey, signature, message, nonce []byte) error {
130+
verifier := BBSPlusVerifier{
131+
PublicKey: pubKey,
132+
}
133+
return verifier.VerifyDerived(message, signature, nonce)
134+
}
135+
136+
// helpers
137+
138+
// prepareBBSMessage transforms a message into a message that can be used to verify a BBS+ signature
139+
// as per https://w3c-ccg.github.io/ldp-bbs2020/#create-verify-data-algorithm
140+
func prepareBBSMessage(msg []byte) [][]byte {
141+
rows := strings.Split(string(msg), "\n")
142+
msgs := make([][]byte, 0, len(rows))
143+
for _, row := range rows {
144+
if strings.TrimSpace(row) == "" {
145+
continue
146+
}
147+
msgs = append(msgs, []byte(row))
148+
}
149+
return msgs
150+
}
151+
152+
// prepareBBSDerivedMessage transforms a message from a derived proof into a message that can be used
153+
// to verify the derived proof as per https://w3c-ccg.github.io/ldp-bbs2020/#create-verify-data-algorithm
154+
func prepareBBSDerivedMessage(msg []byte) [][]byte {
155+
rows := strings.Split(string(msg), "\n")
156+
msgs := make([][]byte, 0, len(rows))
157+
for _, row := range rows {
158+
if strings.TrimSpace(row) == "" {
159+
continue
160+
}
161+
transformedRow := transformFromBlankNode(row)
162+
msgs = append(msgs, []byte(transformedRow))
163+
}
164+
return msgs
165+
}
166+
167+
// necessary to patch this version of the go JSON-LD library; taken from:
168+
// https://github.com/hyperledger/aries-framework-go/blob/02f80847168a99c8eb3baeaafcba8d0367bd9551/pkg/doc/signature/jsonld/processor.go#L453
169+
func transformFromBlankNode(row string) string {
170+
// transform from "urn:bnid:_:c14n0" to "_:c14n0"
171+
const (
172+
emptyNodePlaceholder = "<urn:bnid:_:c14n"
173+
emptyNodePrefixLen = 10
174+
)
175+
176+
prefixIndex := strings.Index(row, emptyNodePlaceholder)
177+
if prefixIndex < 0 {
178+
return row
179+
}
180+
181+
sepIndex := strings.Index(row[prefixIndex:], ">")
182+
if sepIndex < 0 {
183+
return row
184+
}
185+
186+
sepIndex += prefixIndex
187+
188+
prefix := row[:prefixIndex]
189+
blankNode := row[prefixIndex+emptyNodePrefixLen : sepIndex]
190+
suffix := row[sepIndex+1:]
191+
192+
return fmt.Sprintf("%s%s%s", prefix, blankNode, suffix)
193+
}

crypto/bbs_test.go

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package crypto
2+
3+
import (
4+
"encoding/base64"
5+
"testing"
6+
7+
bbs "github.com/hyperledger/aries-framework-go/pkg/crypto/primitive/bbs12381g2pub"
8+
"github.com/mr-tron/base58"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestGenerateBBSKeyPair(t *testing.T) {
14+
t.Run("generate key pair", func(tt *testing.T) {
15+
pubKey, privKey, err := GenerateBBSKeyPair()
16+
assert.NotEmpty(tt, pubKey)
17+
assert.NotEmpty(tt, privKey)
18+
assert.NoError(tt, err)
19+
})
20+
21+
t.Run("sign and verify message", func(tt *testing.T) {
22+
pubKey, privKey, err := GenerateBBSKeyPair()
23+
assert.NoError(tt, err)
24+
25+
msg := []byte("hello world")
26+
signature, err := SignBBSMessage(privKey, msg)
27+
assert.NoError(tt, err)
28+
assert.NotEmpty(tt, signature)
29+
30+
err = VerifyBBSMessage(pubKey, signature, msg)
31+
assert.NoError(tt, err)
32+
})
33+
34+
// This test aims to verify implementation compatibility with the aries-framework-go, taken from here:
35+
// https://github.com/hyperledger/aries-framework-go/blob/02f80847168a99c8eb3baeaafcba8d0367bd9551/pkg/doc/signature/verifier/public_key_verifier_test.go#L452
36+
t.Run("verify test vector", func(tt *testing.T) {
37+
// pkBase58 from did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2
38+
pubKeyBase58 := "nEP2DEdbRaQ2r5Azeatui9MG6cj7JUHa8GD7khub4egHJREEuvj4Y8YG8w51LnhPEXxVV1ka93HpSLkVzeQuuPE1mH9oCMrqoHXAKGBsuDT1yJvj9cKgxxLCXiRRirCycki"
39+
pubKeyBytes, err := base58.Decode(pubKeyBase58)
40+
assert.NoError(tt, err)
41+
42+
bbsPubKey, err := bbs.UnmarshalPublicKey(pubKeyBytes)
43+
assert.NoError(tt, err)
44+
45+
signatureB64 := `qPrB+1BLsVSeOo1ci8dMF+iR6aa5Q6iwV/VzXo2dw94ctgnQGxaUgwb8Hd68IiYTVabQXR+ZPuwJA//GOv1OwXRHkHqXg9xPsl8HcaXaoWERanxYClgHCfy4j76Vudr14U5AhT3v8k8f0oZD+zBIUQ==`
46+
signatureBytes, err := base64.StdEncoding.DecodeString(signatureB64)
47+
require.NoError(tt, err)
48+
49+
// Case 16 (https://github.com/w3c-ccg/vc-http-api/pull/128)
50+
msg := `
51+
_:c14n0 <http://purl.org/dc/terms/created> "2021-02-23T19:31:12Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
52+
_:c14n0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/security#BbsBlsSignature2020> .
53+
_:c14n0 <https://w3id.org/security#proofPurpose> <https://w3id.org/security#assertionMethod> .
54+
_:c14n0 <https://w3id.org/security#verificationMethod> <did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2#zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2> .
55+
<did:example:b34ca6cd37bbf23> <http://schema.org/birthDate> "1958-07-17"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
56+
<did:example:b34ca6cd37bbf23> <http://schema.org/familyName> "SMITH" .
57+
<did:example:b34ca6cd37bbf23> <http://schema.org/gender> "Male" .
58+
<did:example:b34ca6cd37bbf23> <http://schema.org/givenName> "JOHN" .
59+
<did:example:b34ca6cd37bbf23> <http://schema.org/image> <data:image/png;base64,iVBORw0KGgo...kJggg==> .
60+
<did:example:b34ca6cd37bbf23> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
61+
<did:example:b34ca6cd37bbf23> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/citizenship#PermanentResident> .
62+
<did:example:b34ca6cd37bbf23> <https://w3id.org/citizenship#birthCountry> "Bahamas" .
63+
<did:example:b34ca6cd37bbf23> <https://w3id.org/citizenship#commuterClassification> "C1" .
64+
<did:example:b34ca6cd37bbf23> <https://w3id.org/citizenship#lprCategory> "C09" .
65+
<did:example:b34ca6cd37bbf23> <https://w3id.org/citizenship#lprNumber> "999-999-999" .
66+
<did:example:b34ca6cd37bbf23> <https://w3id.org/citizenship#residentSince> "2015-01-01"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
67+
<https://issuer.oidp.uscis.gov/credentials/83627465> <http://schema.org/description> "Government of Example Permanent Resident Card." .
68+
<https://issuer.oidp.uscis.gov/credentials/83627465> <http://schema.org/name> "Permanent Resident Card" .
69+
<https://issuer.oidp.uscis.gov/credentials/83627465> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/citizenship#PermanentResidentCard> .
70+
<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> .
71+
<https://issuer.oidp.uscis.gov/credentials/83627465> <https://www.w3.org/2018/credentials#credentialSubject> <did:example:b34ca6cd37bbf23> .
72+
<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> .
73+
<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> .
74+
<https://issuer.oidp.uscis.gov/credentials/83627465> <https://www.w3.org/2018/credentials#issuer> <did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2> .
75+
`
76+
err = VerifyBBSMessage(bbsPubKey, signatureBytes, []byte(msg))
77+
assert.NoError(tt, err)
78+
})
79+
}
80+
81+
func TestBBSSignatureEncoding(t *testing.T) {
82+
pubKey, privKey, err := GenerateBBSKeyPair()
83+
assert.NotNil(t, pubKey)
84+
assert.NotNil(t, privKey)
85+
assert.NoError(t, err)
86+
87+
signature, err := SignBBSMessage(privKey, []byte("hello world"))
88+
assert.NoError(t, err)
89+
assert.NotEmpty(t, signature)
90+
91+
encoded := base64.RawStdEncoding.EncodeToString(signature)
92+
assert.NotEmpty(t, encoded)
93+
94+
decoded, err := base64.RawStdEncoding.DecodeString(encoded)
95+
assert.NoError(t, err)
96+
assert.NotEmpty(t, decoded)
97+
98+
assert.Equal(t, signature, decoded)
99+
100+
err = VerifyBBSMessage(pubKey, decoded, []byte("hello world"))
101+
assert.NoError(t, err)
102+
}

crypto/jwt.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,12 @@ func jwtSignerVerifier(kid string, key any) (jwk.Key, *jwa.SignatureAlgorithm, e
128128
if err != nil {
129129
return nil, nil, errors.Wrap(err, "could not get verification alg from jwk")
130130
}
131-
// TODO(gabe) distinguish between issuer and kid
131+
// TODO(gabe) distinguish between issuer and KID
132132
if err = parsedKey.Set(jwt.IssuerKey, kid); err != nil {
133-
return nil, nil, fmt.Errorf("could not set kid with provided value: %s", kid)
133+
return nil, nil, fmt.Errorf("could not set KID with provided value: %s", kid)
134134
}
135135
if err = parsedKey.Set(jwk.KeyIDKey, kid); err != nil {
136-
return nil, nil, fmt.Errorf("could not set kid with provided value: %s", kid)
136+
return nil, nil, fmt.Errorf("could not set KID with provided value: %s", kid)
137137
}
138138
if err = parsedKey.Set(jwk.AlgorithmKey, alg); err != nil {
139139
return nil, nil, fmt.Errorf("could not set alg with value: %s", alg)

0 commit comments

Comments
 (0)