diff --git a/.github/ISSUE_TEMPLATE/idea-submission.md b/.github/ISSUE_TEMPLATE/idea-submission.md index 86733619..6a2134f7 100644 --- a/.github/ISSUE_TEMPLATE/idea-submission.md +++ b/.github/ISSUE_TEMPLATE/idea-submission.md @@ -3,7 +3,7 @@ name: Idea Submission about: Suggest an idea for this project title: "[Idea] " labels: enhancement -assignees: decentralgabe, nitro-neal +assignees: decentralgabe, nitro-neal, andresuribe87 --- diff --git a/crypto/bbs.go b/crypto/bbs.go new file mode 100644 index 00000000..db90238b --- /dev/null +++ b/crypto/bbs.go @@ -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 { + 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") + 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 { + // transform from "urn:bnid:_:c14n0" to "_:c14n0" + const ( + emptyNodePlaceholder = "") + 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) +} diff --git a/crypto/bbs_test.go b/crypto/bbs_test.go new file mode 100644 index 00000000..a72ff0ac --- /dev/null +++ b/crypto/bbs_test.go @@ -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) + msg := ` +_:c14n0 "2021-02-23T19:31:12Z"^^ . +_:c14n0 . +_:c14n0 . +_:c14n0 . + "1958-07-17"^^ . + "SMITH" . + "Male" . + "JOHN" . + . + . + . + "Bahamas" . + "C1" . + "C09" . + "999-999-999" . + "2015-01-01"^^ . + "Government of Example Permanent Resident Card." . + "Permanent Resident Card" . + . + . + . + "2029-12-03T12:19:52Z"^^ . + "2019-12-03T12:19:52Z"^^ . + . +` + 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) +} diff --git a/crypto/jwt.go b/crypto/jwt.go index 9b922acf..5b2ddfff 100644 --- a/crypto/jwt.go +++ b/crypto/jwt.go @@ -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) diff --git a/cryptosuite/bbsplussignatureproofsuite.go b/cryptosuite/bbsplussignatureproofsuite.go new file mode 100644 index 00000000..4667fe8e --- /dev/null +++ b/cryptosuite/bbsplussignatureproofsuite.go @@ -0,0 +1,450 @@ +package cryptosuite + +import ( + gocrypto "crypto" + "encoding/base64" + "strings" + + "github.com/TBD54566975/ssi-sdk/crypto" + . "github.com/TBD54566975/ssi-sdk/util" + "github.com/goccy/go-json" + "github.com/pkg/errors" +) + +const ( + BBSPlusSignatureProof2020 SignatureType = "BbsBlsSignatureProof2020" +) + +type BBSPlusSignatureProofSuite struct{} + +func GetBBSPlusSignatureProofSuite() *BBSPlusSignatureProofSuite { + return new(BBSPlusSignatureProofSuite) +} + +// CryptoSuiteInfo interface + +var _ CryptoSuiteInfo = (*BBSPlusSignatureProofSuite)(nil) + +func (BBSPlusSignatureProofSuite) ID() string { + return BBSPlusSignatureSuiteID +} + +func (BBSPlusSignatureProofSuite) Type() LDKeyType { + return BBSPlusSignatureSuiteType +} + +func (BBSPlusSignatureProofSuite) CanonicalizationAlgorithm() string { + return BBSPlusSignatureSuiteCanonicalizationAlgorithm +} + +func (BBSPlusSignatureProofSuite) MessageDigestAlgorithm() gocrypto.Hash { + return BBSPlusSignatureSuiteDigestAlgorithm +} + +func (BBSPlusSignatureProofSuite) SignatureAlgorithm() SignatureType { + return BBSPlusSignatureProof2020 +} + +func (BBSPlusSignatureProofSuite) RequiredContexts() []string { + return []string{BBSSecurityContext} +} + +// SelectivelyDisclose takes in a credential (parameter `p` that's Provable) and a map of fields to disclose as an LD frame, and produces a map of the JSON representation of the derived credential. The derived credential only contains the information that was specified in the LD frame, and a proof that's derived from the original credential. Note that a requirement for `p` is that the property `"proof"` must be present when it's marshaled to JSON, and it's value MUST be an object that conforms to a `BBSPlusProof`. +func (b BBSPlusSignatureProofSuite) SelectivelyDisclose(v BBSPlusVerifier, p Provable, toDiscloseFrame map[string]any, nonce []byte) (map[string]any, error) { + // first compact the document with the security context + compactProvable, compactProof, err := b.compactProvable(p) + if err != nil { + return nil, err + } + + deriveProofResult, err := b.CreateDeriveProof(compactProvable, toDiscloseFrame) + if err != nil { + return nil, err + } + + bbsPlusProof, err := BBSPlusProofFromGenericProof(compactProof) + if err != nil { + return nil, err + } + + // prepare the statements and indicies to be revealed + statements, revealIndicies, err := b.prepareRevealData(*deriveProofResult, *bbsPlusProof) + if err != nil { + return nil, err + } + + // pull of signature from original provable + signatureBytes, err := decodeProofValue(bbsPlusProof.ProofValue) + if err != nil { + return nil, err + } + + // derive the proof + derivedProofValue, err := v.DeriveProof(statements, signatureBytes, nonce, revealIndicies) + if err != nil { + return nil, err + } + + // attach the proof to the derived credential + derivedProof := &BBSPlusSignature2020Proof{ + Type: BBSPlusSignatureProof2020, + Created: bbsPlusProof.Created, + VerificationMethod: bbsPlusProof.VerificationMethod, + ProofPurpose: bbsPlusProof.ProofPurpose, + ProofValue: base64.StdEncoding.EncodeToString(derivedProofValue), + Nonce: base64.StdEncoding.EncodeToString(nonce), + } + derivedCred := deriveProofResult.RevealedDocument + derivedCred["proof"] = derivedProof + return derivedCred, nil +} + +func (BBSPlusSignatureProofSuite) compactProvable(p Provable) (Provable, *crypto.Proof, error) { + var genericProvable map[string]any + provableBytes, err := json.Marshal(p) + if err != nil { + return nil, nil, err + } + if err = json.Unmarshal(provableBytes, &genericProvable); err != nil { + return nil, nil, errors.Wrap(err, "unmarshalling provable to generic map") + } + compactProvable, err := LDCompact(genericProvable, W3CSecurityContext) + if err != nil { + return nil, nil, errors.Wrap(err, "compacting provable") + } + + // create a copy of the proof and remove it from the provable + compactProof := crypto.Proof(compactProvable["proof"]) + delete(compactProvable, "proof") + + // turn the compact provable back to a generic credential + compactedProvableBytes, err := json.Marshal(compactProvable) + if err != nil { + return nil, nil, err + } + var genericCred GenericProvable + if err = json.Unmarshal(compactedProvableBytes, &genericCred); err != nil { + return nil, nil, errors.Wrap(err, "unmarshalling compacted provable to generic credential") + } + return &genericCred, &compactProof, nil +} + +func (b BBSPlusSignatureProofSuite) prepareRevealData(deriveProofResult DeriveProofResult, bbsPlusProof BBSPlusSignature2020Proof) (statementBytesArrays [][]byte, revealIndices []int, err error) { + // prepare proof by removing the proof value and canonicalizing + canonicalProofStatements, err := b.prepareBLSProof(bbsPlusProof) + if err != nil { + return nil, nil, err + } + + // total # indicies to be revealed = total statements in the proof - original proof result + revealed indicies + numProofStatements := len(canonicalProofStatements) + revealIndices = make([]int, numProofStatements+len(deriveProofResult.RevealedIndicies)) + + // add the original proof result to the beginning of the reveal indicies + for i := range canonicalProofStatements { + revealIndices[i] = i + } + + // add the other statements to the indicies + for i := range deriveProofResult.RevealedIndicies { + revealIndices[i+numProofStatements] = numProofStatements + deriveProofResult.RevealedIndicies[i] + } + + // turn all statements into bytes before signing + statements := append(canonicalProofStatements, deriveProofResult.InputProofDocumentStatements...) + statementBytesArrays = make([][]byte, len(statements)) + for i := range statements { + statementBytesArrays[i] = []byte(statements[i]) + } + return statementBytesArrays, revealIndices, nil +} + +func (b BBSPlusSignatureProofSuite) prepareBLSProof(bbsPlusProof BBSPlusSignature2020Proof) ([]string, error) { + // canonicalize proof after removing the proof value + bbsPlusProof.SetProofValue("") + + marshaledProof, err := b.Marshal(bbsPlusProof) + if err != nil { + return nil, err + } + + // add the security context before canonicalization + var genericProof map[string]any + if err = json.Unmarshal(marshaledProof, &genericProof); err != nil { + return nil, err + } + genericProof["@context"] = W3CSecurityContext + + proofBytes, err := json.Marshal(genericProof) + if err != nil { + return nil, err + } + + canonicalProof, err := b.Canonicalize(proofBytes) + if err != nil { + return nil, err + } + return canonicalizedLDToStatements(*canonicalProof), nil +} + +type DeriveProofResult struct { + RevealedIndicies []int + InputProofDocumentStatements []string + RevealedDocument map[string]any +} + +// CreateDeriveProof https://w3c-ccg.github.io/vc-di-bbs/#create-derive-proof-data-algorithm +func (b BBSPlusSignatureProofSuite) CreateDeriveProof(inputProofDocument any, revealDocument map[string]any) (*DeriveProofResult, error) { + // 1. Apply the canonicalization algorithm to the input proof document to obtain a set of statements represented + // as n-quads. Let this set be known as the input proof document statements. + marshaledInputProofDoc, err := b.Marshal(inputProofDocument) + if err != nil { + return nil, err + } + inputProofDocumentStatements, err := b.Canonicalize(marshaledInputProofDoc) + if err != nil { + return nil, err + } + + // 2. Record the total number of statements in the input proof document statements. + // Let this be known as the total statements. + statements := canonicalizedLDToStatements(*inputProofDocumentStatements) + totalStatements := len(statements) + + // 3. Apply the framing algorithm to the input proof document. + // Let the product of the framing algorithm be known as the revealed document. + revealedDocument, err := LDFrame(inputProofDocument, revealDocument) + if err != nil { + return nil, err + } + + // 4. Canonicalize the revealed document using the canonicalization algorithm to obtain the set of statements + // represented as n-quads. Let these be known as the revealed statements. + marshaledRevealedDocument, err := b.Marshal(revealedDocument) + if err != nil { + return nil, err + } + canonicalRevealedStatements, err := b.Canonicalize(marshaledRevealedDocument) + if err != nil { + return nil, err + } + revealedStatements := canonicalizedLDToStatements(*canonicalRevealedStatements) + + // 5. Initialize an empty array of length equal to the number of revealed statements. + // Let this be known as the revealed indicies array. + revealedIndicies := make([]int, len(revealedStatements)) + + // 6. For each statement in order: + // 6.1 Find the numerical index the statement occupies in the set input proof document statements. + // 6.2. Insert this numerical index into the revealed indicies array + + // create an index of all statements in the original doc + documentStatementsMap := make(map[string]int, totalStatements) + for i, statement := range statements { + documentStatementsMap[statement] = i + } + + // find index of each revealed statement in the original doc + for i := range revealedStatements { + statement := revealedStatements[i] + statementIndex := documentStatementsMap[statement] + revealedIndicies[i] = statementIndex + } + + return &DeriveProofResult{ + RevealedIndicies: revealedIndicies, + InputProofDocumentStatements: statements, + RevealedDocument: revealedDocument.(map[string]any), + }, nil +} + +// Verify verifies a BBS Plus derived proof. Note that the underlying value for `v` must be of type `*BBSPlusVerifier`. Bug here: https://github.com/w3c-ccg/ldp-bbs2020/issues/62 +func (b BBSPlusSignatureProofSuite) Verify(v Verifier, p Provable) error { + proof := p.GetProof() + gotProof, err := BBSPlusProofFromGenericProof(*proof) + if err != nil { + return errors.Wrap(err, "coercing proof into BBSPlusSignature2020Proof proof") + } + + // remove proof before verifying + p.SetProof(nil) + + // make sure we set it back after we're done verifying + defer p.SetProof(proof) + + // remove the proof value in the proof before verification + signatureValue, err := decodeProofValue(gotProof.ProofValue) + if err != nil { + return errors.Wrap(err, "decoding proof value") + } + gotProof.SetProofValue("") + + // prepare proof options + contexts, err := GetContextsFromProvable(p) + if err != nil { + return errors.Wrap(err, "getting contexts from provable") + } + + // make sure the suite's context(s) are included + contexts = ensureRequiredContexts(contexts, b.RequiredContexts()) + opts := &ProofOptions{Contexts: contexts} + + // run the create verify hash algorithm on both provable and the proof + var genericProvable map[string]any + pBytes, err := json.Marshal(p) + if err != nil { + return errors.Wrap(err, "marshaling provable") + } + if err = json.Unmarshal(pBytes, &genericProvable); err != nil { + return errors.Wrap(err, "unmarshaling provable") + } + tbv, err := b.CreateVerifyHash(genericProvable, gotProof, opts) + if err != nil { + return errors.Wrap(err, "running create verify hash algorithm") + } + + bbsPlusVerifier, ok := v.(*BBSPlusVerifier) + if !ok { + return errors.New("verifier does not implement BBSPlusVerifier") + } + + nonce, err := base64.StdEncoding.DecodeString(gotProof.Nonce) + if err != nil { + return errors.Wrap(err, "decoding nonce") + } + if err = bbsPlusVerifier.VerifyDerived(tbv, signatureValue, nonce); err != nil { + return errors.Wrap(err, "verifying BBS+ signature") + } + return nil +} + +// CryptoSuiteProofType interface + +var _ CryptoSuiteProofType = (*BBSPlusSignatureProofSuite)(nil) + +func (BBSPlusSignatureProofSuite) Marshal(data any) ([]byte, error) { + // JSONify the provable object + jsonBytes, err := json.Marshal(data) + if err != nil { + return nil, err + } + return jsonBytes, nil +} + +func (BBSPlusSignatureProofSuite) Canonicalize(marshaled []byte) (*string, error) { + // the LD library anticipates a generic golang json object to normalize + var generic map[string]any + if err := json.Unmarshal(marshaled, &generic); err != nil { + return nil, err + } + normalized, err := LDNormalize(generic) + if err != nil { + return nil, errors.Wrap(err, "canonicalizing provable document") + } + canonicalString := normalized.(string) + return &canonicalString, nil +} + +func canonicalizedLDToStatements(canonicalized string) []string { + lines := strings.Split(canonicalized, "\n") + res := make([]string, 0, len(lines)) + for i := range lines { + if strings.TrimSpace(lines[i]) != "" { + res = append(res, lines[i]) + } + } + return res +} + +// CreateVerifyHash https://w3c-ccg.github.io/data-integrity-spec/#create-verify-hash-algorithm +// augmented by https://w3c-ccg.github.io/ldp-bbs2020/#create-verify-data-algorithm +func (b BBSPlusSignatureProofSuite) CreateVerifyHash(doc map[string]any, proof crypto.Proof, opts *ProofOptions) ([]byte, error) { + // first, make sure "created" exists in the proof and insert an LD context property for the proof vocabulary + preparedProof, err := b.prepareProof(proof, opts) + if err != nil { + return nil, errors.Wrap(err, "preparing proof for the create verify hash algorithm") + } + + // marshal doc to prepare for canonicalizaiton + marshaledProvable, err := b.Marshal(doc) + if err != nil { + return nil, errors.Wrap(err, "marshalling doc") + } + + // canonicalize doc using the suite's method + canonicalProvable, err := b.Canonicalize(marshaledProvable) + if err != nil { + return nil, errors.Wrap(err, "canonicalizing doc") + } + + // marshal proof to prepare for canonicalizaiton + marshaledOptions, err := b.Marshal(preparedProof) + if err != nil { + return nil, errors.Wrap(err, "marshalling proof") + } + + // 4.1 canonicalize proof using the suite's method + canonicalizedOptions, err := b.Canonicalize(marshaledOptions) + if err != nil { + return nil, errors.Wrap(err, "canonicalizing proof") + } + + // 4.2 set output to the result of the hash of the canonicalized options document + canonicalizedOptionsBytes := []byte(*canonicalizedOptions) + optionsDigest, err := b.Digest(canonicalizedOptionsBytes) + if err != nil { + return nil, errors.Wrap(err, "taking digest of proof") + } + + // 4.3 hash the canonicalized doc and append it to the output + canonicalDoc := []byte(*canonicalProvable) + documentDigest, err := b.Digest(canonicalDoc) + if err != nil { + return nil, errors.Wrap(err, "taking digest of doc") + } + + // 5. return the output + output := append(optionsDigest, documentDigest...) + return output, nil +} + +func (b BBSPlusSignatureProofSuite) prepareProof(proof crypto.Proof, opts *ProofOptions) (*crypto.Proof, error) { + proofBytes, err := json.Marshal(proof) + if err != nil { + return nil, err + } + + var genericProof map[string]any + if err = json.Unmarshal(proofBytes, &genericProof); err != nil { + return nil, err + } + + // must make sure the proof does not have a proof value or nonce before signing/verifying + delete(genericProof, "proofValue") + delete(genericProof, "nonce") + + // make sure the proof has a timestamp + created, ok := genericProof["created"] + if !ok || created == "" { + genericProof["created"] = GetRFC3339Timestamp() + } + + // for verification, we must replace the BBS ProofType with the Signature Type + genericProof["type"] = BBSPlusSignature2020 + + var contexts []any + if opts != nil { + contexts = opts.Contexts + } else { + // if none provided, make sure the proof has a context value for this suite + contexts = ArrayStrToInterface(b.RequiredContexts()) + } + genericProof["@context"] = contexts + p := crypto.Proof(genericProof) + return &p, nil +} + +func (BBSPlusSignatureProofSuite) Digest(tbd []byte) ([]byte, error) { + // handled by the algorithm itself + return tbd, nil +} diff --git a/cryptosuite/bbsplussignatureproofsuite_test.go b/cryptosuite/bbsplussignatureproofsuite_test.go new file mode 100644 index 00000000..be1f130a --- /dev/null +++ b/cryptosuite/bbsplussignatureproofsuite_test.go @@ -0,0 +1,209 @@ +package cryptosuite + +import ( + "embed" + "encoding/base64" + "testing" + + "github.com/goccy/go-json" + bbsg2 "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" +) + +const ( + // Case 16, 18 (https://github.com/w3c-ccg/vc-http-api/pull/128) + TestVector1 string = "case16_vc.jsonld" + TestVector1Reveal string = "case16_reveal_doc.jsonld" + TestVector1Revealed string = "case16_revealed.jsonld" + TestVector2 string = "case18_vc.jsonld" + TestVector2Reveal string = "case18_reveal_doc.jsonld" +) + +var ( + //go:embed testdata + knownTestData embed.FS +) + +func TestBBSPlusSignatureProofSuite(t *testing.T) { + t.Run("generate our own credential and frame", func(tt *testing.T) { + // generate a test credential to selectively disclosure just the issuer + suite := GetBBSPlusSignatureSuite() + testCred := TestCredential{ + Context: []any{"https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/bbs/v1"}, + Type: []string{"VerifiableCredential"}, + Issuer: "did:example:123", + IssuanceDate: "2021-01-01T19:23:24Z", + CredentialSubject: map[string]any{ + "id": "did:example:abcd", + }, + } + key, err := GenerateBLSKey2020(BLS12381G2Key2020) + assert.NoError(t, err) + privKey, err := key.GetPrivateKey() + assert.NoError(t, err) + signer := NewBBSPlusSigner("test-key-1", privKey, AssertionMethod) + err = suite.Sign(signer, &testCred) + assert.NoError(t, err) + + proofSuite := GetBBSPlusSignatureProofSuite() + revealDoc := map[string]any{ + "@context": []any{"https://www.w3.org/2018/credentials/v1", "https://w3id.org/security/bbs/v1"}, + "type": "VerifiableCredential", + "issuer": map[string]any{}, + } + verifier := NewBBSPlusVerifier("test-key-1", privKey.PublicKey()) + + nonce, err := base64.StdEncoding.DecodeString("G/hn9Ca9bIWZpJGlhnr/41r8RB0OO0TLChZASr3QJVztdri/JzS8Zf/xWJT5jW78zlM=") + require.NoError(t, err) + + selectiveDisclosure, err := proofSuite.SelectivelyDisclose(*verifier, &testCred, revealDoc, nonce) + assert.NoError(tt, err) + assert.NotEmpty(tt, selectiveDisclosure) + + // now verify the derived credential + genericCred := GenericProvable(selectiveDisclosure) + err = proofSuite.Verify(verifier, &genericCred) + assert.NoError(tt, err) + }) + + t.Run("known test vector", func(tt *testing.T) { + base58PubKey := "nEP2DEdbRaQ2r5Azeatui9MG6cj7JUHa8GD7khub4egHJREEuvj4Y8YG8w51LnhPEXxVV1ka93HpSLkVzeQuuPE1mH9oCMrqoHXAKGBsuDT1yJvj9cKgxxLCXiRRirCycki" + pubKeyBytes, err := base58.Decode(base58PubKey) + assert.NoError(tt, err) + + case16VC, err := getTestVector(TestVector1) + assert.NoError(tt, err) + assert.NotEmpty(tt, case16VC) + + var cred TestCredential + err = json.Unmarshal([]byte(case16VC), &cred) + assert.NoError(tt, err) + + pubKey, err := bbsg2.UnmarshalPublicKey(pubKeyBytes) + assert.NoError(tt, err) + verifier := NewBBSPlusVerifier("did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2#zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2", pubKey) + assert.NotEmpty(tt, verifier) + + // First verify the credential as is + suite := GetBBSPlusSignatureSuite() + err = suite.Verify(verifier, &cred) + assert.NoError(tt, err) + + // Test selective disclosure + proofSuite := GetBBSPlusSignatureProofSuite() + case16RevealDoc, err := getTestVector(TestVector1Reveal) + assert.NoError(tt, err) + assert.NotEmpty(tt, case16RevealDoc) + + var revealDoc map[string]any + err = json.Unmarshal([]byte(case16RevealDoc), &revealDoc) + assert.NoError(tt, err) + + nonce, err := base64.StdEncoding.DecodeString("G/hn9Ca9bIWZpJGlhnr/41r8RB0OO0TLChZASr3QJVztdri/JzS8Zf/xWJT5jW78zlM=") + require.NoError(t, err) + + selectiveDisclosure, err := proofSuite.SelectivelyDisclose(*verifier, &cred, revealDoc, nonce) + assert.NoError(tt, err) + assert.NotEmpty(tt, selectiveDisclosure) + + // now verify the derived credential + genericCred := GenericProvable(selectiveDisclosure) + err = proofSuite.Verify(verifier, &genericCred) + assert.NoError(tt, err) + }) + + t.Run("verify known selective disclosure - case 16", func(tt *testing.T) { + revealedDoc, err := getTestVector(TestVector1Revealed) + assert.NoError(tt, err) + assert.NotEmpty(tt, revealedDoc) + + var genericCred GenericProvable + err = json.Unmarshal([]byte(revealedDoc), &genericCred) + assert.NoError(tt, err) + + base58PubKey := "nEP2DEdbRaQ2r5Azeatui9MG6cj7JUHa8GD7khub4egHJREEuvj4Y8YG8w51LnhPEXxVV1ka93HpSLkVzeQuuPE1mH9oCMrqoHXAKGBsuDT1yJvj9cKgxxLCXiRRirCycki" + pubKeyBytes, err := base58.Decode(base58PubKey) + assert.NoError(tt, err) + + pubKey, err := bbsg2.UnmarshalPublicKey(pubKeyBytes) + assert.NoError(tt, err) + verifier := NewBBSPlusVerifier("did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2#zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2", pubKey) + assert.NotEmpty(tt, verifier) + + suite := GetBBSPlusSignatureProofSuite() + err = suite.Verify(verifier, &genericCred) + assert.NoError(tt, err) + }) + + t.Run("generate and verify known selective disclosure - case 18", func(tt *testing.T) { + case18VC, err := getTestVector(TestVector2) + assert.NoError(tt, err) + assert.NotEmpty(tt, case18VC) + + var cred TestCredential + err = json.Unmarshal([]byte(case18VC), &cred) + assert.NoError(tt, err) + + base58PubKey := "nEP2DEdbRaQ2r5Azeatui9MG6cj7JUHa8GD7khub4egHJREEuvj4Y8YG8w51LnhPEXxVV1ka93HpSLkVzeQuuPE1mH9oCMrqoHXAKGBsuDT1yJvj9cKgxxLCXiRRirCycki" + pubKeyBytes, err := base58.Decode(base58PubKey) + assert.NoError(tt, err) + + pubKey, err := bbsg2.UnmarshalPublicKey(pubKeyBytes) + assert.NoError(tt, err) + verifier := NewBBSPlusVerifier("did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2#zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2", pubKey) + assert.NotEmpty(tt, verifier) + + // First verify the credential as is + suite := GetBBSPlusSignatureSuite() + err = suite.Verify(verifier, &cred) + assert.NoError(tt, err) + + // Test selective disclosure + proofSuite := GetBBSPlusSignatureProofSuite() + + // nonce from case 19 + nonce, err := base64.StdEncoding.DecodeString("lEixQKDQvRecCifKl789TQj+Ii6YWDLSwn3AxR0VpPJ1QV5htod/0VCchVf1zVM0y2E=") + require.NoError(t, err) + + case18RevealDoc, err := getTestVector(TestVector2Reveal) + assert.NoError(tt, err) + assert.NotEmpty(tt, case18RevealDoc) + + var revealDoc map[string]any + err = json.Unmarshal([]byte(case18RevealDoc), &revealDoc) + assert.NoError(tt, err) + + selectivelyDisclosedCred, err := proofSuite.SelectivelyDisclose(*verifier, &cred, revealDoc, nonce) + assert.NoError(tt, err) + assert.NotEmpty(tt, selectivelyDisclosedCred) + + credBytes, err := json.Marshal(selectivelyDisclosedCred) + assert.NoError(tt, err) + var genericCred GenericProvable + err = json.Unmarshal(credBytes, &genericCred) + assert.NoError(tt, err) + + err = proofSuite.Verify(verifier, &genericCred) + assert.NoError(tt, err) + }) +} + +func TestRoundTripTestVector(t *testing.T) { + var cred TestCredential + tv, err := getTestVector(TestVector1) + assert.NoError(t, err) + err = json.Unmarshal([]byte(tv), &cred) + assert.NoError(t, err) + + credBytes, err := json.Marshal(cred) + assert.NoError(t, err) + assert.JSONEq(t, tv, string(credBytes)) +} + +func getTestVector(fileName string) (string, error) { + b, err := knownTestData.ReadFile("testdata/" + fileName) + return string(b), err +} diff --git a/cryptosuite/bbsplussignaturesuite.go b/cryptosuite/bbsplussignaturesuite.go new file mode 100644 index 00000000..463ef0fb --- /dev/null +++ b/cryptosuite/bbsplussignaturesuite.go @@ -0,0 +1,327 @@ +package cryptosuite + +import ( + gocrypto "crypto" + "encoding/base64" + + "github.com/TBD54566975/ssi-sdk/crypto" + . "github.com/TBD54566975/ssi-sdk/util" + "github.com/goccy/go-json" + "github.com/pkg/errors" +) + +const ( + BBSSecurityContext string = "https://w3id.org/security/bbs/v1" + BBSPlusSignature2020 SignatureType = "BbsBlsSignature2020" + BBSPlusSignatureSuiteID string = "https://w3c-ccg.github.io/ldp-bbs2020/#the-bbs-signature-suite-2020" + BBSPlusSignatureSuiteType LDKeyType = BLS12381G2Key2020 + BBSPlusSignatureSuiteCanonicalizationAlgorithm string = "https://w3id.org/security#URDNA2015" + // BBSPlusSignatureSuiteDigestAlgorithm uses https://www.rfc-editor.org/rfc/rfc4634 + BBSPlusSignatureSuiteDigestAlgorithm gocrypto.Hash = gocrypto.BLAKE2b_384 +) + +type BBSPlusSignatureSuite struct{} + +func GetBBSPlusSignatureSuite() CryptoSuite { + return new(BBSPlusSignatureSuite) +} + +// CryptoSuiteInfo interface + +var _ CryptoSuiteInfo = (*BBSPlusSignatureSuite)(nil) + +func (BBSPlusSignatureSuite) ID() string { + return BBSPlusSignatureSuiteID +} + +func (BBSPlusSignatureSuite) Type() LDKeyType { + return BBSPlusSignatureSuiteType +} + +func (BBSPlusSignatureSuite) CanonicalizationAlgorithm() string { + return BBSPlusSignatureSuiteCanonicalizationAlgorithm +} + +func (BBSPlusSignatureSuite) MessageDigestAlgorithm() gocrypto.Hash { + return BBSPlusSignatureSuiteDigestAlgorithm +} + +func (BBSPlusSignatureSuite) SignatureAlgorithm() SignatureType { + return BBSPlusSignature2020 +} + +func (BBSPlusSignatureSuite) RequiredContexts() []string { + return []string{BBSSecurityContext} +} + +func (b BBSPlusSignatureSuite) Sign(s Signer, p Provable) error { + // create proof before running the create verify hash algorithm + // TODO(gabe) support required reveal values + proof := b.createProof(s.GetKeyID(), s.GetProofPurpose(), nil) + + // prepare proof options + contexts, err := GetContextsFromProvable(p) + if err != nil { + return errors.Wrap(err, "getting contexts from provable") + } + + // make sure the suite's context(s) are included + contexts = ensureRequiredContexts(contexts, b.RequiredContexts()) + opts := &ProofOptions{Contexts: contexts} + + // 3. tbs value as a result of create verify hash + var genericProvable map[string]any + pBytes, err := json.Marshal(p) + if err != nil { + return errors.Wrap(err, "marshaling provable") + } + if err = json.Unmarshal(pBytes, &genericProvable); err != nil { + return errors.Wrap(err, "unmarshaling provable") + } + tbs, err := b.CreateVerifyHash(genericProvable, proof, opts) + if err != nil { + return errors.Wrap(err, "running create verify hash algorithm") + } + + // 4 & 5. create the signature over the provable data as a BBS+ signature + proofValue, err := s.Sign(tbs) + if err != nil { + return errors.Wrap(err, "signing provable value") + } + + // set the signature on the proof object and return + proof.SetProofValue(base64.RawStdEncoding.EncodeToString(proofValue)) + genericProof := crypto.Proof(proof) + p.SetProof(&genericProof) + return nil +} + +func (b BBSPlusSignatureSuite) prepareProof(proof crypto.Proof, opts *ProofOptions) (*crypto.Proof, error) { + proofBytes, err := json.Marshal(proof) + if err != nil { + return nil, err + } + + var genericProof map[string]any + if err = json.Unmarshal(proofBytes, &genericProof); err != nil { + return nil, err + } + + // must make sure there is no proof value before signing/verifying + delete(genericProof, "proofValue") + + // make sure the proof has a timestamp + created, ok := genericProof["created"] + if !ok || created == "" { + genericProof["created"] = GetRFC3339Timestamp() + } + + var contexts []any + if opts != nil { + contexts = opts.Contexts + } else { + // if none provided, make sure the proof has a context value for this suite + contexts = ArrayStrToInterface(b.RequiredContexts()) + } + genericProof["@context"] = contexts + p := crypto.Proof(genericProof) + return &p, nil +} + +func (b BBSPlusSignatureSuite) Verify(v Verifier, p Provable) error { + proof := p.GetProof() + gotProof, err := BBSPlusProofFromGenericProof(*proof) + if err != nil { + return errors.Wrap(err, "coercing proof into BBSPlusSignature2020Proof proof") + } + + // remove proof before verifying + p.SetProof(nil) + + // make sure we set it back after we're done verifying + defer p.SetProof(proof) + + // remove the proof value in the proof before verification + signatureValue, err := decodeProofValue(gotProof.ProofValue) + if err != nil { + return errors.Wrap(err, "decoding proof value") + } + gotProof.SetProofValue("") + + // prepare proof options + contexts, err := GetContextsFromProvable(p) + if err != nil { + return errors.Wrap(err, "getting contexts from provable") + } + + // make sure the suite's context(s) are included + contexts = ensureRequiredContexts(contexts, b.RequiredContexts()) + opts := &ProofOptions{Contexts: contexts} + + // run the create verify hash algorithm on both provable and the proof + var genericProvable map[string]any + pBytes, err := json.Marshal(p) + if err != nil { + return errors.Wrap(err, "marshaling provable") + } + if err = json.Unmarshal(pBytes, &genericProvable); err != nil { + return errors.Wrap(err, "unmarshaling provable") + } + tbv, err := b.CreateVerifyHash(genericProvable, gotProof, opts) + if err != nil { + return errors.Wrap(err, "running create verify hash algorithm") + } + + if err = v.Verify(tbv, signatureValue); err != nil { + return errors.Wrap(err, "verifying BBS+ signature") + } + return nil +} + +// decodeProofValue because the proof could have been encoded in a variety of manners we must try them all +// https://github.com/w3c-ccg/ldp-bbs2020/issues/16#issuecomment-1436148820 +func decodeProofValue(proofValue string) ([]byte, error) { + signatureBytes, err := base64.RawStdEncoding.DecodeString(proofValue) + if err == nil { + return signatureBytes, nil + } + signatureBytes, err = base64.StdEncoding.DecodeString(proofValue) + if err == nil { + return signatureBytes, nil + } + return nil, errors.New("unknown encoding of proof value") +} + +// CryptoSuiteProofType interface + +var _ CryptoSuiteProofType = (*BBSPlusSignatureSuite)(nil) + +func (BBSPlusSignatureSuite) Marshal(data any) ([]byte, error) { + // JSONify the provable object + jsonBytes, err := json.Marshal(data) + if err != nil { + return nil, err + } + return jsonBytes, nil +} + +func (BBSPlusSignatureSuite) Canonicalize(marshaled []byte) (*string, error) { + // the LD library anticipates a generic golang json object to normalize + var generic map[string]any + if err := json.Unmarshal(marshaled, &generic); err != nil { + return nil, err + } + normalized, err := LDNormalize(generic) + if err != nil { + return nil, errors.Wrap(err, "ld normalizing") + } + canonicalString := normalized.(string) + return &canonicalString, nil +} + +// CreateVerifyHash https://w3c-ccg.github.io/data-integrity-spec/#create-verify-hash-algorithm +// augmented by https://w3c-ccg.github.io/ldp-bbs2020/#create-verify-data-algorithm +func (b BBSPlusSignatureSuite) CreateVerifyHash(doc map[string]any, proof crypto.Proof, opts *ProofOptions) ([]byte, error) { + // first, make sure "created" exists in the proof and insert an LD context property for the proof vocabulary + preparedProof, err := b.prepareProof(proof, opts) + if err != nil { + return nil, errors.Wrap(err, "preparing proof for the create verify hash algorithm") + } + + // marshal doc to prepare for canonicalizaiton + marshaledProvable, err := b.Marshal(doc) + if err != nil { + return nil, errors.Wrap(err, "marshalling doc") + } + + // canonicalize doc using the suite's method + canonicalProvable, err := b.Canonicalize(marshaledProvable) + if err != nil { + return nil, errors.Wrap(err, "canonicalizing doc") + } + + // marshal proof to prepare for canonicalizaiton + marshaledOptions, err := b.Marshal(preparedProof) + if err != nil { + return nil, errors.Wrap(err, "marshalling proof") + } + + // 4.1 canonicalize proof using the suite's method + canonicalizedOptions, err := b.Canonicalize(marshaledOptions) + if err != nil { + return nil, errors.Wrap(err, "canonicalizing proof") + } + + // 4.2 set output to the result of the hash of the canonicalized options document + canonicalizedOptionsBytes := []byte(*canonicalizedOptions) + optionsDigest, err := b.Digest(canonicalizedOptionsBytes) + if err != nil { + return nil, errors.Wrap(err, "taking digest of proof") + } + + // 4.3 hash the canonicalized doc and append it to the output + canonicalDoc := []byte(*canonicalProvable) + documentDigest, err := b.Digest(canonicalDoc) + if err != nil { + return nil, errors.Wrap(err, "taking digest of doc") + } + + // 5. return the output + output := append(optionsDigest, documentDigest...) + return output, nil +} + +func (BBSPlusSignatureSuite) Digest(tbd []byte) ([]byte, error) { + // handled by the algorithm itself + return tbd, nil +} + +func (b BBSPlusSignatureSuite) createProof(verificationMethod string, purpose ProofPurpose, requiredRevealStatements []int) BBSPlusSignature2020Proof { + return BBSPlusSignature2020Proof{ + Type: b.SignatureAlgorithm(), + Created: GetRFC3339Timestamp(), + VerificationMethod: verificationMethod, + ProofPurpose: purpose, + RequiredRevealStatements: requiredRevealStatements, + } +} + +type BBSPlusSignature2020Proof struct { + Type SignatureType `json:"type,omitempty"` + Created string `json:"created,omitempty"` + VerificationMethod string `json:"verificationMethod,omitempty"` + ProofPurpose ProofPurpose `json:"proofPurpose,omitempty"` + ProofValue string `json:"proofValue,omitempty"` + Nonce string `json:"nonce,omitempty"` + RequiredRevealStatements []int `json:"requiredRevealStatements,omitempty"` +} + +func (b *BBSPlusSignature2020Proof) SetProofValue(proofValue string) { + b.ProofValue = proofValue +} + +// BBSPlusProofFromGenericProof accepts either a slice with exactly one element, or a single element and creates a +// BBSPlusProofFromGenericProof by unmarshaling the JSON marshaled representation of the element found in `p`. +func BBSPlusProofFromGenericProof(p crypto.Proof) (*BBSPlusSignature2020Proof, error) { + // check if the proof is an array + if proofArray, ok := p.([]any); ok { + if len(proofArray) == 0 { + return nil, errors.New("expected at least one proof") + } + if len(proofArray) > 1 { + return nil, errors.New("expected only one proof") + } + p = proofArray[0] + } + + proofBytes, err := json.Marshal(p) + if err != nil { + return nil, err + } + var result BBSPlusSignature2020Proof + if err = json.Unmarshal(proofBytes, &result); err != nil { + return nil, err + } + + return &result, nil +} diff --git a/cryptosuite/bbsplussignaturesuite_test.go b/cryptosuite/bbsplussignaturesuite_test.go new file mode 100644 index 00000000..b935a9eb --- /dev/null +++ b/cryptosuite/bbsplussignaturesuite_test.go @@ -0,0 +1,75 @@ +package cryptosuite + +import ( + "testing" + + "github.com/TBD54566975/ssi-sdk/crypto" + "github.com/goccy/go-json" + bbs "github.com/hyperledger/aries-framework-go/pkg/crypto/primitive/bbs12381g2pub" + "github.com/mr-tron/base58" + "github.com/stretchr/testify/assert" +) + +const ( + VCTestVector string = "vc_test_vector.jsonld" +) + +func TestBBSPlusSignatureSuite(t *testing.T) { + suite := GetBBSPlusSignatureSuite() + testCred := TestCredential{ + Context: []any{"https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/bbs/v1"}, + Type: []string{"VerifiableCredential"}, + Issuer: "did:example:123", + IssuanceDate: "2021-01-01T19:23:24Z", + CredentialSubject: map[string]any{ + "id": "did:example:abcd", + }, + } + + key, err := GenerateBLSKey2020(BLS12381G2Key2020) + assert.NoError(t, err) + assert.NotEmpty(t, key) + + privKey, err := key.GetPrivateKey() + assert.NoError(t, err) + assert.NotEmpty(t, privKey) + + signer := NewBBSPlusSigner("test-key-1", privKey, Authentication) + assert.NotEmpty(t, signer) + + err = suite.Sign(signer, &testCred) + assert.NoError(t, err) + + err = suite.Verify(signer, &testCred) + assert.NoError(t, err) +} + +// Case 16: https://github.com/w3c-ccg/vc-api/pull/128/files#diff-df503c1c03bdbbb0eba7241edcad059467116947346f8f89d9b49a064c9f00c3 +func TestBBSPlusTestVectors(t *testing.T) { + // first make sure we can marshal and unmarshal the test vector + testCred, err := getTestVector(TestVector1) + assert.NoError(t, err) + + var cred TestCredential + err = json.Unmarshal([]byte(testCred), &cred) + assert.NoError(t, err) + + credBytes, err := json.Marshal(cred) + assert.NoError(t, err) + assert.JSONEq(t, testCred, string(credBytes)) + + // Use the known pk to verify the signature + suite := GetBBSPlusSignatureSuite() + + // pkBase58 from did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2 + pubKeyBase58 := "nEP2DEdbRaQ2r5Azeatui9MG6cj7JUHa8GD7khub4egHJREEuvj4Y8YG8w51LnhPEXxVV1ka93HpSLkVzeQuuPE1mH9oCMrqoHXAKGBsuDT1yJvj9cKgxxLCXiRRirCycki" + pubKeyBytes, err := base58.Decode(pubKeyBase58) + assert.NoError(t, err) + + pubKey, err := bbs.UnmarshalPublicKey(pubKeyBytes) + assert.NoError(t, err) + verifier := crypto.NewBBSPlusVerifier("test-key-1", pubKey) + err = suite.Verify(verifier, &cred) + assert.NoError(t, err) +} diff --git a/cryptosuite/bls12381g2key2020.go b/cryptosuite/bls12381g2key2020.go new file mode 100644 index 00000000..eae7ae5b --- /dev/null +++ b/cryptosuite/bls12381g2key2020.go @@ -0,0 +1,146 @@ +package cryptosuite + +import ( + "github.com/TBD54566975/ssi-sdk/crypto" + bbs "github.com/hyperledger/aries-framework-go/pkg/crypto/primitive/bbs12381g2pub" + "github.com/mr-tron/base58" +) + +const ( + BLS12381G2Key2020 LDKeyType = "Bls12381G2Key2020" + + G1 CRV = "BLS12381_G1" + G2 CRV = "BLS12381_G2" +) + +type BLSKey2020 struct { + ID string `json:"id,omitempty"` + Type LDKeyType `json:"type,omitempty"` + Controller string `json:"controller,omitempty"` + PublicKeyBase58 string `json:"publicKeyBase58,omitempty"` + PrivateKeyBase58 string `json:"privateKeyBase58,omitempty"` +} + +func (b BLSKey2020) GetPublicKey() (*bbs.PublicKey, error) { + pubKeyBytes, err := base58.Decode(b.PublicKeyBase58) + if err != nil { + return nil, err + } + publicKey, err := bbs.UnmarshalPublicKey(pubKeyBytes) + if err != nil { + return nil, err + } + return publicKey, nil +} + +func (b BLSKey2020) GetPrivateKey() (*bbs.PrivateKey, error) { + privKeyBytes, err := base58.Decode(b.PrivateKeyBase58) + if err != nil { + return nil, err + } + privateKey, err := bbs.UnmarshalPrivateKey(privKeyBytes) + if err != nil { + return nil, err + } + return privateKey, nil +} + +// GenerateBLSKey2020 https://w3c-ccg.github.io/vc-di-bbs/#bls12-381 +func GenerateBLSKey2020(keyType LDKeyType) (*BLSKey2020, error) { + pubKey, privKey, err := crypto.GenerateBBSKeyPair() + if err != nil { + return nil, err + } + pubKeyBytes, err := pubKey.Marshal() + if err != nil { + return nil, err + } + privKeyBytes, err := privKey.Marshal() + if err != nil { + return nil, err + } + return &BLSKey2020{ + Type: keyType, + PublicKeyBase58: base58.Encode(pubKeyBytes), + PrivateKeyBase58: base58.Encode(privKeyBytes), + }, nil +} + +type BBSPlusSigner struct { + *crypto.BBSPlusSigner + *crypto.BBSPlusVerifier + purpose ProofPurpose + format PayloadFormat +} + +func NewBBSPlusSigner(kid string, privKey *bbs.PrivateKey, purpose ProofPurpose) *BBSPlusSigner { + signer := crypto.NewBBSPlusSigner(kid, privKey) + return &BBSPlusSigner{ + BBSPlusSigner: signer, + BBSPlusVerifier: signer.BBSPlusVerifier, + purpose: purpose, + } +} + +func (s *BBSPlusSigner) Sign(tbs []byte) ([]byte, error) { + return s.BBSPlusSigner.Sign(tbs) +} + +func (s *BBSPlusSigner) GetKeyID() string { + return s.BBSPlusSigner.GetKeyID() +} + +func (*BBSPlusSigner) GetSignatureType() SignatureType { + return BBSPlusSignature2020 +} + +func (*BBSPlusSigner) GetSigningAlgorithm() string { + return string(BBSPlusSignature2020) +} + +func (s *BBSPlusSigner) SetProofPurpose(purpose ProofPurpose) { + s.purpose = purpose +} + +func (s *BBSPlusSigner) GetProofPurpose() ProofPurpose { + return s.purpose +} + +func (s *BBSPlusSigner) SetPayloadFormat(format PayloadFormat) { + s.format = format +} + +func (s *BBSPlusSigner) GetPayloadFormat() PayloadFormat { + return s.format +} + +type BBSPlusVerifier struct { + *crypto.BBSPlusVerifier +} + +func NewBBSPlusVerifier(kid string, pubKey *bbs.PublicKey) *BBSPlusVerifier { + return &BBSPlusVerifier{ + BBSPlusVerifier: crypto.NewBBSPlusVerifier(kid, pubKey), + } +} + +// DeriveProof derives a proof from the given signature and nonce. It is used in creating selective disclosure +// representations of a signed object. +func (v BBSPlusVerifier) DeriveProof(messages [][]byte, sigBytes, nonce []byte, revealedIndexes []int) ([]byte, error) { + return v.BBSPlusVerifier.DeriveProof(messages, sigBytes, nonce, revealedIndexes) +} + +// Verify is used to verify a signature over a message using a BLS key. +func (v BBSPlusVerifier) Verify(message, signature []byte) error { + return v.BBSPlusVerifier.Verify(message, signature) +} + +// VerifyDerived is used to verify a derived proof over a message using a BLS key. It is used in verifying selective +// disclosure representations of a signed object. +func (v BBSPlusVerifier) VerifyDerived(message, signature, nonce []byte) error { + return v.BBSPlusVerifier.VerifyDerived(message, signature, nonce) +} + +func (v BBSPlusVerifier) GetKeyID() string { + return v.BBSPlusVerifier.GetKeyID() +} diff --git a/cryptosuite/cryptosuite.go b/cryptosuite/cryptosuite.go index c7695d1d..cc3c5b2d 100644 --- a/cryptosuite/cryptosuite.go +++ b/cryptosuite/cryptosuite.go @@ -38,12 +38,15 @@ type CryptoSuiteInfo interface { // CryptoSuiteProofType is an interface that defines functionality needed to sign and verify data // It encapsulates the functionality defined by the data integrity proof type specification -// https://w3c-ccg.github.io/data-integrity-spec/#creating-new-proof-types +// https://www.w3.org/community/reports/credentials/CG-FINAL-data-integrity-20220722/#creating-new-proof-types type CryptoSuiteProofType interface { Marshal(data any) ([]byte, error) Canonicalize(marshaled []byte) (*string, error) - // CreateVerifyHash https://w3c-ccg.github.io/data-integrity-spec/#create-verify-hash-algorithm - CreateVerifyHash(provable Provable, proof crypto.Proof, proofOptions *ProofOptions) ([]byte, error) + // CreateVerifyHash https://www.w3.org/community/reports/credentials/CG-FINAL-data-integrity-20220722/#create-verify-hash-algorithm + CreateVerifyHash(doc map[string]any, proof crypto.Proof, proofOptions *ProofOptions) ([]byte, error) + // Digest runs a given digest algorithm https://www.w3.org/community/reports/credentials/CG-FINAL-data-integrity-20220722/#dfn-message-digest-algorithm + // on a canonizliaed document prior to signing. Sometimes implementations will be a no-op as digesting is handled + // by the signature algorithm itself. Digest(tbd []byte) ([]byte, error) } @@ -56,7 +59,6 @@ type Signer interface { Sign(tbs []byte) ([]byte, error) GetKeyID() string - GetKeyType() string GetSignatureType() SignatureType GetSigningAlgorithm() string @@ -69,14 +71,40 @@ type Signer interface { type Verifier interface { Verify(message, signature []byte) error - GetKeyID() string - GetKeyType() string } type ProofOptions struct { // JSON-LD contexts to add to the proof Contexts []any + + // Indexes of the credential subject to require be revealed in BBS+ signatures + RevealIndexes []int +} + +// GenericProvable represents a provable that is not constrained by a specific type +type GenericProvable map[string]any + +func (g *GenericProvable) GetProof() *crypto.Proof { + if g == nil { + return nil + } + provable := *g + proof, gotProof := provable["proof"] + if !gotProof { + return nil + } + p := crypto.Proof(proof) + return &p +} + +func (g *GenericProvable) SetProof(p *crypto.Proof) { + if g == nil { + return + } + provable := *g + provable["proof"] = p + *g = provable } // GetContextsFromProvable searches from a Linked Data `@context` property in the document and returns the value diff --git a/cryptosuite/jsonwebkey2020.go b/cryptosuite/jsonwebkey2020.go index cce7befc..8abf6d3e 100644 --- a/cryptosuite/jsonwebkey2020.go +++ b/cryptosuite/jsonwebkey2020.go @@ -193,10 +193,6 @@ func (s *JSONWebKeySigner) GetKeyID() string { return s.Key.KeyID() } -func (s *JSONWebKeySigner) GetKeyType() string { - return string(s.Key.KeyType()) -} - func (*JSONWebKeySigner) GetSignatureType() SignatureType { return JSONWebSignature2020 } @@ -246,14 +242,10 @@ func (v *JSONWebKeyVerifier) Verify(message, signature []byte) error { return err } -func (v *JSONWebKeyVerifier) GetKeyID() string { +func (v JSONWebKeyVerifier) GetKeyID() string { return v.Key.KeyID() } -func (v *JSONWebKeyVerifier) GetKeyType() string { - return string(v.Key.KeyType()) -} - func NewJSONWebKeyVerifier(kid string, key crypto.PublicKeyJWK) (*JSONWebKeyVerifier, error) { verifier, err := crypto.NewJWTVerifierFromJWK(kid, key) if err != nil { @@ -276,7 +268,7 @@ func PubKeyBytesToTypedKey(keyBytes []byte, kt LDKeyType) (gocrypto.PublicKey, e convertedKeyType = crypto.Ed25519 case crypto.X25519.String(), X25519KeyAgreementKey2019.String(), X25519KeyAgreementKey2020.String(): convertedKeyType = crypto.X25519 - case crypto.SECP256k1.String(), EcdsaSecp256k1VerificationKey2019.String(): + case crypto.SECP256k1.String(), ECDSASECP256k1VerificationKey2019.String(): convertedKeyType = crypto.SECP256k1 default: return nil, fmt.Errorf("unsupported key type: %s", kt) diff --git a/cryptosuite/jwssignaturesuite.go b/cryptosuite/jwssignaturesuite.go index b982027f..f8e3e687 100644 --- a/cryptosuite/jwssignaturesuite.go +++ b/cryptosuite/jwssignaturesuite.go @@ -28,16 +28,16 @@ const ( JWSSignatureSuiteProofAlgorithm = JSONWebSignature2020 ) -type JWSSignatureSuite struct { - CryptoSuiteProofType -} +type JWSSignatureSuite struct{} func GetJSONWebSignature2020Suite() CryptoSuite { - return &JWSSignatureSuite{} + return new(JWSSignatureSuite) } // CryptoSuiteInfo interface +var _ CryptoSuiteInfo = (*JWSSignatureSuite)(nil) + func (JWSSignatureSuite) ID() string { return JWSSignatureSuiteID } @@ -63,7 +63,7 @@ func (JWSSignatureSuite) RequiredContexts() []string { } func (j JWSSignatureSuite) Sign(s Signer, p Provable) error { - // create proof before CVH + // create proof before running the create verify hash algorithm proof := j.createProof(s.GetKeyID(), s.GetProofPurpose()) // prepare proof options @@ -76,8 +76,16 @@ func (j JWSSignatureSuite) Sign(s Signer, p Provable) error { contexts = ensureRequiredContexts(contexts, j.RequiredContexts()) opts := &ProofOptions{Contexts: contexts} - // 3. tbs value as a result of cvh - tbs, err := j.CreateVerifyHash(p, proof, opts) + // 3. tbs value as a result of create verify hash + var genericProvable map[string]any + pBytes, err := json.Marshal(p) + if err != nil { + return errors.Wrap(err, "marshaling provable") + } + if err = json.Unmarshal(pBytes, &genericProvable); err != nil { + return errors.Wrap(err, "unmarshaling provable") + } + tbs, err := j.CreateVerifyHash(genericProvable, proof, opts) if err != nil { return errors.Wrap(err, "create verify hash algorithm failed") } @@ -97,7 +105,7 @@ func (j JWSSignatureSuite) Sign(s Signer, p Provable) error { func (j JWSSignatureSuite) Verify(v Verifier, p Provable) error { proof := p.GetProof() - gotProof, err := FromGenericProof(*proof) + gotProof, err := JSONWebSignatureProofFromGenericProof(*proof) if err != nil { return errors.Wrap(err, "could not prepare proof for verification; error coercing proof into JsonWebSignature2020 proof") } @@ -122,8 +130,16 @@ func (j JWSSignatureSuite) Verify(v Verifier, p Provable) error { contexts = ensureRequiredContexts(contexts, j.RequiredContexts()) opts := &ProofOptions{Contexts: contexts} - // run CVH on both provable and the proof - tbv, err := j.CreateVerifyHash(p, gotProof, opts) + // run the create verify hash algorithm on both provable and the proof + var genericProvable map[string]any + pBytes, err := json.Marshal(p) + if err != nil { + return errors.Wrap(err, "marshaling provable") + } + if err = json.Unmarshal(pBytes, &genericProvable); err != nil { + return errors.Wrap(err, "unmarshaling provable") + } + tbv, err := j.CreateVerifyHash(genericProvable, gotProof, opts) if err != nil { return errors.Wrap(err, "create verify hash algorithm failed") } @@ -136,6 +152,8 @@ func (j JWSSignatureSuite) Verify(v Verifier, p Provable) error { // CryptoSuiteProofType interface +var _ CryptoSuiteProofType = (*JWSSignatureSuite)(nil) + func (JWSSignatureSuite) Marshal(data any) ([]byte, error) { // JSONify the provable object jsonBytes, err := json.Marshal(data) @@ -159,23 +177,23 @@ func (JWSSignatureSuite) Canonicalize(marshaled []byte) (*string, error) { return &canonicalString, nil } -func (j JWSSignatureSuite) CreateVerifyHash(provable Provable, proof crypto.Proof, opts *ProofOptions) ([]byte, error) { +func (j JWSSignatureSuite) CreateVerifyHash(doc map[string]any, proof crypto.Proof, opts *ProofOptions) ([]byte, error) { // first, make sure "created" exists in the proof and insert an LD context property for the proof vocabulary preparedProof, err := j.prepareProof(proof, opts) if err != nil { return nil, errors.Wrap(err, "could not prepare proof for the create verify hash algorithm") } - // marshal provable to prepare for canonicalizaiton - marshaledProvable, err := j.Marshal(provable) + // marshal doc to prepare for canonicalizaiton + marshaledProvable, err := j.Marshal(doc) if err != nil { - return nil, errors.Wrap(err, "could not marshal provable") + return nil, errors.Wrap(err, "could not marshal doc") } - // canonicalize provable using the suite's method + // canonicalize doc using the suite's method canonicalProvable, err := j.Canonicalize(marshaledProvable) if err != nil { - return nil, errors.Wrap(err, "could not canonicalize provable") + return nil, errors.Wrap(err, "could not canonicalize doc") } // marshal proof to prepare for canonicalizaiton @@ -201,7 +219,7 @@ func (j JWSSignatureSuite) CreateVerifyHash(provable Provable, proof crypto.Proo canonicalDoc := []byte(*canonicalProvable) documentDigest, err := j.Digest(canonicalDoc) if err != nil { - return nil, errors.Wrap(err, "could not take digest of provable") + return nil, errors.Wrap(err, "could not take digest of doc") } // 5. return the output @@ -238,7 +256,7 @@ func (j JWSSignatureSuite) prepareProof(proof crypto.Proof, opts *ProofOptions) } var contexts []any - if opts != nil { + if opts != nil && len(opts.Contexts) > 0 { contexts = opts.Contexts } else { // if none provided, make sure the proof has a context value for this suite @@ -258,47 +276,16 @@ type JSONWebSignature2020Proof struct { VerificationMethod string `json:"verificationMethod,omitempty"` } -func FromGenericProof(p crypto.Proof) (*JSONWebSignature2020Proof, error) { +func JSONWebSignatureProofFromGenericProof(p crypto.Proof) (*JSONWebSignature2020Proof, error) { proofBytes, err := json.Marshal(p) if err != nil { return nil, err } - var generic map[string]any - if err = json.Unmarshal(proofBytes, &generic); err != nil { + var result JSONWebSignature2020Proof + if err = json.Unmarshal(proofBytes, &result); err != nil { return nil, err } - typeValue, ok := generic["type"].(string) - if !ok { - typeValue = "" - } - createdValue, ok := generic["created"].(string) - if !ok { - createdValue = "" - } - jwsValue, ok := generic["jws"].(string) - if !ok { - jwsValue = "" - } - purposeValue, ok := generic["proofPurpose"].(string) - if !ok { - purposeValue = "" - } - challengeValue, ok := generic["challenge"].(string) - if !ok { - challengeValue = "" - } - methodValue, ok := generic["verificationMethod"].(string) - if !ok { - methodValue = "" - } - return &JSONWebSignature2020Proof{ - Type: SignatureType(typeValue), - Created: createdValue, - JWS: jwsValue, - ProofPurpose: ProofPurpose(purposeValue), - Challenge: challengeValue, - VerificationMethod: methodValue, - }, nil + return &result, nil } func (j *JSONWebSignature2020Proof) ToGenericProof() crypto.Proof { diff --git a/cryptosuite/jwssignaturesuite_test.go b/cryptosuite/jwssignaturesuite_test.go index 2f72e168..3cb2d14c 100644 --- a/cryptosuite/jwssignaturesuite_test.go +++ b/cryptosuite/jwssignaturesuite_test.go @@ -9,30 +9,6 @@ import ( "github.com/stretchr/testify/assert" ) -type TestCredential struct { - Context any `json:"@context" validate:"required"` - ID string `json:"id,omitempty"` - Type any `json:"type" validate:"required"` - Issuer any `json:"issuer" validate:"required"` - IssuanceDate string `json:"issuanceDate" validate:"required"` - ExpirationDate string `json:"expirationDate,omitempty"` - CredentialStatus any `json:"credentialStatus,omitempty" validate:"omitempty,dive"` - CredentialSubject any `json:"credentialSubject" validate:"required"` - CredentialSchema any `json:"credentialSchema,omitempty" validate:"omitempty,dive"` - RefreshService any `json:"refreshService,omitempty" validate:"omitempty,dive"` - TermsOfUse []any `json:"termsOfUse,omitempty" validate:"omitempty,dive"` - Evidence []any `json:"evidence,omitempty" validate:"omitempty,dive"` - Proof *crypto.Proof `json:"proof,omitempty"` -} - -func (t *TestCredential) GetProof() *crypto.Proof { - return t.Proof -} - -func (t *TestCredential) SetProof(p *crypto.Proof) { - t.Proof = p -} - func TestJSONWebKey2020ToJWK(t *testing.T) { // https://github.com/decentralized-identity/JWS-Test-Suite/blob/main/data/keys/key-0-ed25519.json signer, jwk := getTestVectorKey0Signer(t, AssertionMethod) @@ -290,6 +266,8 @@ func TestJsonWebSignature2020TestVectorsCredential1(t *testing.T) { assert.NoError(t, err) } +var _ Provable = (*TestVerifiablePresentation)(nil) + type TestVerifiablePresentation struct { Context any `json:"@context,omitempty"` ID string `json:"id,omitempty"` diff --git a/cryptosuite/model.go b/cryptosuite/model.go index 3d9bb835..a8b87c1a 100644 --- a/cryptosuite/model.go +++ b/cryptosuite/model.go @@ -7,8 +7,7 @@ type ( ) const ( - W3CSecurityContext string = "https://w3id.org/security/v1" - JWS2020LinkedDataContext string = "https://w3id.org/security/suites/jws-2020/v1" + W3CSecurityContext string = "https://w3id.org/security/v2" AssertionMethod ProofPurpose = "assertionMethod" Authentication ProofPurpose = "authentication" @@ -24,5 +23,5 @@ const ( Ed25519VerificationKey2020 LDKeyType = "Ed25519VerificationKey2020" X25519KeyAgreementKey2019 LDKeyType = "X25519KeyAgreementKey2019" Ed25519VerificationKey2018 LDKeyType = "Ed25519VerificationKey2018" - EcdsaSecp256k1VerificationKey2019 LDKeyType = "EcdsaSecp256k1VerificationKey2019" + ECDSASECP256k1VerificationKey2019 LDKeyType = "EcdsaSecp256k1VerificationKey2019" ) diff --git a/cryptosuite/testdata/case16_reveal_doc.jsonld b/cryptosuite/testdata/case16_reveal_doc.jsonld new file mode 100644 index 00000000..47490031 --- /dev/null +++ b/cryptosuite/testdata/case16_reveal_doc.jsonld @@ -0,0 +1,15 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": ["VerifiableCredential", "PermanentResidentCard"], + "credentialSubject": { + "@explicit": true, + "type": ["PermanentResident", "Person"], + "givenName": {}, + "familyName": {}, + "gender": {} + } +} \ No newline at end of file diff --git a/cryptosuite/testdata/case16_revealed.jsonld b/cryptosuite/testdata/case16_revealed.jsonld new file mode 100644 index 00000000..8e55515e --- /dev/null +++ b/cryptosuite/testdata/case16_revealed.jsonld @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "credentialSubject": { + "familyName": "SMITH", + "gender": "Male", + "givenName": "JOHN", + "id": "did:example:b34ca6cd37bbf23", + "type": [ + "Person", + "PermanentResident" + ] + }, + "description": "Government of Example Permanent Resident Card.", + "expirationDate": "2029-12-03T12:19:52Z", + "id": "https://issuer.oidp.uscis.gov/credentials/83627465", + "issuanceDate": "2019-12-03T12:19:52Z", + "issuer": "did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2", + "name": "Permanent Resident Card", + "proof": [ + { + "created": "2021-02-23T19:31:12Z", + "nonce": "G/hn9Ca9bIWZpJGlhnr/41r8RB0OO0TLChZASr3QJVztdri/JzS8Zf/xWJT5jW78zlM=", + "proofPurpose": "assertionMethod", + "proofValue": "ABgA/wbvuYCqTkiaV00RSvo8XyW2R5HH0xXLKpJNz5GFanD88MZc14a9BibPX+1AMiQfKxmSq+EL5ORRRHmTqPrs1Z6pCPfa0mUeJkHc42UQIaRAzpAMehMQSBjshQ5x0wpiP0Kcs/ulp9QCY4TCYAeh7BwRc/8IOX+Rt7EJKs1esMHYpDYZ+eEKhEyKzmSTaZ2bi6wQAAAAdLZt0Iq88XHUuEBIUYLLCDVjFENiSnLoyzhk9qd7y2v7ddtQDeKHCSoBE1765VtaTwAAAAIW+/pxhFUkeytoonbuWdl/+SorHd8KlWVd90mpx8Ypfwy06rNGEpus/LAMe/nBcG9Ll9poXyT2BarA+fhgOoZukvY96oSYMh0KPpNA5QIdI8WBrTaba5mnosN7SRMFf4MTwXLaeY3dN1p5ZqMBPw3XAAAACR4dArA234aVMXK4ORN47wY65cRhBaO+LRYeIZAAg0bMMPtl/RGeyi/Ot6FFfDROlMlTYnWChMr3o3xkhywh/kNxt0F/bYh0NtDSMbdZhgIUCn6j1d9dvcfbFdefw9IhZhCbMcD9Guu8UVp02eL4rNbrxWnezdrhqxwc0zdLvzzBIBOzOvrtbHJ5A0mKZV+ZIfsbjtJRa8Vp4+q6xiNxA59aGpwtuO3IMqN7LIKiyZtiWvGAvosWDfXY+yOovYxwnyVEyKCvmELzbIVqAvzAhsA3Hm0WgcW8HukmR9R9+4/wUlWytrm7xquqCYtr7n93fuLCQ4XqCdZOEu41gjZ5qwI10IZelB0ota7283gQrVw8m6IE9k2Mi7J+jCMLXFppTQ==", + "type": "BbsBlsSignatureProof2020", + "verificationMethod": "did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2#zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2" + } + ], + "type": [ + "PermanentResidentCard", + "VerifiableCredential" + ] +} \ No newline at end of file diff --git a/cryptosuite/testdata/case16_vc.jsonld b/cryptosuite/testdata/case16_vc.jsonld new file mode 100644 index 00000000..888bad96 --- /dev/null +++ b/cryptosuite/testdata/case16_vc.jsonld @@ -0,0 +1,41 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/bbs/v1", + "https://w3id.org/citizenship/v1" + ], + "id": "https://issuer.oidp.uscis.gov/credentials/83627465", + "type": [ + "VerifiableCredential", + "PermanentResidentCard" + ], + "name": "Permanent Resident Card", + "description": "Government of Example Permanent Resident Card.", + "issuanceDate": "2019-12-03T12:19:52Z", + "expirationDate": "2029-12-03T12:19:52Z", + "credentialSubject": { + "id": "did:example:b34ca6cd37bbf23", + "type": [ + "PermanentResident", + "Person" + ], + "givenName": "JOHN", + "familyName": "SMITH", + "gender": "Male", + "image": "data:image/png;base64,iVBORw0KGgo...kJggg==", + "residentSince": "2015-01-01", + "lprCategory": "C09", + "lprNumber": "999-999-999", + "commuterClassification": "C1", + "birthCountry": "Bahamas", + "birthDate": "1958-07-17" + }, + "issuer": "did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2", + "proof": { + "type": "BbsBlsSignature2020", + "created": "2021-02-23T19:31:12Z", + "proofPurpose": "assertionMethod", + "proofValue": "qPrB+1BLsVSeOo1ci8dMF+iR6aa5Q6iwV/VzXo2dw94ctgnQGxaUgwb8Hd68IiYTVabQXR+ZPuwJA//GOv1OwXRHkHqXg9xPsl8HcaXaoWERanxYClgHCfy4j76Vudr14U5AhT3v8k8f0oZD+zBIUQ==", + "verificationMethod": "did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2#zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2" + } +} diff --git a/cryptosuite/testdata/case18_reveal_doc.jsonld b/cryptosuite/testdata/case18_reveal_doc.jsonld new file mode 100644 index 00000000..2ce88cf2 --- /dev/null +++ b/cryptosuite/testdata/case18_reveal_doc.jsonld @@ -0,0 +1,15 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": ["UniversityDegreeCredential", "VerifiableCredential"], + "@explicit": true, + "issuer": {}, + "issuanceDate": {}, + "credentialSubject": { + "@explicit": true, + "degree": {} + } +} \ No newline at end of file diff --git a/cryptosuite/testdata/case18_vc.jsonld b/cryptosuite/testdata/case18_vc.jsonld new file mode 100644 index 00000000..2a8a65e6 --- /dev/null +++ b/cryptosuite/testdata/case18_vc.jsonld @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1", + "https://w3id.org/security/bbs/v1" + ], + "id": "http://example.gov/credentials/3732", + "type": [ + "VerifiableCredential", + "UniversityDegreeCredential" + ], + "issuanceDate": "2020-03-10T04:24:12.164Z", + "credentialSubject": { + "id": "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s#zUC7LTa4hWtaE9YKyDsMVGiRNqPMN3s4rjBdB3MFi6PcVWReNfR72y3oGW2NhNcaKNVhMobh7aHp8oZB3qdJCs7RebM2xsodrSm8MmePbN25NTGcpjkJMwKbcWfYDX7eHCJjPGM", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts" + } + }, + "issuer": "did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2", + "proof": { + "type": "BbsBlsSignature2020", + "created": "2021-02-23T19:36:07Z", + "proofPurpose": "assertionMethod", + "proofValue": "qSjCNJzoDV3hv3gBPoUNN9m5lj8saDBBxC0iDHuFTXXz4PbbUhecmn/L3rPoGuySNatqC4I8VE22xQy0RAowIxoZCC+B2mZQIAb+/JGlXeAlWgEQc71WipfvsfqSn+KmR/rN1FREOy3rtSltyQ92rA==", + "verificationMethod": "did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2#zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2" + } +} \ No newline at end of file diff --git a/cryptosuite/testdata/vc_test_vector.jsonld b/cryptosuite/testdata/vc_test_vector.jsonld new file mode 100644 index 00000000..4f2fc2b1 --- /dev/null +++ b/cryptosuite/testdata/vc_test_vector.jsonld @@ -0,0 +1,42 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "id": "https://issuer.oidp.uscis.gov/credentials/83627465", + "type": [ + "VerifiableCredential", + "PermanentResidentCard" + ], + "issuer": "did:example:489398593", + "identifier": "83627465", + "name": "Permanent Resident Card", + "description": "Government of Example Permanent Resident Card.", + "issuanceDate": "2019-12-03T12:19:52Z", + "expirationDate": "2029-12-03T12:19:52Z", + "credentialSubject": { + "id": "did:example:b34ca6cd37bbf23", + "type": [ + "PermanentResident", + "Person" + ], + "givenName": "JOHN", + "familyName": "SMITH", + "gender": "Male", + "image": "data:image/png;base64,iVBORw0KGgokJggg==", + "residentSince": "2015-01-01", + "lprCategory": "C09", + "lprNumber": "999-999-999", + "commuterClassification": "C1", + "birthCountry": "Bahamas", + "birthDate": "1958-07-17" + }, + "proof": { + "type": "BbsBlsSignature2020", + "created": "2020-10-07T16:38:09Z", + "proofPurpose": "assertionMethod", + "proofValue": "o/79UazZRsf3y35mZ8kT6hx2M2R1fGgj2puotSqeLiha5MGRmqHLx1JAQsG3JlJeW5n56Gg+xUKaDPfzyimi0V9ECloPIBJY+dIMjQE15PFAk+/wtnde9QY8cZOmTIiI56HuN6DwADIzo3BLwkL2RQ==", + "verificationMethod": "did:example:489398593#test" + } +} \ No newline at end of file diff --git a/cryptosuite/testutil_test.go b/cryptosuite/testutil_test.go new file mode 100644 index 00000000..09d0d2c6 --- /dev/null +++ b/cryptosuite/testutil_test.go @@ -0,0 +1,32 @@ +package cryptosuite + +import ( + "github.com/TBD54566975/ssi-sdk/crypto" +) + +type TestCredential struct { + Context any `json:"@context" validate:"required"` + ID string `json:"id,omitempty"` + Identifier string `json:"identifier,omitempty"` + Type any `json:"type" validate:"required"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Issuer any `json:"issuer" validate:"required"` + IssuanceDate string `json:"issuanceDate" validate:"required"` + ExpirationDate string `json:"expirationDate,omitempty"` + CredentialStatus any `json:"credentialStatus,omitempty" validate:"omitempty,dive"` + CredentialSubject any `json:"credentialSubject" validate:"required"` + CredentialSchema any `json:"credentialSchema,omitempty" validate:"omitempty,dive"` + RefreshService any `json:"refreshService,omitempty" validate:"omitempty,dive"` + TermsOfUse []any `json:"termsOfUse,omitempty" validate:"omitempty,dive"` + Evidence []any `json:"evidence,omitempty" validate:"omitempty,dive"` + Proof *crypto.Proof `json:"proof,omitempty"` +} + +func (t *TestCredential) GetProof() *crypto.Proof { + return t.Proof +} + +func (t *TestCredential) SetProof(p *crypto.Proof) { + t.Proof = p +} diff --git a/did/key.go b/did/key.go index 3d023fa8..7c9c0deb 100644 --- a/did/key.go +++ b/did/key.go @@ -151,7 +151,7 @@ func codecToLDKeyType(codec multicodec.Code) (cryptosuite.LDKeyType, error) { case X25519MultiCodec: return cryptosuite.X25519KeyAgreementKey2019, nil case Secp256k1MultiCodec: - return cryptosuite.EcdsaSecp256k1VerificationKey2019, nil + return cryptosuite.ECDSASECP256k1VerificationKey2019, nil case P256MultiCodec, P384MultiCodec, P521MultiCodec, RSAMultiCodec: return cryptosuite.JSONWebKey2020Type, nil default: diff --git a/did/key_test.go b/did/key_test.go index d8780719..e80c6e20 100644 --- a/did/key_test.go +++ b/did/key_test.go @@ -408,7 +408,7 @@ func TestKnownTestVectors(t *testing.T) { assert.NoError(tt, err) assert.Equal(tt, did1, didDoc1.ID) assert.Equal(tt, 1, len(didDoc1.VerificationMethod)) - assert.Equal(tt, cryptosuite.EcdsaSecp256k1VerificationKey2019, didDoc1.VerificationMethod[0].Type) + assert.Equal(tt, cryptosuite.ECDSASECP256k1VerificationKey2019, didDoc1.VerificationMethod[0].Type) did2 := "did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2" didKey2 := DIDKey(did2) @@ -416,7 +416,7 @@ func TestKnownTestVectors(t *testing.T) { assert.NoError(tt, err) assert.Equal(tt, did2, didDoc2.ID) assert.Equal(tt, 1, len(didDoc2.VerificationMethod)) - assert.Equal(tt, cryptosuite.EcdsaSecp256k1VerificationKey2019, didDoc2.VerificationMethod[0].Type) + assert.Equal(tt, cryptosuite.ECDSASECP256k1VerificationKey2019, didDoc2.VerificationMethod[0].Type) did3 := "did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N" didKey3 := DIDKey(did3) @@ -424,7 +424,7 @@ func TestKnownTestVectors(t *testing.T) { assert.NoError(tt, err) assert.Equal(tt, did3, didDoc3.ID) assert.Equal(tt, 1, len(didDoc3.VerificationMethod)) - assert.Equal(tt, cryptosuite.EcdsaSecp256k1VerificationKey2019, didDoc3.VerificationMethod[0].Type) + assert.Equal(tt, cryptosuite.ECDSASECP256k1VerificationKey2019, didDoc3.VerificationMethod[0].Type) }) t.Run("P-256", func(tt *testing.T) { diff --git a/did/model.go b/did/model.go index b75fd45f..42efffa3 100644 --- a/did/model.go +++ b/did/model.go @@ -139,7 +139,7 @@ func KeyTypeToLDKeyType(kt crypto.KeyType) (cryptosuite.LDKeyType, error) { case crypto.X25519: return cryptosuite.X25519KeyAgreementKey2019, nil case crypto.SECP256k1: - return cryptosuite.EcdsaSecp256k1VerificationKey2019, nil + return cryptosuite.ECDSASECP256k1VerificationKey2019, nil case crypto.P256, crypto.P384, crypto.P521, crypto.RSA: return cryptosuite.JSONWebKey2020Type, nil default: diff --git a/did/model_test.go b/did/model_test.go index bc55065e..bdcb455c 100644 --- a/did/model_test.go +++ b/did/model_test.go @@ -90,7 +90,7 @@ func TestKeyTypeToLDKeyType(t *testing.T) { kt, err = KeyTypeToLDKeyType(crypto.SECP256k1) assert.NoError(t, err) - assert.Equal(t, kt, cryptosuite.EcdsaSecp256k1VerificationKey2019) + assert.Equal(t, kt, cryptosuite.ECDSASECP256k1VerificationKey2019) kt, err = KeyTypeToLDKeyType(crypto.Ed25519) assert.NoError(t, err) diff --git a/did/pkh.go b/did/pkh.go index 9dda365f..71ac4e3b 100644 --- a/did/pkh.go +++ b/did/pkh.go @@ -38,7 +38,7 @@ const ( EthereumNetworkPrefix = "eip155:1" PolygonNetworkPrefix = "eip155:137" - EcdsaSecp256k1RecoveryMethod2020 = "EcdsaSecp256k1RecoveryMethod2020" + ECDSASECP256k1RecoveryMethod2020 = "EcdsaSecp256k1RecoveryMethod2020" ) // GetDIDPKHContext returns a context which should be manually inserted into each did:pkh document. This will likely @@ -132,7 +132,7 @@ func GetDIDPKHNetworkForDID(did string) (Network, error) { func GetVerificationTypeForNetwork(n Network) (string, error) { switch n { case Bitcoin, Ethereum, Polygon: - return EcdsaSecp256k1RecoveryMethod2020, nil + return ECDSASECP256k1RecoveryMethod2020, nil } return "", fmt.Errorf("unsupported did:pkh network: %s", n) } diff --git a/did/util.go b/did/util.go index 17f9e54e..43c86f15 100644 --- a/did/util.go +++ b/did/util.go @@ -104,7 +104,7 @@ func decodePublicKeyWithType(data []byte) ([]byte, cryptosuite.LDKeyType, error) case SHA256MultiCodec: return pubKeyBytes, cryptosuite.Ed25519VerificationKey2020, nil case Secp256k1MultiCodec: - return pubKeyBytes, cryptosuite.EcdsaSecp256k1VerificationKey2019, nil + return pubKeyBytes, cryptosuite.ECDSASECP256k1VerificationKey2019, nil case P256MultiCodec, P384MultiCodec, P521MultiCodec, RSAMultiCodec: return pubKeyBytes, cryptosuite.JSONWebKey2020Type, nil default: diff --git a/go.mod b/go.mod index a78964a5..c1e57fa5 100644 --- a/go.mod +++ b/go.mod @@ -10,13 +10,14 @@ require ( github.com/goccy/go-json v0.10.2 github.com/google/uuid v1.3.0 github.com/gowebpki/jcs v1.0.0 + github.com/hyperledger/aries-framework-go v0.1.9-0.20230217102417-a948231f8452 github.com/jarcoal/httpmock v1.3.0 github.com/lestrrat-go/jwx/v2 v2.0.8 github.com/magefile/mage v1.14.0 github.com/mr-tron/base58 v1.2.0 github.com/multiformats/go-multibase v0.2.0 github.com/multiformats/go-multicodec v0.8.1 - github.com/multiformats/go-multihash v0.2.1 + github.com/multiformats/go-multihash v0.0.13 github.com/multiformats/go-varint v0.0.7 github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 github.com/piprate/json-gold v0.5.0 @@ -34,21 +35,23 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect - github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/hyperledger/aries-framework-go/spi v0.0.0-20221025204933-b807371b6f1e // indirect + github.com/kilic/bls12-381 v0.1.1-0.20210503002446-7b7597926c69 // indirect + github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.2 // indirect github.com/lestrrat-go/blackmagic v1.0.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc v1.0.4 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/option v1.0.0 // indirect - github.com/minio/sha256-simd v1.0.0 // indirect - github.com/multiformats/go-base32 v0.0.3 // indirect + github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect + github.com/minio/sha256-simd v0.1.1 // indirect + github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect + github.com/pquerna/cachecontrol v0.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect golang.org/x/crypto v0.7.0 // indirect golang.org/x/sys v0.6.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - lukechampine.com/blake3 v1.1.6 // indirect ) diff --git a/go.sum b/go.sum index dc84863b..6a556cf4 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,13 @@ github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD6ZIpQT8= github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= @@ -26,11 +27,17 @@ github.com/gowebpki/jcs v1.0.0 h1:0pZtOgGetfH/L7yXb4KWcJqIyZNA43WXFyMd7ftZACw= github.com/gowebpki/jcs v1.0.0/go.mod h1:CID1cNZ+sHp1CCpAR8mPf6QRtagFBgPJE0FCUQ6+BrI= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/hyperledger/aries-framework-go v0.1.9-0.20230217102417-a948231f8452 h1:0MVQG7a8A6hDuvyV0EWh8KkmAIS3YsWTRt+qFJtxvaQ= +github.com/hyperledger/aries-framework-go v0.1.9-0.20230217102417-a948231f8452/go.mod h1:qrOxEGVsu8M2RahaJgM8nz9AcAHjR/dKd1JIJ3ieJhY= +github.com/hyperledger/aries-framework-go/spi v0.0.0-20221025204933-b807371b6f1e h1:SxbXlF39661T9w/L9PhVdtbJfJ51Pm4JYEEW6XfZHEQ= +github.com/hyperledger/aries-framework-go/spi v0.0.0-20221025204933-b807371b6f1e/go.mod h1:oryUyWb23l/a3tAP9KW+GBbfcfqp9tZD4y5hSkFrkqI= github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= -github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/kilic/bls12-381 v0.1.1-0.20210503002446-7b7597926c69 h1:kMJlf8z8wUcpyI+FQJIdGjAhfTww1y0AbQEv86bpVQI= +github.com/kilic/bls12-381 v0.1.1-0.20210503002446-7b7597926c69/go.mod h1:tlkavyke+Ac7h8R3gZIjI5LKBcvMlSWnXNMgT3vZXo8= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4= github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ= github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= @@ -48,20 +55,25 @@ github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmt github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= -github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= -github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= +github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= -github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= +github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= github.com/multiformats/go-multicodec v0.8.1 h1:ycepHwavHafh3grIbR1jIXnKCsFm0fqsfEOsJ8NtKE8= github.com/multiformats/go-multicodec v0.8.1/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= -github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108= -github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc= +github.com/multiformats/go-multihash v0.0.13 h1:06x+mk/zj1FoMsgNejLpy6QTvJqlSt/BhLEy87zidlc= +github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= @@ -74,8 +86,8 @@ 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/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU= -github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= +github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ= github.com/santhosh-tekuri/jsonschema/v5 v5.2.0 h1:WCcC4vZDS1tYNxjWlwRJZQy28r8CMoggKnxNzxsVDMQ= github.com/santhosh-tekuri/jsonschema/v5 v5.2.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= @@ -93,11 +105,17 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -106,16 +124,15 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 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/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= 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= -lukechampine.com/blake3 v1.1.6 h1:H3cROdztr7RCfoaTpGZFQsrqvweFLrqS73j7L7cmR5c= -lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= diff --git a/util/helpers.go b/util/helpers.go index 9982b32b..5d33a753 100644 --- a/util/helpers.go +++ b/util/helpers.go @@ -8,7 +8,7 @@ import ( "time" "github.com/goccy/go-json" - + "github.com/hyperledger/aries-framework-go/pkg/doc/signature/jsonld" "github.com/piprate/json-gold/ld" "github.com/go-playground/validator/v10" @@ -73,6 +73,39 @@ func LDNormalize(document any) (any, error) { return processor.Normalize(document, processor.GetOptions()) } +// LDFrame runs https://www.w3.org/TR/json-ld11-framing/ to transform the data in a document according to its frame +func LDFrame(document any, frame any) (any, error) { + docAny := document + var err error + if _, ok := document.(map[string]any); !ok { + docAny, err = AnyToJSONInterface(document) + if err != nil { + return nil, err + } + } + + frameAny := frame + if _, ok := frame.(map[string]any); !ok { + frameAny, err = AnyToJSONInterface(frame) + if err != nil { + return nil, err + } + } + docLoader := ld.NewRFC7324CachingDocumentLoader(nil) + // use the aries processor for special framing logic necessary for blank nodes + return jsonld.Default().Frame(docAny.(map[string]any), + frameAny.(map[string]any), jsonld.WithDocumentLoader(docLoader), jsonld.WithFrameBlankNodes()) +} + +// LDCompact runs https://www.w3.org/TR/json-ld-api/#compaction-algorithms which shortens IRIs in the document +func LDCompact(document any, context string) (map[string]any, error) { + processor := NewLDProcessor() + contextsMap := map[string]any{ + "@context": context, + } + return processor.Compact(document, contextsMap, processor.GetOptions()) +} + func GetRFC3339Timestamp() string { return AsRFC3339Timestamp(time.Now()) } @@ -111,6 +144,16 @@ func ToJSONInterface(data string) (any, error) { return result, err } +func AnyToJSONInterface(data any) (any, error) { + dataBytes, err := json.Marshal(data) + if err != nil { + return nil, err + } + var result any + err = json.Unmarshal(dataBytes, &result) + return result, err +} + func validateCopy(src any, dst any) error { if src == nil { return errors.New("src is nil") diff --git a/wasm/static/main.wasm b/wasm/static/main.wasm index adfdd919..47eae7ec 100755 Binary files a/wasm/static/main.wasm and b/wasm/static/main.wasm differ