diff --git a/examples/scripts/client.ts b/examples/scripts/client.ts index 0230571f..fc6e1716 100644 --- a/examples/scripts/client.ts +++ b/examples/scripts/client.ts @@ -46,9 +46,11 @@ async function connect() { let identifiers = client.identifiers() const oobis = client.oobis() const operations = client.operations() + const exchanges = client.exchanges() let salt = 'abcdefghijk0123456789' - let op = await identifiers.create("multisig-ts", {bran: salt}) + let res = identifiers.create("multisig-ts", {bran: salt}) + let op = await res.op() let aid = op["response"] await identifiers.addEndRole("multisig-ts", "agent", d.agent.i) @@ -93,7 +95,7 @@ async function connect() { let sigTs = aid['state'] let states = [sigPy, kli, sigTs] - identifiers.create("multisig", { + let ires = identifiers.create("multisig", { algo: "group", mhab: aid, delpre: "EHpD0-CDWOdu5RJ8jHBSUkOqBZ3cXeDVHWNb_Ul89VI7", toad: 2, @@ -106,5 +108,22 @@ async function connect() { states: states, rstates: states }) + + let serder = ires.serder + let sigs = ires.sigs + let sigers = sigs.map((sig: any) => new signify.Siger({qb64: sig})) + + let ims = signify.d(signify.messagize(serder, sigers)) + let atc = ims.substring(serder.size) + let embeds = { + icp: [serder, atc], + } + + let smids = states.map((state) => state['i']) + let recp = [sigPy, kli].map((state) => state['i']) + + await exchanges.send("multisig-ts", "multisig", aid, "/multisig/icp", + {'gid': serder.pre, smids: smids, rmids: smids}, embeds, recp) + } diff --git a/examples/scripts/create_multisig_aid.py b/examples/scripts/create_multisig_aid.py index 4760509d..c3b1e0db 100644 --- a/examples/scripts/create_multisig_aid.py +++ b/examples/scripts/create_multisig_aid.py @@ -9,7 +9,9 @@ from time import sleep from keri.app.keeping import Algos +from keri.core import eventing, coring from keri.core.coring import Tiers +from keri.peer import exchanging from signify.app.clienting import SignifyClient @@ -23,6 +25,7 @@ def create_multisig_aid(): identifiers = client.identifiers() operations = client.operations() states = client.keyStates() + exchanges = client.exchanges() aid = identifiers.get("multisig-sigpy") sigPy = aid["state"] @@ -37,17 +40,32 @@ def create_multisig_aid(): for state in states: print(json.dumps(state, indent=2)) - op = identifiers.create("multisig", algo=Algos.group, mhab=aid, - delpre="EHpD0-CDWOdu5RJ8jHBSUkOqBZ3cXeDVHWNb_Ul89VI7", - toad=2, - wits=[ - "BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha", - "BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM", - "BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX" - ], - isith=["1/3", "1/3", "1/3"], nsith=["1/3", "1/3", "1/3"], - states=states, - rstates=rstates) + icp, isigs, op = identifiers.create("multisig", algo=Algos.group, mhab=aid, + delpre="EHpD0-CDWOdu5RJ8jHBSUkOqBZ3cXeDVHWNb_Ul89VI7", + toad=2, + wits=[ + "BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha", + "BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM", + "BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX" + ], + isith=["1/3", "1/3", "1/3"], nsith=["1/3", "1/3", "1/3"], + states=states, + rstates=rstates) + + smids = ["EBcIURLpxmVwahksgrsGW6_dUw0zBhyEHYFk17eWrZfk", + "EFBmwh8vdPTofoautCiEjjuA17gSlEnE3xc-xy-fGzWZ", + "ELViLL4JCh-oktYca-pmPLwkmUaeYjyPmCLxELAKZW8V"] + recp = ["EFBmwh8vdPTofoautCiEjjuA17gSlEnE3xc-xy-fGzWZ", + "ELViLL4JCh-oktYca-pmPLwkmUaeYjyPmCLxELAKZW8V"] + + embeds = dict( + icp=eventing.messagize(serder=icp, sigers=[coring.Siger(qb64=sig) for sig in isigs]) + ) + + exchanges.send("multisig-sigpy", "multisig", sender=aid, route="/multisig/icp", + payload=dict(gid=icp.pre, smids=smids, rmids=smids), + embeds=embeds, recipients=recp) + print("waiting on multisig creation...") while not op["done"]: op = operations.get(op["name"]) diff --git a/examples/scripts/create_person_aid.py b/examples/scripts/create_person_aid.py index 6ed77a0f..3ee70636 100644 --- a/examples/scripts/create_person_aid.py +++ b/examples/scripts/create_person_aid.py @@ -33,7 +33,7 @@ def create_aid(): "BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX" ] - op = identifiers.create("multisig-sigpy", bran="0123456789abcdefghijk", wits=wits, toad="2") + _, _, op = identifiers.create("multisig-sigpy", bran="0123456789abcdefghijk", wits=wits, toad="2") while not op["done"]: op = operations.get(op["name"]) diff --git a/examples/scripts/get.py b/examples/scripts/get.py new file mode 100644 index 00000000..17e03e55 --- /dev/null +++ b/examples/scripts/get.py @@ -0,0 +1,27 @@ +# -*- encoding: utf-8 -*- +""" +SIGNIFY +signify.app.clienting module + +Testing clienting with integration tests that require a running KERIA Cloud Agent +""" +from pprint import pprint + +from keri.core.coring import Tiers +from signify.app.clienting import SignifyClient + + +def get(): + url = "http://localhost:3901" + bran = b'9876543210abcdefghijk' + tier = Tiers.low + + client = SignifyClient(passcode=bran, tier=tier, url=url) + + identifiers = client.identifiers() + + aid = identifiers.get("multisig") + pprint(aid) + +if __name__ == "__main__": + get() diff --git a/examples/scripts/list_notifications.py b/examples/scripts/list_notifications.py new file mode 100644 index 00000000..2d59db10 --- /dev/null +++ b/examples/scripts/list_notifications.py @@ -0,0 +1,85 @@ +# -*- encoding: utf-8 -*- +""" +SIGNIFY +signify.app.clienting module + +Testing clienting with integration tests that require a running KERIA Cloud Agent +""" +import json +from pprint import pprint + +from keri.core import coring +from keri.core.coring import Tiers +from keri.core.eventing import messagize, SealEvent +from keri.peer import exchanging +from signify.app.clienting import SignifyClient + + +def list_notifications(): + url = "http://localhost:3901" + bran = b'9876543210abcdefghijk' + tier = Tiers.low + + client = SignifyClient(passcode=bran, tier=tier, url=url) + identifiers = client.identifiers() + notificatons = client.notifications() + groups = client.groups() + registries = client.registries() + + res = notificatons.list() + + for note in res["notes"]: + body = note['a'] + route = body['r'] + match route.split("/"): + case ["", "multisig", "icp"]: + pass + # print(body) + # print(f"Recv: inception request for multisig AID=BLAH") + case ["", "multisig", "vcp"]: + said = body['d'] + res = groups.get_request(said=said) + msg = next(exn for exn in res if exn['exn']['d'] == said) + + sender = msg['sender'] + group = msg["groupName"] + + exn = msg['exn'] + usage = exn['a']["usage"] + print(f"Credential registry inception request for group AID {group}:") + print(f"\tReceived from: {sender}") + print(f"\tPurpose: \"{usage}\"") + yes = input("Approve [Y|n]? ") + + if yes in ('', 'y', 'Y'): + registryName = input("Enter new local name for registry: ") + embeds = exn['e'] + vcp = embeds['vcp'] + ixn = embeds['ixn'] + serder = coring.Serder(ked=ixn) + ghab = identifiers.get(group) + + keeper = client.manager.get(aid=ghab) + sigs = keeper.sign(ser=serder.raw) + + ims = messagize(serder=serder, sigers=[coring.Siger(qb64=sig) for sig in sigs]) + embeds = dict( + vcp=coring.Serder(ked=vcp).raw, + ixn=ims + ) + + sender = ghab["group"]["mhab"] + keeper = client.manager.get(aid=sender) + exn, end = exchanging.exchange(route="/multisig/vcp", + payload={'gid': ghab["prefix"], 'usage': "test"}, + sender=sender["prefix"], embeds=embeds) + + esigs = keeper.sign(ser=exn.raw) + groups.send_request(group, exn.ked, esigs, end) + + return registries.create_from_events(name=group, hab=ghab, registryName=registryName, vcp=vcp, + ixn=ixn, sigs=sigs) + + +if __name__ == "__main__": + list_notifications() diff --git a/examples/scripts/list_notifications.ts b/examples/scripts/list_notifications.ts new file mode 100644 index 00000000..2ae3d241 --- /dev/null +++ b/examples/scripts/list_notifications.ts @@ -0,0 +1,101 @@ + +const prmpt = require("prompt-sync")({ sigint: true }); +// @ts-ignore +let signify: any; + +// @ts-ignore +import('signify-ts').then( + (module) => { + signify = module + signify.ready().then(() => { + console.log("Signify client ready!"); + list_notifications().then(() => { + console.log("Done") + }); + }); + } +) + +async function list_notifications() { + let url = "http://127.0.0.1:3901" + let bran = '0123456789abcdefghijk' + + const client = new signify.SignifyClient(url, bran); + await client.connect() + let d = await client.state() + console.log("Connected: ") + console.log(" Agent: ", d.agent.i, " Controller: ", d.controller.state.i) + + let identifiers = client.identifiers() + let notifications = client.notifications() + let groups = client.groups() + let registries = client.registries() + + let res = await notifications.list() + let notes = res.notes + + for (const note of notes) { + let payload = note.a + let route = payload.r + + if (route === '/multisig/vcp') { + let res = await groups.getRequest(payload.d) + if (res.length == 0) { + console.log("error extracting exns matching nre for " + payload.data) + } + let msg = res[0] + + let sender = msg['sender'] + let group = msg["groupName"] + + let exn = msg['exn'] + let usage = exn['a']["usage"] + console.log("Credential registry inception request for group AID :" + group) + console.log("\tReceived from: " + sender) + console.log("\tPurpose: " + usage) + console.log("\nAuto-creating new registry...") + let yes = prmpt("Approve [Y|n]? ") + if (yes === "y" || yes === "Y" || yes === "") { + try { + let registryName = prmpt("Enter new local name for registry: ") + let embeds = exn['e'] + let vcp = embeds['vcp'] + let ixn = embeds['ixn'] + let serder = new signify.Serder(ixn) + let ghab = await identifiers.get(group) + + let keeper = client.manager.get(ghab) + let sigs = keeper.sign(signify.b(serder.raw)) + let sigers = sigs.map((sig: any) => new signify.Siger({qb64: sig})) + + let ims = signify.d(signify.messagize(serder, sigers)) + let atc = ims.substring(serder.size) + embeds = { + vcp: [new signify.Serder(vcp), undefined], + ixn: [serder, atc] + } + + sender = ghab["group"]["mhab"] + keeper = client.manager.get(sender) + let [nexn, end] = signify.exchange("/multisig/vcp", + {'gid': ghab["prefix"], 'usage': "test"}, + sender["prefix"], undefined, undefined, undefined, undefined, embeds) + + console.log(nexn.pretty()) + let esigs = keeper.sign(nexn.raw) + await groups.sendRequest(group, nexn.ked, esigs, signify.d(end)) + + return await registries.createFromEvents(ghab, group, registryName, vcp, ixn, sigs) + } catch(e: any) { + console.log(e) + } + } + + + + } + + } + +} + diff --git a/examples/scripts/package-lock.json b/examples/scripts/package-lock.json index 40a97808..1b73dd02 100644 --- a/examples/scripts/package-lock.json +++ b/examples/scripts/package-lock.json @@ -8,6 +8,7 @@ "name": "signify-scripts-ts", "version": "0.0.0", "dependencies": { + "prompt-sync": "^4.2.0", "signify-ts": "file:../../" }, "devDependencies": { @@ -28,6 +29,7 @@ "buffer": "^6.0.3", "cbor": "^8.0.0", "collections": "^5.1.12", + "jest-fetch-mock": "^3.0.3", "libsodium-wrappers-sumo": "^0.7.9", "lodash": "^4.17.21", "mathjs": "^11.8.2", @@ -36,9 +38,11 @@ "text-encoding": "^0.7.0", "ts-node": "^10.9.1", "urlsafe-base64": "^1.0.0", + "whatwg-fetch": "^3.6.17", "xregexp": "^5.1.0" }, "devDependencies": { + "@mermaid-js/mermaid-cli": "^10.3.0", "@size-limit/preset-small-lib": "^5.0.4", "@types/lodash": "^4.14.185", "@types/node": "^18.11.18", @@ -46,10 +50,13 @@ "@types/urlsafe-base64": "^1.0.28", "husky": "^7.0.2", "jest": "^29.3.1", + "jsdoc": "^4.0.2", + "minami": "^1.2.3", "size-limit": "^5.0.4", "ts-migrate": "^0.1.23", "tsdx": "^0.14.1", "tslib": "^2.3.1", + "typedoc": "^0.24.8", "typescript": "^4.9.4" } }, @@ -1360,6 +1367,33 @@ "node": ">= 0.8.0" } }, + "node_modules/prompt-sync": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/prompt-sync/-/prompt-sync-4.2.0.tgz", + "integrity": "sha512-BuEzzc5zptP5LsgV5MZETjDaKSWfchl5U9Luiu8SKp7iZWD5tZalOxvNcZRwv+d2phNFr8xlbxmFNcRKfJOzJw==", + "dependencies": { + "strip-ansi": "^5.0.0" + } + }, + "node_modules/prompt-sync/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/prompt-sync/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -2591,6 +2625,29 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prompt-sync": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/prompt-sync/-/prompt-sync-4.2.0.tgz", + "integrity": "sha512-BuEzzc5zptP5LsgV5MZETjDaKSWfchl5U9Luiu8SKp7iZWD5tZalOxvNcZRwv+d2phNFr8xlbxmFNcRKfJOzJw==", + "requires": { + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -2660,6 +2717,7 @@ "signify-ts": { "version": "file:../..", "requires": { + "@mermaid-js/mermaid-cli": "^10.3.0", "@size-limit/preset-small-lib": "^5.0.4", "@types/libsodium-wrappers-sumo": "^0.7.5", "@types/lodash": "^4.14.185", @@ -2672,9 +2730,12 @@ "collections": "^5.1.12", "husky": "^7.0.2", "jest": "^29.3.1", + "jest-fetch-mock": "^3.0.3", + "jsdoc": "^4.0.2", "libsodium-wrappers-sumo": "^0.7.9", "lodash": "^4.17.21", "mathjs": "^11.8.2", + "minami": "^1.2.3", "msgpack5": "^5.3.2", "size-limit": "^5.0.4", "structured-headers": "^0.5.0", @@ -2683,8 +2744,10 @@ "ts-node": "^10.9.1", "tsdx": "^0.14.1", "tslib": "^2.3.1", + "typedoc": "^0.24.8", "typescript": "^4.9.4", "urlsafe-base64": "^1.0.0", + "whatwg-fetch": "^3.6.17", "xregexp": "^5.1.0" } }, diff --git a/examples/scripts/package.json b/examples/scripts/package.json index 5d00e0e2..726a60f1 100644 --- a/examples/scripts/package.json +++ b/examples/scripts/package.json @@ -2,9 +2,9 @@ "name": "signify-scripts-ts", "private": true, "version": "0.0.0", - "scripts": { - }, + "scripts": {}, "dependencies": { + "prompt-sync": "^4.2.0", "signify-ts": "file:../../" }, "devDependencies": { diff --git a/src/index.ts b/src/index.ts index 62919ac7..6306d2ca 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,6 +18,7 @@ export * from './keri/app/signify' export * from './keri/app/apping' export * from './keri/app/controller' export * from './keri/app/habery' +export * from './keri/app/exchanging' export * from './keri/app/signify' export * from './keri/core/authing' diff --git a/src/keri/app/exchanging.ts b/src/keri/app/exchanging.ts new file mode 100644 index 00000000..89ec6e67 --- /dev/null +++ b/src/keri/app/exchanging.ts @@ -0,0 +1,84 @@ +import {b, Dict, Ident, Ilks, Serials, versify} from "../core/core"; +import {Serder} from "../core/serder"; +import {nowUTC} from "../core/utils"; +import {Pather} from "../core/pather"; +import {Counter, CtrDex} from "../core/counter"; +import {Saider} from "../core/saider"; + + +export function exchange(route: string, + payload: Dict, + sender: string, + recipient?: string, + date?: string, + dig?: string, + modifiers?: Dict, + embeds?: Dict): [Serder, Uint8Array] { + + + const vs = versify(Ident.KERI, undefined, Serials.JSON, 0) + const ilk = Ilks.exn + const dt = date !== undefined ? date : nowUTC().toISOString() + const p = dig !== undefined ? dig : "" + const q = modifiers !== undefined ? modifiers : {} + const ems = embeds != undefined ? embeds : {} + + let e = {} as Dict + let end = "" + Object.entries(ems) + .forEach(([key, value]) => { + let serder = value[0]; + let atc = value[1] + e[key] = serder.ked + + if (atc == undefined) { + return + } + let pathed = "" + let pather = new Pather({}, undefined, ["e", key]) + pathed += pather.qb64 + pathed += atc + + let counter = new Counter({ + code: CtrDex.PathedMaterialQuadlets, + count: Math.floor(pathed.length / 4) + }) + end += counter.qb64 + end += pathed + }) + + if (Object.keys(e).length > 0) { + e["d"] = ""; + [, e] = Saider.saidify(e) + } + + const attrs = {} as Dict + + if (recipient !== undefined) { + attrs['i'] = recipient + } + + let a = { + ...attrs, + ...payload + } + + let _ked = { + v: vs, + t: ilk, + d: "", + i: sender, + p: p, + dt: dt, + r: route, + q: q, + a: a, + e: e + } + let [, ked] = Saider.saidify(_ked) + + let exn = new Serder(ked) + + return [exn, b(end)] + +} \ No newline at end of file diff --git a/src/keri/app/signify.ts b/src/keri/app/signify.ts index f5dee32e..9421c4fc 100644 --- a/src/keri/app/signify.ts +++ b/src/keri/app/signify.ts @@ -1,20 +1,20 @@ -import { Controller, Agent } from "./controller" -import { Tier } from "../core/salter" -import { Authenticater } from "../core/authing" -import { KeyManager } from "../core/keeping" -import { Algos } from '../core/manager' -import { incept, rotate, interact, reply, messagize } from "../core/eventing" -import { b, Serials, Versionage, Ilks, versify, Ident} from "../core/core" -import { Tholder } from "../core/tholder" -import { MtrDex } from "../core/matter" -import { Saider } from "../core/saider" -import { Serder } from "../core/serder" -import { Siger } from "../core/siger" -import { Prefixer } from "../core/prefixer" -import { Salter } from "../core/salter" -import { randomNonce } from "./apping" -import { parseRangeHeaders } from "../core/httping" -import { TextDecoder } from "util" +import {Agent, Controller} from "./controller" +import {Salter, Tier} from "../core/salter" +import {Authenticater} from "../core/authing" +import {KeyManager} from "../core/keeping" +import {Algos} from '../core/manager' +import {incept, interact, messagize, reply, rotate} from "../core/eventing" +import {b, d, Dict, Ident, Ilks, Serials, versify, Versionage} from "../core/core" +import {Tholder} from "../core/tholder" +import {MtrDex} from "../core/matter" +import {Saider} from "../core/saider" +import {Serder} from "../core/serder" +import {Siger} from "../core/siger" +import {Prefixer} from "../core/prefixer" +import {randomNonce} from "./apping" +import {parseRangeHeaders} from "../core/httping" +import {TextDecoder} from "util" +import {exchange} from "./exchanging"; const DEFAULT_BOOT_URL = "http://localhost:3903" @@ -420,6 +420,22 @@ export class SignifyClient { escrows(): Escrows { return new Escrows(this) } + + /** + * Get groups resource + * @returns {Groups} + */ + groups(): Groups { + return new Groups(this) + } + + /** + * Get groups resource + * @returns {Exchanges} + */ + exchanges(): Exchanges { + return new Exchanges(this) + } } /** Arguments required to create an identfier */ @@ -463,6 +479,31 @@ export interface RotateIdentifierArgs { rstates?: any[] } +export class InceptionResult { + private readonly _serder: Serder + private readonly _sigs: string[] + private readonly promise: Promise + + constructor(serder: Serder, sigs: string[], promise: Promise) { + this._serder = serder + this._sigs = sigs + this.promise = promise + } + + get serder() { + return this._serder + } + + get sigs() { + return this._sigs + } + + async op(): Promise { + let res = await this.promise + return await res.json() + } +} + /** Identifier */ export class Identifier { public client: SignifyClient @@ -523,7 +564,7 @@ export class Identifier { * @param {CreateIdentiferArgs} [kargs] Optional parameters to create the identifier * @returns {Promise} A promise to the long-running operation */ - async create(name: string, kargs:CreateIdentiferArgs={}): Promise { + create(name: string, kargs:CreateIdentiferArgs={}): InceptionResult { const algo = kargs.algo == undefined ? Algos.salty : kargs.algo @@ -577,8 +618,9 @@ export class Identifier { let keeper = this.client.manager!.new(algo, this.client.pidx, xargs) let [keys, ndigs] = keeper!.incept(transferable) wits = wits !== undefined ? wits : [] + let serder: Serder|undefined = undefined if (delpre == undefined) { - var serder = incept({ + serder = incept({ keys: keys!, isith: isith, ndigs: ndigs, @@ -594,7 +636,7 @@ export class Identifier { }) } else { - var serder = incept({ + serder = incept({ keys: keys!, isith: isith, ndigs: ndigs, @@ -620,11 +662,11 @@ export class Identifier { smids: states != undefined ? states.map(state => state.i) : undefined, rmids: rstates != undefined ? rstates.map(state => state.i) : undefined } - jsondata[algo] = keeper.params(), + jsondata[algo] = keeper.params() this.client.pidx = this.client.pidx + 1 - let res = await this.client.fetch("/identifiers", "POST", jsondata) - return await res.json() + let res = this.client.fetch("/identifiers", "POST", jsondata) + return new InceptionResult(serder, sigs, res) } /** @@ -1406,7 +1448,7 @@ export class Registries { let hab = await this.client.identifiers().get(name) let pre: string = hab.prefix - nonce = nonce !== undefined? nonce : randomNonce() + nonce = nonce !== undefined ? nonce : randomNonce() const vs = versify(Ident.KERI, undefined, Serials.JSON, 0) let vcp = { @@ -1430,12 +1472,11 @@ export class Registries { let sigs = [] let state = hab.state + let estOnly = false if (state.c !== undefined && state.c.includes("EO")) { - var estOnly = true - } - else { - var estOnly = false + estOnly = true } + if (estOnly) { // TODO implement rotation event throw new Error("establishment only not implemented") @@ -1445,26 +1486,37 @@ export class Registries { let sn = Number(state.s) let dig = state.d - let data:any = [{ + let data: any = [{ i: prefixer.qb64, s: "0", d: prefixer.qb64 }] - let serder = interact({ pre: pre, sn: sn + 1, data: data, dig: dig, version: undefined, kind: undefined }) + let serder = interact({pre: pre, sn: sn + 1, data: data, dig: dig, version: undefined, kind: undefined}) let keeper = this.client!.manager!.get(hab) sigs = keeper.sign(b(serder.raw)) ixn = serder.ked } + return await this.createFromEvents(hab, name, registryName, vcp, ixn, sigs) + } + + async createFromEvents(hab: Dict, name: string, registryName: string, vcp: Dict, ixn: Dict, sigs: any[]) { + let path = `/identifiers/${name}/registries` let method = 'POST' - let data = { + + let data: any = { name: registryName, vcp: vcp, ixn: ixn!, sigs: sigs } + let keeper = this.client!.manager!.get(hab) + data[keeper.algo] = keeper.params() + + console.log(data) + let res = await this.client.fetch(path, method, data) return await res.json() } @@ -1801,3 +1853,136 @@ export class Escrows { return await res.json() } } + +/** + * Groups + */ +export class Groups { + client: SignifyClient + + /** + * Groups + * @param {SignifyClient} client + */ + constructor(client: SignifyClient) { + this.client = client + } + + /** + * Get group request messages + * @async + * @param {string} [said] SAID of exn message to load + * @returns {Promise} A promise to the list of replay messages + */ + async getRequest(said:string): Promise { + + let path = `/multisig/request/` + said + let method = 'GET' + let res = await this.client.fetch(path, method, null) + return await res.json() + } + + /** + * Send multisig exn request messages to other group members + * @async + * @param {string} [name] human readable name of group AID + * @param {Dict} [exn] exn message to send to other members + * @param {string[]} [sigs] signature of the participant over the exn + * @param {string} [atc] additional attachments from embedded events in exn + * @returns {Promise} A promise to the list of replay messages + */ + async sendRequest(name: string, exn:Dict, sigs: string[], atc: string): Promise { + + let path = `/identifiers/${name}/multisig/request` + let method = 'POST' + let data = { + exn: exn, + sigs: sigs, + atc: atc + } + let res = await this.client.fetch(path, method, data) + return await res.json() + } +} + + +/** + * Exchanges + */ +export class Exchanges { + client: SignifyClient + + /** + * Exchanges + * @param {SignifyClient} client + */ + constructor(client: SignifyClient) { + this.client = client + } + + /** + * Send exn messaget to list of recipients + * @async + * @returns {Promise} A promise to the list of replay messages + * @param sender + * @param route + * @param payload + * @param embeds + */ + createExchangeMessage(sender: Dict, route:string, payload: Dict, embeds: Dict): [Serder, string[], string]{ + let keeper = this.client.manager!.get(sender) + let [exn, end] = exchange(route, + payload, + sender["prefix"], undefined, undefined, undefined, undefined, embeds) + + let sigs = keeper.sign(b(exn.raw)) + return [exn, sigs, d(end)] + } + + /** + * Send exn messaget to list of recipients + * @async + * @returns {Promise} A promise to the list of replay messages + * @param name + * @param topic + * @param sender + * @param route + * @param payload + * @param embeds + * @param recipients + */ + async send(name:string, topic: string, sender: Dict, route:string, payload: Dict, embeds: Dict, + recipients: string[]): Promise { + + let [exn, sigs, atc] = this.createExchangeMessage(sender, route, payload, embeds) + return await this.sendFromEvents(name, topic, exn, sigs, atc, recipients) + + } + + /** + * Send exn messaget to list of recipients + * @async + * @returns {Promise} A promise to the list of replay messages + * @param name + * @param topic + * @param exn + * @param sigs + * @param atc + * @param recipients + */ + async sendFromEvents(name:string, topic: string, exn:Serder, sigs: string[], atc: string, recipients: string[]): Promise { + + let path = `/identifiers/${name}/exchanges` + let method = 'POST' + let data: any = { + tpc: topic, + exn: exn.ked, + sigs: sigs, + atc: atc, + rec: recipients + } + + let res = await this.client.fetch(path, method, data) + return await res.json() + } +} diff --git a/src/keri/core/bexter.ts b/src/keri/core/bexter.ts new file mode 100644 index 00000000..db015faf --- /dev/null +++ b/src/keri/core/bexter.ts @@ -0,0 +1,133 @@ +import {BexDex, Matter, MatterArgs, MtrDex} from "./matter"; +import {EmptyMaterialError} from "./kering"; +import Base64 from "urlsafe-base64"; + +const B64REX = "^[A-Za-z0-9\\-_]*$" +export const Reb64 = new RegExp(B64REX) + + +/* + + Bexter is subclass of Matter, cryptographic material, for variable length + strings that only contain Base64 URL safe characters, i.e. Base64 text (bext). + When created using the 'bext' paramaeter, the encoded matter in qb64 format + in the text domain is more compact than would be the case if the string were + passed in as raw bytes. The text is used as is to form the value part of the + qb64 version not including the leader. + + Due to ambiguity that arises from pre-padding bext whose length is a multiple of + three with one or more 'A' chars. Any bext that starts with an 'A' and whose length + is either a multiple of 3 or 4 may not round trip. Bext with a leading 'A' + whose length is a multiple of four may have the leading 'A' stripped when + round tripping. + + Bexter(bext='ABBB').bext == 'BBB' + Bexter(bext='BBB').bext == 'BBB' + Bexter(bext='ABBB').qb64 == '4AABABBB' == Bexter(bext='BBB').qb64 + + To avoid this problem, only use for applications of base 64 strings that + never start with 'A' + + Examples: base64 text strings: + + bext = "" + qb64 = '4AAA' + + bext = "-" + qb64 = '6AABAAA-' + + bext = "-A" + qb64 = '5AABAA-A' + + bext = "-A-" + qb64 = '4AABA-A-' + + bext = "-A-B" + qb64 = '4AAB-A-B' + + + Example uses: + CESR encoded paths for nested SADs and SAIDs + CESR encoded fractionally weighted threshold expressions + + + Attributes: + + Inherited Properties: (See Matter) + .pad is int number of pad chars given raw + + .code is str derivation code to indicate cypher suite + .raw is bytes crypto material only without code + .index is int count of attached crypto material by context (receipts) + .qb64 is str in Base64 fully qualified with derivation code + crypto mat + .qb64b is bytes in Base64 fully qualified with derivation code + crypto mat + .qb2 is bytes in binary with derivation code + crypto material + .transferable is Boolean, True when transferable derivation code False otherwise + + Properties: + .text is the Base64 text value, .qb64 with text code and leader removed. + + Hidden: + ._pad is method to compute .pad property + ._code is str value for .code property + ._raw is bytes value for .raw property + ._index is int value for .index property + ._infil is method to compute fully qualified Base64 from .raw and .code + ._exfil is method to extract .code and .raw from fully qualified Base64 + + Methods: + + + + + */ + +export class Bexter extends Matter { + constructor({raw, code = MtrDex.StrB64_L0, qb64b, qb64, qb2}: MatterArgs, bext?: string) { + if (raw === undefined && qb64b === undefined && qb64 === undefined && qb2 === undefined) { + if (bext === undefined) + throw new EmptyMaterialError("Missing bext string.") + + let match = Reb64.exec(bext) + if (!match) + throw new Error("Invalid Base64.") + + raw = Bexter._rawify(bext) + } + + super({raw, code, qb64b, qb64, qb2}); + + if (!BexDex.has(this.code)) + throw new Error(`Invalid code = ${this.code} for Bexter.`) + } + + static _rawify(bext: string): Uint8Array { + let ts = bext.length % 4 // bext size mod 4 + let ws = (4 - ts) % 4 // pre conv wad size in chars + let ls = (3 - ts) % 3 // post conv lead size in bytes + let wad = new Array(ws) + wad.fill('A') + let base = wad.join('') + bext // pre pad with wad of zeros in Base64 == 'A' + let raw = Base64.decode(base) // [ls:] // convert and remove leader + + return Uint8Array.from(raw).subarray(ls) // raw binary equivalent of text + + } + + get bext(): string { + let sizage = Matter.Sizes.get(this.code) + let wad = Uint8Array.from(new Array(sizage?.ls).fill(0)) + let bext = Base64.encode(Buffer.from([...wad, ...this.raw])) + + let ws = 0 + if (sizage?.ls === 0 && bext !== undefined) { + if (bext[0] === 'A') { + ws = 1 + } + } else { + ws = (sizage?.ls! + 1) % 4 + } + + return bext.substring(ws) + } +} \ No newline at end of file diff --git a/src/keri/core/matter.ts b/src/keri/core/matter.ts index 30f24e0b..052addd7 100644 --- a/src/keri/core/matter.ts +++ b/src/keri/core/matter.ts @@ -23,6 +23,12 @@ export class MatterCodex extends Codex { X25519_Cipher_Salt: string = '1AAH' // X25519 100 char b64 Cipher of 24 char qb64 Salt Salt_128: string = '0A' // 128 bit random salt or 128 bit number (see Huge) Ed25519_Sig: string = '0B' // Ed25519 signature. + StrB64_L0: string = '4A' //String Base64 Only Lead Size 0 + StrB64_L1: string = '5A' //String Base64 Only Lead Size 1 + StrB64_L2: string = '6A' //String Base64 Only Lead Size 2 + StrB64_Big_L0: string = '7AAA' //String Base64 Only Big Lead Size 0 + StrB64_Big_L1: string = '8AAA' //String Base64 Only Big Lead Size 1 + StrB64_Big_L2: string = '9AAA' //String Base64 Only Big Lead Size 2 } export const MtrDex = new MatterCodex() @@ -71,6 +77,22 @@ export class BexCodex extends Codex { export const BexDex = new BexCodex() +class SmallVarRawSizeCodex extends Codex { + Lead0: string = '4' // First Selector Character for all ls == 0 codes + Lead1: string = '5' // First Selector Character for all ls == 1 codes + Lead2: string = '6' // First Selector Character for all ls == 2 codes +} + +export const SmallVrzDex = new SmallVarRawSizeCodex() + +class LargeVarRawSizeCodex extends Codex { + Lead0_Big: string = '7' // First Selector Character for all ls == 0 codes + Lead1_Big: string = '8' // First Selector Character for all ls == 1 codes + Lead2_Big: string = '9' // First Selector Character for all ls == 2 codes +} + +export const LargeVrzDex = new LargeVarRawSizeCodex() + export class Sizage { public hs: number; public ss: number; @@ -81,7 +103,7 @@ export class Sizage { this.hs = hs; this.ss = ss; this.fs = fs; - this.ls = ls; + this.ls = ls!; } } @@ -91,6 +113,7 @@ export interface MatterArgs { qb64b?: Uint8Array | undefined qb64?: string qb2?: Uint8Array | undefined + rize?: number } export class Matter { @@ -160,7 +183,7 @@ export class Matter { private _size: number = -1; private _raw: Uint8Array = new Uint8Array(0); - constructor({raw, code = MtrDex.Ed25519N, qb64b, qb64, qb2}: MatterArgs) { + constructor({raw, code = MtrDex.Ed25519N, qb64b, qb64, qb2, rize}: MatterArgs) { let size = -1 if (raw != undefined) { @@ -168,13 +191,47 @@ export class Matter { throw new Error("Improper initialization need either (raw and code) or qb64b or qb64 or qb2.") } - // Add support for variable size codes here if needed, this code only works for stable size codes - let sizage = Matter.Sizes.get(code) - if (sizage!.fs == -1) { // invalid - throw new Error(`Unsupported variable size code=${code}`) - } - let rize = Matter._rawSize(code) + if (SmallVrzDex.has(code[0]) || LargeVrzDex.has(code[0])) { + if (rize !== undefined) { + if (rize < 0) + throw new Error(`missing var raw size for code=${code}`) + } else { + rize = raw.length + } + + let ls = (3 - (rize % 3)) % 3 // calc actual lead (pad) size + size = Math.floor((rize + ls) / 3) // calculate value of size in triplets + if (SmallVrzDex.has(code[0])) { + if (size <= (64 ** 2 - 1)) { + let hs = 2 + let s = Object.values(SmallVrzDex)[ls] + code = `${s}${code.substring(1, hs)}` + } else if (size <= (64 ** 4 - 1)) { + let hs = 4 + let s = Object.values(LargeVrzDex)[ls] + code = `${s}${'AAAA'.substring(0, hs - 2)}${code[1]}` + } else { + throw new Error(`Unsupported raw size for code=${code}`) + } + } else { + if (size <= (64 ** 4 - 1)) { + let hs= 4 + let s = Object.values(LargeVrzDex)[ls] + code = `${s}${code.substring(1, hs)}` + } else { + throw new Error(`Unsupported raw size for code=${code}`) + } + } + } else { + let sizage = Matter.Sizes.get(code) + if (sizage!.fs == -1) { // invalid + throw new Error(`Unsupported variable size code=${code}`) + } + + rize = Matter._rawSize(code) + + } raw = raw.slice(0, rize) // copy only exact size from raw stream if (raw.length != rize) { // forbids shorter throw new Error(`Not enougth raw bytes for code=${code} expected ${rize} got ${raw.length}.`) @@ -242,19 +299,43 @@ export class Matter { get both() { let sizage = Matter.Sizes.get(this.code); - return `${this.code}${intToB64(this.size, sizage!.ls)}` + return `${this.code}${intToB64(this.size, sizage!.ss)}` } private _infil() { let code = this.code; + let size = this.size let raw = this.raw; let ps = ((3 - (raw.length % 3)) % 3); // pad size chars or lead size bytes let sizage = Matter.Sizes.get(code); - if (sizage!.fs === -1) { // Variable size code, NOT SUPPORTED - throw new Error("Variable sized codes not supported... yet"); + if (sizage!.fs === undefined) { // Variable size code, NOT SUPPORTED + let cs = sizage!.hs + sizage!.ss + if (cs % 4) { + throw new Error(`Whole code size not multiple of 4 for variable length material. cs=${cs}`) + } + if (size < 0 || size > (64 ** sizage!.ss - 1)) { + throw new Error(`Invalid size=${size} for code=${code}.`) + } + + let both = `${code}${intToB64(size, sizage!.ss)}` + if (both.length % 4 !== ps - sizage!.ls!) { + throw new Error(`Invalid code=${both} for converted raw pad size=${ps}.`) + } + + let bytes = new Uint8Array(sizage!.ls! + raw.length) + for (let i = 0; i < sizage!.ls!; i++) { + bytes[i] = 0; + } + for (let i = 0; i < raw.length; i++) { + let odx = i + ps + bytes[odx] = raw[i]; + } + + return both + Base64.encode(Buffer.from(bytes)) + } else { let both = code let cs = both.length diff --git a/src/keri/core/pather.ts b/src/keri/core/pather.ts new file mode 100644 index 00000000..fd7c21b2 --- /dev/null +++ b/src/keri/core/pather.ts @@ -0,0 +1,91 @@ + +import {Bexter, Reb64} from "./bexter"; +import {MatterArgs, MtrDex} from "./matter"; +import {EmptyMaterialError} from "./kering"; + +/* + Pather is a subclass of Bexter that provides SAD Path language specific functionality + for variable length strings that only contain Base64 URL safe characters. Pather allows + the specification of SAD Paths as a list of field components which will be converted to the + Base64 URL safe character representation. + + Additionally, Pather provides .rawify for extracting and serializing the content targeted by + .path for a SAD, represented as an instance of Serder. Pather enforces Base64 URL character + safety by leveraging the fact that SADs must have static field ordering. Any field label can + be replaced by its field ordinal to allow for path specification and traversal for any field + labels that contain non-Base64 URL safe characters. + + + Examples: strings: + path = [] + text = "-" + qb64 = '6AABAAA-' + + path = ["A"] + text = "-A" + qb64 = '5AABAA-A' + + path = ["A", "B"] + text = "-A-B" + qb64 = '4AAB-A-B' + + path = ["A", 1, "B", 3] + text = "-A-1-B-3" + qb64 = '4AAC-A-1-B-3' + + */ + +export class Pather extends Bexter { + constructor({raw, code = MtrDex.StrB64_L0, qb64b, qb64, qb2}: MatterArgs, bext?: string, path?: string[]) { + if (raw === undefined && bext === undefined && qb64b === undefined && qb64 === undefined && qb2 === undefined) { + if (path === undefined) + throw new EmptyMaterialError("Missing bext string.") + + bext = Pather._bextify(path) + } + + super({raw, code, qb64b, qb64, qb2}, bext); + + } + + // TODO: implement SAD access methods like resolve, root, strip, startswith and tail + + get path(): string[] { + if (!this.bext.startsWith("-")) { + throw new Error("invalid SAD ptr") + } + + let path = this.bext + while(path.charAt(0) === '-') + { + path = path.substring(1); + } + + let apath = path.split("-") + if (apath[0] !== '') { + return apath + } else { + return [] + } + } + + static _bextify(path: any[]): string { + let vath = [] + for (const p of path) { + let sp = "" + if (typeof(p) === "number") { + sp = p.toString() + } else { + sp = p + } + + let match = Reb64.exec(sp) + if (!match) { + throw new Error(`"Non Base64 path component = ${p}.`) + } + + vath.push(sp) + } + return "-" + vath.join("-") + } +} \ No newline at end of file diff --git a/test/core/bexter.test.ts b/test/core/bexter.test.ts new file mode 100644 index 00000000..90130030 --- /dev/null +++ b/test/core/bexter.test.ts @@ -0,0 +1,115 @@ +import {strict as assert} from "assert"; +import {Bexter} from "../../src/keri/core/bexter"; +import {b, MtrDex} from "../../src"; + + +describe("Bexter", () => { + it("should bext-ify stuff (and back again)", () => { + assert.throws(() => { + new Bexter({}) + }) + + let bext = "@!" + assert.throws(() => { + new Bexter({}, bext) + }) + + bext = "" + let bexter = new Bexter({}, bext) + assert.equal(bexter.code, MtrDex.StrB64_L0) + assert.equal(bexter.both, '4AAA') + assert.deepStrictEqual(bexter.raw, b('')) + assert.equal(bexter.qb64, '4AAA') + assert.equal(bexter.bext, bext) + + bext = "-" + bexter = new Bexter({}, bext) + assert.equal(bexter.code, MtrDex.StrB64_L2) + assert.equal(bexter.both, '6AAB') + assert.deepStrictEqual(bexter.raw, b('>')) + assert.equal(bexter.qb64, '6AABAAA-') + assert.equal(bexter.bext, bext) + + bext = "-A" + bexter = new Bexter({}, bext) + assert.equal(bexter.code, MtrDex.StrB64_L1) + assert.equal(bexter.both, '5AAB') + assert.deepStrictEqual(bexter.raw, Uint8Array.from([15, 128])) + assert.equal(bexter.qb64, '5AABAA-A') + assert.equal(bexter.bext, bext) + + bext = "-A-" + bexter = new Bexter({}, bext) + assert.equal(bexter.code, MtrDex.StrB64_L0) + assert.equal(bexter.both, '4AAB') + assert.deepStrictEqual(bexter.raw, Uint8Array.from([3, 224, 62])) + assert.equal(bexter.qb64, '4AABA-A-') + assert.equal(bexter.bext, bext) + + bext = "-A-B" + bexter = new Bexter({}, bext) + assert.equal(bexter.code, MtrDex.StrB64_L0) + assert.equal(bexter.both, '4AAB') + assert.deepStrictEqual(bexter.raw, Uint8Array.from([248, 15, 129])) + assert.equal(bexter.qb64, '4AAB-A-B') + assert.equal(bexter.bext, bext) + + bext = "A" + bexter = new Bexter({}, bext) + assert.equal(bexter.code, MtrDex.StrB64_L2) + assert.equal(bexter.both, '6AAB') + assert.deepStrictEqual(bexter.raw, Uint8Array.from([0])) + assert.equal(bexter.qb64, '6AABAAAA') + assert.equal(bexter.bext, bext) + + bext = "AA" + bexter = new Bexter({}, bext) + assert.equal(bexter.code, MtrDex.StrB64_L1) + assert.equal(bexter.both, '5AAB') + assert.deepStrictEqual(bexter.raw, Uint8Array.from([0, 0])) + assert.equal(bexter.qb64, '5AABAAAA') + assert.equal(bexter.bext, bext) + + bext = "AAA" + bexter = new Bexter({}, bext) + assert.equal(bexter.code, MtrDex.StrB64_L0) + assert.equal(bexter.both, '4AAB') + assert.deepStrictEqual(bexter.raw, Uint8Array.from([0, 0, 0])) + assert.equal(bexter.qb64, '4AABAAAA') + assert.equal(bexter.bext, bext) + + bext = "AAAA" + bexter = new Bexter({}, bext) + assert.equal(bexter.code, MtrDex.StrB64_L0) + assert.equal(bexter.both, '4AAB') + assert.deepStrictEqual(bexter.raw, Uint8Array.from([0, 0, 0])) + assert.equal(bexter.qb64, '4AABAAAA') + assert.equal(bexter.bext, "AAA") + assert.notEqual(bexter.bext, bext) + + bext = "ABB" + bexter = new Bexter({}, bext) + assert.equal(bexter.code, MtrDex.StrB64_L0) + assert.equal(bexter.both, '4AAB') + assert.deepStrictEqual(bexter.raw, Uint8Array.from([0, 0, 65])) + assert.equal(bexter.qb64, '4AABAABB') + assert.equal(bexter.bext, bext) + + bext = "BBB" + bexter = new Bexter({}, bext) + assert.equal(bexter.code, MtrDex.StrB64_L0) + assert.equal(bexter.both, '4AAB') + assert.deepStrictEqual(bexter.raw, Uint8Array.from([0, 16, 65])) + assert.equal(bexter.qb64, '4AABABBB') + assert.equal(bexter.bext, bext) + + bext = "ABBB" + bexter = new Bexter({}, bext) + assert.equal(bexter.code, MtrDex.StrB64_L0) + assert.equal(bexter.both, '4AAB') + assert.deepStrictEqual(bexter.raw, Uint8Array.from([0, 16, 65])) + assert.equal(bexter.qb64, '4AABABBB') + assert.equal(bexter.bext, 'BBB') + assert.notEqual(bexter.bext, bext) + }) +}) \ No newline at end of file diff --git a/test/core/exchanging.test.ts b/test/core/exchanging.test.ts new file mode 100644 index 00000000..e454720f --- /dev/null +++ b/test/core/exchanging.test.ts @@ -0,0 +1,108 @@ +import {strict as assert} from "assert"; +import {b, d, Dict, Diger, exchange, Ilks, MtrDex, Salter, Serder, Tier} from "../../src"; +import libsodium from "libsodium-wrappers-sumo"; + + +describe("exchange", () => { + it("should create an exchange message with no transposed attachments", async () => { + await libsodium.ready + let dt = "2023-08-30T17:22:54.183Z" + + let [exn, end] = exchange("/multisig/vcp", {}, "test", undefined, dt) + assert.deepStrictEqual(exn.ked, { + "a": {}, "d": "EMhxioc6Ud9b3JZ4X9o79uytSRIXXNDUf27ruwiOmNdQ", "dt": "2023-08-30T17:22:54.183Z", "e": {}, + "i": "test", + "p": "", "q": {}, "r": "/multisig/vcp", "t": "exn", "v": "KERI10JSON0000b1_" + } + ) + assert.deepStrictEqual(end, new Uint8Array()) + + let sith = 1 + let nsith = 1 + let sn = 0 + let toad = 0 + + let raw = new Uint8Array([5, 170, 143, 45, 83, 154, 233, 250, 85, 156, 2, 156, 155, 8, 72, 117]) + let salter = new Salter({raw: raw}) + let skp0 = salter.signer(MtrDex.Ed25519_Seed, true, "A", Tier.low, true) + let keys = [skp0.verfer.qb64] + + let skp1 = salter.signer(MtrDex.Ed25519_Seed, true, "N", Tier.low, true) + let ndiger = new Diger({}, skp1.verfer.qb64b) + let nxt = [ndiger.qb64] + assert.deepStrictEqual(nxt, ['EAKUR-LmLHWMwXTLWQ1QjxHrihBmwwrV2tYaSG7hOrWj']) + + let ked0 = { + v: "KERI10JSON000000_", + t: Ilks.icp, + d: "", + i: "", + s: sn.toString(16), + kt: sith.toString(16), + k: keys, + nt: nsith.toString(16), + n: nxt, + bt: toad.toString(16), + b: [], + c: [], + a: [], + } as Dict + + let serder = new Serder(ked0) + let siger = skp0.sign(b(serder.raw), 0) + assert.equal(siger.qb64, "AAAPkMTS3LrrhVuQB0k4UndDN0xIfEiKYaN7rTlQ_q9ImnBcugwNO8VWTALXzWoaldJEC1IOpEGkEnjZfxxIleoI") + + let ked1 = { + v: "KERI10JSON000000_", + t: Ilks.vcp, + d: "", + i: "", + s: "0", + bt: toad.toString(16), + b: [] + } as Dict + let vcp = new Serder(ked1) + + + let embeds = { + icp: [serder, siger.qb64], + vcp: [vcp, undefined] + } as Dict + + [exn, end] = exchange("/multisig/vcp", {}, "test", undefined, dt, undefined, undefined, embeds) + + assert.deepStrictEqual(exn.ked, { + "a": {}, + "d": "EHDEXQx-i0KlQ8iVnITMLa144dAb7Kjq2KDTufDUyLcm", + "dt": "2023-08-30T17:22:54.183Z", + "e": { + "d": "EDPWpKtMoPwro_Of8TQzpNMGdtmfyWzqTcRKQ01fGFRi", + "icp": { + "a": [], + "b": [], + "bt": "0", + "c": [], + "d": "", + "i": "", + "k": ["DAUDqkmn-hqlQKD8W-FAEa5JUvJC2I9yarEem-AAEg3e"], + "kt": "1", + "n": ["EAKUR-LmLHWMwXTLWQ1QjxHrihBmwwrV2tYaSG7hOrWj"], + "nt": "1", + "s": "0", + "t": "icp", + "v": "KERI10JSON0000d3_" + }, + "vcp": {"b": [], "bt": "0", "d": "", "i": "", "s": "0", "t": "vcp", "v": "KERI10JSON000049_"} + }, + "i": "test", + "p": "", + "q": {}, + "r": "/multisig/vcp", + "t": "exn", + "v": "KERI10JSON00020d_" + }) + assert.equal(d(end), "-LAZ5AACAA-e-icpAAAPkMTS3LrrhVuQB0k4UndDN0xIfEiKYaN7rTlQ_q9ImnBcugwNO8VWTALXzWoaldJEC1IOpEGkEnjZfxxIleoI") + + + }) +}) \ No newline at end of file diff --git a/test/core/pather.test.ts b/test/core/pather.test.ts new file mode 100644 index 00000000..5cc927dc --- /dev/null +++ b/test/core/pather.test.ts @@ -0,0 +1,46 @@ +import {strict as assert} from "assert"; +import {Pather} from "../../src/keri/core/pather"; +import {b} from "../../src"; + + +describe("Pather", () => { + it("should path-ify stuff (and back again)", () => { + assert.throws(() => { + new Pather({}) + }) + + let path: string[] = [] + let pather = new Pather({}, undefined, path) + assert.equal(pather.bext, "-") + assert.equal(pather.qb64, "6AABAAA-") + assert.deepStrictEqual(pather.raw, b('>')) + assert.deepStrictEqual(pather.path, path) + + path = ["a", "b", "c"] + pather = new Pather({}, undefined, path) + assert.equal(pather.bext, "-a-b-c") + assert.equal(pather.qb64, "5AACAA-a-b-c") + assert.deepStrictEqual(pather.raw, new Uint8Array( [15, 154, 249, 191, 156])) + assert.deepStrictEqual(pather.path, path) + + path = ["0", "1", "2"] + pather = new Pather({}, undefined, path) + assert.equal(pather.bext, "-0-1-2") + assert.equal(pather.qb64, "5AACAA-0-1-2") + assert.deepStrictEqual(pather.raw, new Uint8Array( [15, 180, 251, 95, 182])) + assert.deepStrictEqual(pather.path, path) + + path = ["field0", "1", "0"] + pather = new Pather({}, undefined, path) + assert.equal(pather.bext, "-field0-1-0") + assert.equal(pather.qb64, "4AADA-field0-1-0") + assert.deepStrictEqual(pather.raw, new Uint8Array( [3, 231, 226, 122, 87, 116, 251, 95, 180])) + assert.deepStrictEqual(pather.path, path) + + path = ["Not$Base64", "@moreso", "*again"] + assert.throws(() => { + new Pather({}, undefined, path) + }) + + }) +}) \ No newline at end of file diff --git a/test/core/signer.test.ts b/test/core/signer.test.ts index e1255940..a38b98dc 100644 --- a/test/core/signer.test.ts +++ b/test/core/signer.test.ts @@ -1,9 +1,9 @@ import {strict as assert} from "assert"; import libsodium from "libsodium-wrappers-sumo"; -import { Signer } from '../../src/keri/core/signer'; -import {Matter, MtrDex} from "../../src/keri/core/matter"; -import {b} from "../../src/keri/core/core"; +import { Signer } from '../../src'; +import {Matter, MtrDex} from "../../src"; +import {b} from "../../src"; describe('Signer', () => { it('should sign things', async () => {