Skip to content

Commit

Permalink
Merge pull request #378 from hypersign-protocol/credential-schema-vc-…
Browse files Browse the repository at this point in the history
…support

JSON-LD signing support for Credential Schema
  • Loading branch information
arnabghose997 authored Nov 25, 2023
2 parents feca14a + f3abe4f commit dfd19c8
Show file tree
Hide file tree
Showing 10 changed files with 323 additions and 109 deletions.
24 changes: 24 additions & 0 deletions client/docs/swagger-ui/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,10 @@ paths:
credentialSchemaDocument:
type: object
properties:
context:
type: array
items:
type: string
type:
type: string
modelVersion:
Expand Down Expand Up @@ -685,6 +689,10 @@ paths:
credentialSchemaDocument:
type: object
properties:
context:
type: array
items:
type: string
type:
type: string
modelVersion:
Expand Down Expand Up @@ -24570,6 +24578,10 @@ definitions:
hypersign.ssi.v1.CredentialSchemaDocument:
type: object
properties:
context:
type: array
items:
type: string
type:
type: string
modelVersion:
Expand Down Expand Up @@ -24622,6 +24634,10 @@ definitions:
credentialSchemaDocument:
type: object
properties:
context:
type: array
items:
type: string
type:
type: string
modelVersion:
Expand Down Expand Up @@ -24918,6 +24934,10 @@ definitions:
credentialSchemaDocument:
type: object
properties:
context:
type: array
items:
type: string
type:
type: string
modelVersion:
Expand Down Expand Up @@ -24981,6 +25001,10 @@ definitions:
credentialSchemaDocument:
type: object
properties:
context:
type: array
items:
type: string
type:
type: string
modelVersion:
Expand Down
49 changes: 39 additions & 10 deletions cmd/hid-noded/cmd/debug_extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,13 +413,13 @@ func signDidDocCmd() *cobra.Command {

func signSchemaDocCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "schema-doc [doc] [private-key] [signing-algo]",
Use: "schema-doc [doc] [private-key] [proof-object-without-signature]",
Short: "Schema Document signature",
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
argSchemaDoc := args[0]
argPrivateKey := args[1]
argSigningAlgo := args[2]
argProofObjectWithoutSignature := args[2]

clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
Expand All @@ -432,38 +432,67 @@ func signSchemaDocCmd() *cobra.Command {
if err != nil {
return err
}
schemaDocBytes := schemaDoc.GetSignBytes()

// Unmarshal Proof Object
var credSchemaDocProof types.DocumentProof
err = clientCtx.Codec.UnmarshalJSON([]byte(argProofObjectWithoutSignature), &credSchemaDocProof)
if err != nil {
return err
}

// Sign Schema Document
var signature string
switch argSigningAlgo {
switch credSchemaDocProof.Type {
case types.Ed25519Signature2020:
signature, err = hidnodecli.GetEd25519Signature2020(argPrivateKey, schemaDocBytes)
credSchemaDocBytes, err := ldcontext.Ed25519Signature2020Normalize(&schemaDoc, &credSchemaDocProof)
if err != nil {
return err
}

signature, err = hidnodecli.GetEd25519Signature2020(argPrivateKey, credSchemaDocBytes)
if err != nil {
return err
}
case types.EcdsaSecp256k1Signature2019:
signature, err = hidnodecli.GetEcdsaSecp256k1Signature2019(argPrivateKey, schemaDocBytes)
credSchemaDocBytes, err := ldcontext.EcdsaSecp256k1Signature2019Normalize(&schemaDoc, &credSchemaDocProof)
if err != nil {
return err
}

signature, err = hidnodecli.GetEcdsaSecp256k1Signature2019(argPrivateKey, credSchemaDocBytes)
if err != nil {
return err
}
case types.EcdsaSecp256k1RecoverySignature2020:
signature, err = hidnodecli.GetEcdsaSecp256k1RecoverySignature2020(argPrivateKey, schemaDocBytes)
credSchemaDocBytes, err := ldcontext.EcdsaSecp256k1RecoverySignature2020Normalize(&schemaDoc, &credSchemaDocProof)
if err != nil {
return err
}

signature, err = hidnodecli.GetEcdsaSecp256k1RecoverySignature2020(argPrivateKey, credSchemaDocBytes)
if err != nil {
return err
}
case types.BbsBlsSignature2020:
signature, err = hidnodecli.GetBbsBlsSignature2020(argPrivateKey, schemaDocBytes)
credSchemaDocBytes, err := ldcontext.BbsBlsSignature2020Normalize(&schemaDoc, &credSchemaDocProof)
if err != nil {
return err
}

signature, err = hidnodecli.GetBbsBlsSignature2020(argPrivateKey, credSchemaDocBytes)
if err != nil {
return err
}
case types.BabyJubJubSignature2023:
signature, err = hidnodecli.GetBabyJubJubSignature2023(argPrivateKey, schemaDocBytes)
signature, err = hidnodecli.GetBabyJubJubSignature2023(argPrivateKey, schemaDoc.GetSignBytes())
if err != nil {
return err
}
default:
panic("recieved unsupported signing-algo. Supported algorithms are: [Ed25519Signature2020, EcdsaSecp256k1Signature2019, EcdsaSecp256k1RecoverySignature2020, BbsBlsSignature2020, BabyJubJubSignature2023]")
panic(fmt.Sprintf(
"recieved unsupported signing-algo '%v'. Supported algorithms are: [Ed25519Signature2020, EcdsaSecp256k1Signature2019, EcdsaSecp256k1RecoverySignature2020, BbsBlsSignature2020, BabyJubJubSignature2023]",
credSchemaDocProof.Type,
))
}

_, err = fmt.Fprintln(cmd.OutOrStdout(), signature)
Expand Down
16 changes: 9 additions & 7 deletions proto/ssi/v1/credential_schema.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ syntax = "proto3";
package hypersign.ssi.v1;

import "ssi/v1/proof.proto";
import "gogoproto/gogo.proto";

option go_package = "github.com/hypersign-protocol/hid-node/x/ssi/types";

message CredentialSchemaDocument {
string type = 1;
string modelVersion = 2;
string id = 3;
string name = 4;
string author = 5;
string authored = 6;
CredentialSchemaProperty schema = 7;
repeated string context = 1 [json_name = "@context", (gogoproto.jsontag) = "@context"];
string type = 2;
string modelVersion = 3;
string id = 4;
string name = 5;
string author = 6;
string authored = 7;
CredentialSchemaProperty schema = 8;
}

message CredentialSchemaProperty {
Expand Down
10 changes: 8 additions & 2 deletions tests/e2e/ssi_tests/generate_doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
SECP256K1_VER_KEY_2019_CONTEXT = "https://ns.did.ai/suites/secp256k1-2019/v1"
BBS_CONTEXT = "https://ns.did.ai/suites/bls12381-2020/v1"
CREDENTIAL_STATUS_CONTEXT = "https://raw.githubusercontent.com/hypersign-protocol/hypersign-contexts/main/CredentialStatus.jsonld"
CREDENTIAL_SCHEMA_CONTEXT = "https://raw.githubusercontent.com/hypersign-protocol/hypersign-contexts/main/CredentialSchema.jsonld"

def generate_did_document(key_pair, algo="Ed25519Signature2020", bech32prefix="hid", is_uuid=False):
base_document = {
Expand Down Expand Up @@ -96,6 +97,7 @@ def generate_did_document(key_pair, algo="Ed25519Signature2020", bech32prefix="h

def generate_schema_document(key_pair, schema_author, vm, signature=None, algo="Ed25519Signature2020", updated_schema=None):
base_schema_doc = {
"@context": [CREDENTIAL_SCHEMA_CONTEXT],
"type": "https://schema.org/Person",
"modelVersion": "v1.0",
"id": "",
Expand All @@ -114,12 +116,16 @@ def generate_schema_document(key_pair, schema_author, vm, signature=None, algo="
proof_type = ""
if algo == "Ed25519Signature2020":
proof_type = "Ed25519Signature2020"
base_schema_doc["@context"].append(ED25519_CONTEXT)
elif algo == "EcdsaSecp256k1Signature2019":
proof_type = "EcdsaSecp256k1Signature2019"
base_schema_doc["@context"].append(SECP256K1_VER_KEY_2019_CONTEXT)
elif algo == "EcdsaSecp256k1RecoverySignature2020":
proof_type = "EcdsaSecp256k1RecoverySignature2020"
base_schema_doc["@context"].append(SECP256K1_RECOVERY_CONTEXT)
elif algo == "BbsBlsSignature2020":
proof_type = "BbsBlsSignature2020"
base_schema_doc["@context"].append(BBS_CONTEXT)
elif algo == "BabyJubJubSignature2023":
proof_type = "BabyJubJubSignature2023"
else:
Expand All @@ -141,12 +147,12 @@ def generate_schema_document(key_pair, schema_author, vm, signature=None, algo="
# Form Signature
if not updated_schema:
if not signature:
signature = get_document_signature(base_schema_doc, "schema", key_pair, algo)
signature = get_document_signature(base_schema_doc, "schema", key_pair, algo, proofObj=base_schema_proof)
base_schema_proof["proofValue"] = signature
return base_schema_doc, base_schema_proof
else:
if not signature:
signature = get_document_signature(updated_schema, "schema", key_pair, algo)
signature = get_document_signature(updated_schema, "schema", key_pair, algo, proofObj=base_schema_proof)
base_schema_proof["proofValue"] = signature
return updated_schema, base_schema_proof

Expand Down
5 changes: 1 addition & 4 deletions tests/e2e/ssi_tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,7 @@ def get_document_signature(doc: dict, doc_type: str, key_pair: dict, algo: str =
else:
raise Exception("Invalid value for doc_type param: " + doc_type)

if doc_type == "did" or doc_type == "cred-status":
cmd_string = f"hid-noded debug sign-ssi-doc {doc_cmd} '{json.dumps(doc)}' {private_key} '{json.dumps(proofObj)}'"
else:
cmd_string = f"hid-noded debug sign-ssi-doc {doc_cmd} '{json.dumps(doc)}' {private_key} {algo}"
cmd_string = f"hid-noded debug sign-ssi-doc {doc_cmd} '{json.dumps(doc)}' {private_key} '{json.dumps(proofObj)}'"
signature, _ = run_command(cmd_string)

if signature == "":
Expand Down
46 changes: 46 additions & 0 deletions x/ssi/ld-context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const BbsSignature2020Context string = "https://ns.did.ai/suites/bls12381-2020/v
const Secp256k12019Context string = "https://ns.did.ai/suites/secp256k1-2019/v1"
const X25519KeyAgreementKeyEIP5630Context string = "https://raw.githubusercontent.com/hypersign-protocol/hypersign-contexts/main/X25519KeyAgreementKeyEIP5630.jsonld"
const CredentialStatusContext string = "https://raw.githubusercontent.com/hypersign-protocol/hypersign-contexts/main/CredentialStatus.jsonld"
const CredentialSchemaContext string = "https://raw.githubusercontent.com/hypersign-protocol/hypersign-contexts/main/CredentialSchema.jsonld"

// As hid-node is not supposed to perform any GET request, the complete Context body of their
// respective Context urls has been maintained below.
Expand Down Expand Up @@ -676,4 +677,49 @@ var ContextUrlMap map[string]contextObject = map[string]contextObject{
"@type": "xsd:string",
},
},
CredentialSchemaContext: {
"@version": 1.1,
"hypersign-vocab": "urn:uuid:13fe9318-bb82-4d95-8bf5-8e7fdf8b2026#",
"xsd": "http://www.w3.org/2001/XMLSchema#",
"id": "@id",
"type": map[string]interface{}{
"@id": "hypersign-vocab:type",
},
"modelVersion": map[string]interface{}{
"@id": "hypersign-vocab:modelVersion",
"@type": "xsd:string",
},
"name": map[string]interface{}{
"@id": "hypersign-vocab:name",
"@type": "xsd:string",
},
"author": map[string]interface{}{
"@id": "hypersign-vocab:author",
"@type": "xsd:string",
},
"authored": map[string]interface{}{
"@id": "hypersign-vocab:authored",
"@type": "xsd:dateTime",
},
"schema": map[string]interface{}{
"@id": "hypersign-vocab:schema",
"@type": "xsd:string",
},
"additionalProperties": map[string]interface{}{
"@id": "hypersign-vocab:additionalProperties",
"@type": "xsd:boolean",
},
"description": map[string]interface{}{
"@id": "hypersign-vocab:description",
"@type": "xsd:string",
},
"properties": map[string]interface{}{
"@id": "hypersign-vocab:properties",
"@type": "xsd:string",
},
"required": map[string]interface{}{
"@id": "hypersign-vocab:required",
"@container": "@set",
},
},
}
53 changes: 52 additions & 1 deletion x/ssi/ld-context/normalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package ldcontext

import (
"crypto/sha256"
"encoding/json"
"fmt"

"github.com/hypersign-protocol/hid-node/x/ssi/types"
"github.com/piprate/json-gold/ld"
)

// NormalizeByVerificationMethodType normalizes DID Document based on the input Verification
Expand Down Expand Up @@ -64,7 +67,13 @@ func normalizeDocumentWithProof(msg types.SsiMsg, docProof *types.DocumentProof)
}
context = doc.Context
case *types.CredentialSchemaDocument:
return doc.GetSignBytes(), nil
var err error
jsonLdCredentialSchema := NewJsonLdCredentialSchema(doc)
canonizedDocument, err = normalizeWithURDNA2015(jsonLdCredentialSchema)
if err != nil {
return nil, err
}
context = doc.Context
}

canonizedDocumentHash := sha256.Sum256([]byte(canonizedDocument))
Expand All @@ -85,6 +94,48 @@ func normalizeDocumentWithProof(msg types.SsiMsg, docProof *types.DocumentProof)
return finalNormalizedHash, nil
}

// normalizeWithURDNA2015 performs RDF Canonization upon JsonLdDid using URDNA2015
// algorithm and returns the canonized document in string
func normalizeWithURDNA2015(jsonLdDocument JsonLdDocument) (string, error) {
return normalize(ld.AlgorithmURDNA2015, jsonLdDocument)
}

func normalize(algorithm string, jsonLdDocument JsonLdDocument) (string, error) {
proc := ld.NewJsonLdProcessor()
options := ld.NewJsonLdOptions("")
options.Algorithm = algorithm // ld.AlgorithmURDNA2015
options.Format = "application/n-quads"

normalisedJsonLd, err := proc.Normalize(jsonLdDocToInterface(jsonLdDocument), options)
if err != nil {
return "", fmt.Errorf("unable to Normalize DID Document: %v", err.Error())
}

canonizedDocString := normalisedJsonLd.(string)
if canonizedDocString == "" {
return "", fmt.Errorf("normalization of JSON-LD document yielded empty RDF string")
}

return canonizedDocString, nil
}

// Convert JsonLdDid to interface
func jsonLdDocToInterface(jsonLd any) interface{} {
var intf interface{}

jsonLdBytes, err := json.Marshal(jsonLd)
if err != nil {
panic(err)
}

err = json.Unmarshal(jsonLdBytes, &intf)
if err != nil {
panic(err)
}

return intf
}

// Ed25519Signature2020Normalize normalizes DID Document in accordance with
// EdDSA Cryptosuite v2020 (https://www.w3.org/community/reports/credentials/CG-FINAL-di-eddsa-2020-20220724/)
func Ed25519Signature2020Normalize(ssiMsg types.SsiMsg, didDocProof *types.DocumentProof) ([]byte, error) {
Expand Down
Loading

0 comments on commit dfd19c8

Please sign in to comment.