diff --git a/examples/integration-scripts/modules/bip39_shim.ts b/examples/integration-scripts/modules/bip39_shim.ts index 355297f8..9642e63b 100644 --- a/examples/integration-scripts/modules/bip39_shim.ts +++ b/examples/integration-scripts/modules/bip39_shim.ts @@ -1,7 +1,7 @@ import { mnemonicToSeedSync, generateMnemonic } from 'bip39'; -import { Diger, Signer, MtrDex } from 'signify-ts'; +import { Diger, Signer, MtrDex, Keeper, KeeperResult, Algos } from 'signify-ts'; -export class BIP39Shim { +export class BIP39Shim implements Keeper { private icount: number; private ncount: number; private dcode: string | undefined; @@ -10,6 +10,8 @@ export class BIP39Shim { private transferable: boolean; private stem: string; private mnemonics: string = ''; + algo: Algos = Algos.extern; + signers: Signer[] = []; constructor(pidx: number, kargs: any) { this.icount = kargs.icount ?? 1; @@ -47,7 +49,7 @@ export class BIP39Shim { return keys; } - incept(transferable: boolean) { + async incept(transferable: boolean): Promise { const signers = this.keys(this.icount, this.kidx, transferable); const verfers = signers.map((signer) => signer.verfer.qb64); @@ -63,7 +65,12 @@ export class BIP39Shim { return [verfers, digers]; } - rotate(ncount: number, transferable: boolean) { + async rotate( + // TODO: This signature is incompatible with Keeper + // eslint-disable-next-line @typescript-eslint/no-explicit-any + count: any, //number, + transferable: boolean + ): Promise { const signers = this.keys( this.ncount, this.kidx + this.icount, @@ -73,7 +80,9 @@ export class BIP39Shim { this.kidx = this.kidx + this.icount; this.icount = this.ncount; - this.ncount = ncount; + + // TODO: Due to incompatible signature. + this.ncount = count as number; const nsigners = this.keys( this.ncount, @@ -88,7 +97,7 @@ export class BIP39Shim { return [verfers, digers]; } - sign( + async sign( ser: Uint8Array, indexed = true, indices: number[] | undefined = undefined, @@ -133,7 +142,7 @@ export class BIP39Shim { return sigers.map((siger) => siger.qb64); } else { const cigars = []; - for (const [_, signer] of signers.entries()) { + for (const [, signer] of signers.entries()) { cigars.push(signer.sign(ser)); } return cigars.map((cigar) => cigar.qb64); diff --git a/examples/integration-scripts/multisig-vlei-issuance.test.ts b/examples/integration-scripts/multisig-vlei-issuance.test.ts index 930d95ff..e429940a 100644 --- a/examples/integration-scripts/multisig-vlei-issuance.test.ts +++ b/examples/integration-scripts/multisig-vlei-issuance.test.ts @@ -17,6 +17,7 @@ import { waitForNotifications, } from './utils/test-util'; import { getOrCreateClients, getOrCreateContact } from './utils/test-setup'; +import { HabState } from '../../src/keri/core/state'; const { vleiServerUrl, witnessIds } = resolveEnvironment(); @@ -66,12 +67,6 @@ const ECR_RULES = Saider.saidify({ }, })[1]; -interface Aid { - name: string; - prefix: string; - state: any; -} - test('multisig-vlei-issuance', async function run() { /** * The abbreviations used in this script follows GLEIF vLEI @@ -190,7 +185,7 @@ test('multisig-vlei-issuance', async function run() { // Create a multisig AID for the GEDA. // Skip if a GEDA AID has already been incepted. - let aidGEDAbyGAR1, aidGEDAbyGAR2: Aid; + let aidGEDAbyGAR1, aidGEDAbyGAR2: HabState; try { aidGEDAbyGAR1 = await clientGAR1.identifiers().get('GEDA'); aidGEDAbyGAR2 = await clientGAR2.identifiers().get('GEDA'); @@ -291,7 +286,7 @@ test('multisig-vlei-issuance', async function run() { // Create a multisig AID for the QVI. // Skip if a QVI AID has already been incepted. - let aidQVIbyQAR1, aidQVIbyQAR2, aidQVIbyQAR3: Aid; + let aidQVIbyQAR1, aidQVIbyQAR2, aidQVIbyQAR3: HabState; try { aidQVIbyQAR1 = await clientQAR1.identifiers().get('QVI'); aidQVIbyQAR2 = await clientQAR2.identifiers().get('QVI'); @@ -655,7 +650,7 @@ test('multisig-vlei-issuance', async function run() { // Create a multisig AID for the LE. // Skip if a LE AID has already been incepted. - let aidLEbyLAR1, aidLEbyLAR2, aidLEbyLAR3: Aid; + let aidLEbyLAR1, aidLEbyLAR2, aidLEbyLAR3: HabState; try { aidLEbyLAR1 = await clientLAR1.identifiers().get('LE'); aidLEbyLAR2 = await clientLAR2.identifiers().get('LE'); @@ -1249,31 +1244,30 @@ async function getOrCreateAID( client: SignifyClient, name: string, kargs: CreateIdentiferArgs -): Promise { - let aid: Aid; +): Promise { try { - aid = await client.identifiers().get(name); + return await client.identifiers().get(name); } catch { const result: EventResult = await client .identifiers() .create(name, kargs); await waitOperation(client, await result.op()); - aid = await client.identifiers().get(name); + const aid = await client.identifiers().get(name); const op = await client .identifiers() .addEndRole(name, 'agent', client!.agent!.pre); await waitOperation(client, await op.op()); console.log(name, 'AID:', aid.prefix); + return aid; } - return aid; } async function createAIDMultisig( client: SignifyClient, - aid: Aid, - otherMembersAIDs: Aid[], + aid: HabState, + otherMembersAIDs: HabState[], groupName: string, kargs: CreateIdentiferArgs, isInitiator: boolean = false @@ -1311,9 +1305,9 @@ async function createAIDMultisig( async function interactMultisig( client: SignifyClient, - aid: Aid, - otherMembersAIDs: Aid[], - multisigAID: Aid, + aid: HabState, + otherMembersAIDs: HabState[], + multisigAID: HabState, anchor: { i: string; s: string; d: string }, isInitiator: boolean = false ) { @@ -1351,9 +1345,9 @@ async function interactMultisig( async function addEndRoleMultisig( client: SignifyClient, - aid: Aid, - otherMembersAIDs: Aid[], - multisigAID: Aid, + aid: HabState, + otherMembersAIDs: HabState[], + multisigAID: HabState, timestamp: string, isInitiator: boolean = false ) { @@ -1411,9 +1405,9 @@ async function addEndRoleMultisig( async function createRegistryMultisig( client: SignifyClient, - aid: Aid, - otherMembersAIDs: Aid[], - multisigAID: Aid, + aid: HabState, + otherMembersAIDs: HabState[], + multisigAID: HabState, registryName: string, nonce: string, isInitiator: boolean = false @@ -1456,8 +1450,8 @@ async function createRegistryMultisig( async function getIssuedCredential( issuerClient: SignifyClient, - issuerAID: Aid, - recipientAID: Aid, + issuerAID: HabState, + recipientAID: HabState, schemaSAID: string ) { const credentialList = await issuerClient.credentials().list({ @@ -1473,8 +1467,8 @@ async function getIssuedCredential( async function issueCredentialMultisig( client: SignifyClient, - aid: Aid, - otherMembersAIDs: Aid[], + aid: HabState, + otherMembersAIDs: HabState[], multisigAIDName: string, kargsIss: CredentialData, isInitiator: boolean = false @@ -1516,10 +1510,10 @@ async function issueCredentialMultisig( async function grantMultisig( client: SignifyClient, - aid: Aid, - otherMembersAIDs: Aid[], - multisigAID: Aid, - recipientAID: Aid, + aid: HabState, + otherMembersAIDs: HabState[], + multisigAID: HabState, + recipientAID: HabState, credential: any, timestamp: string, isInitiator: boolean = false @@ -1568,10 +1562,10 @@ async function grantMultisig( async function admitMultisig( client: SignifyClient, - aid: Aid, - otherMembersAIDs: Aid[], - multisigAID: Aid, - recipientAID: Aid, + aid: HabState, + otherMembersAIDs: HabState[], + multisigAID: HabState, + recipientAID: HabState, timestamp: string // numGrantMsgs: number ) { @@ -1617,8 +1611,8 @@ async function admitMultisig( async function admitSinglesig( client: SignifyClient, - aid: Aid, - recipientAid: Aid + aid: HabState, + recipientAid: HabState ) { const grantMsgSaid = await waitAndMarkNotification( client, diff --git a/examples/integration-scripts/singlesig-vlei-issuance.test.ts b/examples/integration-scripts/singlesig-vlei-issuance.test.ts index 6aacbc51..6734d68a 100644 --- a/examples/integration-scripts/singlesig-vlei-issuance.test.ts +++ b/examples/integration-scripts/singlesig-vlei-issuance.test.ts @@ -106,6 +106,13 @@ const ECR_AUTH_RULES = Saider.saidify({ const OOR_RULES = LE_RULES; const OOR_AUTH_RULES = LE_RULES; +const CRED_RETRY_DEFAULTS = { + maxSleep: 1000, + minSleep: 10, + maxRetries: 5, + timeout: 10000, +}; + interface Aid { name: string; prefix: string; @@ -193,8 +200,9 @@ test('singlesig-vlei-issuance', async function run() { const cred = await getGrantedCredential(qviClient, qviCred.sad.d); assert(cred !== undefined); return cred; - }); + }, CRED_RETRY_DEFAULTS); } + assert.equal(qviCredHolder.sad.d, qviCred.sad.d); assert.equal(qviCredHolder.sad.s, QVI_SCHEMA_SAID); assert.equal(qviCredHolder.sad.i, gleifAid.prefix); @@ -232,8 +240,9 @@ test('singlesig-vlei-issuance', async function run() { const cred = await getGrantedCredential(leClient, leCred.sad.d); assert(cred !== undefined); return cred; - }); + }, CRED_RETRY_DEFAULTS); } + assert.equal(leCredHolder.sad.d, leCred.sad.d); assert.equal(leCredHolder.sad.s, LE_SCHEMA_SAID); assert.equal(leCredHolder.sad.i, qviAid.prefix); @@ -273,8 +282,9 @@ test('singlesig-vlei-issuance', async function run() { const cred = await getGrantedCredential(roleClient, ecrCred.sad.d); assert(cred !== undefined); return cred; - }); + }, CRED_RETRY_DEFAULTS); } + assert.equal(ecrCredHolder.sad.d, ecrCred.sad.d); assert.equal(ecrCredHolder.sad.s, ECR_SCHEMA_SAID); assert.equal(ecrCredHolder.sad.i, leAid.prefix); @@ -320,8 +330,9 @@ test('singlesig-vlei-issuance', async function run() { ); assert(cred !== undefined); return cred; - }); + }, CRED_RETRY_DEFAULTS); } + assert.equal(ecrAuthCredHolder.sad.d, ecrAuthCred.sad.d); assert.equal(ecrAuthCredHolder.sad.s, ECR_AUTH_SCHEMA_SAID); assert.equal(ecrAuthCredHolder.sad.i, leAid.prefix); @@ -363,8 +374,9 @@ test('singlesig-vlei-issuance', async function run() { const cred = await getGrantedCredential(roleClient, ecrCred2.sad.d); assert(cred !== undefined); return cred; - }); + }, CRED_RETRY_DEFAULTS); } + assert.equal(ecrCredHolder2.sad.d, ecrCred2.sad.d); assert.equal(ecrCredHolder2.sad.s, ECR_SCHEMA_SAID); assert.equal(ecrCredHolder2.sad.i, qviAid.prefix); @@ -409,8 +421,9 @@ test('singlesig-vlei-issuance', async function run() { ); assert(cred !== undefined); return cred; - }); + }, CRED_RETRY_DEFAULTS); } + assert.equal(oorAuthCredHolder.sad.d, oorAuthCred.sad.d); assert.equal(oorAuthCredHolder.sad.s, OOR_AUTH_SCHEMA_SAID); assert.equal(oorAuthCredHolder.sad.i, leAid.prefix); @@ -451,8 +464,9 @@ test('singlesig-vlei-issuance', async function run() { const cred = await getGrantedCredential(roleClient, oorCred.sad.d); assert(cred !== undefined); return cred; - }); + }, CRED_RETRY_DEFAULTS); } + assert.equal(oorCredHolder.sad.d, oorCred.sad.d); assert.equal(oorCredHolder.sad.s, OOR_SCHEMA_SAID); assert.equal(oorCredHolder.sad.i, qviAid.prefix); diff --git a/src/keri/app/aiding.ts b/src/keri/app/aiding.ts index f508f182..011848fa 100644 --- a/src/keri/app/aiding.ts +++ b/src/keri/app/aiding.ts @@ -7,6 +7,7 @@ import { MtrDex } from '../core/matter'; import { Serder } from '../core/serder'; import { parseRangeHeaders } from '../core/httping'; import { KeyManager } from '../core/keeping'; +import { HabState } from '../core/state'; /** Arguments required to create an identfier */ export interface CreateIdentiferArgs { @@ -25,9 +26,9 @@ export interface CreateIdentiferArgs { rstates?: any[]; prxs?: any[]; nxts?: any[]; - mhab?: any; - keys?: any[]; - ndigs?: any[]; + mhab?: HabState; + keys?: string[]; + ndigs?: string[]; bran?: string; count?: number; ncount?: number; @@ -111,7 +112,7 @@ export class Identifier { * @param {string} name Name or alias of the identifier * @returns {Promise} A promise to the identifier information */ - async get(name: string): Promise { + async get(name: string): Promise { const path = `/identifiers/${encodeURIComponent(name)}`; const data = null; const method = 'GET'; diff --git a/src/keri/app/credentialing.ts b/src/keri/app/credentialing.ts index ca08a9ab..f6a4e4f3 100644 --- a/src/keri/app/credentialing.ts +++ b/src/keri/app/credentialing.ts @@ -1,5 +1,4 @@ import { SignifyClient } from './clienting'; -import { Salter } from '../core/salter'; import { interact, messagize } from '../core/eventing'; import { vdr } from '../core/vdring'; import { @@ -21,6 +20,7 @@ import { serializeIssExnAttachment, } from '../core/utils'; import { Operation } from './coring'; +import { HabState } from '../core/state'; /** Types of credentials */ export class CredentialTypes { @@ -211,9 +211,13 @@ export class Credentials { const [, acdc] = Saider.saidify({ v: versify(Ident.ACDC, undefined, Serials.JSON, 0), d: '', + u: args.u, i: args.i ?? hab.prefix, - ...args, + ri: args.ri, + s: args.s, a: subject, + e: args.e, + r: args.r, }); const [, iss] = Saider.saidify({ @@ -408,7 +412,7 @@ export class Credentials { const keeper = this.client!.manager!.get(hab); - const sig = keeper.sign(b(exn.raw), true); + const sig = await keeper.sign(b(exn.raw), true); const siger = new Siger({ qb64: sig[0] }); const seal = ['SealLast', { i: pre }]; @@ -634,7 +638,7 @@ export class Registries { } createFromEvents( - hab: Dict, + hab: HabState, name: string, registryName: string, vcp: Dict, @@ -743,7 +747,7 @@ export class Ipex { let atc = args.ancAttachment; if (atc === undefined) { - const keeper = this.client.manager?.get(hab); + const keeper = this.client.manager!.get(hab); const sigs = await keeper.sign(b(args.anc.raw)); const sigers = sigs.map((sig: string) => new Siger({ qb64: sig })); const ims = d(messagize(args.anc, sigers)); diff --git a/src/keri/app/exchanging.ts b/src/keri/app/exchanging.ts index d000c5a8..00dd0809 100644 --- a/src/keri/app/exchanging.ts +++ b/src/keri/app/exchanging.ts @@ -5,6 +5,7 @@ import { nowUTC } from '../core/utils'; import { Pather } from '../core/pather'; import { Counter, CtrDex } from '../core/counter'; import { Saider } from '../core/saider'; +import { HabState } from '../core/state'; /** * Exchanges @@ -33,7 +34,7 @@ export class Exchanges { * @param dig */ async createExchangeMessage( - sender: Dict, + sender: HabState, route: string, payload: Dict, embeds: Dict, @@ -72,7 +73,7 @@ export class Exchanges { async send( name: string, topic: string, - sender: Dict, + sender: HabState, route: string, payload: Dict, embeds: Dict, diff --git a/src/keri/core/eventing.ts b/src/keri/core/eventing.ts index d03e4473..3130b159 100644 --- a/src/keri/core/eventing.ts +++ b/src/keri/core/eventing.ts @@ -67,9 +67,9 @@ export function rotate({ throw new Error(`Invalid ilk = ${ilk} for rot or drt.`); } - const sner = Number(sn); - if (sner < 1) { - throw new Error(`Invalid sn = 0x${sner.toString()} for rot or drt.`); + const sner = new CesrNumber({}, sn); + if (sner.num < 1) { + throw new Error(`Invalid sn = 0x${sner.numh} for rot or drt.`); } let _isit: number; @@ -199,7 +199,7 @@ export function rotate({ t: _ilk, d: '', i: pre, - s: sner.toString(16), + s: sner.numh, p: dig, kt: tholder.num && diff --git a/src/keri/core/keeping.ts b/src/keri/core/keeping.ts index 13f199b6..8ec0ec9b 100644 --- a/src/keri/core/keeping.ts +++ b/src/keri/core/keeping.ts @@ -9,28 +9,79 @@ import { Cipher } from './cipher'; import { Diger } from './diger'; import { Prefixer } from './prefixer'; import { Signer } from './signer'; -import { Siger } from './siger'; -import { Cigar } from './cigar'; - -export {}; +import { HabState, State } from './state'; /** External module definition */ +export interface ExternalModuleType { + new (pidx: number, args: KeeperParams): Keeper; +} + export interface ExternalModule { type: string; name: string; - module: any; + module: ExternalModuleType; +} + +export type KeeperResult = [string[], string[]]; +export type SignResult = string[]; + +export interface KeeperParams { + [key: string]: unknown; +} + +export interface SaltyParams extends KeeperParams { + pidx: number; + kidx: number; + tier: Tier; + transferable: boolean; + stem: string | undefined; + icodes: string[] | undefined; + ncodes: string[] | undefined; + dcode: string | undefined; + sxlt: string | undefined; +} + +export interface RandyParams extends KeeperParams { + nxts?: string[]; + prxs?: string[]; + transferable: boolean; +} + +export interface GroupParams extends KeeperParams { + mhab: HabState; +} + +export interface Keeper { + algo: Algos; + signers: Signer[]; + params(): T; + incept(transferable: boolean): Promise; + rotate( + ncodes: string[], + transferable: boolean, + states?: State[], + rstates?: State[] + ): Promise; + sign( + ser: Uint8Array, + indexed?: boolean, + indices?: number[], + ondices?: number[] + ): Promise; } export class KeyManager { - private salter?: Salter; - private modules?: any; + private modules: Record = {}; - constructor(salter: Salter, externalModules: ExternalModule[] = []) { + constructor( + private salter: Salter, + externalModules: ExternalModule[] = [] + ) { this.salter = salter; - this.modules = []; - externalModules.forEach((mod) => { + + for (const mod of externalModules) { this.modules[mod.type] = mod.module; - }); + } } new(algo: Algos, pidx: number, kargs: any) { @@ -76,67 +127,69 @@ export class KeyManager { kargs['keys'], kargs['ndigs'] ); - case Algos.extern: - const typ = kargs.extern_type; - if (typ in this.modules) { - const mod = new this.modules[typ](pidx, kargs); - return mod; - } else { - throw new Error(`unsupported external module type ${typ}`); + case Algos.extern: { + const ModuleConstructor = this.modules[kargs.extern_type]; + if (!ModuleConstructor) { + throw new Error( + `unsupported external module type ${kargs.extern_type}` + ); } + + return new ModuleConstructor(pidx, kargs); + } default: throw new Error('Unknown algo'); } } - get(aid: any) { - const pre = new Prefixer({ qb64: aid['prefix'] }); - if (Algos.salty in aid) { + get(aid: HabState): Keeper { + if (aid[Algos.salty]) { const kargs = aid[Algos.salty]; return new SaltyKeeper( - this.salter!, + this.salter, kargs['pidx'], kargs['kidx'], kargs['tier'], kargs['transferable'], kargs['stem'], - kargs['code'], - kargs['count'], + undefined, + undefined, kargs['icodes'], - kargs['ncode'], - kargs['ncount'], + undefined, + undefined, kargs['ncodes'], kargs['dcode'], - kargs['bran'], + undefined, kargs['sxlt'] ); - } else if (Algos.randy in aid) { - const kargs = aid[Algos.randy]; + } else if (aid[Algos.randy]) { + const pre = new Prefixer({ qb64: aid['prefix'] }); + const kargs = aid[Algos.randy]!; return new RandyKeeper( - this.salter!, - kargs['code'], - kargs['count'], - kargs['icodes'], + this.salter, + undefined, + undefined, + undefined, pre.transferable, - kargs['ncode'], - kargs['ncount'], - kargs['ncodes'], - kargs['dcode'], + undefined, + undefined, + [], + undefined, kargs['prxs'], kargs['nxts'] ); - } else if (Algos.group in aid) { + } else if (aid[Algos.group]) { const kargs = aid[Algos.group]; return new GroupKeeper( this, kargs['mhab'], - kargs['states'], - kargs['rstates'], + undefined, + undefined, kargs['keys'], kargs['ndigs'] ); - } else if (Algos.extern in aid) { - const kargs = aid[Algos.randy]; + } else if (aid[Algos.extern]) { + const kargs = aid[Algos.extern]; const typ = kargs.extern_type; if (typ in this.modules) { const mod = new this.modules[typ](kargs['pidx'], kargs); @@ -150,7 +203,7 @@ export class KeyManager { } } -export class SaltyKeeper { +export class SaltyKeeper implements Keeper { private aeid: string; private encrypter: Encrypter; private decrypter: Decrypter; @@ -179,7 +232,7 @@ export class SaltyKeeper { kidx: number = 0, tier = Tier.low, transferable = false, - stem = undefined, + stem: string | undefined = undefined, code = MtrDex.Ed25519_Seed, count = 1, icodes: string[] | undefined = undefined, @@ -188,7 +241,7 @@ export class SaltyKeeper { ncodes: string[] | undefined = undefined, dcode = MtrDex.Blake3_256, bran: string | undefined = undefined, - sxlt = undefined + sxlt: string | undefined = undefined ) { // # Salter is the entered passcode and used for enc/dec of salts for each AID this.salter = salter; @@ -228,7 +281,7 @@ export class SaltyKeeper { const ciph = new Cipher({ qb64: this.sxlt }); this.creator = new SaltyCreator( this.decrypter.decrypt(null, ciph).qb64, - (tier = tier), + tier, this.stem ); } @@ -245,9 +298,7 @@ export class SaltyKeeper { ).signers; } - params() { - // Get AID parameters to store externally - + params(): SaltyParams { return { sxlt: this.sxlt, pidx: this.pidx, @@ -261,16 +312,7 @@ export class SaltyKeeper { }; } - incept(transferable: boolean) { - // Create verfers and digers for inception event for AID represented by this Keeper - - // Args: - // transferable (bool): True if the AID for this keeper can establish new keys - - // Returns: - // verfers(list): qualified base64 of signing public keys - // digers(list): qualified base64 of hash of rotation public keys - + async incept(transferable: boolean): Promise { this.transferable = transferable; this.kidx = 0; @@ -304,17 +346,10 @@ export class SaltyKeeper { return [verfers, digers]; } - rotate(ncodes: string[], transferable: boolean, ..._: any[]) { - // Rotate and return verfers and digers for next rotation event for AID represented by this Keeper - - // Args: - // ncodes (list): - // transferable (bool): derivation codes for rotation key creation - - // Returns: - // verfers(list): qualified base64 of signing public keys - // digers(list): qualified base64 of hash of rotation public keys - + async rotate( + ncodes: string[], + transferable: boolean + ): Promise<[string[], string[]]> { this.ncodes = ncodes; this.transferable = transferable; const signers = this.creator.create( @@ -348,12 +383,12 @@ export class SaltyKeeper { return [verfers, digers]; } - sign( + async sign( ser: Uint8Array, indexed = true, indices: number[] | undefined = undefined, ondices: number[] | undefined = undefined - ) { + ): Promise { const signers = this.creator.create( this.icodes, this.ncount, @@ -402,7 +437,7 @@ export class SaltyKeeper { return sigers.map((siger) => siger.qb64); } else { const cigars = []; - for (const [_, signer] of signers.signers.entries()) { + for (const [, signer] of signers.signers.entries()) { cigars.push(signer.sign(ser)); } return cigars.map((cigar) => cigar.qb64); @@ -410,7 +445,7 @@ export class SaltyKeeper { } } -export class RandyKeeper { +export class RandyKeeper implements Keeper { private salter: Salter; private code: string; private count: number; @@ -480,7 +515,7 @@ export class RandyKeeper { ); } - params() { + params(): RandyParams { return { nxts: this.nxts, prxs: this.prxs, @@ -488,7 +523,7 @@ export class RandyKeeper { }; } - incept(transferable: boolean) { + async incept(transferable: boolean): Promise { this.transferable = transferable; const signers = this.creator.create( @@ -522,7 +557,10 @@ export class RandyKeeper { return [verfers, digers]; } - rotate(ncodes: string[], transferable: boolean, ..._: any[]) { + async rotate( + ncodes: string[], + transferable: boolean + ): Promise { this.ncodes = ncodes; this.transferable = transferable; this.prxs = this.nxts; @@ -554,12 +592,12 @@ export class RandyKeeper { return [verfers, digers]; } - sign( + async sign( ser: Uint8Array, indexed = true, indices: number[] | undefined = undefined, ondices: number[] | undefined = undefined - ) { + ): Promise { const signers = this.prxs!.map((prx) => this.decrypter.decrypt( new Cipher({ qb64: prx }).qb64b, @@ -605,28 +643,29 @@ export class RandyKeeper { return sigers.map((siger) => siger.qb64); } else { const cigars = []; - for (const [_, signer] of signers.entries()) { + for (const [, signer] of signers.entries()) { cigars.push(signer.sign(ser)); } return cigars.map((cigar) => cigar.qb64); } } } -export class GroupKeeper { + +export class GroupKeeper implements Keeper { private manager: KeyManager; - private mhab: any; - private gkeys: string[] | undefined; - private gdigs: string[] | undefined; + private mhab: HabState; + private gkeys: string[] = []; + private gdigs: string[] = []; public algo: Algos = Algos.group; public signers: Signer[]; constructor( manager: KeyManager, - mhab = undefined, - states: any[] | undefined = undefined, - rstates: any[] | undefined = undefined, - keys: any[] | undefined = undefined, - ndigs: any[] | undefined = undefined + mhab: HabState, + states: State[] | undefined = undefined, + rstates: State[] | undefined = undefined, + keys: string[] = [], + ndigs: string[] = [] ) { this.manager = manager; if (states != undefined) { @@ -637,33 +676,32 @@ export class GroupKeeper { ndigs = rstates.map((state) => state['n'][0]); } - this.gkeys = keys; - this.gdigs = ndigs; + this.gkeys = states?.map((state) => state['k'][0]) ?? keys; + this.gdigs = rstates?.map((state) => state['n'][0]) ?? ndigs; this.mhab = mhab; this.signers = []; } - incept(..._: any) { + + async incept(): Promise { return [this.gkeys, this.gdigs]; } - rotate( + async rotate( _ncodes: string[], _transferable: boolean, - states: any[], - rstates: any[], - ..._: any - ) { + states: State[], + rstates: State[] + ): Promise { this.gkeys = states.map((state) => state['k'][0]); this.gdigs = rstates.map((state) => state['n'][0]); return [this.gkeys, this.gdigs]; } - async sign( - ser: Uint8Array, - indexed: boolean = true, - _indices: number[] | undefined = undefined, - _ondices: number[] | undefined = undefined - ): Promise { + async sign(ser: Uint8Array, indexed: boolean = true): Promise { + if (!this.mhab.state) { + throw new Error(`No state in mhab`); + } + const key = this.mhab['state']['k'][0]; const ndig = this.mhab['state']['n'][0]; diff --git a/src/keri/core/state.ts b/src/keri/core/state.ts new file mode 100644 index 00000000..ea908378 --- /dev/null +++ b/src/keri/core/state.ts @@ -0,0 +1,68 @@ +import { Algos } from './manager'; +import { Tier } from './salter'; + +export interface State { + vn: [number, number]; + i: string; + s: string; + p?: string; + d: string; + f: string; + dt: string; + et: string; + kt: string | string[]; + k: string[]; + nt: string | string[]; + n: string[]; + bt: string; + b: string[]; + c: string[]; + ee: EstablishmentState; + di?: string; +} + +export interface EstablishmentState { + d: string; + s: string; +} + +export interface SaltyState { + sxlt: string; + pidx: number; + kidx: number; + stem: string; + tier: Tier; + dcode: string; + icodes: string[]; + ncodes: string[]; + transferable: boolean; +} + +export interface RandyState { + prxs: string[]; + nxts: string[]; +} + +export interface GroupState { + mhab: HabState; + keys: string[]; + ndigs: string[]; +} + +export interface ExternState { + extern_type: string; + pidx: number; + [key: string]: unknown; +} + +export interface HabState { + name: string; + prefix: string; + transferable: boolean; + state: State; + windexes: unknown[]; + [Algos.salty]?: SaltyState; + [Algos.randy]?: RandyState; + [Algos.group]?: GroupState; + [Algos.extern]?: ExternState; +} diff --git a/test/app/aiding.test.ts b/test/app/aiding.test.ts index b2e333f9..0872fc6c 100644 --- a/test/app/aiding.test.ts +++ b/test/app/aiding.test.ts @@ -366,7 +366,7 @@ describe('Aiding', () => { rstates: [member1.state, member2.state], }; - await client.identifiers().rotate(group.alias, args); + await client.identifiers().rotate(group.name, args); const request = client.getLastMockRequest(); const body = request.body; expect(body).toMatchObject({ @@ -395,7 +395,7 @@ describe('Aiding', () => { client.fetch.mockResolvedValueOnce(Response.json(group)); client.fetch.mockResolvedValueOnce(Response.json({})); - await client.identifiers().rotate(group.alias, { + await client.identifiers().rotate(group.name, { nsith: '1', states: [member1.state, member2.state], rstates: [member1.state, member2.state], diff --git a/test/app/credentialing.test.ts b/test/app/credentialing.test.ts index 3e822bc0..dfefeaca 100644 --- a/test/app/credentialing.test.ts +++ b/test/app/credentialing.test.ts @@ -7,7 +7,6 @@ import libsodium from 'libsodium-wrappers-sumo'; import fetchMock from 'jest-fetch-mock'; import 'whatwg-fetch'; import { - b, d, Ident, Ilks, @@ -25,24 +24,75 @@ fetchMock.enableMocks(); const url = 'http://127.0.0.1:3901'; const boot_url = 'http://127.0.0.1:3903'; -const mockConnect = - '{"agent":{"vn":[1,0],"i":"EEXekkGu9IAzav6pZVJhkLnjtjM5v3AcyA-pdKUcaGei",' + - '"s":"0","p":"","d":"EEXekkGu9IAzav6pZVJhkLnjtjM5v3AcyA-pdKUcaGei","f":"0",' + - '"dt":"2023-08-19T21:04:57.948863+00:00","et":"dip","kt":"1",' + - '"k":["DMZh_y-H5C3cSbZZST-fqnsmdNTReZxIh0t2xSTOJQ8a"],"nt":"1",' + - '"n":["EM9M2EQNCBK0MyAhVYBvR98Q0tefpvHgE-lHLs82XgqC"],"bt":"0","b":[],' + - '"c":[],"ee":{"s":"0","d":"EEXekkGu9IAzav6pZVJhkLnjtjM5v3AcyA-pdKUcaGei","br":[],"ba":[]},' + - '"di":"ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose"},"controller":{"state":{"vn":[1,0],' + - '"i":"ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose","s":"0","p":"",' + - '"d":"ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose","f":"0","dt":"2023-08-19T21:04:57.959047+00:00",' + - '"et":"icp","kt":"1","k":["DAbWjobbaLqRB94KiAutAHb_qzPpOHm3LURA_ksxetVc"],"nt":"1",' + - '"n":["EIFG_uqfr1yN560LoHYHfvPAhxQ5sN6xZZT_E3h7d2tL"],"bt":"0","b":[],"c":[],"ee":{"s":"0",' + - '"d":"ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose","br":[],"ba":[]},"di":""},' + - '"ee":{"v":"KERI10JSON00012b_","t":"icp","d":"ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose",' + - '"i":"ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose","s":"0","kt":"1",' + - '"k":["DAbWjobbaLqRB94KiAutAHb_qzPpOHm3LURA_ksxetVc"],"nt":"1",' + - '"n":["EIFG_uqfr1yN560LoHYHfvPAhxQ5sN6xZZT_E3h7d2tL"],"bt":"0","b":[],"c":[],"a":[]}},"ridx":0,' + - '"pidx":0}'; +const mockConnect = { + agent: { + vn: [1, 0], + i: 'EEXekkGu9IAzav6pZVJhkLnjtjM5v3AcyA-pdKUcaGei', + s: '0', + p: '', + d: 'EEXekkGu9IAzav6pZVJhkLnjtjM5v3AcyA-pdKUcaGei', + f: '0', + dt: '2023-08-19T21:04:57.948863+00:00', + et: 'dip', + kt: '1', + k: ['DMZh_y-H5C3cSbZZST-fqnsmdNTReZxIh0t2xSTOJQ8a'], + nt: '1', + n: ['EM9M2EQNCBK0MyAhVYBvR98Q0tefpvHgE-lHLs82XgqC'], + bt: '0', + b: [], + c: [], + ee: { + s: '0', + d: 'EEXekkGu9IAzav6pZVJhkLnjtjM5v3AcyA-pdKUcaGei', + br: [], + ba: [], + }, + di: 'ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose', + }, + controller: { + state: { + vn: [1, 0], + i: 'ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose', + s: '0', + p: '', + d: 'ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose', + f: '0', + dt: '2023-08-19T21:04:57.959047+00:00', + et: 'icp', + kt: '1', + k: ['DAbWjobbaLqRB94KiAutAHb_qzPpOHm3LURA_ksxetVc'], + nt: '1', + n: ['EIFG_uqfr1yN560LoHYHfvPAhxQ5sN6xZZT_E3h7d2tL'], + bt: '0', + b: [], + c: [], + ee: { + s: '0', + d: 'ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose', + br: [], + ba: [], + }, + di: '', + }, + ee: { + v: 'KERI10JSON00012b_', + t: 'icp', + d: 'ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose', + i: 'ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose', + s: '0', + kt: '1', + k: ['DAbWjobbaLqRB94KiAutAHb_qzPpOHm3LURA_ksxetVc'], + nt: '1', + n: ['EIFG_uqfr1yN560LoHYHfvPAhxQ5sN6xZZT_E3h7d2tL'], + bt: '0', + b: [], + c: [], + a: [], + }, + }, + ridx: 0, + pidx: 0, +}; const mockGetAID = { name: 'aid1', prefix: 'ELUvZ8aJEHAQE-0nsevyYTP98rBbGJUrTj5an-pCmwrK', @@ -125,7 +175,10 @@ const mockCredential = { fetchMock.mockResponse((req) => { if (req.url.startsWith(url + '/agent')) { - return Promise.resolve({ body: mockConnect, init: { status: 202 } }); + return Promise.resolve({ + body: JSON.stringify(mockConnect), + init: { status: 202 }, + }); } else if (req.url == boot_url + '/boot') { return Promise.resolve({ body: '', init: { status: 202 } }); } else { @@ -330,7 +383,7 @@ describe('Ipex', () => { const ipex = client.ipex(); const holder = 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k'; - const [acdcSaider, acdc] = Saider.saidify(mockCredential.sad); + const [, acdc] = Saider.saidify(mockCredential.sad); // Create iss const vs = versify(Ident.KERI, undefined, Serials.JSON, 0); @@ -344,7 +397,7 @@ describe('Ipex', () => { dt: mockCredential.sad.a.dt, }; - const [issSaider, iss] = Saider.saidify(_iss); + const [, iss] = Saider.saidify(_iss); const iserder = new Serder(iss); const anc = interact({ pre: mockCredential.sad.i, diff --git a/test/app/registry.test.ts b/test/app/registry.test.ts index 14971493..3e72355b 100644 --- a/test/app/registry.test.ts +++ b/test/app/registry.test.ts @@ -5,6 +5,7 @@ import 'whatwg-fetch'; import { Registries } from '../../src/keri/app/credentialing'; import { Identifier, KeyManager, SaltyKeeper } from '../../src'; import { strict as assert } from 'assert'; +import { HabState, State } from '../../src/keri/core/state'; describe('registry', () => { it('should create a registry', async () => { @@ -14,12 +15,15 @@ describe('registry', () => { const mockedKeyManager = mock(KeyManager); const mockedKeeper = mock(SaltyKeeper); - const hab = { prefix: 'hab prefix', state: { s: 0, d: 'a digest' } }; + const hab = { + prefix: 'hab prefix', + state: { s: '0', d: 'a digest' } as State, + } as HabState; when(mockedClient.manager).thenReturn(instance(mockedKeyManager)); when(mockedKeyManager.get(hab)).thenReturn(instance(mockedKeeper)); - when(mockedKeeper.sign(anyOfClass(Uint8Array))).thenReturn([ + when(mockedKeeper.sign(anyOfClass(Uint8Array))).thenResolve([ 'a signature', ]); @@ -62,8 +66,11 @@ describe('registry', () => { const hab = { prefix: 'hab prefix', - state: { s: 0, d: 'a digest', c: ['EO'] }, - }; + state: { s: 0, d: 'a digest', c: ['EO'] } as unknown as State, + name: 'a name', + transferable: true, + windexes: [], + } as HabState; when(mockedIdentifiers.get('a name')).thenResolve(hab); when(mockedClient.identifiers()).thenReturn( diff --git a/test/app/test-utils.ts b/test/app/test-utils.ts index 93fdca82..8d233b09 100644 --- a/test/app/test-utils.ts +++ b/test/app/test-utils.ts @@ -9,12 +9,13 @@ import { Versionage, incept, } from '../../src'; +import { EstablishmentState, HabState, State } from '../../src/keri/core/state'; export async function createMockIdentifierState( name: string, bran: string, kargs: CreateIdentiferArgs = {} -) { +): Promise { const controller = new Controller(bran, Tier.low); const manager = new KeyManager(controller.salter); const algo = kargs.algo == undefined ? Algos.salty : kargs.algo; @@ -89,12 +90,14 @@ export async function createMockIdentifierState( name: name, prefix: serder.pre, [algo]: keeper.params(), + transferable, + windexes: [], state: { vn: [serder.version.major, serder.version.minor], s: serder.ked.s, d: serder.ked.d, i: serder.pre, - ee: serder.ked, + ee: serder.ked as EstablishmentState, kt: serder.ked.kt, k: serder.ked.k, nt: serder.ked.nt, @@ -102,8 +105,11 @@ export async function createMockIdentifierState( bt: serder.ked.bt, b: serder.ked.b, p: serder.ked.p ?? '', + f: '', + dt: new Date().toISOString().replace('Z', '000+00:00'), + et: '', c: [], di: serder.ked.di ?? '', - }, + } as State, }; } diff --git a/test/core/eventing.test.ts b/test/core/eventing.test.ts index b730a364..b5b697b6 100644 --- a/test/core/eventing.test.ts +++ b/test/core/eventing.test.ts @@ -2,11 +2,12 @@ import libsodium from 'libsodium-wrappers-sumo'; import { Signer } from '../../src/keri/core/signer'; import { strict as assert } from 'assert'; import { MtrDex } from '../../src/keri/core/matter'; -import { incept, messagize } from '../../src/keri/core/eventing'; +import { incept, messagize, rotate } from '../../src/keri/core/eventing'; import { Saider } from '../../src/keri/core/saider'; import { Diger } from '../../src/keri/core/diger'; import { b, d, Ilks } from '../../src/keri/core/core'; import { Siger } from '../../src/keri/core/siger'; +import { randomBytes } from 'crypto'; describe('key event function', () => { it('incept should create inception events', async () => { @@ -190,4 +191,30 @@ describe('key event function', () => { 'BhfNFh5uk-WxvhsL-AABAABB3MJGmBXxSEryNHw3YwZZLRl_6Ws4Me2WFq8PrQ6WlluSOpPqbwXuiG9RvNWZkqeW8A_0VRjokGMVRZ3m-c0I' ); }); + + it('Rotate should create rotation event with hex sequence number', async () => { + await libsodium.ready; + + const signer0 = new Signer({ transferable: true }); + const signer1 = new Signer({ transferable: true }); + const keys0 = [signer0.verfer.qb64]; + const ndigs = [new Diger({}, signer1.verfer.qb64b).qb64]; + const serder = incept({ keys: keys0, ndigs }); + + function createRotation(sn: number) { + return rotate({ + keys: keys0, + pre: serder.ked.i, + ndigs: serder.ked.n, + sn, + isith: 1, + nsith: 1, + }).ked['s']; + } + + assert.equal(createRotation(1), '1'); + assert.equal(createRotation(10), 'a'); + assert.equal(createRotation(14), 'e'); + assert.equal(createRotation(255), 'ff'); + }); }); diff --git a/test/core/manager.test.ts b/test/core/manager.test.ts index 2fc78a3e..47e43ad6 100644 --- a/test/core/manager.test.ts +++ b/test/core/manager.test.ts @@ -19,6 +19,15 @@ import { Diger } from '../../src/keri/core/diger'; import { Siger } from '../../src/keri/core/siger'; import { b } from '../../src/keri/core/core'; import { Cigar } from '../../src/keri/core/cigar'; +import { + Keeper, + KeeperParams, + KeyManager, + Prefixer, + RandyKeeper, +} from '../../src'; +import { RandyState, State } from '../../src/keri/core/state'; +import { randomUUID } from 'crypto'; describe('RandyCreator', () => { it('should create sets of random signers', async () => { @@ -695,4 +704,151 @@ describe('Manager', () => { 'AACRPqO6vdXm1oSSa82rmVVHikf7NdN4JXjOWEk30Ub5JHChL0bW6DzJfA-7VlgLm_B1XR0Z61FweP87bBQpVawI' ); }); + + it('Should support creating and getting randy keeper', async () => { + const passcode = '0123456789abcdefghijk'; + const salter = new Salter({ raw: b(passcode) }); + + const manager = new KeyManager(salter, []); + + const keeper0 = manager.new(Algos.randy, 0, {}) as RandyKeeper; + const [keys] = await keeper0.incept(false); + const prefixes = new Prefixer({ qb64: keys[0] }); + + const keeper1 = manager.get({ + prefix: prefixes.qb64, + name: '', + state: {} as State, + randy: keeper0.params() as RandyState, + transferable: false, + windexes: [], + }); + + assert(keeper0 instanceof RandyKeeper); + assert(keeper1 instanceof RandyKeeper); + }); + + it('Should throw if algo is not supported', async () => { + const passcode = '0123456789abcdefghijk'; + const salter = new Salter({ raw: b(passcode) }); + + const manager = new KeyManager(salter, []); + + expect(() => manager.new(randomUUID() as Algos, 0, {})).toThrow( + 'Unknown algo' + ); + expect(() => + manager.get({ + prefix: '', + name: '', + state: {} as State, + transferable: false, + windexes: [], + }) + ).toThrow('Algo not allowed yet'); + }); + + describe('External Module ', () => { + class MockModule implements jest.Mocked { + #params: Record; + + constructor( + public pidx: number, + params: KeeperParams + ) { + this.#params = params; + } + + signers: Signer[] = []; + sign = jest.fn(); + algo: Algos = Algos.extern; + incept = jest.fn(); + rotate = jest.fn(); + params = jest.fn(() => this.#params); + } + + it('Should support creating external keeper module', async () => { + const passcode = '0123456789abcdefghijk'; + const salter = new Salter({ raw: b(passcode) }); + + const manager = new KeyManager(salter, [ + { module: MockModule, name: 'mock', type: 'mock' }, + ]); + + const param = randomUUID(); + const keeper = manager.new(Algos.extern, 0, { + extern_type: 'mock', + param, + }); + + assert(keeper instanceof MockModule); + expect(keeper.params()).toMatchObject({ param }); + }); + + it('Should throw if external keeper module is not addede', async () => { + const passcode = '0123456789abcdefghijk'; + const salter = new Salter({ raw: b(passcode) }); + + const manager = new KeyManager(salter, []); + + const param = randomUUID(); + expect(() => + manager.new(Algos.extern, 0, { + extern_type: 'mock', + param, + }) + ).toThrow('unsupported external module type mock'); + }); + + it('Should support getting external keeper module', async () => { + const passcode = '0123456789abcdefghijk'; + const salter = new Salter({ raw: b(passcode) }); + + const manager = new KeyManager(salter, [ + { module: MockModule, name: 'mock', type: 'mock' }, + ]); + + const param = randomUUID(); + + const keeper = manager.get({ + name: randomUUID(), + prefix: '', + state: {} as unknown as State, + windexes: [], + extern: { + extern_type: 'mock', + pidx: 3, + param, + }, + transferable: true, + }); + + assert(keeper instanceof MockModule); + expect(keeper.params()).toMatchObject({ param, pidx: 3 }); + }); + + it('Should throw when trying to get external keeper that is not registered', async () => { + const passcode = '0123456789abcdefghijk'; + const salter = new Salter({ raw: b(passcode) }); + + const manager = new KeyManager(salter, []); + + const param = randomUUID(); + + expect(() => + manager.get({ + name: randomUUID(), + prefix: '', + state: {} as unknown as State, + windexes: [], + extern: { + extern_type: 'mock', + pidx: 3, + param, + }, + transferable: true, + }) + ).toThrow('unsupported external module type mock'); + }); + }); });