UNPKG

wallet-storage

Version:

BRC100 conforming wallet, wallet storage and wallet signer components

153 lines (121 loc) 7.38 kB
import { Certificate as BsvCertificate, CompletedProtoWallet, KeyDeriver, MasterCertificate, PrivateKey, Utils, WalletCertificate } from "@bsv/sdk" import { sdk } from '../../index.all' describe('CertificateLifeCycle tests', () => { jest.setTimeout(99999999) test('0 encrypt decrypt sign verify', async () => { const subjectWallet = new CompletedProtoWallet(PrivateKey.fromRandom()) const { cert, certifier, subject } = makeSampleCert(subjectWallet.keyDeriver.rootKey.toString()) const c = new BsvCertificate(cert.type, cert.serialNumber, cert.subject, cert.certifier, cert.revocationOutpoint, cert.fields) const certifierWallet = new CompletedProtoWallet(certifier) const imc = await 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 sdk.CertOps(certifierWallet, cert) expect(co.signature).toBe('') await expect(co.verify()).rejects.toThrow('Signature DER must start with 0x30') await co.sign(new CompletedProtoWallet(new 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 CompletedProtoWallet(new 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 CompletedProtoWallet(new KeyDeriver(PrivateKey.fromHex('2'.repeat(64)))) const co2 = new 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 CompletedProtoWallet(subject) const co = new 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 CompletedProtoWallet(certifier) const co = new 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 CompletedProtoWallet(subject) const cs = await 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 = 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 CompletedProtoWallet(verifier) const cv = await 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?: string): { cert: WalletCertificate, subject: PrivateKey, certifier: PrivateKey } { const subject = subjectRootKeyHex ? PrivateKey.fromString(subjectRootKeyHex) : PrivateKey.fromRandom() const certifier = PrivateKey.fromRandom() const verifier = PrivateKey.fromRandom() const cert: WalletCertificate = { type: Utils.toBase64(new Array(32).fill(1)), serialNumber: 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 } }