-
Couldn't load subscription status.
- Fork 5
Description
This will be resolved in !209 https://gitlab.com/MatrixAI/Engineering/Polykey/js-polykey/-/merge_requests/209
Specification
!195 has changed the way that node -> node cryptolink claims will need to be created.
Recall that a claim (e.g. a cryptolink) has the following structure:
{
signatures: [
{
signature: <base64 encoding of signature, signed on header + payload>,
protected: <base64 encoding of serialized header (currently containing signing algorithm + node ID of signee>
},
...
],
payload: <base64 encoding of serialized payload (see Claim type for payload contents)>
}
For a keynode, these claims are stored on the node's sigchain.
Therefore, suppose a node X wants to establish a cryptolink to node Y. Both nodes require a claim on their respective sigchain that states they have a cryptolink to the other.
However, we require that any claim from a node -> node be signed by both nodes. Therefore, node X will need to sign both its own claim, and Y's claim on its sigchain (and vice-versa).
This allows any arbitrary node to easily verify the claim. Suppose A receives the cryptolink claim from X, stating that it has a cryptolink to Y. A would verify the claim with X's public key, but importantly, A doesn't need to see the corresponding claim on Y's sigchain - it only needs to verify this claim on X's sigchain with Y's public key.
We need a procedure in place to synchronise the creation of these claims between nodes.
Additional context
- original idea discussed here https://gitlab.com/MatrixAI/Engineering/Polykey/js-polykey/-/merge_requests/195#note_624698872
Tasks
This will likely make use of the notifications domain. The following process (or some other variation of it) will need to be implemented:
To create a cryptolink from X to Y:
Xsends a "cryptolink request" of some sort toYY(at some point in time) accepts this request.Xcreates the claim, and transfers the 'unsigned' claim toYYchecks the claim to see its correct, signs it, and sends it back toXXchecks the claim again and signs it itself.Xadds this claim to its sigchain- Repeat steps 3-6 in the reverse order to create a claim to store on
Y's sigchain
See the following code snippet for a working prototype of this process:
import type { NodeId } from './src/nodes/types';
import * as sigchainUtils from './src/sigchain/utils';
import * as claimsUtils from './src/claims/utils';
import * as keysUtils from './src/keys/utils';
import { KeyManager } from './src/keys';
import { GeneralSign } from 'jose/jws/general/sign';
import canonicalize from 'canonicalize';
import { createPrivateKey } from 'crypto'
async function main() {
// We want to establish a cryptolink connection between:
// Node A <--> Node C
// On Node A:
const aKeyPair = await keysUtils.generateKeyPair(4096);
const aKeyPairPem = keysUtils.keyPairToPem(aKeyPair);
const aPrivateKey = createPrivateKey(aKeyPairPem.privateKey);
const aHashPrev = 'hashA';
const aSeq = 2;
// On Node C:
const cKeyPair = await keysUtils.generateKeyPair(4096);
const cKeyPairPem = keysUtils.keyPairToPem(cKeyPair);
const cPrivateKey = createPrivateKey(cKeyPairPem.privateKey);
const cHashPrev = 'hashC';
const cSeq = 5;
// 1. On Node A:
// Node A constructs a claim from A -> C
const payload = {
hPrev: aHashPrev,
seq: aSeq,
data: {
type: 'node',
node1: 'A' as NodeId,
node2: 'C' as NodeId,
},
iat: Date.now()
}
const canonicalizedPayload = canonicalize(payload);
const byteEncoder = new TextEncoder();
const claim = new GeneralSign(byteEncoder.encode(canonicalizedPayload));
// Node A sends this claim over to Node C
// 2. On Node C:
// Node C should check the fields of the claim and make sure they're correct
// Then, Node C signs the claim
claim.addSignature(cPrivateKey).setProtectedHeader({ alg: 'RS256', kid: 'C' });
// Node C sends this signed claim back to Node A
// 3. On Node A:
// Node A should check the fields of the signed claim and make sure they're correct
// Then, Node A also signs the claim
claim.addSignature(aPrivateKey).setProtectedHeader({ alg: 'RS256', kid: 'A' });
// Finally, we perform the '.sign' operation
const jws = await claim.sign();
console.log(jws);
// Node A stores this JWS claim on its signchain
}
main();This will require refactoring the createClaim utility function in claims/utils. See this todo note:
// TODO: Potentially need a createUnsignedClaim function that returns a
// claim = new GeneralSign(payload). This will be needed to sign the claim by
// both nodes, when creating a node -> node claim.utils.test.ts should be extended to ensure multiple signatures can be added and subsequently verified (currently, they only test claims where 1 signature exists).