wallet-storage
Version:
BRC100 conforming wallet, wallet storage and wallet signer components
123 lines • 7.52 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const sdk_1 = require("@bsv/sdk");
const index_all_1 = require("../../index.all");
describe('CertificateLifeCycle tests', () => {
jest.setTimeout(99999999);
test('0 encrypt decrypt sign verify', async () => {
const subjectWallet = new sdk_1.CompletedProtoWallet(sdk_1.PrivateKey.fromRandom());
const { cert, certifier, subject } = makeSampleCert(subjectWallet.keyDeriver.rootKey.toString());
const c = new sdk_1.Certificate(cert.type, cert.serialNumber, cert.subject, cert.certifier, cert.revocationOutpoint, cert.fields);
const certifierWallet = new sdk_1.CompletedProtoWallet(certifier);
const imc = await sdk_1.MasterCertificate.issueCertificateForSubject(certifierWallet, c.subject, c.fields, c.type, async () => c.revocationOutpoint);
const imcSignature = imc.signature;
await expect(imc.sign(certifierWallet)).rejects.toThrow('Certificate has already been signed');
expect(imcSignature).toBeTruthy();
expect(imcSignature).toBe(imc.signature);
const imcVerified = await imc.verify();
expect(imcVerified).toBe(true);
const dfs = await imc.decryptFields(subjectWallet);
for (const fn of Object.keys(cert.fields)) {
// decrypted fields should be original un-encrypted fields
expect(cert.fields[fn]).toBe(dfs[fn]);
// issued certificate fields should encrypted, not the original un-encrypted fields
expect(cert.fields[fn]).not.toBe(imc.fields[fn]);
}
await c.sign(certifierWallet);
const verified = await c.verify();
expect(verified).toBe(true);
const co = new index_all_1.sdk.CertOps(certifierWallet, cert);
expect(co.signature).toBe('');
await expect(co.verify()).rejects.toThrow('Signature DER must start with 0x30');
await co.sign(new sdk_1.CompletedProtoWallet(new sdk_1.KeyDeriver(certifier)));
expect(await co.verify()).toBe(true);
{
await co.encryptFields(subject.toPublicKey().toString());
await expect(co.verify()).rejects.toThrow('Signature is not valid');
co.signature = undefined;
await co.sign(new sdk_1.CompletedProtoWallet(new sdk_1.KeyDeriver(certifier)));
expect(await co.verify()).toBe(true);
}
await co.decryptFields();
for (const n of Object.keys(co.fields)) {
expect(co.fields[n]).toBe(co._decryptedFields[n]);
}
{
await co.encryptFields();
const crypto2 = new sdk_1.CompletedProtoWallet(new sdk_1.KeyDeriver(sdk_1.PrivateKey.fromHex('2'.repeat(64))));
const co2 = new index_all_1.sdk.CertOps(crypto2, co.toWalletCertificate());
// even with the keyring, without the right crypto root key decryption will fail.
co2._keyring = co._keyring;
await expect(co2.decryptFields()).rejects.toThrow('Decryption failed!');
}
});
test('1 createKeyringForVerifier', async () => {
const { cert, certifier, subject } = makeSampleCert();
const crypto = new sdk_1.CompletedProtoWallet(subject);
const co = new index_all_1.sdk.CertOps(crypto, cert);
});
test('2 complete flow', async () => {
// Issuer beging with an un-encrypted (decrypted) raw certificate template:
// The public keys of both the certifier (the authority issuing the certificate),
// and the subject (who the certificate pertains to) are included in the certificate.
const { cert, certifier, subject } = makeSampleCert();
// Next the certifier must encrypt the field values for privacy and sign the certificate
// such that the values it contains can be attributed to the certifier through its public key.
// Encryption is done with random symmetric keys and the keys are then encrypted by the certifier
// such that each key can also be decrypted by the subject:
const certifierWallet = new sdk_1.CompletedProtoWallet(certifier);
const co = new index_all_1.sdk.CertOps(certifierWallet, cert);
await co.encryptAndSignNewCertificate();
// Grab a copy of the certificate to send to the subject
const exportForSubject = co.exportForSubject();
// The subject imports their copy of the new certificate:
const subjectWallet = new sdk_1.CompletedProtoWallet(subject);
const cs = await index_all_1.sdk.CertOps.fromCertifier(subjectWallet, exportForSubject);
// The subject's imported certificate should verify
expect(await cs.verify()).toBe(true);
// Confirm subject can decrypt the certifier's copy of the cert:
await co.decryptFields(subject.toPublicKey().toString());
// Confirm subject can decrypt their own copy of the cert:
await cs.decryptFields(cs.certifier, co._keyring);
// Restore the encrypted field values.
cs.fields = cs._encryptedFields;
// Prepare to send certificate to third party veifier of the 'name' and 'email' fields.
// The verifier must be able to confirm the signature on the original certificate's encrypted values.
// And then use a keyRing that their public key will work to reveal decrypted values for 'name' and 'email' only.
const verifier = sdk_1.PrivateKey.fromRandom();
// subject makes a keyring for the verifier
const exportForVerifier = await cs.exportForCounterparty(verifier.toPublicKey().toString(), ['name', 'email']);
// The verifier uses their own wallet to import the certificate, verify it, and decrypt their designated fields.
const verifierWallet = new sdk_1.CompletedProtoWallet(verifier);
const cv = await index_all_1.sdk.CertOps.fromCounterparty(verifierWallet, exportForVerifier);
// verifier must check that the certifier's public key generates a matching signature over all the encrypted
// certificate field values before using their keyring to decrypt the fields they've been authorized to see.
expect(await cv.verify()).toBe(true);
// The wallet's private key is the verifier's, so the counterparty is the certificate subject (who sent the cert to verifier).
// This decrypt is using the keyring provided for verification by the subject.
await cv.decryptFields();
expect(cv.fields['name']).toBe('Alice');
expect(cv.fields['email']).toBe('alice@example.com');
expect(cv.fields['organization']).not.toBe('Example Corp');
});
});
function makeSampleCert(subjectRootKeyHex) {
const subject = subjectRootKeyHex ? sdk_1.PrivateKey.fromString(subjectRootKeyHex) : sdk_1.PrivateKey.fromRandom();
const certifier = sdk_1.PrivateKey.fromRandom();
const verifier = sdk_1.PrivateKey.fromRandom();
const cert = {
type: sdk_1.Utils.toBase64(new Array(32).fill(1)),
serialNumber: sdk_1.Utils.toBase64(new Array(32).fill(2)),
revocationOutpoint: 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef.1',
subject: subject.toPublicKey().toString(),
certifier: certifier.toPublicKey().toString(),
fields: {
name: 'Alice',
email: 'alice@example.com',
organization: 'Example Corp'
},
signature: "",
};
return { cert, subject, certifier };
}
//# sourceMappingURL=CertificateLifeCycle.test.js.map