Closed wsdOgawa closed 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.
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.
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": []
}
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"
}
}
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.
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.
did:key
to did:elem
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.
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
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.
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
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.
Once the contents of the Verifiable Business Card have been read, the application shows a modal asking to confirm the contents of the contact.
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.
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.
vbc-presentation-contacts
/presentation/available
instead of/messages
/presentation/submissions