-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(backup): introduce new schema to minimize backup length
This commit adds a new backup schema designed to reduce the overall length of backups. The new schema is implemented in version 0.0.2, enhancing efficiency and reducing storage requirements. Signed-off-by: jeyem <me@e-mahmoudi.me>
- Loading branch information
Showing
11 changed files
with
628 additions
and
355 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,17 @@ | ||
import { Schema as v0_0_1 } from "./v0_0_1"; | ||
import { Schema as v0_0_2 } from "./v0_0_2"; | ||
|
||
/** | ||
* All supported backup schemas | ||
*/ | ||
export type Schema = v0_0_1; | ||
export type Schema = v0_0_1 | v0_0_2; | ||
export type Version = "0.0.1" | "0.0.2"; | ||
|
||
export const defaultVersion = "0.0.1"; | ||
|
||
export { v0_0_1 }; | ||
export const versions: Version[] = [ | ||
"0.0.1", | ||
"0.0.2", | ||
]; | ||
|
||
export { v0_0_1, v0_0_2 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/** | ||
* Schema definition for Backup V0.0.2 | ||
*/ | ||
import * as TB from "@sinclair/typebox"; | ||
|
||
const credential = TB.Object({ | ||
recovery_id: TB.String(), | ||
data: TB.String(), | ||
}); | ||
|
||
const did = TB.Object({ | ||
did: TB.String(), | ||
alias: TB.Optional(TB.String()), | ||
}); | ||
|
||
const didpair = TB.Object({ | ||
holder: TB.String(), | ||
recipient: TB.String(), | ||
alias: TB.String(), | ||
}); | ||
|
||
const key = TB.Object({ | ||
recovery_id: TB.String(), | ||
key: TB.String(), | ||
did: TB.Optional(TB.String()), | ||
index: TB.Optional(TB.Number()), | ||
}); | ||
|
||
|
||
export const Schema = TB.Object({ | ||
version: TB.Optional(TB.Literal("0.0.2")), | ||
credentials: TB.Array(credential), | ||
dids: TB.Array(did), | ||
did_pairs: TB.Array(didpair), | ||
keys: TB.Array(key), | ||
}); | ||
|
||
export type Schema = TB.Static<typeof Schema>; | ||
|
||
export namespace Schema { | ||
export type Credential = TB.Static<typeof credential>; | ||
export type DID = TB.Static<typeof did>; | ||
export type DIDPair = TB.Static<typeof didpair>; | ||
export type Key = TB.Static<typeof key>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import * as Domain from "../../../../domain"; | ||
import * as Models from "../../../models"; | ||
import { JWTVerifiableCredentialRecoveryId } from "../../../../pollux/models/JWTVerifiableCredential"; | ||
import { repositoryFactory } from "../../../repositories/builders/factory"; | ||
import { IBackupTask } from "../interfaces"; | ||
import { SDJWTVerifiableCredentialRecoveryId } from "../../../../pollux/models/SDJWTVerifiableCredential"; | ||
import { base64url } from "multiformats/bases/base64"; | ||
|
||
export class BackupTask implements IBackupTask { | ||
constructor( | ||
private readonly Pluto: Domain.Pluto, | ||
private readonly Repositories: ReturnType<typeof repositoryFactory> | ||
) {} | ||
|
||
async run(): Promise<Domain.Backup.Schema> { | ||
const credentials = await this.getCredentialBackups(); | ||
const didModels = await this.Repositories.DIDs.getModels(); | ||
const dids = didModels.map(this.mapDid); | ||
const did_pairs = await this.getDidPairBackups(); | ||
const keys = await this.getKeyBackups(didModels); | ||
|
||
const json: Domain.Backup.v0_0_2 = { | ||
version: "0.0.2", | ||
credentials, | ||
dids, | ||
did_pairs, | ||
keys, | ||
}; | ||
|
||
return json; | ||
} | ||
|
||
async getCredentialBackups(): Promise<Domain.Backup.v0_0_1.Credential[]> { | ||
const credentialModels = await this.Repositories.Credentials.getModels(); | ||
return credentialModels.map(this.mapCredential); | ||
} | ||
|
||
async getDidPairBackups(): Promise<Domain.Backup.v0_0_1.DIDPair[]> { | ||
const pairLinks = await this.Repositories.DIDLinks.getModels({ selector: { role: Models.DIDLink.role.pair } }); | ||
const didTuples = pairLinks.map<Domain.Backup.v0_0_1.DIDPair>(link => ({ | ||
alias: link.alias ?? "", | ||
holder: link.hostId, | ||
recipient: link.targetId | ||
})); | ||
|
||
return didTuples; | ||
} | ||
|
||
async getKeyBackups(didModels: Models.DID[]): Promise<Domain.Backup.v0_0_1.Key[]> { | ||
const keys = await this.Repositories.Keys.get(); | ||
const didKeyLinks = await this.Repositories.DIDKeyLinks.getModels(); | ||
|
||
const backupKeys = keys.reduce<Domain.Backup.v0_0_1.Key[]>((acc, key) => { | ||
if (key.isExportable() && key.isStorable()) { | ||
const keyLink = didKeyLinks.find(x => x.keyId === key.uuid); | ||
const did = didModels.find(x => x.uuid === keyLink?.didId); | ||
const jwk = key.to.JWK(); | ||
|
||
const backup: Domain.Backup.v0_0_1.Key = { | ||
recovery_id: key.recoveryId, | ||
key: base64url.baseEncode(Buffer.from(JSON.stringify(jwk))), | ||
index: key.index, | ||
did: did?.uuid, | ||
}; | ||
return acc.concat(backup); | ||
} | ||
|
||
return acc; | ||
}, []); | ||
|
||
return backupKeys; | ||
} | ||
|
||
async getLinkSecretBackup(): Promise<Domain.Backup.v0_0_1.LinkSecret> { | ||
const linksecret = await this.Repositories.LinkSecrets.findOne(); | ||
|
||
return linksecret?.secret ?? undefined; | ||
} | ||
|
||
|
||
private mapCredential = (model: Models.Credential): Domain.Backup.v0_0_1.Credential => { | ||
const isJWT = model.recoveryId === JWTVerifiableCredentialRecoveryId; | ||
const isSDJWT = model.recoveryId === SDJWTVerifiableCredentialRecoveryId; | ||
const recoveryId = isJWT ? "jwt" : isSDJWT ? "sdjwt" : "anoncred"; | ||
const data = isJWT || isSDJWT ? JSON.parse(model.dataJson).id : model.dataJson; | ||
|
||
return { | ||
recovery_id: recoveryId, | ||
data: base64url.baseEncode(Buffer.from(data)), | ||
}; | ||
}; | ||
|
||
private mapDid = (model: Models.DID): Domain.Backup.v0_0_1.DID => ({ | ||
did: model.uuid, | ||
alias: model.alias | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import * as Domain from "../../../../domain"; | ||
import { Ed25519PrivateKey } from "../../../../apollo/utils/Ed25519PrivateKey"; | ||
import { Secp256k1PrivateKey } from "../../../../apollo/utils/Secp256k1PrivateKey"; | ||
import { X25519PrivateKey } from "../../../../apollo/utils/X25519PrivateKey"; | ||
import { AnonCredsCredential } from "../../../../pollux/models/AnonCredsVerifiableCredential"; | ||
import { JWTCredential } from "../../../../pollux/models/JWTVerifiableCredential"; | ||
import { notEmptyString, notNil, validate } from "../../../../utils"; | ||
import { IRestoreTask } from "../interfaces"; | ||
import { base64url } from "multiformats/bases/base64"; | ||
|
||
export class RestoreTask implements IRestoreTask { | ||
constructor( | ||
private readonly Pluto: Domain.Pluto, | ||
private readonly backup: Domain.Backup.v0_0_2, | ||
) { } | ||
|
||
async run(): Promise<void> { | ||
validate(this.backup, Domain.Backup.v0_0_2); | ||
await this.restoreCredentials(); | ||
await this.restoreDids(); | ||
await this.restoreDidPairs(); | ||
await this.restoreKeys(); | ||
} | ||
|
||
async restoreCredentials() { | ||
const credentials = this.backup.credentials.map<Domain.Credential>(item => { | ||
const decoded = Buffer.from(base64url.baseDecode(item.data)).toString() | ||
if (item.recovery_id === "jwt") { | ||
return JWTCredential.fromJWS(decoded); | ||
} | ||
if (item.recovery_id === "anoncred") { | ||
return AnonCredsCredential.fromJson(decoded); | ||
} | ||
throw new Domain.PlutoError.RestoreCredentialInvalidError(); | ||
}); | ||
|
||
await Promise.all(credentials.map(x => this.Pluto.storeCredential(x))); | ||
} | ||
|
||
async restoreDids() { | ||
await Promise.all( | ||
this.backup.dids.map(x => this.Pluto.storeDID(Domain.DID.from(x.did), [], x.alias)) | ||
); | ||
} | ||
|
||
async restoreDidPairs() { | ||
await Promise.all( | ||
this.backup.did_pairs.map(item => { | ||
const host = Domain.DID.from(item.holder); | ||
const target = Domain.DID.from(item.recipient); | ||
return this.Pluto.storeDIDPair(host, target, item.alias); | ||
}) | ||
); | ||
} | ||
|
||
async restoreKeys() { | ||
return Promise.all(this.backup.keys.map(item => { | ||
|
||
const jwk = JSON.parse(Buffer.from(base64url.baseDecode(item.key)).toString()); | ||
const key = this.jwkToDomain(jwk); | ||
if (notNil(item.index)) { | ||
key.keySpecification.set(Domain.KeyProperties.index, item.index.toString()); | ||
} | ||
if (notEmptyString(item.did)) { | ||
return this.Pluto.storeDID(Domain.DID.from(item.did), key); | ||
} else { | ||
return this.Pluto.storePrivateKey(key); | ||
} | ||
}) | ||
) | ||
} | ||
|
||
private jwkToDomain(jwk: Domain.JWK): Domain.PrivateKey { | ||
if ((jwk.kty === "OKP" || jwk.kty === "EC") && notEmptyString(jwk.d)) { | ||
switch (jwk.crv) { | ||
case Domain.Curve.SECP256K1.toLowerCase(): | ||
return Secp256k1PrivateKey.from.String(jwk.d, "base64url"); | ||
|
||
case Domain.Curve.ED25519: | ||
return Ed25519PrivateKey.from.String(jwk.d, "base64url"); | ||
|
||
case Domain.Curve.X25519: | ||
return X25519PrivateKey.from.String(jwk.d, "base64url"); | ||
} | ||
|
||
throw new Domain.PlutoError.RestoreKeyInvalidError(); | ||
} | ||
|
||
throw new Domain.PlutoError.RestoreJWKInvalidError(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from "./Backup"; | ||
export * from "./Restore"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import * as v0_0_1 from "./0_0_1"; | ||
import * as v0_0_2 from "./0_0_2"; | ||
|
||
export { v0_0_1 }; | ||
export { v0_0_1, v0_0_2 }; |
Oops, something went wrong.