WebServiceDevelopment / PrivateInvoice

P2P based invoicing system where anyone can create their own Node
https://privateinvoice.io/
Apache License 2.0
1 stars 0 forks source link

Update Contacts to use Verifiable Business Cards #3

Closed wsdOgawa closed 2 years ago

wsdOgawa commented 2 years ago

For contacts, the approach is to use Verifiable Business Cards as defined in the w3c-ccg traceability-vocab repository.

Issue Verifiable Business Card

As Alice

PR  VBC Contacts - Contacts Layout(3)

For this proof concept phase, we're only interested in the minimum functionality related to core features, so we will only have the option to invite Partners with no other options to to keep it simple.

  {
    "@context": [
      "https://www.w3.org/2018/credentials/v1",
      "https://w3id.org/traceability/v1"
    ],
    "id": contacts._id (internal uuid), 
    "type": [
      "VerifiableCredential",
      "VerifiableBusinessCard"
    ],
    "name": "Verifiable Business Card",
    "relatedLink": [
      {
        "type": "LinkRole",
        "target": "https://my-instance.com/presentations/available",
        "linkRelationship": "Client / Supplier/ Partner - OrganizationPresentationEndpoint"
      }
    ],
    "issuanceDate": "onclick time",
    "issuer": {
      "id": did:elem:member.did_suffix,
      "type": "Entity",
      "entityType": "Person",
      "name": member.display_name,
      "worksFor": {
        "type": "Organization",
        "name": organizations[member.organization_did_suffix].display_name ,
        "taxId": organizations[member.organization_did_suffix].taxId,
        "url": organizations[member.organization_did_suffix].homepage
        "addressCountry": "US"
      },
      "email": member.contact_email,
      "phoneNumber": member.phone_num
    },
    "credentialSubject": {
      "id": did:elem:member.did_suffix,
      "type": "Entity",
      "entityType": "Person",
      "name": member.display_name,
      "worksFor": {
        "type": "Organization",
        "name": organizations[member.organization_did_suffix].display_name ,
        "taxId": organizations[member.organization_did_suffix].taxId,
        "url": organizations[member.organization_did_suffix].homepage
        "addressCountry": "US"
      },
      "email": member.contact_email,
      "phoneNumber": member.phone_num
    },
    "proof": {
      "type": "Ed25519Signature2018",
      "created": "2022-03-25T14:58:05Z",
      "verificationMethod": "did:key:z6MktHQo3fRRohk44dsbE76CuiTpBmyMWq2VVjvV6aBSeE3U#z6MktHQo3fRRohk44dsbE76CuiTpBmyMWq2VVjvV6aBSeE3U",
      "proofPurpose": "assertionMethod",
      "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..4Bn--GdSHVheEcn0ARPVVMHkzrA2QdFhc0NdNYcGH-8nVIZgdguuTspxOPSAIDntcLDP49eR4eEQyTuC7EdMCg"
    }
  }

When the Verifiable Business Card is issued, Alice's Node creates a pending

Send Verifiable Business Card

The method in which the Verifiable Business Card is handed to the other contact is not covered within the functionality of the application. The Verifiable Business Card is intended to be handed to the other party via email, or private message through another service in which the two contacts are already accepted on.

Import Verifiable Business Card

As Bob

PR  VBC Contacts - Import Contact

Once the Verifiable Business Card has been transferred from Alice to Bob, we switch over to Bob, who drag and drops Alice's Verifiable Business Card into his contacts drag and drop area.

PR  VBC Contacts - Confirm Contact(1)

Once the contents of the Verifiable Business Card have been read, the application shows a modal asking to confirm the contents of the contact.

PR  VBC Contacts - Server Flow

Once Bob clicks on Add Contact, it should trigger the flow as shown above, to use Presentations to send Bob's Verifiable Business Card to Alice's node, so that Alice's node can confirm the invitation, and register the contact. Once Bob's Node gets a confirmation response, Bob's node will register the contact as well.

Approach

The current flow already performs a similar function. The main two aspects to keep in mind are as follows.

  1. Changes from issue: https://github.com/WebServiceDevelopment/PrivateInvoice/issues/2
  2. Setting up the presentations end point, and having it react to Verifiable Business Cards

Which means that we might not want to update contacts in that issue, but leave it as-is and map to the updated domain model as needed. And then in this issue, we can update contacts, and then remove the mappings for the domain model updates.

  1. Create a branch named vbc-presentation-contacts
  2. Add the ability to export a VBC from the contacts
  3. Change the add contact flow to use /presentation/available instead of /messages
  4. Change the add contact flow to use /presentation/submissions
  5. Submit Pull Request
wsdOgawa commented 2 years ago

Edit notes: Mentions about functionality that is not going to be included in this issue have been moved to https://github.com/WebServiceDevelopment/PrivateInvoice/issues/4.

wsdOgawa commented 2 years ago

The traceability schema gives me somewhere to start. Looking back at this issue I would prefer to define my own JSON schema, but that would be adding more complexity on top of what already needs to be implemented. So we'll have to defer that for a later time.

Right now the next step is that we have confirmed how to make a did in issue https://github.com/WebServiceDevelopment/PrivateInvoice/issues/2. The next step is that we want to make a simple as possible example for signing a credential with private keys and a did. Since a verifiable business card is one of the simpler credentials for us to sign, we might as well do that code snippet in this function.

First I think we want to do a simple sign example. Then I think we want to expand that out to decide on what relationships we want for the verifiable business card to have with relation to contacts.

wsdOgawa commented 2 years ago

We can use these values can an example. In this case we might want to have our document loader load this specific did document instead of allowing it to go to the network.

did

did:elem:ganache:EiClyGV3hyFWAq3zFBDbXIh6gDpSrqWeiL4Dlrv9Gjdbxw

did document

{
  "@context": [
    "https://www.w3.org/ns/did/v1",
    "https://w3id.org/security/suites/jws-2020/v1",
    {
      "@vocab": "https://www.w3.org/ns/did#"
    }
  ],
  "id": "did:elem:ganache:EiClyGV3hyFWAq3zFBDbXIh6gDpSrqWeiL4Dlrv9Gjdbxw",
  "verificationMethod": [
    {
      "id": "#z6Mkf56ZMux5Di3vjhUKh1TFPhJLFXCRshEZ5s6hSFDXFqUK",
      "controller": "did:elem:ganache:EiClyGV3hyFWAq3zFBDbXIh6gDpSrqWeiL4Dlrv9Gjdbxw",
      "type": "JsonWebKey2020",
      "publicKeyJwk": {
        "kty": "OKP",
        "crv": "Ed25519",
        "x": "CS4utW8JLa1uK2m8s1MGmfLadVBir3tKVQql9C8cBoA"
      }
    }
  ],
  "authentication": [
    "#z6Mkf56ZMux5Di3vjhUKh1TFPhJLFXCRshEZ5s6hSFDXFqUK"
  ],
  "assertionMethod": [
    "#z6Mkf56ZMux5Di3vjhUKh1TFPhJLFXCRshEZ5s6hSFDXFqUK"
  ]
}

Keys

{
  "id": "did:key:z6Mkf56ZMux5Di3vjhUKh1TFPhJLFXCRshEZ5s6hSFDXFqUK#z6Mkf56ZMux5Di3vjhUKh1TFPhJLFXCRshEZ5s6hSFDXFqUK",
  "type": "JsonWebKey2020",
  "controller": "did:key:z6Mkf56ZMux5Di3vjhUKh1TFPhJLFXCRshEZ5s6hSFDXFqUK",
  "publicKeyJwk": {
    "kty": "OKP",
    "crv": "Ed25519",
    "x": "CS4utW8JLa1uK2m8s1MGmfLadVBir3tKVQql9C8cBoA"
  },
  "privateKeyJwk": {
    "kty": "OKP",
    "crv": "Ed25519",
    "x": "CS4utW8JLa1uK2m8s1MGmfLadVBir3tKVQql9C8cBoA",
    "d": "A_uk3z4SI_6ml15iOiuLkF-aUiqd210SoVmH2KhGYp0"
  },
  "@context": [
    "https://w3id.org/wallet/v1"
  ],
  "name": "Sidetree Key",
  "image": "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
  "description": "Generated by @sidetree/wallet.",
  "tags": []
}
wsdOgawa commented 2 years ago

And then something like this is probably the simplest credential that we can sign over.

{
  "@context": [
    "https://www.w3.org/2018/credentials/v1"
  ],
  "id": "urn:uuid:6e902b3b-295d-431f-a9aa-8e02a7c24e03",
  "type": [
    "VerifiableCredential"
  ],
  "issuer": "did:elem:ganache:EiClyGV3hyFWAq3zFBDbXIh6gDpSrqWeiL4Dlrv9Gjdbxw",
  "issuanceDate": "2010-01-01T19:23:24Z",
  "credentialSubject": {
    "id": "did:example:123"
  }
}
wsdOgawa commented 2 years ago

This code is a little messy, but it seems to work.

"use strict";

const transmute = require('@transmute/vc.js');
const {
  Ed25519Signature2018,
  Ed25519VerificationKey2018,
} = require('@transmute/ed25519-signature-2018');

const context = {
    "https://www.w3.org/2018/credentials/v1" : require('./context/v1.json')
}

const documentLoader = async (iri) => {

    if(context[iri]) {
        return { document: context[iri] };
    }

    const message = `Unsupported iri: ${iri}`;
    console.error(message);
    throw new Error(message);

}

const keyPair = {
    "id": "did:key:z6Mkf56ZMux5Di3vjhUKh1TFPhJLFXCRshEZ5s6hSFDXFqUK#z6Mkf56ZMux5Di3vjhUKh1TFPhJLFXCRshEZ5s6hSFDXFqUK",
    "type": "JsonWebKey2020",
    "controller": "did:key:z6Mkf56ZMux5Di3vjhUKh1TFPhJLFXCRshEZ5s6hSFDXFqUK",
    "publicKeyJwk": {
        "kty": "OKP",
        "crv": "Ed25519",
        "x": "CS4utW8JLa1uK2m8s1MGmfLadVBir3tKVQql9C8cBoA"
    },
    "privateKeyJwk": {
        "kty": "OKP",
        "crv": "Ed25519",
        "x": "CS4utW8JLa1uK2m8s1MGmfLadVBir3tKVQql9C8cBoA",
        "d": "A_uk3z4SI_6ml15iOiuLkF-aUiqd210SoVmH2KhGYp0"
    },
    "@context": [
        "https://w3id.org/wallet/v1"
    ],
    "name": "Sidetree Key",
    "image": "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
    "description": "Generated by @sidetree/wallet.",
    "tags": []
}

const credential = {
    "@context": [
        "https://www.w3.org/2018/credentials/v1"
    ],
    "id": "urn:uuid:6e902b3b-295d-431f-a9aa-8e02a7c24e03",
    "type": [
        "VerifiableCredential"
    ],
    "issuer": "did:elem:ganache:EiClyGV3hyFWAq3zFBDbXIh6gDpSrqWeiL4Dlrv9Gjdbxw",
    "issuanceDate": "2010-01-01T19:23:24Z",
    "credentialSubject": {
        "id": "did:example:123"
    }
}

const signCredential = async() => {

    const { items } = await transmute.verifiable.credential.create({
        credential,
        format: ['vc'],
        documentLoader,
        suite: new Ed25519Signature2018({
            key: await Ed25519VerificationKey2018.from(keyPair)
        })
    });

    const [ signedCredential ] = items;
    console.log(signedCredential);

}

signCredential();

We have three main pieces of information here.

  1. Our Key Pair
  2. Our Credential to be signed
  3. Our document Loader

The key pair is the public / private key that we previously generated. The credential to be signed is our simple-as-possible credential example. And the document loader is a simple function that takes an iri and returns a document. For example we need to send a network request for https://www.w3.org/2018/credentials/v1, so we do that ahead of time and return the in-memory version of it.

Which produces our signed document.

{
  '@context': [ 'https://www.w3.org/2018/credentials/v1' ],
  id: 'urn:uuid:6e902b3b-295d-431f-a9aa-8e02a7c24e03',
  type: [ 'VerifiableCredential' ],
  issuer: 'did:elem:ganache:EiClyGV3hyFWAq3zFBDbXIh6gDpSrqWeiL4Dlrv9Gjdbxw',
  issuanceDate: '2010-01-01T19:23:24Z',
  credentialSubject: { id: 'did:example:123' },
  proof: {
    type: 'Ed25519Signature2018',
    created: '2022-05-30T00:52:18Z',
    verificationMethod: 'did:key:z6Mkf56ZMux5Di3vjhUKh1TFPhJLFXCRshEZ5s6hSFDXFqUK#z6Mkf56ZMux5Di3vjhUKh1TFPhJLFXCRshEZ5s6hSFDXFqUK',
    proofPurpose: 'assertionMethod',
    jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..WtTqYBya2wQkMO_GacPSUja6YO48QNYPWv8nD3HqLULV14Z8RKQ5p4yCPW3-BokmL-encOhpJlfp_-YJ_IVtBA'
  }
}

As a kind of off-hand musing. I'm realizing that my design for this application might as well be using did:key instead of did:elem:ganache. As the requirement is that each party already has a contact with a public key embedded into it. Which means that there's not a lot of reason to resolve a public-key from the network if it already exists on the contact's node. The benefit of allowing a third party to resolve the public key seems debatable as well. However, there's no specific reason to deviate from the current design.

wsdOgawa commented 2 years ago
  1. Need to change did:key to did:elem
  2. Need to consume invite code
wsdOgawa commented 2 years ago

Spill over issues have been moved to https://github.com/WebServiceDevelopment/PrivateInvoice/issues/15. Contacts have been moved from custom json to verifiable business cards. Marking this as resolved.