From 7de13845863dc7507d95c96953f2650244ff8567 Mon Sep 17 00:00:00 2001 From: pfeairheller Date: Mon, 4 Sep 2023 13:58:14 -0700 Subject: [PATCH 1/2] Addition of Bexter for CESR encoding of variable length data and Pather for SAD Path language encoding. Exchanging for creating EXN with support for embedded events. New client classes, Exchanges and Groups for performing group operations. --- examples/scripts/client.ts | 23 ++- examples/scripts/create_multisig_aid.py | 40 ++-- examples/scripts/create_person_aid.py | 2 +- examples/scripts/get.py | 27 +++ examples/scripts/list_notifications.py | 85 ++++++++ examples/scripts/list_notifications.ts | 101 ++++++++++ examples/scripts/package-lock.json | 63 ++++++ examples/scripts/package.json | 4 +- src/index.ts | 1 + src/keri/app/exchanging.ts | 84 ++++++++ src/keri/app/signify.ts | 247 +++++++++++++++++++++--- src/keri/core/bexter.ts | 133 +++++++++++++ src/keri/core/matter.ts | 103 ++++++++-- src/keri/core/pather.ts | 91 +++++++++ test/core/bexter.test.ts | 115 +++++++++++ test/core/exchanging.test.ts | 109 +++++++++++ test/core/pather.test.ts | 46 +++++ test/core/signer.test.ts | 6 +- 18 files changed, 1219 insertions(+), 61 deletions(-) create mode 100644 examples/scripts/get.py create mode 100644 examples/scripts/list_notifications.py create mode 100644 examples/scripts/list_notifications.ts create mode 100644 src/keri/app/exchanging.ts create mode 100644 src/keri/core/bexter.ts create mode 100644 src/keri/core/pather.ts create mode 100644 test/core/bexter.test.ts create mode 100644 test/core/exchanging.test.ts create mode 100644 test/core/pather.test.ts 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..f2f75f41 --- /dev/null +++ b/test/core/exchanging.test.ts @@ -0,0 +1,109 @@ +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.ked, siger.qb64], + vcp: [vcp.ked, 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), "-LAD5AACAA-e-icpAAAPkMTS3LrrhVuQB0k4UndDN0xIfEiKYaN7rTlQ_q9ImnBcugwNO8VWTALXzWoaldJE" + + "C1IOpEGkEnjZfxxIleoI") + + + }) +}) \ 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 () => { From 7e1d3fb91664aef2eb29539ac07061769b1c3788 Mon Sep 17 00:00:00 2001 From: pfeairheller Date: Mon, 4 Sep 2023 14:24:35 -0700 Subject: [PATCH 2/2] Updated tests --- test/core/exchanging.test.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/core/exchanging.test.ts b/test/core/exchanging.test.ts index f2f75f41..e454720f 100644 --- a/test/core/exchanging.test.ts +++ b/test/core/exchanging.test.ts @@ -65,8 +65,8 @@ describe("exchange", () => { let embeds = { - icp: [serder.ked, siger.qb64], - vcp: [vcp.ked, undefined] + icp: [serder, siger.qb64], + vcp: [vcp, undefined] } as Dict [exn, end] = exchange("/multisig/vcp", {}, "test", undefined, dt, undefined, undefined, embeds) @@ -101,8 +101,7 @@ describe("exchange", () => { "t": "exn", "v": "KERI10JSON00020d_" }) - assert.equal(d(end), "-LAD5AACAA-e-icpAAAPkMTS3LrrhVuQB0k4UndDN0xIfEiKYaN7rTlQ_q9ImnBcugwNO8VWTALXzWoaldJE" + - "C1IOpEGkEnjZfxxIleoI") + assert.equal(d(end), "-LAZ5AACAA-e-icpAAAPkMTS3LrrhVuQB0k4UndDN0xIfEiKYaN7rTlQ_q9ImnBcugwNO8VWTALXzWoaldJEC1IOpEGkEnjZfxxIleoI") })