-
Notifications
You must be signed in to change notification settings - Fork 56
Add support for BLS keys and BBS+ signatures #288
Changes from 58 commits
5d3e9cf
47d5a63
ff80ef6
ba641de
425adbd
f28b789
50e56c6
24c1d20
835c3e3
f783cc0
42cc8e2
7eeb7b3
1402b90
e477161
d6e597d
39c8c84
4e34014
384f5e4
2d8630e
c196a03
340d947
c72ce22
23f5e49
77be15e
2319afb
7e4c63f
e2cb937
70bba0a
1650236
90b1106
28d9e70
c6d2e8d
cdb2cba
a90fa5a
8b317af
38119e6
2ca6a48
54cda26
b6ae137
eb8fdf3
310b499
e7883da
8f98524
805f1a2
b84cfdb
425b0c6
2b73629
a981ef8
3e816a5
0d36a93
7d968e2
247339f
b172d0a
625fe9c
5562aa0
66c7a3f
f1fee95
4f1c00b
fc73c86
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
package crypto | ||
|
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some pointers to what There was a problem hiding this comment. Choose a reason for hiding this commentThe 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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lolwut, why? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added a comment, it's required as per https://w3c-ccg.github.io/ldp-bbs2020/#create-verify-data-algorithm There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 :/ There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure what this function is doing.... is it replacing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. before after There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} |
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make sense to include more cases from the PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure where this Either way, can you add docs please? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interesting stuff here pt1