diff --git a/.github/workflows/publish-keripy.yml b/.github/workflows/publish-keripy.yml index 494babc0b..7455eef88 100644 --- a/.github/workflows/publish-keripy.yml +++ b/.github/workflows/publish-keripy.yml @@ -32,5 +32,7 @@ jobs: context: . file: images/keripy.dockerfile push: true - tags: gleif/keri:${{ github.event.inputs.version }} + tags: | + gleif/keri:${{ github.event.inputs.version }} + gleif/keri:latest labels: ${{ github.event.inputs.version }} \ No newline at end of file diff --git a/.github/workflows/python-app-ci.yml b/.github/workflows/python-app-ci.yml index f21e8cd46..2df15d859 100644 --- a/.github/workflows/python-app-ci.yml +++ b/.github/workflows/python-app-ci.yml @@ -54,12 +54,15 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install pytest pytest-cov codecov hio + pip install pytest pytest-cov hio if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Run core KERI tests run: | pytest --cov=./ --cov-report=xml - codecov + - name: Upload + uses: codecov/codecov-action@v3 + with: + token: 5eb1c60e-8b43-45f1-a003-357b240061cd scripts: runs-on: ubuntu-latest @@ -72,7 +75,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install pytest pytest-cov codecov hio pytest-shell + pip install pytest pytest-cov hio pytest-shell if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Run KERI kli tests run: | diff --git a/Contributing.md b/Contributing.md new file mode 100644 index 000000000..ccef9a424 --- /dev/null +++ b/Contributing.md @@ -0,0 +1,94 @@ +# Contributors' Guide + +## Introduction + +Welcome to keripy's contribution guide! This project aims to develop a reference implementation of the KERI standards and protocols developed in WebOfTrust on github. We deeply appreciate the time and effort of contributors like you! + +## Legal & Licensing +This project has a split license whose details are contained [here](https://github.com/WebOfTrust/ietf-keri/blob/main/LICENSE.md) + +## Prerequisites +Before you start, make sure you have: + +- Read the [README.md](./README.md) (best quickstart guide for using the project) and can build, install, and run the tests yourself from those instructions (from both the main and development branch). This is the assumed baseline for any issues, proposed features, or pull requests that you wish you contribute. +- Are somewhat familiar with the [KERI, ACDC, CESR standards and the KERI whitepapers](https://github.com/WebOfTrust/keri). Jargon isn't always the best, but it does speed communication with maintainers whose time is often in short supply and appreciate clarity in all things. A very helpful website [kerisse.org](https://kerisse.org) has been set up to ease this process if you don't necessarily have the time to read all of those things. However, an in depth knowledge will surely help if you are looking to make a lot of contributions. + +## How to Start Contributing +The KERI community welcomes [all kinds of contributions](https://opensource.guide/how-to-contribute/). + +For simple contributions like fixing typos sometimes you can just submit a naked PR with a title like "I'm fixing a spelling issue in x,y,z". If these PRs are small often times they'll just get merged without further review. Typos or fixing names that were mistakenly applied are human nature. + +A really simple way to contribute is just to spread the word. Let people know about KERI, create a welcoming community to newcomers by actively participating and helping with what you know, and making cools things and talking about them is helpful to grow. + +Issues and discussions are meant to be discussed, commented upon. Even a +1 to an issue that you're also experiencing can help the core maintainers decide where to focus their energies. Discussions can help provide clarity if things aren't exactly clear. Feel free to contribute (but also note the homework in [PREREQUISITES](#prerequisites), its frustrating for people to explain things that are already explained elsewhere). + +However, this particular file is more about contributing code. The general principal in this (and all open source really) is if you can think of a way that things might be better, instead of just suggesting it or discussing it, often times its a good idea to do some work to show what/how/when an idea might look like or contributing code/scripts/tests/issues. Code >> discussion 9 times out of ten. Balanced of course with the caveat not to go off into the ivory tower for months and do lots of work before presenting your work and finding out that the community doesn't like it. Small prototypes/examples are often best. The rest of the details of contributing code are below. + +## Contribution Guidelines + +### Code Style/Conventions +[PythonStyleGuide](./ref/PythonStyleGuide.md) + +### Commit Message Guidelines +There are no hard and fast guidelines but it is helpful to: +- Descriptive, to the point messages are ideal +- Link to the issue you're trying to solve with a message of how the commit does or doesn't solve that issue. +- Be sure to note important issues for checkpoint commits like "does not build" +- Most importantly, the commit message should explain the WHY of all the code changes. When reviewing, reviewers will be confused if code changes for things you didn't list in the commit aren't there. + +### Branching Strategy +Branch from development, name your feature branches something like `feature-name-of-my-feature` and bugs something like `bug-bug-name` and link the github issue that you (or someone else) should have created for most non-trivial bugs/features. When you have fully implemented or fixed, submit a PR to WebOfTrust/keripy. If you need feedback it can also be appropriate to submit a **draft** PR to this repository and ask for comments. + +### Testing +See [README.md](README.md). Always add tests if you fix a bug or add a feature. This should conform to the conventions of the repository (ie if you're fixing some issue in IPEX, put your tests with the tests for IPEX). If you're doing a greenfield implementation of something, even a few simple unit tests can provide clarity to future developers. + +## Process to Submit Changes +1. Find and issue and let the developers on discord know you're working on it (and maybe comment on the issue to let people know you're picking it up). +2. Work on issue +3. Add tests +4. Submit a PR to [WebOfTrust/keripy] +5. Let maintainers know on discord in the appropriate channel +6. Come to Thursday KERI development meeting to discuss if at all possible (sometimes its easier for maintainers to provide feedback directly rather than through async text, particularly if its a large or complex change). +7. Iterate 2-6 if your change needs some fixes/updates + +## Reporting Bugs or Requesting Features +- **SEARCH FOR THE BUG OR FEATURE IN THE CURRENT ISSUES IN REPO**. If it already exists, add a comment/script/test/+1/whatever there. Duplicates BAD. +- If the bug/feature doesn't exist create an issue wherein you describe the bug, feature, or issue with as much detail as possible (but maybe not enough that you overload the reader with details). +- Code snippets, scripts, or test cases should be added to the issue if possible. It helps with saving maintainers time and can drastically speed the development process. +- Message in the appropriate discord channel to let people know about your bug/feature/issue, but remember that maintainers maintain at their own pace and discretion on issues of their choosing. Its best not to ping them more than once a week. As with all open source, if its an ultra critical bug/feature for you, cash bounties certainly incentivize people to pay attention and offer to help you directly. + +## Code of Conduct and Respect +[From the discord channel](https://discord.com/channels/1148629222647148624/1148686277269532703/1148686279945498624) + +We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic. + +Please avoid using overtly sexual aliases or other nicknames that might detract from a friendly, safe and welcoming environment for all. + +Please be kind and courteous. There’s no need to be mean or rude. + +Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer. + +Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works. + +We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term “harassment” as including the definition in the Citizen Code of Conduct; if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don’t tolerate behavior that excludes people in socially marginalized groups. + +Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel admins immediately. Whether you’re a regular contributor or a newcomer, we care about making this community a safe place for you and we’ve got your back. + +Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome. +Attribution +Adapted from the Rust Code of Conduct: [https://www.rust-lang.org/policies/code-of-conduct](https://www.rust-lang.org/policies/code-of-conduct) + +## Getting Help + +For questions or clarifications, reach out via: +- Discord: [https://discord.gg/edGDD632tP](https://discord.gg/edGDD632tP) +- KERI Development Meetings: [https://github.com/WebOfTrust/keri#implementors-call] +- ACDC Standards Meeting@TOIP (technically must be a member of ToIP to contribute): [https://github.com/WebOfTrust/keri#specification-call] + +## Acknowledgments +Thanks to all our wonderful contributors. + +## Additional Resources + +- [Project Documentation and Search Engine](https://kerisse.org) +- [Related Projects](https://github.com/WebOfTrust) \ No newline at end of file diff --git a/Makefile b/Makefile index 6abe2e6e0..ddc8f7555 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,16 @@ .PHONY: build-keri build-keri: - @docker build --no-cache -f images/keripy.dockerfile --tag gleif/keri:1.0.0 . + @docker buildx build --platform=linux/amd64 -f images/keripy.dockerfile --tag weboftrust/keri:1.1.0 . .PHONY: build-witness-demo build-witness-demo: - @docker build --no-cache -f images/witness.demo.dockerfile --tag gleif/keri-witness-demo:1.0.0 . + @@docker buildx build --platform=linux/amd64 -f images/witness.demo.dockerfile --tag weboftrust/keri-witness-demo:1.1.0 . + +.PHONY: publish-keri +publish-keri: + @docker push weboftrust/keri --all-tags + +.PHONY: publish-keri-witness-demo +publish-keri-witness-demo: + @docker push weboftrust/keri-witness-demo --all-tags \ No newline at end of file diff --git a/docs/naming.md b/docs/naming.md new file mode 100644 index 000000000..8002f29b3 --- /dev/null +++ b/docs/naming.md @@ -0,0 +1,366 @@ + +# Python Style Guide for keripy + +The Python PEPs on style have many options or allowed variants. +The purpose of this document is to select a single preferred style +in every case. In general this guide follows PEP-8 but specifies styles +where PEP-8 is silent and varies from PEP-8 in a couple of places namely camelCase +versus underscore_case. The latter (camelCase instead of underscore_case) is +the most violated PEP-8 convention in the Python standard library. +For example the well known logging and unittest packages use camelCase. +Therefore, we are in good company. Because camelCase variables are equally +readable but also more concise than underscore_case_, that improved conciseness +contributes to shorter statement lengths which are themselves more readable. +Readability trumps convention. + +## Indentation + +4 spaces (detab ie no tabs convert tabs to spaces) + +## Naming Convention Labels: +These are used in the rules below. + + + alllowercase + ALLUPPERCASE + lowerCamelCase + UpperCamelCase + lower_case_with_underscores + UPPER_CASE_WITH_UNDERSCORES + Capitalized_With_Underscores + _LeadingUnderscoreUpperCamelCase + _leadingUnderscoreLowerCamelCase + __LeadingDoubleUnderscoreUpperCamelCase + __leadingDoubleUnderscoreLowerCamelCase + + + +## Rules + +### Python Standard Library methods and attributes + +alllowercase +startswith() +In some cases may be lower_case_with_underscores + +### Python builtins +alllowercase no underscores +setattr() + +### Vertical Spacing +Spaces between methods and top level functions: + two 2 + +Spaces between Class Definitions: + two 2 + +### DocStrings + Triple double quotes. """ """ + +```python + """If one line doc string then may be all on one line""" + + """If more than one line doc string then first line starts after triple quotes. + Following lines outdent. Embedded strings use 'single quotes'. + """ + + Format for code documentation in the the Google flavor of sphinx.ext.napolean format. + See + https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html + and + https://www.sphinx-doc.org/en/master/usage/extensions/example_google.html#example-google + + The Google style uses indentation to separate sections. +``` + Google style: + +```python +def func(arg1, arg2): + """Summary line. + + Extended description of function. + + Args: + arg1 (int): Description of arg1 + arg2 (str): Description of arg2 + + Returns: + bool: Description of return value + + """ + return True +``` + +### Acronyms + +When using underscores acronyms should be all uppercase if start uppercase, +or all lowercase if start lowercase. + http_send Send_HTTP + +When using CapCamelCase or mixedCase the acronyms should be treated as words + httpSend sendHttp + + +### Local Variables and function parameters: + lowerCamelCase. + +### Any name that conflicts with python reserved word + add trailing underscore: + Examples: id_, file_ + +### Package Names: + alllowercase. Pithy and short but evocative. + + +### Module Names + alllowercase. End name in 'ing' so can distinquish package and module references + when namespacing. First ref is module not package variable such as: + core.behaving, core.clustering + +### Public Module Level Methods and Attributes: + lowerCamelCase. + Methods use verbs. + Attributes that are sequences use plural nouns. + Examples: getName setStyle, display, first, last, itemCount, entities, books, data + +### Private Module Level Methods and Attributes (not exported by from import `*`): + leadingUnderscoreLowerCamelCase. + Methods use verbs. + Attributes that are sequences use plural nouns. + Examples: `_dirtyBit` + +### Constants Module Level: + UPPER_CASE_WITH_UNDERSCORES + Not meant to be changed should be numbers or strings. + Examples: MY_CONSTANT + +### Dynamic Global Variables (not constants) Module Level: + Capitalized_With_Underscores. + These are reserved for module level globals that may be changed in + multiple places intentionally. + Usually this is bad practice so special syntax is used to indicate + such practice only when necessary. + Examples: Bad_Practice REO_Lat_Lon_NE + + +### Exception Classes: + UpperCamelCase. + Example: StandardMuxError + +### Class Names: + UpperCamelCase. + Examples: Person BigDogs + +### Public Class Attributes, Class Methods, Static Methods: + UpperCamelCase. + Methods use verbs. + Attributes that are sequences use plural nouns. + Example: TotalInstances, Storage, Store, Redact + +### Private Class Attributes, Class Methods, Static Methods: + LeadingUnderscoreUpperCamelCase, + Methods use verbs. + Attributes that are sequences use plural nouns. + Example: `_TotalInstances _Storage __Entries` + +### Very Private Class Attributes, Class Methods, Static Methods (mangled with class name): + __LeadingDoubleUnderscoreUpperCamelCase. + Methods use verbs. + Attributes that are sequences use plural nouns. + Example: `__TotalInstances __Storage __Entries` + +### Public Instance Methods and Attributes: + lowerCamelCase' + Methods use verbs. + Attributes that are sequences use plural nouns. + Examples: getName setStyle, display, first, last, itemCount, entities, books, data + + +### Private Instance and Attributes (not exported with from import `*``): + leadingUnderscoreLowerCamelCase. + Methods use verbs. + Attributes that are sequences use plural nouns. + Examples: `_getScore _setInternal _lastName _count _entries` + + +### Very Private Instance Methods or Attributes (mangled with class name): + __leadingDoubleUnderscoreLowerCamelCase. + Methods use verbs. + Attributes that are sequences use plural nouns. + Examples:` __getMyName __displayMoney __creature __secretData __entries` + + + +## Readability + +The book C Elements of Style by Qualline used a quantitaive approach +to measuring readability in code. It measured error rate in reading and +understanding code as a function of style conventions. +These include horizontal and veritical white space, +line length, varible name length, code block demarcation, etc. + +Results were that too much indentation and too little indentation both reduce +readability. Ideal is 2, 3, or 4 space indentation. We use 4 because it is the +python standard and too hard to fight uphill for the slightly more optimal 3. + +Verticle white space matters. Code should have paragraphs. Balanced brackets, +indendation and blank lines demarcate paragraphs + +For example + +void display(void) +{ + int start; + + start = -1; + + if(start == -1) + return; +} + +is more readable than + +void display(void){ +int start; start = -1; +if(start == -1) return; +} + +Line length matters. Which means variable length matters. +Simple logic statments that wrap are no longer simple. +Context may provide meaning. +Scope and class Nesting may provide meaning. + +Shorter evocative names are more readable than long descriptive names when +composing code because the long names make the statements that use the variables +too long to be easily readable. + +aviary vs flyingBirdCage + +aviary.bird vs flyingBirdCage.bird + +hawk.wing.size vs hawkWingSize + +### Acronyms which are abbreviations that form pronounceable words may be +highly evocative. +radar (RAdio Detecting And Ranging), +laser (Light Amplification through Stimulated Emission and Radiation) +keri (Key Event Reciept Infrastructure) + + +## Evocative Semantic Naming + +evocative -adjective tending to evoke: + The perfume was evocative of spring. + +evoke -verb (used with object), + to call up or produce (memories, feelings, etc.): + to evoke a memory. + to elicit or draw forth: + His comment evoked protests from the shocked listener + +An evocative semanitic name is a name that calls up the meaning without having +to explicity detail the meaning. Its a type of mneumonic that balances semantics +with conciseness which balance improves overall readability and understanding. + +aviary vs flyingBirdCage + +Use English suffix composition rules to create pithy terse more consise names +that are sufficiently evocative. + + +## Suffix Mapping + +Adjective describing module. What does the module enable one to do. +Verb is the deed or act +Object is actor doer +Place to keep track of or create Objects container or factory + doery actorery doerage actorage + of an Doer is a doery or doage or dodom or dohood + +-er -or -eur -ster Agent one who does something brewer (Object Classes) +-ery a place for an actor to act factory brewery (Object Factories) +-ing action of running, wishing (Module Names) +-age state of acting actor to act brewerage +-dom state of doing acting kingdom +-hood state of being childhood +-ship quality of or state of rank of midship +-ize to make itemize +-izer Someone who makes one do itemizer +-ive having nature of active rotative inceptive +-acy -isy -ty quality of linty piracy clerisy +-ion -tion -sion act or state of action itemization +-y -ly like full of happening noisy monthly + +patron +patroner +patronist +patronery +patronage +patrondom +patronship +patronacy +patronhood +patronize +patronish +patronive +patronlet +patronly +patrony + + + +### Rules for English suffixes + +http://www.paulnoll.com/Books/Clear-English/English-suffixes-1.html +http://www.prefixsuffix.com/rootchart.php + + +Suffix Meaning Examples Used +able, ible capable of, worthy agreeable, comfortable, credible +age act of or state of salvage, bondage +acy, isy quality hypocrisy, piracy +al, eal, ial on account of related to, action of judicial, official arrival, refusal +ance, ence act or fact of doing state of violence, dependence allowance, insurance +ant quality of one who defiant, expectant, reliant occupant, accountant +er, or, eur agent, one who author, baker, winner, dictator, chauffeur, worker +ed past jumped, baked +ery a place to practice of condition of nunnery, cannery surgery bravery, drudgery +dom state, condition of wisdom, kingdom, martyrdom +ent having the quality of different, dependent, innocent +en made of, to make woolen, wooden, darken + +er degree of comparison harder, newer, older 5440 +est highest of comparison cleanest, hardest, softest 1339 +ful full of graceful, restful, faithful 212 +hood state of being boyhood, knighthood, womanhood 61 +ible, ile, il capable of being digestible, responsible, docile, civil 783 +ier, ior one who carrier, warrior 1114 +ify to make magnify, beautify, falsify 125 +ic like, made of metallic, toxic, poetic 3774 +ing action of running, wishing 5440 +ion act or state of confusion, correction, protection 3362 +ism fact of being communism, socialism 1147 +ish like childish, sheepish, foolish 566 +ist a person who does artist, geologist 1375 +ity, ty state of majesty, chastity, humanity 4795 +itis inflammation of appendicitis, tonsillitis 124 +ive having nature of attractive, active 961 +ize to make pasteurize, motorize 637 +less without motionless, careless, childless 282 + +let small starlet, eaglet 185 +ly like, in a manner happening heavenly, remarkably, suddenly every absolutely, monthly 5440 +ment state or quality act of doing accomplishment, excitement placement, movement 680 +meter device for measuring thermometer, barometer 166 +ness state of blindness, kindness 3322 +ology study of geology, zoology, archaeology 374 +ous, ious full of joyous, marvelous, furious 1615 +ship quality of or state of rank of friendship, leadership lordship +scope instrument for seeing telescope, microscope +some like tiresome, lonesome +tion, sion action, state of being condition, attention, fusion +ty quality or state of liberty, majesty +ward toward southward, forward +y like, full of, diminutive: noisy, sooty, kitty +ure noun from verv indicating act or office seizure prefecture + diff --git a/images/keripy.dockerfile b/images/keripy.dockerfile index aa75fbbc3..ca12837fc 100644 --- a/images/keripy.dockerfile +++ b/images/keripy.dockerfile @@ -1,22 +1,50 @@ +# Builder layer +FROM python:3.10-alpine as builder -FROM python:3.10.4-alpine3.16 +# Install compilation dependencies +RUN apk --no-cache add \ + bash \ + alpine-sdk \ + libffi-dev \ + libsodium \ + libsodium-dev -RUN apk update -RUN apk add bash SHELL ["/bin/bash", "-c"] -RUN apk add alpine-sdk -RUN apk add libffi-dev -RUN apk add libsodium -RUN apk add libsodium-dev - # Setup Rust for blake3 dependency build RUN curl https://sh.rustup.rs -sSf | bash -s -- -y -COPY . /keripy WORKDIR /keripy -# Install KERIpy dependencies -# Must source the Cargo environment for the blake3 library to see the Rust intallation during requirements install -RUN source "$HOME/.cargo/env" && pip install -r requirements.txt +RUN python -m venv venv + +ENV PATH=/keripy/venv/bin:${PATH} + +RUN pip install --upgrade pip && \ + mkdir /keripy/src + +# Copy Python dependency files in +COPY requirements.txt setup.py ./ +# Set up Rust environment and install Python dependencies +# Must source the Cargo environment for the blake3 library to see +# the Rust intallation during requirements install +RUN . ${HOME}/.cargo/env && \ + pip install -r requirements.txt + +# Runtime layer +FROM python:3.10.13-alpine3.18 + +RUN apk --no-cache add \ + bash \ + alpine-sdk \ + libsodium-dev + +WORKDIR /keripy + +COPY --from=builder /keripy /keripy +COPY src/ src/ + +ENV PATH=/keripy/venv/bin:${PATH} + +ENTRYPOINT [ "kli" ] diff --git a/images/witness.demo.dockerfile b/images/witness.demo.dockerfile index 6705c6cfe..f19b068ac 100644 --- a/images/witness.demo.dockerfile +++ b/images/witness.demo.dockerfile @@ -1,4 +1,4 @@ -FROM gleif/keri:1.0.0 +FROM gleif/keri:1.1.0 SHELL ["/bin/bash", "-c"] EXPOSE 5632 diff --git a/ref/MultiHab.md b/ref/MultiHab.md new file mode 100644 index 000000000..7e9c398c0 --- /dev/null +++ b/ref/MultiHab.md @@ -0,0 +1,34 @@ + + +MultiHab can consist of the following: + - 1 or more local AIDs, 1 or more remote AIDs: GroupHab + - 1 or more Signify Clients, 0 or more remote AIDs: SignifyHab + +The key to implementing them all is allowing the had to incept, rotate and interact by accepting +the Serder of the event and the signatures. + +Helper methods are need to create the (icp, rot, ixn) events given the Verfers, Digers and all other parameters. + +Helper methods are needed to parse the event. + +Helper method needed to extract Merfers / Digers from smids and rmids + +Signing the event only occurs when there are 1 or more local AIDs so we have to account for that in the larger group logic. So when event +creation occurs, IF THERE ARE ANY LOCAL AIDs, THEY MUST ALSO SIGN THE EVENT + +When you create a MultiHab you must be able to specify the consituents: + +What if this was a Hab that just knows about verfers, merfers for inception and rotation to create the events. + +Then a method to parse the event that will, before parsing, sign the event with any local AIDs that are present. + +createEvent(merger, diger) - + +parseEvent(serder, sigers) -> signs with local Habs if there + +updateHabRecord()... + +Allow for creation of the event by the lead that is either local or Signify client or other multisig AID + +Start with SkelHab for just parse and sign event. + diff --git a/ref/tel.md b/ref/tel.md index c8cb45772..75ee693b8 100644 --- a/ref/tel.md +++ b/ref/tel.md @@ -92,6 +92,7 @@ credentials to be issued. |Label|Description|Notes| |---|---|---| |v| version string | | +|d| said of event | | |i| namespaced identifier of Registry | | |s| sequence number of event | | |t| message type of event | | @@ -104,6 +105,7 @@ credentials to be issued. |bt| backer threshold | | |ba| list of backers to add (ordered backer set) | | |br| list of backers to remove (ordered backer set) | | +|n| nonce to provide uniqueness if more than one registry identifier exists for a given issuer | | ### Configuration @@ -121,32 +123,36 @@ the `ri` field will be the simple identifier referencing the management TEL. ### Registry Inception Event -``` +```json { - "v" : "KERI10JSON00011c_", + "v" : "KERI10JSON00016d_", + "d" : "EOMBBz0AKgL1-n9OK5DAIQLM6Z1FHE35YGFUuveYiM1A", "i" : "ELh3eYC2W_Su1izlvm0xxw01n3XK8bdV2Zb09IqlXB7A", "ii": "EJJR2nmwyYAfSVPzhzS6b5CMZAoTNZH3ULvaU6Z-i0d8", "s" : "0", "t" : "vcp", "b" : ["BbIg_3-11d3PYxSInLN-Q9_T2axD6kkXd3XRgbGZTm6s"], - "c" : [] + "c" : [], "a" : { "d": "EEBp64Aw2rsjdJpAR0e2qCq3jX7q7gLld3LjAwZgaLXU" - } + }, + "n" : "AAd7Zfk6072acq_37bw29qiHOkG3-vErjQGdtjPRmVE_" }-GAB0AAAAAAAAAAAAAAAAAAAAABwEOWdT7a7fZwRz0jiZ0DJxZEM3vsNbLDPEUk-ODnif3O0 ``` Registry inception event for establishing the list of Backers -``` +```json { - "v" : "KERI10JSON00011c_", + "v" : "KERI10JSON00010a_", + "d" : "EJwVS89RBfBNhX6PpAmpsssMSZDMW68FaYmEOSw8EFP6", "i" : "ELh3eYC2W_Su1izlvm0xxw01n3XK8bdV2Zb09IqlXB7A", "ii": "EJJR2nmwyYAfSVPzhzS6b5CMZAoTNZH3ULvaU6Z-i0d8", "s" : "0", "t" : "vcp", "b" : [], - "c" : ["NB"] + "c" : ["NB"], + "n" : "AAd7Zfk6072acq_37bw29qiHOkG3-vErjQGdtjPRmVE_" }-GAB0AAAAAAAAAAAAAAAAAAAAABwEOWdT7a7fZwRz0jiZ0DJxZEM3vsNbLDPEUk-ODnif3O0 ``` @@ -154,9 +160,10 @@ Registry inception event for "backer-less" configuration ### Registry Rotation Event -``` +```json { - "v" : "KERI10JSON00011c_", + "v" : "KERI10JSON000130_", + "d" : "EL0dsWLeW88h1yruLnEC5_7TT4jIKRiCZjssVRWAQRTz", "i" : "ELh3eYC2W_Su1izlvm0xxw01n3XK8bdV2Zb09IqlXB7A", "p" : "EY2L3ycqK9645aEeQKP941xojSiuiHsw4Y6yTW-PmsBg", "s" : "1", @@ -245,6 +252,7 @@ signatures can be indexed into the proper list of backers at the time of issuanc |Label|Description|Notes| |---|---|---| |v| version string | | +|d| said of event | | |i| namespaced identifier of VC | | |s| sequence number of event | | |t| message type of event | | @@ -252,38 +260,45 @@ signatures can be indexed into the proper list of backers at the time of issuanc |p| prior event digest | | |ri| registry identifier from management TEL | | |ra| registry anchor to management TEL | | +|n| nonce for uniqueness | | ### Simple Credential Issuance Event -``` +```json { - "v" : "KERI10JSON00011c_", + "v" : "KERI10JSON000120_", + "d" : "ECW3ieodNbpJAb-_KG2jJ-cZ_f_C7p0rj7OtckG-14vU", "i" : "Ezpq06UecHwzy-K9FpNoRxCJp2wIGM9u2Edk-PLMZ1H4", "s" : "0", "t" : "iss", "dt": "2021-05-27T19:16:50.750302+00:00", - "ri": "ELh3eYC2W_Su1izlvm0xxw01n3XK8bdV2Zb09IqlXB7A" + "ri": "ELh3eYC2W_Su1izlvm0xxw01n3XK8bdV2Zb09IqlXB7A", + "n" : "AAd7Zfk6072acq_37bw29qiHOkG3-vErjQGdtjPRmVE_" }-GAB0AAAAAAAAAAAAAAAAAAAAAAwELvaU6Z-i0d8JJR2nmwyYAZAoTNZH3UfSVPzhzS6b5CM ``` ### Simple Credential Revocation Event -``` +```json { - "v" : "KERI10JSON00011c_", + "v" : "KERI10JSON000153_", + "d" : "EJRlvg9NB7a52AuQUMPBS3iIJqlUmeYpPguHVANkaZv5", "i" : "Ezpq06UecHwzy-K9FpNoRxCJp2wIGM9u2Edk-PLMZ1H4", "s" : "1", "t" : "rev", "dt": "2021-05-27T19:16:50.750302+00:00", - "p" : "EY2L3ycqK9645aEeQKP941xojSiuiHsw4Y6yTW-PmsBg" + "p" : "EY2L3ycqK9645aEeQKP941xojSiuiHsw4Y6yTW-PmsBg", + "ri": "ELh3eYC2W_Su1izlvm0xxw01n3XK8bdV2Zb09IqlXB7A", + "n" : "AAd7Zfk6072acq_37bw29qiHOkG3-vErjQGdtjPRmVE_" }-GAB0AAAAAAAAAAAAAAAAAAAAABAELvaU6Z-i0d8JJR2nmwyYAZAoTNZH3UfSVPzhzS6b5CM ``` ### Credential Issuance Event -``` +```json { - "v" : "KERI10JSON00011c_", + "v" : "KERI10JSON000161_", + "d" : "ELH85H7iZageLI6hUg0eVnvBGepKhnkw4J1PXVrZjsoX", "i" : "Ezpq06UecHwzy-K9FpNoRxCJp2wIGM9u2Edk-PLMZ1H4", "s" : "0", "t" : "bis", @@ -292,15 +307,17 @@ signatures can be indexed into the proper list of backers at the time of issuanc "i": "ELh3eYC2W_Su1izlvm0xxw01n3XK8bdV2Zb09IqlXB7A", "s": "2", "d": "Ezpq06UecHwzy-K9FpNoRxCJp2wIGM9u2Edk-PLMZ1H4" - } + }, + "n" : "AAd7Zfk6072acq_37bw29qiHOkG3-vErjQGdtjPRmVE_" }-GAB0AAAAAAAAAAAAAAAAAAAAAAwELvaU6Z-i0d8JJR2nmwyYAZAoTNZH3UfSVPzhzS6b5CM ``` ### Credential Revocation Event -``` +```json { - "v" : "KERI10JSON00011c_", + "v" : "KERI10JSON000194_", + "d" : "ECx_SICUfbWjKIqexdFMJ2Ic57ytw7xi3csX8PU16gMx", "i" : "Ezpq06UecHwzy-K9FpNoRxCJp2wIGM9u2Edk-PLMZ1H4", "s" : "1", "t" : "brv", @@ -310,7 +327,8 @@ signatures can be indexed into the proper list of backers at the time of issuanc "i": "ELh3eYC2W_Su1izlvm0xxw01n3XK8bdV2Zb09IqlXB7A", "s": "4", "d": "Ezpq06UecHwzy-K9FpNoRxCJp2wIGM9u2Edk-PLMZ1H4" - } + }, + "n" : "AAd7Zfk6072acq_37bw29qiHOkG3-vErjQGdtjPRmVE_" }-GAB0AAAAAAAAAAAAAAAAAAAAABAELvaU6Z-i0d8JJR2nmwyYAZAoTNZH3UfSVPzhzS6b5CM ``` diff --git a/scripts/demo/README.md b/scripts/demo/README.md index dd9a22e42..65572bdf9 100644 --- a/scripts/demo/README.md +++ b/scripts/demo/README.md @@ -42,7 +42,7 @@ a running agent. To run the `-agent` commands, a single agent or set of agents many scripts require a set of witnesses be running locally. The following section details how to run agents and witnesses. ### Running Witnesses -Witnesses can be started in several ways using the `kli witness` subcommands or the shell script `demo/start-witness.sh`. The +Witnesses can be started in several ways using the `kli witness` subcommands or the shell script `demo/basic/start-witness.sh`. The following 2 subcommands are available for starting witnesses: * `kli witness start` - starts a single witness (used inside the start-witness.sh script) @@ -51,7 +51,7 @@ following 2 subcommands are available for starting witnesses: For most of the scripts that require witnesses you will use `kli witness demo` to start the 3 known witnesses. ### Running Agents -Agents can be started in several ways using the `kli agent` subcommands or the shell script `demo/start-agent.sh` for the +Agents can be started in several ways using the `kli agent` subcommands or the shell script `demo/basic/start-agent.sh` for the scripts that execute `curl` commands against running agents. The following 3 subcommands are available for starting agents: @@ -85,4 +85,4 @@ and running the scripts. For example, before each script running something like `rm -rf /usr/local/var/keri/*;kli witness demo` -creates a clean environment and starts the demo set of 3 known witnesses. \ No newline at end of file +creates a clean environment and starts the demo set of 3 known witnesses. diff --git a/scripts/demo/basic/alice-bob-agent.sh b/scripts/demo/basic/alice-bob-agent.sh deleted file mode 100755 index 3e0290b5f..000000000 --- a/scripts/demo/basic/alice-bob-agent.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -# To run this script you need to run the following 2 commands in separate terminals: -# > kli agent demo --config-file demo-witness-oobis -# > kli witness demo -# DoB26Fj4x9LboAFWJra17O -curl -s -X POST "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"agent5623\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9aBc\"}" | jq -curl -s -X POST "http://localhost:5723/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"agent5723\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9abc\"}" | jq -# curl -s -X POST "http://localhost:5823/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"agent5823\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9def\"}" | jq -# curl -s -X POST "http://localhost:5923/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"agent5923\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9ghi\"}" | jq -sleep 3 - -curl -s -X PUT "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"agent5623\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5723/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"agent5723\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -# curl -s -X PUT "http://localhost:5823/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"agent5823\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -# curl -s -X PUT "http://localhost:5923/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"agent5923\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -sleep 6 - -curl -s -X POST "http://localhost:5623/ids/Alice" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -curl -s -X POST "http://localhost:5723/ids/Bob" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -# curl -s -X POST "http://localhost:5823/ids/Vic" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -# curl -s -X POST "http://localhost:5923/ids/Han" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -sleep 3 - -curl -s -X POST "http://localhost:5623/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"Bob\", \"url\":\"http://127.0.0.1:5643/oobi/EHlNJJ1O-s2BlqngkY7mYbVamK0Z7ISkru6TkjLG7yUi/witness/BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\"}" | jq -curl -s -X POST "http://localhost:5723/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"Alice\", \"url\":\"http://127.0.0.1:5644/oobi/EBxc36-ii4IltbOKPkgdDvTRIgaH-kI7jMEXVDafbYk4/witness/BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"}" | jq - -sleep 2 -echo "Alice's Contacts:" -curl -s -X GET "http://localhost:5623/contacts" -H "accept: */*" | jq -echo "Bob's Contacts:" -curl -s -X GET "http://localhost:5723/contacts" -H "accept: */*" | jq diff --git a/scripts/demo/basic/challenge.sh b/scripts/demo/basic/challenge.sh index 49dd5bffd..c3fcc6ccb 100755 --- a/scripts/demo/basic/challenge.sh +++ b/scripts/demo/basic/challenge.sh @@ -2,9 +2,11 @@ kli init --name cha1 --nopasscode --config-dir "${KERI_SCRIPT_DIR}" --config-file demo-witness-oobis kli incept --name cha1 --alias cha1 --file ${KERI_DEMO_SCRIPT_DIR}/data/challenge-sample.json +kli ends add --name cha1 --alias cha1 --eid BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM --role mailbox kli init --name cha2 --nopasscode --config-dir "${KERI_SCRIPT_DIR}" --config-file pool2-witness-oobis kli incept --name cha2 --alias cha2 --file ${KERI_DEMO_SCRIPT_DIR}/data/challenge-sample-pool2.json +kli ends add --name cha2 --alias cha2 --eid BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM --role mailbox cha1_oobi="$(kli oobi generate --name cha1 --alias cha1 --role witness | sed -n '2 p')" cha2_oobi="$(kli oobi generate --name cha2 --alias cha2 --role witness | sed -n '2 p')" diff --git a/scripts/demo/basic/delegate-agent.sh b/scripts/demo/basic/delegate-agent.sh deleted file mode 100755 index 3e55a4049..000000000 --- a/scripts/demo/basic/delegate-agent.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -# To run the following scripts, open 2 other console windows and run: -# $ kli witness demo -# $ kli agent demo --config-file demo-witness-oobis - -# Initialize and Unlock 2 agents -curl -s -X POST "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"delegate\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9aBc\"}" | jq -curl -s -X POST "http://localhost:5723/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"delegator\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpsaw\"}" | jq -sleep 2 -curl -s -X PUT "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"delegate\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5723/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"delegator\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -sleep 3 - -# Create Delegator ID -curl -s -X POST "http://localhost:5723/ids/delegator" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -sleep 3 -curl -s -X POST "http://localhost:5623/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"\", \"url\":\"http://127.0.0.1:5642/oobi/EHpD0-CDWOdu5RJ8jHBSUkOqBZ3cXeDVHWNb_Ul89VI7/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq - -# Create Delegate ID and Approve with Rotation of Delegator -sleep 2 -curl -s -X POST "http://localhost:5623/ids/delegate" -H "accept: */*" -H "Content-Type: application/json" -d "{\"delpre\":\"EHpD0-CDWOdu5RJ8jHBSUkOqBZ3cXeDVHWNb_Ul89VI7\", \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -sleep 3 -curl -s -X PUT "http://localhost:5723/ids/delegator/rot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"adds\":[],\"count\":1,\"cuts\":[],\"data\":[{\"i\":\"ENXX5omYUZwM4Dg7WOOHuNmjSeKE2nfNuQbEQBNo4c3c\",\"s\":\"0\", \"d\":\"ENXX5omYUZwM4Dg7WOOHuNmjSeKE2nfNuQbEQBNo4c3c\"}],\"isith\":\"1\",\"toad\":2,\"wits\":[]}" | jq - -# Rotate Delegate ID and Approve with Rotation of Delegator -sleep 3 -curl -s -X PUT "http://localhost:5623/ids/delegate/rot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"adds\":[],\"count\":1,\"cuts\":[],\"data\":[],\"isith\":\"1\",\"toad\":3,\"wits\":[]}" | jq -sleep 3 -curl -s -X PUT "http://localhost:5723/ids/delegator/rot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"adds\":[],\"count\":1,\"cuts\":[],\"data\":[{\"i\":\"ENXX5omYUZwM4Dg7WOOHuNmjSeKE2nfNuQbEQBNo4c3c\",\"s\":\"1\", \"d\":\"EKwpwCeuV-78blh1JPa8pdhpeqYN_VhuIzYUPD3SFFBN\"}],\"isith\":\"1\",\"toad\":3,\"wits\":[]}" | jq diff --git a/scripts/demo/basic/delegate.sh b/scripts/demo/basic/delegate.sh index f3c9d8fde..a1018299d 100755 --- a/scripts/demo/basic/delegate.sh +++ b/scripts/demo/basic/delegate.sh @@ -4,7 +4,8 @@ kli init --name delegator --nopasscode --config-dir ${KERI_SCRIPT_DIR} --config- kli incept --name delegator --alias delegator --file ${KERI_DEMO_SCRIPT_DIR}/data/delegator.json kli oobi resolve --name delegate --oobi-alias delegator --oobi http://127.0.0.1:5642/oobi/EHpD0-CDWOdu5RJ8jHBSUkOqBZ3cXeDVHWNb_Ul89VI7/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha -kli incept --name delegate --alias delegate --file ${KERI_DEMO_SCRIPT_DIR}/data/delegatee.json & +kli incept --name delegate --alias proxy --file ${KERI_DEMO_SCRIPT_DIR}/data/delegator.json +kli incept --name delegate --alias delegate --proxy proxy --file ${KERI_DEMO_SCRIPT_DIR}/data/delegatee.json & pid=$! PID_LIST+=" $pid" diff --git a/scripts/demo/basic/demo-witness-async-script.sh b/scripts/demo/basic/demo-witness-async-script.sh new file mode 100755 index 000000000..57885a7cb --- /dev/null +++ b/scripts/demo/basic/demo-witness-async-script.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# WITNESSES +# To run the following scripts, open another console window and run: +# $ kli witness demo + +function isSuccess() { + ret=$? + if [ $ret -ne 0 ]; then + echo "Error $ret" + exit $ret + fi +} + +# CREATE DATABASE AND KEYSTORE +kli init --name witness-test --base "${KERI_TEMP_DIR}" --nopasscode +isSuccess + +# RESOLVE WITNESS OOBIs +kli oobi resolve --name witness-test --base "${KERI_TEMP_DIR}" --oobi-alias wan --oobi http://127.0.0.1:5643/oobi/BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM/controller +isSuccess +kli oobi resolve --name witness-test --base "${KERI_TEMP_DIR}" --oobi-alias wil --oobi http://127.0.0.1:5642/oobi/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha/controller +isSuccess +kli oobi resolve --name witness-test --base "${KERI_TEMP_DIR}" --oobi-alias wes --oobi http://127.0.0.1:5644/oobi/BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX/controller +isSuccess + +## INCEPT AND PROPOGATE EVENTS AND RECEIPTS TO WITNESSES +kli incept --name witness-test --base "${KERI_TEMP_DIR}" --alias trans-wits --file "${KERI_DEMO_SCRIPT_DIR}/data/trans-wits-sample.json" +isSuccess + +kli incept --name witness-test --base "${KERI_TEMP_DIR}" --alias inquisitor --file "${KERI_DEMO_SCRIPT_DIR}/data/inquisitor-sample.json" +isSuccess + +kli status --name witness-test --base "${KERI_TEMP_DIR}" --alias trans-wits + +kli rotate --name witness-test --base "${KERI_TEMP_DIR}" --alias trans-wits --witness-cut BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX +isSuccess + +kli status --name witness-test --base "${KERI_TEMP_DIR}" --alias trans-wits + +kli rotate --name witness-test --base "${KERI_TEMP_DIR}" --alias trans-wits --witness-add BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX +isSuccess + +kli status --name witness-test --base "${KERI_TEMP_DIR}" --alias trans-wits + +echo 'Test Complete' \ No newline at end of file diff --git a/scripts/demo/basic/demo-witness-script.sh b/scripts/demo/basic/demo-witness-script.sh index 57885a7cb..c001af373 100755 --- a/scripts/demo/basic/demo-witness-script.sh +++ b/scripts/demo/basic/demo-witness-script.sh @@ -25,7 +25,7 @@ kli oobi resolve --name witness-test --base "${KERI_TEMP_DIR}" --oobi-alias wes isSuccess ## INCEPT AND PROPOGATE EVENTS AND RECEIPTS TO WITNESSES -kli incept --name witness-test --base "${KERI_TEMP_DIR}" --alias trans-wits --file "${KERI_DEMO_SCRIPT_DIR}/data/trans-wits-sample.json" +kli incept --name witness-test --base "${KERI_TEMP_DIR}" --receipt-endpoint --alias trans-wits --file "${KERI_DEMO_SCRIPT_DIR}/data/trans-wits-sample.json" isSuccess kli incept --name witness-test --base "${KERI_TEMP_DIR}" --alias inquisitor --file "${KERI_DEMO_SCRIPT_DIR}/data/inquisitor-sample.json" @@ -33,12 +33,12 @@ isSuccess kli status --name witness-test --base "${KERI_TEMP_DIR}" --alias trans-wits -kli rotate --name witness-test --base "${KERI_TEMP_DIR}" --alias trans-wits --witness-cut BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX +kli rotate --name witness-test --base "${KERI_TEMP_DIR}" --receipt-endpoint --alias trans-wits --witness-cut BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX isSuccess kli status --name witness-test --base "${KERI_TEMP_DIR}" --alias trans-wits -kli rotate --name witness-test --base "${KERI_TEMP_DIR}" --alias trans-wits --witness-add BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX +kli rotate --name witness-test --base "${KERI_TEMP_DIR}" --receipt-endpoint --alias trans-wits --witness-add BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX isSuccess kli status --name witness-test --base "${KERI_TEMP_DIR}" --alias trans-wits diff --git a/scripts/demo/basic/essr.sh b/scripts/demo/basic/essr.sh new file mode 100755 index 000000000..e3a5cb957 --- /dev/null +++ b/scripts/demo/basic/essr.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +#kli init --name sender --salt 0ACDEyMzQ1Njc4OWxtbm9aBc --nopasscode --config-dir ${KERI_SCRIPT_DIR} --config-file demo-witness-oobis +#kli incept --name sender --alias sender --file ${KERI_DEMO_SCRIPT_DIR}/data/gleif-sample.json +# +#kli init --name recipient --salt 0ACDEyMzQ1Njc4OWxtbm9qWc --nopasscode --config-dir ${KERI_SCRIPT_DIR} --config-file demo-witness-oobis +#kli incept --name recipient --alias recipient --file ${KERI_DEMO_SCRIPT_DIR}/data/gleif-sample.json +# +#kli oobi resolve --name sender --oobi-alias recipient --oobi http://127.0.0.1:5642/oobi/EFXJsTFSo10FAZGR-8_Uw1DlhU8nuRFOAN9Z8ajJ56ci/witness +#kli oobi resolve --name recipient --oobi-alias sender --oobi http://127.0.0.1:5642/oobi/EJf7MfzNmehwY5310MUWXPSxhAA_3ifPW2bdsjwqnvae/witness +# +#kli vc registry incept --name sender --alias sender --registry-name vLEI +# +#kli vc create --name sender --alias sender --registry-name vLEI --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k --data @${KERI_DEMO_SCRIPT_DIR}/data/credential-data.json +#SAID=$(kli vc list --name sender --alias sender --issued --said --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao) +# +#kli ipex grant --name sender --alias sender --said "${SAID}" --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k + +kli essr send --name sender --alias sender --recipient recipient \ No newline at end of file diff --git a/scripts/demo/basic/multisig-agent.sh b/scripts/demo/basic/multisig-agent.sh deleted file mode 100755 index ac160a52a..000000000 --- a/scripts/demo/basic/multisig-agent.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -# To run the following scripts, open 2 other console windows and run: -# $ kli witness demo -# $ kli agent demo --config-file demo-witness-oobis - -# Create and initialize agents with passcode DoB26Fj4x9LboAFWJra17O -curl -s -X POST "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"multisig1\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9aBc\"}" | jq -curl -s -X POST "http://localhost:5723/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"multisig2\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpsaw\"}" | jq -sleep 3 -curl -s -X PUT "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"multisig1\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5723/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"multisig2\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq - -# Create 2 single sig AIDs -sleep 3 -curl -s -X POST "http://localhost:5623/ids/multisig1" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -curl -s -X POST "http://localhost:5723/ids/multisig2" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq - -# Exchange OOBIs between participants -sleep 3 -curl -s -X POST "http://localhost:5623/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"multisig2\", \"url\":\"http://127.0.0.1:5642/oobi/EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5723/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"multisig1\", \"url\":\"http://127.0.0.1:5642/oobi/EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq - -# Create distributed multisig AID -sleep 3 -curl -s -X POST "http://localhost:5623/groups/issuer/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1\",\"EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq -curl -s -X PUT "http://localhost:5723/groups/issuer/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1\",\"EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq - -# Rotate distributed multisig AID -sleep 3 -curl -s -X POST "http://localhost:5623/groups/issuer/rot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"adds\":[],\"aids\":[\"EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1\", \"EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4\"],\"count\":2,\"cuts\":[],\"data\":[],\"isith\":\"2\",\"toad\":2, \"wits\":[]}" | jq -curl -s -X PUT "http://localhost:5723/groups/issuer/rot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"adds\":[],\"aids\":[\"EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1\", \"EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4\"],\"count\":2,\"cuts\":[],\"data\":[],\"isith\":\"2\",\"toad\":2, \"wits\":[]}" | jq - -# Create interaction event for distributed multisig AID -sleep 3 -curl -s -X PUT "http://localhost:5723/groups/issuer/ixn" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1\",\"EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4\"], \"data\":[{\"i\":\"EAXJtG-Ek349v43ztpFdRXozyP7YnALdB0DdCEanlHmg\",\"s\":\"0\", \"d\":\"EAR75fE1ZmuCSfDwKPfbLowUWLqqi0ZX4502DLIo857Q\"}]}" | jq -curl -s -X POST "http://localhost:5623/groups/issuer/ixn" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1\",\"EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4\"], \"data\":[{\"i\":\"EAXJtG-Ek349v43ztpFdRXozyP7YnALdB0DdCEanlHmg\",\"s\":\"0\", \"d\":\"EAR75fE1ZmuCSfDwKPfbLowUWLqqi0ZX4502DLIo857Q\"}]}" | jq - -echo "Script complete" \ No newline at end of file diff --git a/scripts/demo/basic/multisig-delegate-agent.sh b/scripts/demo/basic/multisig-delegate-agent.sh deleted file mode 100755 index dee1bbec1..000000000 --- a/scripts/demo/basic/multisig-delegate-agent.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -# To run the following scripts, open 2 other console windows and run: -# $ kli witness demo -# $ kli agent demo --config-file demo-witness-oobis - -# Create and initialize agents with passcode DoB26Fj4x9LboAFWJra17O -curl -s -X POST "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"multisig1\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9aBc\"}" | jq -curl -s -X POST "http://localhost:5723/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"multisig2\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpsaw\"}" | jq -curl -s -X POST "http://localhost:5823/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"delegator\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpex3\"}" | jq -sleep 2 - -curl -s -X PUT "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"multisig1\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5723/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"multisig2\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5823/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"delegator\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -sleep 2 - - -curl -s -X POST "http://localhost:5623/ids/multisig1" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -curl -s -X POST "http://localhost:5723/ids/multisig2" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -curl -s -X POST "http://localhost:5823/ids/delegator" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -sleep 3 - -curl -s -X POST "http://localhost:5623/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"multisig2\", \"url\":\"http://127.0.0.1:5642/oobi/EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" -curl -s -X POST "http://localhost:5723/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"multisig1\", \"url\":\"http://127.0.0.1:5642/oobi/EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" -curl -s -X POST "http://localhost:5623/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"delegator\", \"url\":\"http://127.0.0.1:5642/oobi/EOKKt70pKAQsJ5DIkEWnz8-RO4uSlowduwZcQ49xAROK/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" -curl -s -X POST "http://localhost:5723/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"delegator\", \"url\":\"http://127.0.0.1:5642/oobi/EOKKt70pKAQsJ5DIkEWnz8-RO4uSlowduwZcQ49xAROK/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" -sleep 2 - -curl -s -X POST "http://localhost:5623/groups/multisig/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"delpre\":\"EOKKt70pKAQsJ5DIkEWnz8-RO4uSlowduwZcQ49xAROK\",\"aids\":[\"EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1\",\"EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4\"],\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3,\"isith\":2,\"nsith\":2}" | jq -curl -s -X PUT "http://localhost:5723/groups/multisig/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"delpre\":\"EOKKt70pKAQsJ5DIkEWnz8-RO4uSlowduwZcQ49xAROK\",\"aids\":[\"EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1\",\"EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4\"],\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3,\"isith\":2,\"nsith\":2}" | jq -sleep 2 - -curl -s -X PUT "http://localhost:5823/ids/delegator/rot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"data\":[{\"i\":\"EICfVXUtHA2XXos9hSeWbcsml0s-qM44OfPzoViLt1Vi\",\"s\":\"0\",\"d\":\"EICfVXUtHA2XXos9hSeWbcsml0s-qM44OfPzoViLt1Vi\"}]}" | jq - -echo "Script Complete" - diff --git a/scripts/demo/basic/multisig-delegate-delegator-agent.sh b/scripts/demo/basic/multisig-delegate-delegator-agent.sh deleted file mode 100755 index 43ef7aa75..000000000 --- a/scripts/demo/basic/multisig-delegate-delegator-agent.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -# To run the following scripts, open 2 other console windows and run: -# $ kli witness demo -# $ kli agent demo --config-file demo-witness-oobis - -# Create and initialize agents with passcode DoB26Fj4x9LboAFWJra17O -curl -s -X POST "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"delegate1\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9aBc\"}" | jq -curl -s -X POST "http://localhost:5723/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"delegate2\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpsaw\"}" | jq -curl -s -X POST "http://localhost:5823/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"delegator1\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpdo1\"}" | jq -curl -s -X POST "http://localhost:5923/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"delegator2\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpdo2\"}" | jq -sleep 2 -curl -s -X PUT "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"delegate1\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5723/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"delegate2\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5823/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"delegator1\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5923/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"delegator2\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -sleep 2 -curl -s -X POST "http://localhost:5623/ids/delegate1" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"],\"toad\":1,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -curl -s -X POST "http://localhost:5723/ids/delegate2" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"],\"toad\":1,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -curl -s -X POST "http://localhost:5823/ids/delegator1" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -curl -s -X POST "http://localhost:5923/ids/delegator2" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -sleep 3 - -curl -s -X POST "http://localhost:5623/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"delegate2\", \"url\":\"http://127.0.0.1:5642/oobi/ELZyCjnSL2Haors35LKM19T4qWT4K8Gfz1FPDD9oJN33/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5723/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"delegate1\", \"url\":\"http://127.0.0.1:5642/oobi/EJ97lUuRH3xz0OMKhdMAU6V2TcSF9X6m1CKyIbIUcRxp/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5823/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"delegator2\", \"url\":\"http://127.0.0.1:5642/oobi/EGv3deIs7pc01NnZZAhQ14Cbe9VGq4wF3n4oyhQfrB9j/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5923/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"delegator1\", \"url\":\"http://127.0.0.1:5642/oobi/EIKUq-JkZGpgVZ_x9Hr2Gt_LLdPDzyI2JyGnHl3EBCPl/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -sleep 3 -curl -s -X POST "http://localhost:5823/groups/delegator/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EIKUq-JkZGpgVZ_x9Hr2Gt_LLdPDzyI2JyGnHl3EBCPl\",\"EGv3deIs7pc01NnZZAhQ14Cbe9VGq4wF3n4oyhQfrB9j\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq -curl -s -X PUT "http://localhost:5923/groups/delegator/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EIKUq-JkZGpgVZ_x9Hr2Gt_LLdPDzyI2JyGnHl3EBCPl\",\"EGv3deIs7pc01NnZZAhQ14Cbe9VGq4wF3n4oyhQfrB9j\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq - -sleep 2 -curl -s -X POST "http://localhost:5623/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"delegator\", \"url\":\"http://127.0.0.1:5642/oobi/EK7j7BobKFpH9yki4kwyIUuT-yQANSntS8u1hlhFYFcg/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" -curl -s -X POST "http://localhost:5723/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"delegator\", \"url\":\"http://127.0.0.1:5642/oobi/EK7j7BobKFpH9yki4kwyIUuT-yQANSntS8u1hlhFYFcg/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" - -sleep 2 -curl -s -X POST "http://localhost:5623/groups/delegate/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"delpre\":\"EK7j7BobKFpH9yki4kwyIUuT-yQANSntS8u1hlhFYFcg\", \"aids\":[\"EJ97lUuRH3xz0OMKhdMAU6V2TcSF9X6m1CKyIbIUcRxp\",\"ELZyCjnSL2Haors35LKM19T4qWT4K8Gfz1FPDD9oJN33\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq -curl -s -X PUT "http://localhost:5723/groups/delegate/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"delpre\":\"EK7j7BobKFpH9yki4kwyIUuT-yQANSntS8u1hlhFYFcg\", \"aids\":[\"EJ97lUuRH3xz0OMKhdMAU6V2TcSF9X6m1CKyIbIUcRxp\",\"ELZyCjnSL2Haors35LKM19T4qWT4K8Gfz1FPDD9oJN33\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq - -sleep 3 -curl -s -X POST "http://localhost:5923/groups/delegator/ixn" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EIKUq-JkZGpgVZ_x9Hr2Gt_LLdPDzyI2JyGnHl3EBCPl\",\"EGv3deIs7pc01NnZZAhQ14Cbe9VGq4wF3n4oyhQfrB9j\"], \"data\":[{\"i\":\"EOL2umo-DHgO9t22LR_iwmiR_cfsF531hcCh-zZ0p0gL\",\"s\":\"0\", \"d\":\"EOL2umo-DHgO9t22LR_iwmiR_cfsF531hcCh-zZ0p0gL\"}]}" | jq -curl -s -X PUT "http://localhost:5823/groups/delegator/ixn" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EIKUq-JkZGpgVZ_x9Hr2Gt_LLdPDzyI2JyGnHl3EBCPl\",\"EGv3deIs7pc01NnZZAhQ14Cbe9VGq4wF3n4oyhQfrB9j\"], \"data\":[{\"i\":\"EOL2umo-DHgO9t22LR_iwmiR_cfsF531hcCh-zZ0p0gL\",\"s\":\"0\", \"d\":\"EOL2umo-DHgO9t22LR_iwmiR_cfsF531hcCh-zZ0p0gL\"}]}" | jq diff --git a/scripts/demo/basic/multisig-delegate-delegator.sh b/scripts/demo/basic/multisig-delegate-delegator.sh index d56619b78..520da0102 100755 --- a/scripts/demo/basic/multisig-delegate-delegator.sh +++ b/scripts/demo/basic/multisig-delegate-delegator.sh @@ -52,11 +52,13 @@ PID_LIST+=" $pid" # Wait for 3 seconds to allow the delegation request to complete and then launch the approval in parallel sleep 3 +echo "Waiting to approve the delegation request for delegator1/delegator with confirm" kli delegate confirm --name delegator1 --alias delegator --interact --auto & #kli multisig interact --name delegator1 --alias delegator --data @${KERI_DEMO_SCRIPT_DIR}/data/multisig-delegate-icp-anchor.json & pid=$! PID_LIST+=" $pid" +echo "Waiting to approve the delegation request for delegator2/delegator with confirm" kli delegate confirm --name delegator2 --alias delegator --interact --auto & #kli multisig interact --name delegator2 --alias delegator --data @${KERI_DEMO_SCRIPT_DIR}/data/multisig-delegate-icp-anchor.json & pid=$! diff --git a/scripts/demo/basic/multisig-partial-rotation.sh b/scripts/demo/basic/multisig-partial-rotation.sh index 79545d164..d49bbde66 100755 --- a/scripts/demo/basic/multisig-partial-rotation.sh +++ b/scripts/demo/basic/multisig-partial-rotation.sh @@ -37,10 +37,17 @@ kli status --name multisig1 --alias multisig PID_LIST="" -kli multisig rotate --name multisig1 --alias multisig --smids EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4 --smids EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1 --isith '["1/2", "1/2"]' --nsith '["1/2", "1/2", "1/2"]' --rmids EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4 --rmids EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1 --rmids ENkjt7khEI5edCMw5qugagbJw1QvGnQEtcewxb0FnU9U & +kli rotate --name multisig1 --alias multisig1 +kli query --name multisig2 --alias multisig2 --prefix EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4 +kli query --name multisig3 --alias multisig3 --prefix EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4 +kli rotate --name multisig2 --alias multisig2 +kli query --name multisig1 --alias multisig1 --prefix EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1 +kli query --name multisig3 --alias multisig3 --prefix EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1 + +kli multisig rotate --name multisig1 --alias multisig --smids EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4:1 --smids EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1:1 --smids ENkjt7khEI5edCMw5qugagbJw1QvGnQEtcewxb0FnU9U:0 --isith '["1/2", "1/2", "1/2"]' --nsith '["1/2", "1/2", "1/2"]' --rmids EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4 --rmids EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1 --rmids ENkjt7khEI5edCMw5qugagbJw1QvGnQEtcewxb0FnU9U & pid=$! PID_LIST+=" $pid" -kli multisig rotate --name multisig2 --alias multisig --smids EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4 --smids EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1 --isith '["1/2", "1/2"]' --nsith '["1/2", "1/2", "1/2"]' --rmids EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4 --rmids EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1 --rmids ENkjt7khEI5edCMw5qugagbJw1QvGnQEtcewxb0FnU9U & +kli multisig rotate --name multisig2 --alias multisig --smids EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4:1 --smids EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1:1 --smids ENkjt7khEI5edCMw5qugagbJw1QvGnQEtcewxb0FnU9U:0 --isith '["1/2", "1/2", "1/2"]' --nsith '["1/2", "1/2", "1/2"]' --rmids EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4 --rmids EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1 --rmids ENkjt7khEI5edCMw5qugagbJw1QvGnQEtcewxb0FnU9U & pid=$! PID_LIST+=" $pid" diff --git a/scripts/demo/basic/multisig-signify-rotation.sh b/scripts/demo/basic/multisig-signify-rotation.sh new file mode 100755 index 000000000..6bdaba805 --- /dev/null +++ b/scripts/demo/basic/multisig-signify-rotation.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +# WITNESSES +# To run the following scripts, open another console window and run: +# $ kli witness demo + +kli init --name multisig1 --salt 0ACDEyMzQ1Njc4OWxtbm9aBc --nopasscode --config-dir "${KERI_SCRIPT_DIR}" --config-file demo-witness-oobis +kli incept --name multisig1 --alias multisig1 --file ${KERI_DEMO_SCRIPT_DIR}/data/multisig-1-sample.json + +kli init --name multisig2 --salt 0ACDEyMzQ1Njc4OWdoaWpsaw --nopasscode --config-dir "${KERI_SCRIPT_DIR}" --config-file demo-witness-oobis +kli incept --name multisig2 --alias multisig2 --file ${KERI_DEMO_SCRIPT_DIR}/data/multisig-2-sample.json + +kli oobi resolve --name multisig1 --oobi-alias multisig2 --oobi http://127.0.0.1:5642/oobi/EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha +kli oobi resolve --name multisig2 --oobi-alias multisig1 --oobi http://127.0.0.1:5642/oobi/EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha + +read -n 1 -r -p "Press any key after agent0 has created their AID:" + +kli oobi resolve --name multisig1 --oobi-alias agent0 --oobi http://127.0.0.1:3902/oobi/EOGvmhJDBbJP4zeXaRun5vSz0O3_1zB10DwNMyjXlJEv/agent/EEXekkGu9IAzav6pZVJhkLnjtjM5v3AcyA-pdKUcaGei +kli oobi resolve --name multisig2 --oobi-alias agent0 --oobi http://127.0.0.1:3902/oobi/EOGvmhJDBbJP4zeXaRun5vSz0O3_1zB10DwNMyjXlJEv/agent/EEXekkGu9IAzav6pZVJhkLnjtjM5v3AcyA-pdKUcaGei + +# Follow commands run in parallel +kli multisig incept --name multisig1 --alias multisig1 --group multisig --file ${KERI_DEMO_SCRIPT_DIR}/data/multisig-signify-sample.json & +pid=$! +PID_LIST+=" $pid" +kli multisig incept --name multisig2 --alias multisig2 --group multisig --file ${KERI_DEMO_SCRIPT_DIR}/data/multisig-signify-sample.json & +pid=$! + +wait $PID_LIST +PID_LIST="" + +kli status --name multisig1 --alias multisig + + +echo "Multisig1 interacting" +kli multisig interact --name multisig1 --alias multisig --data '{"i": "EE77q3_zWb5ojgJr-R1vzsL5yiL4Nzm-bfSOQzQl02dy"}' & +pid=$! +PID_LIST+=" $pid" + +echo "Multisig2 interacting" +kli multisig interact --name multisig2 --alias multisig --data '{"i": "EE77q3_zWb5ojgJr-R1vzsL5yiL4Nzm-bfSOQzQl02dy"}' & +pid=$! +PID_LIST+=" $pid" + +wait $PID_LIST +PID_LIST="" + +read -n 1 -r -p "Press any key after agent0 has rotated:" + +kli rotate --name multisig1 --alias multisig1 +kli query --name multisig2 --alias multisig2 --prefix EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4 +kli query --name multisig2 --alias multisig2 --prefix EOGvmhJDBbJP4zeXaRun5vSz0O3_1zB10DwNMyjXlJEv +kli rotate --name multisig2 --alias multisig2 +kli query --name multisig1 --alias multisig1 --prefix EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1 +kli query --name multisig1 --alias multisig1 --prefix EOGvmhJDBbJP4zeXaRun5vSz0O3_1zB10DwNMyjXlJEv + +kli multisig rotate --name multisig1 --alias multisig --smids EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4:1 --smids EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1:1 --smids EOGvmhJDBbJP4zeXaRun5vSz0O3_1zB10DwNMyjXlJEv:1 --isith '["1/3", "1/3", "1/3"]' --nsith '["1/3", "1/3", "1/3"]' --rmids EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4 --rmids EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1 --rmids EOGvmhJDBbJP4zeXaRun5vSz0O3_1zB10DwNMyjXlJEv & +pid=$! +PID_LIST+=" $pid" +kli multisig rotate --name multisig2 --alias multisig --smids EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4:1 --smids EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1:1 --smids EOGvmhJDBbJP4zeXaRun5vSz0O3_1zB10DwNMyjXlJEv:1 --isith '["1/3", "1/3", "1/3"]' --nsith '["1/3", "1/3", "1/3"]' --rmids EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4 --rmids EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1 --rmids EOGvmhJDBbJP4zeXaRun5vSz0O3_1zB10DwNMyjXlJEv & +pid=$! +PID_LIST+=" $pid" + +wait $PID_LIST + +kli status --name multisig1 --alias multisig + +PID_LIST="" + + + + diff --git a/scripts/demo/basic/multisig.sh b/scripts/demo/basic/multisig.sh index 5dfa45773..e6c6a8000 100755 --- a/scripts/demo/basic/multisig.sh +++ b/scripts/demo/basic/multisig.sh @@ -6,12 +6,14 @@ kli init --name multisig1 --base "${KERI_TEMP_DIR}" --salt 0ACDEyMzQ1Njc4OWxtbm9aBc --nopasscode --config-dir ${KERI_SCRIPT_DIR} --config-file demo-witness-oobis kli incept --name multisig1 --base "${KERI_TEMP_DIR}" --alias multisig1 --file ${KERI_DEMO_SCRIPT_DIR}/data/multisig-1-sample.json +kli ends add --name multisig1 --base "${KERI_TEMP_DIR}" --alias multisig1 --eid BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM --role mailbox kli init --name multisig2 --base "${KERI_TEMP_DIR}" --salt 0ACDEyMzQ1Njc4OWdoaWpsaw --nopasscode --config-dir ${KERI_SCRIPT_DIR} --config-file demo-witness-oobis kli incept --name multisig2 --base "${KERI_TEMP_DIR}" --alias multisig2 --file ${KERI_DEMO_SCRIPT_DIR}/data/multisig-2-sample.json +kli ends add --name multisig2 --base "${KERI_TEMP_DIR}" --alias multisig2 --eid BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX --role mailbox -kli oobi resolve --name multisig1 --base "${KERI_TEMP_DIR}" --oobi-alias multisig2 --oobi http://127.0.0.1:5642/oobi/EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha -kli oobi resolve --name multisig2 --base "${KERI_TEMP_DIR}" --oobi-alias multisig1 --oobi http://127.0.0.1:5642/oobi/EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha +kli oobi resolve --name multisig1 --base "${KERI_TEMP_DIR}" --oobi-alias multisig2 --oobi http://127.0.0.1:5642/oobi/EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1 +kli oobi resolve --name multisig2 --base "${KERI_TEMP_DIR}" --oobi-alias multisig1 --oobi http://127.0.0.1:5642/oobi/EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4 # Follow commands run in parallel kli multisig incept --name multisig1 --base "${KERI_TEMP_DIR}" --alias multisig1 --group multisig --file ${KERI_DEMO_SCRIPT_DIR}/data/multisig-sample.json & @@ -25,4 +27,15 @@ wait $PID_LIST kli status --name multisig1 --base "${KERI_TEMP_DIR}" --alias multisig +TIME=$(date -Iseconds -u | sed 's/+00:00//').000000+00:00 +kli ends add --base "${KERI_TEMP_DIR}" --name multisig1 --alias multisig --eid BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM --role mailbox --time "${TIME}" & +pid=$! +PID_LIST="$pid" + +kli ends add --base "${KERI_TEMP_DIR}" --name multisig2 --alias multisig --eid BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM --role mailbox --time "${TIME}" & +pid=$! +PID_LIST+=" $pid" + +wait $PID_LIST + echo "Test Complete" \ No newline at end of file diff --git a/scripts/demo/basic/oobi-quadlet.sh b/scripts/demo/basic/oobi-quadlet.sh new file mode 100755 index 000000000..0fed94355 --- /dev/null +++ b/scripts/demo/basic/oobi-quadlet.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +kli oobi resolve --name multisig1 --oobi-alias new-multisig1 --oobi http://127.0.0.1:3902/oobi/EBFg-5SGDCv5YfwpkArWRBdTxNRUXU8uVcDKNzizOQZc --force +kli oobi resolve --name multisig1 --oobi-alias new-multisig2 --oobi http://127.0.0.1:3902/oobi/EBmW2bXbgsP3HITwW3FmITzAb3wVmHlxCusZ46vgGgP5 --force +kli oobi resolve --name multisig1 --oobi-alias new-multisig3 --oobi http://127.0.0.1:3902/oobi/EL4RpdS2Atb2Syu5xLdpz9CcNNYoFUUDlLHxHD09vcgh --force +kli oobi resolve --name multisig1 --oobi-alias new-multisig4 --oobi http://127.0.0.1:3902/oobi/EAiBVuuhCZrgckeHc9KzROVGJpmGbk2-e1B25GaeRrJs --force + +kli oobi resolve --name multisig2 --oobi-alias new-multisig1 --oobi http://127.0.0.1:3902/oobi/EBFg-5SGDCv5YfwpkArWRBdTxNRUXU8uVcDKNzizOQZc --force +kli oobi resolve --name multisig2 --oobi-alias new-multisig2 --oobi http://127.0.0.1:3902/oobi/EBmW2bXbgsP3HITwW3FmITzAb3wVmHlxCusZ46vgGgP5 --force +kli oobi resolve --name multisig2 --oobi-alias new-multisig3 --oobi http://127.0.0.1:3902/oobi/EL4RpdS2Atb2Syu5xLdpz9CcNNYoFUUDlLHxHD09vcgh --force +kli oobi resolve --name multisig2 --oobi-alias new-multisig4 --oobi http://127.0.0.1:3902/oobi/EAiBVuuhCZrgckeHc9KzROVGJpmGbk2-e1B25GaeRrJs --force + +kli oobi resolve --name multisig3 --oobi-alias new-multisig1 --oobi http://127.0.0.1:3902/oobi/EBFg-5SGDCv5YfwpkArWRBdTxNRUXU8uVcDKNzizOQZc --force +kli oobi resolve --name multisig3 --oobi-alias new-multisig2 --oobi http://127.0.0.1:3902/oobi/EBmW2bXbgsP3HITwW3FmITzAb3wVmHlxCusZ46vgGgP5 --force +kli oobi resolve --name multisig3 --oobi-alias new-multisig3 --oobi http://127.0.0.1:3902/oobi/EL4RpdS2Atb2Syu5xLdpz9CcNNYoFUUDlLHxHD09vcgh --force +kli oobi resolve --name multisig3 --oobi-alias new-multisig4 --oobi http://127.0.0.1:3902/oobi/EAiBVuuhCZrgckeHc9KzROVGJpmGbk2-e1B25GaeRrJs --force + +kli oobi resolve --name multisig4 --oobi-alias new-multisig1 --oobi http://127.0.0.1:3902/oobi/EBFg-5SGDCv5YfwpkArWRBdTxNRUXU8uVcDKNzizOQZc --force +kli oobi resolve --name multisig4 --oobi-alias new-multisig2 --oobi http://127.0.0.1:3902/oobi/EBmW2bXbgsP3HITwW3FmITzAb3wVmHlxCusZ46vgGgP5 --force +kli oobi resolve --name multisig4 --oobi-alias new-multisig3 --oobi http://127.0.0.1:3902/oobi/EL4RpdS2Atb2Syu5xLdpz9CcNNYoFUUDlLHxHD09vcgh --force +kli oobi resolve --name multisig4 --oobi-alias new-multisig4 --oobi http://127.0.0.1:3902/oobi/EAiBVuuhCZrgckeHc9KzROVGJpmGbk2-e1B25GaeRrJs --force + diff --git a/scripts/demo/basic/query-for-anchor.sh b/scripts/demo/basic/query-for-anchor.sh new file mode 100755 index 000000000..186b35e40 --- /dev/null +++ b/scripts/demo/basic/query-for-anchor.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +kli init --name searcher --salt 0ACDEyMzQ1Njc4OWxtbm9aBc --nopasscode --config-dir ${KERI_SCRIPT_DIR} --config-file demo-witness-oobis +kli incept --name searcher --alias searcher --file ${KERI_DEMO_SCRIPT_DIR}/data/searcher-sample.json +kli ends add --name searcher --alias searcher --eid BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM --role mailbox + +kli init --name anchorer --salt 0ACDEyMzQ1Njc4OWdoaWpsaw --nopasscode --config-dir ${KERI_SCRIPT_DIR} --config-file demo-witness-oobis +kli incept --name anchorer --alias anchorer --file ${KERI_DEMO_SCRIPT_DIR}/data/anchorer-sample.json +kli ends add --name anchorer --alias anchorer --eid BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX --role mailbox + +kli oobi resolve --name searcher --oobi-alias anchorer --oobi http://127.0.0.1:5644/oobi/EK5bvqO2RP8MRTJnE_PHzAsESDj2dHU5avT5I8tuuIzK/witness +kli oobi resolve --name anchorer --oobi-alias searcher --oobi http://127.0.0.1:5643/oobi/EDbnNfFc1DqFLAOdGg_FGFDo5lo6EnYLyV7X9ZsAytT8/witness + +kli query --name searcher --alias searcher --prefix EK5bvqO2RP8MRTJnE_PHzAsESDj2dHU5avT5I8tuuIzK --anchor ./scripts/demo/data/anchor.json & +pid=$! +PID_LIST+=" $pid" + +kli interact --name anchorer --alias anchorer --data @./scripts/demo/data/anchor.json + +wait $PID_LIST + + + diff --git a/scripts/demo/basic/rotate-new-quartet.sh b/scripts/demo/basic/rotate-new-quartet.sh new file mode 100755 index 000000000..33c0b83b9 --- /dev/null +++ b/scripts/demo/basic/rotate-new-quartet.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +kli rotate --name multisig1 --alias multisig1 +kli query --name multisig2 --alias multisig2 --prefix EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4 +kli rotate --name multisig2 --alias multisig2 +kli query --name multisig1 --alias multisig1 --prefix EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1 + +# Perform rotation of mulisig AID from local kli AIDs that roll themselves out and the new AIDs in +kli multisig rotate --name multisig1 --alias multisig \ + --smids EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4:2 \ + --smids EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1:2 \ + --smids EBFg-5SGDCv5YfwpkArWRBdTxNRUXU8uVcDKNzizOQZc:0 \ + --smids EBmW2bXbgsP3HITwW3FmITzAb3wVmHlxCusZ46vgGgP5:0 \ + --smids EL4RpdS2Atb2Syu5xLdpz9CcNNYoFUUDlLHxHD09vcgh:0 \ + --smids EAiBVuuhCZrgckeHc9KzROVGJpmGbk2-e1B25GaeRrJs:0 \ + --isith '["0", "0", "1/2", "1/2", "1/2", "1/2"]' \ + --rmids EBFg-5SGDCv5YfwpkArWRBdTxNRUXU8uVcDKNzizOQZc:0 \ + --rmids EBmW2bXbgsP3HITwW3FmITzAb3wVmHlxCusZ46vgGgP5:0 \ + --rmids EL4RpdS2Atb2Syu5xLdpz9CcNNYoFUUDlLHxHD09vcgh:0 \ + --rmids EAiBVuuhCZrgckeHc9KzROVGJpmGbk2-e1B25GaeRrJs:0 \ + --nsith '["1/2", "1/2", "1/2", "1/2"]' & +pid=$! +PID_LIST="$pid" +kli multisig rotate --name multisig2 --alias multisig \ + --smids EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4:2 \ + --smids EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1:2 \ + --smids EBFg-5SGDCv5YfwpkArWRBdTxNRUXU8uVcDKNzizOQZc:0 \ + --smids EBmW2bXbgsP3HITwW3FmITzAb3wVmHlxCusZ46vgGgP5:0 \ + --smids EL4RpdS2Atb2Syu5xLdpz9CcNNYoFUUDlLHxHD09vcgh:0 \ + --smids EAiBVuuhCZrgckeHc9KzROVGJpmGbk2-e1B25GaeRrJs:0 \ + --isith '["0", "0", "1/2", "1/2", "1/2", "1/2"]' \ + --rmids EBFg-5SGDCv5YfwpkArWRBdTxNRUXU8uVcDKNzizOQZc:0 \ + --rmids EBmW2bXbgsP3HITwW3FmITzAb3wVmHlxCusZ46vgGgP5:0 \ + --rmids EL4RpdS2Atb2Syu5xLdpz9CcNNYoFUUDlLHxHD09vcgh:0 \ + --rmids EAiBVuuhCZrgckeHc9KzROVGJpmGbk2-e1B25GaeRrJs:0 \ + --nsith '["1/2", "1/2", "1/2", "1/2"]' & +pid=$! +PID_LIST+=" $pid" + +wait $PID_LIST diff --git a/scripts/demo/basic/single-witness-create-agent.sh b/scripts/demo/basic/single-witness-create-agent.sh deleted file mode 100755 index bf335beab..000000000 --- a/scripts/demo/basic/single-witness-create-agent.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -# To run this script you need to run the following 2 commands in separate terminals: -# > kli agent demo --config-file demo-witness-oobis-schema -# > kli witness demo -# and from the vLEI repo run: -# > vLEI-server -s ./schema/acdc -c ./samples/acdc/ -o ./samples/oobis/ - -# DoB26Fj4x9LboAFWJra17O -curl -s -X POST "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"issuer\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9aBc\"}" | jq -sleep 3 - -curl -s -X PUT "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"issuer\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -sleep 3 - -curl -s -X POST "http://localhost:5623/ids/issuer" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":1, \"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq diff --git a/scripts/demo/basic/start-agent.sh b/scripts/demo/basic/start-agent.sh deleted file mode 100755 index 7d5ce3034..000000000 --- a/scripts/demo/basic/start-agent.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -kli agent start --insecure --tcp 5921 --admin-http-port 5923 diff --git a/scripts/demo/credentials/multisig-holder-agent.sh b/scripts/demo/credentials/multisig-holder-agent.sh deleted file mode 100755 index 1960e9617..000000000 --- a/scripts/demo/credentials/multisig-holder-agent.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash -# DoB26Fj4x9LboAFWJra17O -curl -s -X POST "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"multisig1\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9aBc\"}" | jq -curl -s -X POST "http://localhost:5723/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"multisig2\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpsaw\"}" | jq -curl -s -X POST "http://localhost:5823/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"issuer\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9abc\"}" | jq - -sleep 3 -curl -s -X PUT "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"multisig1\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5723/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"multisig2\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5823/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"issuer\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq - -sleep 4 -curl -s -X POST "http://localhost:5623/ids/multisig1" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -curl -s -X POST "http://localhost:5723/ids/multisig2" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -curl -s -X POST "http://localhost:5823/ids/issuer" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq - -sleep 4 -curl -s -X POST "http://localhost:5623/oobi/multisig1" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"multisig2\", \"url\":\"http://127.0.0.1:5642/oobi/EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5723/oobi/multisig2" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"multisig1\", \"url\":\"http://127.0.0.1:5642/oobi/EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5623/oobi/multisig1" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"issuer\", \"url\":\"http://127.0.0.1:5643/oobi/EIRdVAl2ItmJf8K82h1cwd5QNF5iVAT37uf8gyIS38QE/witness/BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\"}" | jq -curl -s -X POST "http://localhost:5723/oobi/multisig2" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"issuer\", \"url\":\"http://127.0.0.1:5643/oobi/EIRdVAl2ItmJf8K82h1cwd5QNF5iVAT37uf8gyIS38QE/witness/BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\"}" | jq - -echo "Adding oobis" -echo "--issuer resolving oobis from multisig1 & multisig2" -curl -s -X POST "http://localhost:5823/oobi/issuer" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"multisig2\", \"url\":\"http://127.0.0.1:5642/oobi/EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5823/oobi/issuer" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"multisig1\", \"url\":\"http://127.0.0.1:5642/oobi/EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq - -echo "--schema oobis" -curl -s -X POST "http://localhost:5623/oobi/multisig1" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"multisig1\", \"url\":\"http://127.0.0.1:7723/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao\"}" | jq -curl -s -X POST "http://localhost:5723/oobi/multisig2" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"multisig2\", \"url\":\"http://127.0.0.1:7723/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao\"}" | jq -curl -s -X POST "http://localhost:5823/oobi/issuer" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"issuer\", \"url\":\"http://127.0.0.1:7723/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao\"}" | jq -echo "finished adding oobis" - -echo "inception event for multisig holder" -sleep 3 -curl -s -X POST "http://localhost:5623/groups/holder/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1\",\"EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq -curl -s -X PUT "http://localhost:5723/groups/holder/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1\",\"EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq - -sleep 3 -echo oobi holder to issuer -curl -s -X POST "http://localhost:5823/oobi/issuer" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"holder\", \"url\":\"http://127.0.0.1:5642/oobi/EOWwyMU3XA7RtWdelFt-6waurOTH_aW_Z9VTaU-CshGk/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq - -echo "post registries" -curl -s -X POST "http://localhost:5823/registries" -H "accept: */*" -H "Content-Type: application/json" -d "{\"alias\":\"issuer\",\"baks\":[],\"estOnly\":false,\"name\":\"vLEI\",\"noBackers\":true,\"toad\":0}" | jq - -#curl -s -X POST "http://localhost:5823/registries" -H "accept: */*" -H "Content-Type: application/json" -d "{\"alias\":\"issuer\",\"nonce\":\"AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s\",\"baks\":[],\"estOnly\":false,\"name\":\"vLEI\",\"noBackers\":true,\"toad\":0}" | jq - -# sleep 3 -# curl -s -X POST "http://localhost:5623/groups/issuer/rot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"adds\":[],\"aids\":[\"EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1\", \"EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4\"],\"count\":2,\"cuts\":[],\"data\":[],\"isith\":\"2\",\"toad\":2, \"wits\":[]}" | jq -# curl -s -X PUT "http://localhost:5723/groups/issuer/rot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"adds\":[],\"aids\":[\"EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1\", \"EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4\"],\"count\":2,\"cuts\":[],\"data\":[],\"isith\":\"2\",\"toad\":2, \"wits\":[]}" | jq - -sleep 3 -echo "Issue Credential" -curl -X POST "http://localhost:5823/credentials/issuer" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"credentialData\":{\"LEI\":\"5493001KJTIIGC8Y1R17\"},\"recipient\":\"EOWwyMU3XA7RtWdelFt-6waurOTH_aW_Z9VTaU-CshGk\",\"registry\":\"vLEI\",\"schema\":\"EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao\",\"source\":{}}" \ No newline at end of file diff --git a/scripts/demo/credentials/multisig-issuer-agent.sh b/scripts/demo/credentials/multisig-issuer-agent.sh deleted file mode 100755 index 1cbe2852b..000000000 --- a/scripts/demo/credentials/multisig-issuer-agent.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash -# To run this script you need to run the following 2 commands in separate terminals: -# > kli agent demo --config-file demo-witness-oobis-schema -# > kli witness demo -# and from the vLEI repo run: -# > vLEI-server -s ./schema/acdc -c ./samples/acdc/ -o ./samples/oobis/ -# - -curl -s -X POST "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"multisig1\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9aBc\"}" | jq -curl -s -X POST "http://localhost:5723/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"multisig2\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpsaw\"}" | jq -curl -s -X POST "http://localhost:5823/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"holder\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9abc\"}" | jq - -sleep 3 -curl -s -X PUT "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"multisig1\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5723/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"multisig2\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5823/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"holder\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq - -sleep 4 -curl -s -X POST "http://localhost:5623/ids/multisig1" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -curl -s -X POST "http://localhost:5723/ids/multisig2" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -curl -s -X POST "http://localhost:5823/ids/holder" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq - -sleep 4 -curl -s -X POST "http://localhost:5623/oobi/multisig1" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"multisig2\", \"url\":\"http://127.0.0.1:5642/oobi/EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5723/oobi/multisig2" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"multisig1\", \"url\":\"http://127.0.0.1:5642/oobi/EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5623/oobi/multisig1" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"holder\", \"url\":\"http://127.0.0.1:5643/oobi/Ew9ae1KDP6apL8N7WeyaUBCXOEbEmCcO6uzgCo3WU72A/witness/BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\"}" | jq -curl -s -X POST "http://localhost:5723/oobi/multisig2" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"holder\", \"url\":\"http://127.0.0.1:5643/oobi/Ew9ae1KDP6apL8N7WeyaUBCXOEbEmCcO6uzgCo3WU72A/witness/BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\"}" | jq - -sleep 3 -curl -s -X POST "http://localhost:5623/groups/issuer/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1\",\"EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq -curl -s -X PUT "http://localhost:5723/groups/issuer/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1\",\"EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq - -sleep 3 -curl -s -X POST "http://localhost:5623/registries" -H "accept: */*" -H "Content-Type: application/json" -d "{\"alias\":\"issuer\",\"nonce\":\"AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s\",\"baks\":[],\"estOnly\":false,\"name\":\"vLEI\",\"noBackers\":true,\"toad\":0}" | jq -curl -s -X POST "http://localhost:5723/registries" -H "accept: */*" -H "Content-Type: application/json" -d "{\"alias\":\"issuer\",\"nonce\":\"AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s\",\"baks\":[],\"estOnly\":false,\"name\":\"vLEI\",\"noBackers\":true,\"toad\":0}" | jq - -# sleep 3 -# curl -s -X POST "http://localhost:5623/groups/issuer/rot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"adds\":[],\"aids\":[\"EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1\", \"EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4\"],\"count\":2,\"cuts\":[],\"data\":[],\"isith\":\"2\",\"toad\":2, \"wits\":[]}" | jq -# curl -s -X PUT "http://localhost:5723/groups/issuer/rot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"adds\":[],\"aids\":[\"EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1\", \"EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4\"],\"count\":2,\"cuts\":[],\"data\":[],\"isith\":\"2\",\"toad\":2, \"wits\":[]}" | jq - -sleep 3 -CRED=`curl -s -X POST "http://localhost:5623/groups/issuer/credentials" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"credentialData\":{\"LEI\":\"5493001KJTIIGC8Y1R17\"},\"recipient\":\"Ew9ae1KDP6apL8N7WeyaUBCXOEbEmCcO6uzgCo3WU72A\",\"registry\":\"vLEI\",\"schema\":\"EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao\",\"source\":{}}"` -ESCAPED=`echo -n $CRED | jq '{credential: . }'` -curl -s -X PUT "http://localhost:5723/groups/issuer/credentials" -H "accept: application/json" -H "Content-Type: application/json" -d "${ESCAPED}" | jq - -sleep 3 -echo "Holders Received Credentials..." -curl -s -X GET "http://localhost:5823/credentials/holder?type=received" -H "accept: application/json" | jq \ No newline at end of file diff --git a/scripts/demo/credentials/multisig-issuer-interactive.sh b/scripts/demo/credentials/multisig-issuer-interactive.sh new file mode 100755 index 000000000..281d1bb6b --- /dev/null +++ b/scripts/demo/credentials/multisig-issuer-interactive.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# To run this script you need to run the following command in separate terminals: +# > kli witness demo +# and from the vLEI repo run: +# > vLEI-server -s ./schema/acdc -c ./samples/acdc/ -o ./samples/oobis/ +# + +# Create local environments for multisig group +kli init --name multisig1 --salt 0ACDEyMzQ1Njc4OWxtbm9aBc --nopasscode --config-dir ${KERI_SCRIPT_DIR} --config-file demo-witness-oobis +kli incept --name multisig1 --alias multisig1 --file ${KERI_DEMO_SCRIPT_DIR}/data/multisig-1-sample.json + +# Incept both local identifiers for multisig group +kli init --name multisig2 --salt 0ACDEyMzQ1Njc4OWdoaWpsaw --nopasscode --config-dir ${KERI_SCRIPT_DIR} --config-file demo-witness-oobis +kli incept --name multisig2 --alias multisig2 --file ${KERI_DEMO_SCRIPT_DIR}/data/multisig-2-sample.json + +# Exchange OOBIs between multisig group +kli oobi resolve --name multisig1 --oobi-alias multisig2 --oobi http://127.0.0.1:5642/oobi/EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1/witness +kli oobi resolve --name multisig2 --oobi-alias multisig1 --oobi http://127.0.0.1:5642/oobi/EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4/witness + +# Create the identifier to which the credential will be issued +kli init --name holder --salt 0ACDEyMzQ1Njc4OWxtbm9qWc --nopasscode --config-dir ${KERI_SCRIPT_DIR} --config-file demo-witness-oobis +kli incept --name holder --alias holder --file ${KERI_DEMO_SCRIPT_DIR}/data/gleif-sample.json + +# Introduce multisig to Holder +kli oobi resolve --name holder --oobi-alias multisig2 --oobi http://127.0.0.1:5642/oobi/EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1/witness +kli oobi resolve --name holder --oobi-alias multisig1 --oobi http://127.0.0.1:5642/oobi/EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4/witness + +# Introduce the holder to all participants in the multisig group +kli oobi resolve --name multisig1 --oobi-alias holder --oobi http://127.0.0.1:5642/oobi/ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k/witness +kli oobi resolve --name multisig2 --oobi-alias holder --oobi http://127.0.0.1:5642/oobi/ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k/witness + +# Load Data OOBI for schema of credential to issue +kli oobi resolve --name multisig1 --oobi-alias vc --oobi http://127.0.0.1:7723/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao +kli oobi resolve --name multisig2 --oobi-alias vc --oobi http://127.0.0.1:7723/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao +kli oobi resolve --name holder --oobi-alias vc --oobi http://127.0.0.1:7723/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao + +# Run the follow in parallel and wait for the group to be created: +kli multisig incept --name multisig1 --alias multisig1 --group multisig --file ${KERI_DEMO_SCRIPT_DIR}/data/multisig-sample.json & +pid=$! +PID_LIST+=" $pid" + +kli multisig incept --name multisig2 --alias multisig2 --group multisig --file ${KERI_DEMO_SCRIPT_DIR}/data/multisig-sample.json & +pid=$! +PID_LIST+=" $pid" + +wait $PID_LIST +kli oobi resolve --name holder --oobi-alias multisig --oobi http://127.0.0.1:5642/oobi/EC61gZ9lCKmHAS7U5ehUfEbGId5rcY0D7MirFZHDQcE2/witness + +# Create a credential registry owned by the multisig issuer +kli vc registry incept --name multisig1 --alias multisig --registry-name vLEI --usage "Issue vLEIs" --nonce AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s & +pid=$! +PID_LIST=" $pid" + +#kli vc registry incept --name multisig2 --alias multisig --registry-name vLEI --usage "Issue vLEIs" --nonce AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s & +#pid=$! +#PID_LIST+=" $pid" + +echo "Multisig2 looking to join credential registry creation" +kli multisig join --name multisig2 + +wait $PID_LIST + +# Issue Credential +kli vc create --name multisig1 --alias multisig --registry-name vLEI --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k --data @${KERI_DEMO_SCRIPT_DIR}/data/credential-data.json & +pid=$! +PID_LIST+=" $pid" + +# Wait for 3 seconds to allow credential.json to be created, but still launch in parallel because they will wait for each other +echo "Multisig2 looking to join credential creation" +kli multisig join --name multisig2 + +wait $PID_LIST + +SAID=$(kli vc list --name multisig1 --alias multisig --issued --said) + +kli ipex grant --name multisig1 --alias multisig --said "${SAID}" --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k & +pid=$! +PID_LIST="$pid" + +kli multisig join --name multisig2 + +wait ${PID_LIST} + +echo "Polling for holder's IPEX message..." +SAID=$(kli ipex list --name holder --alias holder --poll --said) + +echo "Admitting GRANT ${SAID}" +kli ipex admit --name holder --alias holder --said "${SAID}" + +kli vc list --name holder --alias holder + +SAID=$(kli vc list --name holder --alias holder --said --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao) + +echo "Revoking ${SAID}..." +kli vc revoke --name multisig1 --alias multisig --registry-name vLEI --said "${SAID}"& +pid=$! +PID_LIST="$pid" + +kli multisig join --name multisig2 + +wait $PID_LIST +kli vc list --name holder --alias holder --poll diff --git a/scripts/demo/credentials/multisig-issuer.sh b/scripts/demo/credentials/multisig-issuer.sh index fd2cad9e8..8e8969884 100755 --- a/scripts/demo/credentials/multisig-issuer.sh +++ b/scripts/demo/credentials/multisig-issuer.sh @@ -44,46 +44,69 @@ pid=$! PID_LIST+=" $pid" wait $PID_LIST -kli oobi resolve --name holder --oobi-alias multisig --oobi http://127.0.0.1:5642/oobi/EC61gZ9lCKmHAS7U5ehUfEbGId5rcY0D7MirFZHDQcE2/witness # Create a credential registry owned by the multisig issuer -kli vc registry incept --name multisig1 --alias multisig --registry-name vLEI --nonce AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s & +kli vc registry incept --name multisig1 --alias multisig --registry-name vLEI --usage "Issue vLEIs" --nonce AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s & pid=$! PID_LIST=" $pid" -kli vc registry incept --name multisig2 --alias multisig --registry-name vLEI --nonce AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s & +kli vc registry incept --name multisig2 --alias multisig --registry-name vLEI --usage "Issue vLEIs" --nonce AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s & pid=$! PID_LIST+=" $pid" wait $PID_LIST -# Rotate multisig keys: -kli multisig rotate --name multisig1 --alias multisig & + +## Rotate multisig keys: +kli rotate --name multisig1 --alias multisig1 +kli query --name multisig2 --alias multisig2 --prefix EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4 +kli rotate --name multisig2 --alias multisig2 +kli query --name multisig1 --alias multisig1 --prefix EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1 + +kli multisig rotate --name multisig1 --alias multisig --smids EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1:1 --smids EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4:1 & pid=$! PID_LIST=" $pid" -kli multisig rotate --name multisig2 --alias multisig & +kli multisig rotate --name multisig2 --alias multisig --smids EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1:1 --smids EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4:1 & pid=$! PID_LIST+=" $pid" wait $PID_LIST - # Issue Credential -kli vc issue --name multisig1 --alias multisig --registry-name vLEI --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k --data @${KERI_DEMO_SCRIPT_DIR}/data/credential-data.json & +TIME=$(date -Iseconds -u) +kli vc create --name multisig1 --alias multisig --registry-name vLEI --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k --data @${KERI_DEMO_SCRIPT_DIR}/data/credential-data.json --time "${TIME}" & +pid=$! +PID_LIST+=" $pid" + +kli vc create --name multisig2 --alias multisig --registry-name vLEI --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k --data @${KERI_DEMO_SCRIPT_DIR}/data/credential-data.json --time "${TIME}" & +pid=$! +PID_LIST+=" $pid" + +wait $PID_LIST + +SAID=$(kli vc list --name multisig1 --alias multisig --issued --said) + +kli oobi resolve --name holder --oobi-alias multisig --oobi http://127.0.0.1:5642/oobi/EC61gZ9lCKmHAS7U5ehUfEbGId5rcY0D7MirFZHDQcE2/witness + +kli ipex grant --name multisig1 --alias multisig --said "${SAID}" --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k --time "${TIME}" & pid=$! PID_LIST+=" $pid" -# Wait for 3 seconds to allow credential.json to be created, but still launch in parallel because they will wait for each other -sleep 3 -kli vc issue --name multisig2 --alias multisig --credential @./credential.json & +kli ipex grant --name multisig2 --alias multisig --said "${SAID}" --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k --time "${TIME}" & pid=$! PID_LIST+=" $pid" wait $PID_LIST +echo "Polling for holder's IPEX message..." +SAID=$(kli ipex list --name holder --alias holder --poll --said) + +echo "Admitting GRANT ${SAID}" +kli ipex admit --name holder --alias holder --said "${SAID}" + kli vc list --name holder --alias holder --poll -SAID=`kli vc list --name holder --alias holder --said --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao` +SAID=$(kli vc list --name holder --alias holder --said --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao) echo "Revoking ${SAID}..." TIME=$(date -Iseconds -u) diff --git a/scripts/demo/credentials/multisig-signify-issue.sh b/scripts/demo/credentials/multisig-signify-issue.sh new file mode 100755 index 000000000..32fb19dd4 --- /dev/null +++ b/scripts/demo/credentials/multisig-signify-issue.sh @@ -0,0 +1,99 @@ +#!/bin/bash + +# WITNESSES +# To run the following scripts, open another console window and run: +# $ kli witness demo + +kli init --name multisig1 --salt 0ACDEyMzQ1Njc4OWxtbm9aBc --nopasscode --config-dir "${KERI_SCRIPT_DIR}" --config-file demo-witness-oobis +kli incept --name multisig1 --alias multisig1 --file ${KERI_DEMO_SCRIPT_DIR}/data/multisig-1-sample.json + +kli init --name multisig2 --salt 0ACDEyMzQ1Njc4OWdoaWpsaw --nopasscode --config-dir "${KERI_SCRIPT_DIR}" --config-file demo-witness-oobis +kli incept --name multisig2 --alias multisig2 --file ${KERI_DEMO_SCRIPT_DIR}/data/multisig-2-sample.json + +# Create the identifier to which the credential will be issued +kli init --name holder --salt 0ACDEyMzQ1Njc4OWxtbm9qWc --nopasscode --config-dir ${KERI_SCRIPT_DIR} --config-file demo-witness-oobis +kli incept --name holder --alias holder --file ${KERI_DEMO_SCRIPT_DIR}/data/gleif-sample.json + +kli oobi resolve --name multisig1 --oobi-alias multisig2 --oobi http://127.0.0.1:5642/oobi/EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha +kli oobi resolve --name multisig2 --oobi-alias multisig1 --oobi http://127.0.0.1:5642/oobi/EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha +kli oobi resolve --name multisig1 --oobi-alias holder --oobi http://127.0.0.1:5642/oobi/ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k/witness +kli oobi resolve --name multisig2 --oobi-alias holder --oobi http://127.0.0.1:5642/oobi/ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k/witness + + +kli oobi resolve --name multisig1 --oobi-alias vc --oobi http://127.0.0.1:7723/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao +kli oobi resolve --name multisig2 --oobi-alias vc --oobi http://127.0.0.1:7723/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao +kli oobi resolve --name holder --oobi-alias vc --oobi http://127.0.0.1:7723/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao + +read -n 1 -r -p "Press any key after agent0 has created their AID:" + +kli oobi resolve --name multisig1 --oobi-alias agent0 --oobi http://127.0.0.1:3902/oobi/EOGvmhJDBbJP4zeXaRun5vSz0O3_1zB10DwNMyjXlJEv/agent/EEXekkGu9IAzav6pZVJhkLnjtjM5v3AcyA-pdKUcaGei +kli oobi resolve --name multisig2 --oobi-alias agent0 --oobi http://127.0.0.1:3902/oobi/EOGvmhJDBbJP4zeXaRun5vSz0O3_1zB10DwNMyjXlJEv/agent/EEXekkGu9IAzav6pZVJhkLnjtjM5v3AcyA-pdKUcaGei + +# Follow commands run in parallel +kli multisig incept --name multisig1 --alias multisig1 --group multisig --file ${KERI_DEMO_SCRIPT_DIR}/data/multisig-signify-sample.json & +pid=$! +PID_LIST=" $pid" +kli multisig incept --name multisig2 --alias multisig2 --group multisig --file ${KERI_DEMO_SCRIPT_DIR}/data/multisig-signify-sample.json & +pid=$! +PID_LIST+=" $pid" + +wait $PID_LIST +PID_LIST="" + +kli status --name multisig1 --alias multisig + +PID_LIST="" + +# Create a credential registry owned by the multisig issuer +kli vc registry incept --name multisig1 --alias multisig --registry-name vLEI --usage "Issue vLEIs" --nonce AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s & +pid=$! +PID_LIST=" $pid" + +kli vc registry incept --name multisig2 --alias multisig --registry-name vLEI --usage "Issue vLEIs" --nonce AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s & +pid=$! +PID_LIST+=" $pid" + +wait $PID_LIST + +# Issue Credential +#TIME=$(date -Iseconds -u) + +TIME="2023-09-25T16:01:37.000000+00:00" +kli vc create --name multisig1 --alias multisig --registry-name vLEI --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k --data @${KERI_DEMO_SCRIPT_DIR}/data/credential-data.json --time "${TIME}" & +pid=$! +PID_LIST="$pid" + +kli vc create --name multisig2 --alias multisig --registry-name vLEI --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k --data @${KERI_DEMO_SCRIPT_DIR}/data/credential-data.json --time "${TIME}" & +pid=$! +PID_LIST+=" $pid" + +wait $PID_LIST + +SAID=$(kli vc list --name multisig1 --alias multisig --issued --said) + +kli ipex grant --name multisig1 --alias multisig --said "${SAID}" --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k --time "${TIME}" & +pid=$! +PID_LIST="$pid" + +kli ipex grant --name multisig2 --alias multisig --said "${SAID}" --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k --time "${TIME}" & +pid=$! +PID_LIST+=" $pid" + +wait $PID_LIST + +kli oobi resolve --name holder --oobi-alias multisig --oobi http://127.0.0.1:5642/oobi/ENaQNFvFDTCWkoE3O5qTX_JfWLT3HxA2TvmOE-LeSsix/witness +kli oobi resolve --name holder --oobi-alias vc --oobi http://127.0.0.1:7723/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao + +echo "Polling for holder's IPEX message..." +SAID=$(kli ipex list --name holder --alias holder --poll --said) + +echo "Admitting GRANT ${SAID}" +kli ipex admit --name holder --alias holder --said "${SAID}" + +echo "Admitted the GRANT" +kli ipex list --name holder --alias holder + +echo "Now possess the credential" +kli vc list --name holder --alias holder --poll + + diff --git a/scripts/demo/credentials/single-issue-spurn.sh b/scripts/demo/credentials/single-issue-spurn.sh new file mode 100755 index 000000000..7c70e68a9 --- /dev/null +++ b/scripts/demo/credentials/single-issue-spurn.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +kli init --name issuer --salt 0ACDEyMzQ1Njc4OWxtbm9aBc --nopasscode --config-dir ${KERI_SCRIPT_DIR} --config-file demo-witness-oobis +kli incept --name issuer --alias issuer --file ${KERI_DEMO_SCRIPT_DIR}/data/gleif-sample.json + +kli init --name holder --salt 0ACDEyMzQ1Njc4OWxtbm9qWc --nopasscode --config-dir ${KERI_SCRIPT_DIR} --config-file demo-witness-oobis +kli incept --name holder --alias holder --file ${KERI_DEMO_SCRIPT_DIR}/data/gleif-sample.json + +kli oobi resolve --name issuer --oobi-alias holder --oobi http://127.0.0.1:5642/oobi/ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha +kli oobi resolve --name holder --oobi-alias issuer --oobi http://127.0.0.1:5642/oobi/EKxICWTx5Ph4EKq5xie2znZf7amggUn4Sd-2-46MIQTg/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha +kli oobi resolve --name issuer --oobi-alias issuer --oobi http://127.0.0.1:7723/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao +kli oobi resolve --name holder --oobi-alias holder --oobi http://127.0.0.1:7723/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao + +kli vc registry incept --name issuer --alias issuer --registry-name vLEI + +kli vc create --name issuer --alias issuer --registry-name vLEI --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k --data @${KERI_DEMO_SCRIPT_DIR}/data/credential-data.json +SAID=$(kli vc list --name issuer --alias issuer --issued --said --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao) + +kli ipex grant --name issuer --alias issuer --said "${SAID}" --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k + +echo "Checking holder for grant messages..." +GRANT=$(kli ipex list --name holder --alias holder --poll --said) + +echo "Spurning credential from grant ${GRANT}" +kli ipex spurn --name holder --alias holder --said "${GRANT}" + +kli vc list --name holder --alias holder + +kli ipex list --name issuer --alias issuer --sent --poll diff --git a/scripts/demo/credentials/single-issuer-agent.sh b/scripts/demo/credentials/single-issuer-agent.sh deleted file mode 100755 index 842928b16..000000000 --- a/scripts/demo/credentials/single-issuer-agent.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -# To run this script you need to run the following 2 commands in separate terminals: -# > kli agent demo --config-file demo-witness-oobis-schema -# > kli witness demo -# and from the vLEI repo run: -# > vLEI-server -s ./schema/acdc -c ./samples/acdc/ -o ./samples/oobis/ - -# DoB26Fj4x9LboAFWJra17O -curl -s -X POST "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"issuer\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9aBc\"}" | jq -curl -s -X POST "http://localhost:5723/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"holder\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9abc\"}" | jq -sleep 3 - -curl -s -X PUT "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"issuer\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5723/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"holder\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -sleep 3 - -curl -s -X POST "http://localhost:5623/ids/issuer" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -curl -s -X POST "http://localhost:5723/ids/holder" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -sleep 3 - -curl -s -X POST "http://localhost:5623/registries" -H "accept: */*" -H "Content-Type: application/json" -d "{\"alias\":\"issuer\",\"baks\":[],\"estOnly\":false,\"name\":\"vLEI\",\"noBackers\":true,\"toad\":0}" | jq -sleep 3 - -curl -s -X POST "http://localhost:5623/oobi/issuer" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"holder\", \"url\":\"http://127.0.0.1:5643/oobi/Ew9ae1KDP6apL8N7WeyaUBCXOEbEmCcO6uzgCo3WU72A/witness/BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\"}" | jq -curl -s -X POST "http://localhost:5723/oobi/holder" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"issuer\", \"url\":\"http://127.0.0.1:5644/oobi/Ew-o5dU5WjDrxDBK4b4HrF82_rYb6MX6xsegjq4n0Y7M/witness/BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"}" | jq -sleep 2 - -curl -X POST "http://localhost:5623/credentials/issuer" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"credentialData\":{\"LEI\":\"5493001KJTIIGC8Y1R17\"},\"recipient\":\"Ew9ae1KDP6apL8N7WeyaUBCXOEbEmCcO6uzgCo3WU72A\",\"registry\":\"vLEI\",\"schema\":\"EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao\",\"source\":{}}" | jq - -sleep 2 -echo "Holders Received Credentials..." -curl -s -X GET "http://localhost:5723/credentials/holder?type=received" -H "accept: application/json" | jq \ No newline at end of file diff --git a/scripts/demo/credentials/single-issuer.sh b/scripts/demo/credentials/single-issuer.sh index 240505b5b..441129bee 100755 --- a/scripts/demo/credentials/single-issuer.sh +++ b/scripts/demo/credentials/single-issuer.sh @@ -13,11 +13,21 @@ kli oobi resolve --name holder --oobi-alias holder --oobi http://127.0.0.1:7723/ kli vc registry incept --name issuer --alias issuer --registry-name vLEI -kli vc issue --name issuer --alias issuer --registry-name vLEI --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k --data @${KERI_DEMO_SCRIPT_DIR}/data/credential-data.json -sleep 2 -kli vc list --name holder --alias holder --poll -SAID=`kli vc list --name holder --alias holder --said --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao` +kli vc create --name issuer --alias issuer --registry-name vLEI --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k --data @${KERI_DEMO_SCRIPT_DIR}/data/credential-data.json +SAID=$(kli vc list --name issuer --alias issuer --issued --said --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao) + +kli ipex grant --name issuer --alias issuer --said "${SAID}" --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k + +echo "Checking holder for grant messages..." +GRANT=$(kli ipex list --name holder --alias holder --poll --said) + +echo "Admitting credential from grant ${GRANT}" +kli ipex admit --name holder --alias holder --said "${GRANT}" + +kli vc list --name holder --alias holder + +exit 0 -kli vc revoke --name issuer --alias issuer --registry-name vLEI --said ${SAID} +kli vc revoke --name issuer --alias issuer --registry-name vLEI --said "${SAID}" sleep 2 kli vc list --name holder --alias holder --poll diff --git a/scripts/demo/data/anchorer-sample.json b/scripts/demo/data/anchorer-sample.json new file mode 100644 index 000000000..3eaaaf6dc --- /dev/null +++ b/scripts/demo/data/anchorer-sample.json @@ -0,0 +1,12 @@ +{ + "transferable": true, + "wits": [ + "BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX" + ], + "toad": 1, + "icount": 1, + "ncount": 1, + "isith": "1", + "nsith": "1" +} + diff --git a/scripts/demo/data/multisig-signify-sample.json b/scripts/demo/data/multisig-signify-sample.json new file mode 100644 index 000000000..923243d7d --- /dev/null +++ b/scripts/demo/data/multisig-signify-sample.json @@ -0,0 +1,16 @@ +{ + "aids": [ + "EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1", + "EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4", + "EOGvmhJDBbJP4zeXaRun5vSz0O3_1zB10DwNMyjXlJEv" + ], + "transferable": true, + "wits": [ + "BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha", + "BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM", + "BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX" + ], + "toad": 3, + "isith": ["1/3", "1/3", "1/3"], + "nsith": ["1/3", "1/3", "1/3"] +} diff --git a/scripts/demo/data/searcher-sample.json b/scripts/demo/data/searcher-sample.json new file mode 100644 index 000000000..b0e0a482d --- /dev/null +++ b/scripts/demo/data/searcher-sample.json @@ -0,0 +1,12 @@ +{ + "transferable": true, + "wits": [ + "BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM" + ], + "toad": 1, + "icount": 1, + "ncount": 1, + "isith": "1", + "nsith": "1" +} + diff --git a/scripts/demo/test_scripts.sh b/scripts/demo/test_scripts.sh index 2a6c1b0b1..0463eb8e5 100755 --- a/scripts/demo/test_scripts.sh +++ b/scripts/demo/test_scripts.sh @@ -22,13 +22,38 @@ function isSuccess() { } # Test scripts +printf "\n************************************\n" +printf "Running demo-script.sh" +printf "\n************************************\n" "${script_dir}/basic/demo-script.sh" isSuccess + +printf "\n************************************\n" +printf "Running demo-witness-script.sh" +printf "\n************************************\n" "${script_dir}/basic/demo-witness-script.sh" isSuccess + +printf "\n************************************\n" +printf "Running demo-witness-async-script.sh" +printf "\n************************************\n" +"${script_dir}/basic/demo-witness-async-script.sh" +isSuccess + +printf "\n************************************\n" +printf "Running multisig.sh" +printf "\n************************************\n" "${script_dir}/basic/multisig.sh" isSuccess + +printf "\n************************************\n" +printf "Running multisig-delegate-delegator.sh" +printf "\n************************************\n" "${script_dir}/basic/multisig-delegate-delegator.sh" isSuccess + +printf "\n************************************\n" +printf "Running challenge.sh" +printf "\n************************************\n" "${script_dir}/basic/challenge.sh" isSuccess diff --git a/scripts/demo/vLEI/README.md b/scripts/demo/vLEI/README.md index 0f2e1cfaa..6b026dbc7 100644 --- a/scripts/demo/vLEI/README.md +++ b/scripts/demo/vLEI/README.md @@ -11,7 +11,6 @@ using single signature identifiers. They all require the following commands run in separate terminal windows prior to execution: -* `kli agent vlei` * `kli witness demo` and from the vLEI repo run: diff --git a/scripts/demo/vLEI/internal-gar.sh b/scripts/demo/vLEI/internal-gar.sh deleted file mode 100755 index a4fc29e9f..000000000 --- a/scripts/demo/vLEI/internal-gar.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash -# DoB2-6Fj4x-9Lbo-AFWJr-a17O -# Create local QAR keystores -curl -s -X POST "http://localhost:5626/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"keep-qar-5626\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpdo1\"}" | jq -curl -s -X POST "http://localhost:5627/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"keep-qar-5627\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpdo2\"}" | jq - -# Create local IntGAR keystore -curl -s -X POST "http://localhost:5624/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"keep-internal-gar-5624\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpdo3\"}" | jq -curl -s -X POST "http://localhost:5625/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"keep-internal-gar-5625\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpdo4\"}" | jq -sleep 2 - -# Unlock local QAR agents -curl -s -X PUT "http://localhost:5626/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"keep-qar-5626\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5627/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"keep-qar-5627\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq - -# Unlock local IntGAR agents -curl -s -X PUT "http://localhost:5624/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"keep-internal-gar-5624\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5625/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"keep-internal-gar-5625\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -sleep 2 - -# Create local QAR AIDs -# qar1: EKyS_K3auADxLDhKN2JiT0k6neX_LwfGJQxGg4f7Gp3g -# qar2: EUS3cZM8f55JyMpIAMAirr91369PbEkY2WqI28Uo4uys -curl -s -X POST "http://localhost:5626/ids/qar1" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -curl -s -X POST "http://localhost:5627/ids/qar2" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq - -# Create local IntGAR AIDs -# intgar1: EeoS9aaqWggd6hTBDbvM7aKTSxDrM1R4tZp-Vg2IVkMA -# intgar2: ECV586ydtrGHiVSeU_wJQCxGm3HQCMaSiD-vzSKm-AqI -curl -s -X POST "http://localhost:5624/ids/intgar1" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -curl -s -X POST "http://localhost:5625/ids/intgar2" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -sleep 3 - -# OOBI between local QARs -curl -s -X POST "http://localhost:5626/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"qar2\", \"url\":\"http://127.0.0.1:5642/oobi/EUS3cZM8f55JyMpIAMAirr91369PbEkY2WqI28Uo4uys/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5627/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"qar1\", \"url\":\"http://127.0.0.1:5642/oobi/EKyS_K3auADxLDhKN2JiT0k6neX_LwfGJQxGg4f7Gp3g/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq - -# OOBI between local IntGARs -curl -s -X POST "http://localhost:5624/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"intgar2\", \"url\":\"http://127.0.0.1:5642/oobi/ECV586ydtrGHiVSeU_wJQCxGm3HQCMaSiD-vzSKm-AqI/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5625/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"intgar1\", \"url\":\"http://127.0.0.1:5642/oobi/EeoS9aaqWggd6hTBDbvM7aKTSxDrM1R4tZp-Vg2IVkMA/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -sleep 3 - -# Initiate and join QVI multisig AID -# QVI: ESrWiCbP5K0Q7-m1e3GPaY8HJCvqioEIIJhqpz16zk6w -curl -s -X POST "http://localhost:5626/groups/QAR/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EKyS_K3auADxLDhKN2JiT0k6neX_LwfGJQxGg4f7Gp3g\",\"EUS3cZM8f55JyMpIAMAirr91369PbEkY2WqI28Uo4uys\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq -curl -s -X PUT "http://localhost:5627/groups/QAR/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EKyS_K3auADxLDhKN2JiT0k6neX_LwfGJQxGg4f7Gp3g\",\"EUS3cZM8f55JyMpIAMAirr91369PbEkY2WqI28Uo4uys\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq - -# Initiate and join Internal GAR multisig AID -# IntGAR: ESQjNQnVk1N8nTdS7g6m17IWD0iuliABV-RMA-drjgIs -curl -s -X POST "http://localhost:5624/groups/IntGAR/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EeoS9aaqWggd6hTBDbvM7aKTSxDrM1R4tZp-Vg2IVkMA\",\"ECV586ydtrGHiVSeU_wJQCxGm3HQCMaSiD-vzSKm-AqI\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq -curl -s -X PUT "http://localhost:5625/groups/IntGAR/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EeoS9aaqWggd6hTBDbvM7aKTSxDrM1R4tZp-Vg2IVkMA\",\"ECV586ydtrGHiVSeU_wJQCxGm3HQCMaSiD-vzSKm-AqI\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq diff --git a/scripts/demo/vLEI/intgar-challenge.sh b/scripts/demo/vLEI/intgar-challenge.sh deleted file mode 100755 index dc717217a..000000000 --- a/scripts/demo/vLEI/intgar-challenge.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# OOBI between local QARs -curl -s -X POST "http://localhost:5626/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"IntGAR\", \"url\":\"http://127.0.0.1:5642/oobi/ESQjNQnVk1N8nTdS7g6m17IWD0iuliABV-RMA-drjgIs/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5627/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"IntGAR\", \"url\":\"http://127.0.0.1:5642/oobi/ESQjNQnVk1N8nTdS7g6m17IWD0iuliABV-RMA-drjgIs/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq - -# OOBI between local IntGARs -curl -s -X POST "http://localhost:5624/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"QVI\", \"url\":\"http://127.0.0.1:5642/oobi/ESrWiCbP5K0Q7-m1e3GPaY8HJCvqioEIIJhqpz16zk6w/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5625/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"QVI\", \"url\":\"http://127.0.0.1:5642/oobi/ESrWiCbP5K0Q7-m1e3GPaY8HJCvqioEIIJhqpz16zk6w/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -sleep 3 - -curl -s -X POST 'http://localhost:5624/challenge/IntGAR' -H "accept: */*" -H "Content-Type: application/json" -d '{"recipient":"ESrWiCbP5K0Q7-m1e3GPaY8HJCvqioEIIJhqpz16zk6w","words":["final","hard","reveal","car","city","style","throw","slim","smile","jeans","math","liberty"]}' -curl -s -X POST 'http://localhost:5625/challenge/IntGAR' -H "accept: */*" -H "Content-Type: application/json" -d '{"recipient":"ESrWiCbP5K0Q7-m1e3GPaY8HJCvqioEIIJhqpz16zk6w","words":["final","hard","reveal","car","city","style","throw","slim","smile","jeans","math","liberty"]}' \ No newline at end of file diff --git a/scripts/demo/vLEI/issue-xbrl-attestation-agent.sh b/scripts/demo/vLEI/issue-xbrl-attestation-agent.sh deleted file mode 100755 index ef885bcfe..000000000 --- a/scripts/demo/vLEI/issue-xbrl-attestation-agent.sh +++ /dev/null @@ -1,130 +0,0 @@ -#!/bin/bash - -# To run this script you need to run the following 2 commands in separate terminals: -# > kli agent vlei -# > kli witness demo -# and from the vLEI repo run: -# > vLEI-server -s ./schema/acdc -c ./samples/acdc/ -o ./samples/oobis/ -# -echo "KERI_SCRIPT_DIR=" +${KERI_SCRIPT_DIR} -echo "KERI_DEMO_SCRIPT_DIR=" +${KERI_DEMO_SCRIPT_DIR} - -echo "create/open external wallet; issue icp event" -# EHOuGiHMxJShXHgSb6k_9pqxmRb8H-LT0R2hQouHp8pW - external -curl -s -X POST "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"external\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9GhI\"}" | jq -sleep 1 -curl -s -X PUT "http://localhost:5623/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"external\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -sleep 5 -curl -s -X POST "http://localhost:5623/ids/external" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq - -echo "create/open qvi wallet; issue icp event" -## EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX - qvi -curl -s -X POST "http://localhost:5626/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"qvi\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9aBc\"}" | jq -sleep 1 -curl -s -X PUT "http://localhost:5626/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"qvi\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -sleep 5 -curl -s -X POST "http://localhost:5626/ids/qvi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq - -echo "create/open LE wallet; issue icp event" -## EIitNxxiNFXC1HDcPygyfyv3KUlBfS_Zf-ZYOvwjpTuz -curl -s -X POST "http://localhost:5628/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"legal-entity\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9AbC\"}" | jq -sleep 1 -curl -s -X PUT "http://localhost:5628/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"legal-entity\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -sleep 5 -curl -s -X POST "http://localhost:5628/ids/legal-entity" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq - -echo "create/open person wallet; issue icp event" -## EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe -## Passcode: DoB2-6Fj4x-9Lbo-AFWJr-a17O -curl -s -X POST "http://localhost:5630/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"person\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"salt\":\"0ACDEyMzQ1Njc4OWxtbm9dEf\"}" | jq -sleep 1 -curl -s -X PUT "http://localhost:5630/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"person\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -sleep 5 -curl -s -X POST "http://localhost:5630/ids/person" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq - -echo 'resolving external' -sleep 3 -curl -s -X POST "http://localhost:5626/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"external\", \"url\":\"http://127.0.0.1:5642/oobi/EHOuGiHMxJShXHgSb6k_9pqxmRb8H-LT0R2hQouHp8pW/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5628/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"external\", \"url\":\"http://127.0.0.1:5642/oobi/EHOuGiHMxJShXHgSb6k_9pqxmRb8H-LT0R2hQouHp8pW/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5630/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"external\", \"url\":\"http://127.0.0.1:5642/oobi/EHOuGiHMxJShXHgSb6k_9pqxmRb8H-LT0R2hQouHp8pW/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq - -echo 'resolving qvi' -curl -s -X POST "http://localhost:5623/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"qvi\", \"url\":\"http://127.0.0.1:5642/oobi/EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5628/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"qvi\", \"url\":\"http://127.0.0.1:5642/oobi/EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5630/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"qvi\", \"url\":\"http://127.0.0.1:5642/oobi/EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq - -echo 'resolving legal-entity' -curl -s -X POST "http://localhost:5623/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"legal-entity\", \"url\":\"http://127.0.0.1:5642/oobi/EIitNxxiNFXC1HDcPygyfyv3KUlBfS_Zf-ZYOvwjpTuz/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5626/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"legal-entity\", \"url\":\"http://127.0.0.1:5642/oobi/EIitNxxiNFXC1HDcPygyfyv3KUlBfS_Zf-ZYOvwjpTuz/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5630/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"legal-entity\", \"url\":\"http://127.0.0.1:5642/oobi/EIitNxxiNFXC1HDcPygyfyv3KUlBfS_Zf-ZYOvwjpTuz/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq - -echo 'resolving person' -curl -s -X POST "http://localhost:5623/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"person\", \"url\":\"http://127.0.0.1:5642/oobi/EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5626/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"person\", \"url\":\"http://127.0.0.1:5642/oobi/EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5628/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"person\", \"url\":\"http://127.0.0.1:5642/oobi/EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq - -echo 'create registries' -sleep 3 -curl -s -X POST "http://localhost:5623/registries" -H "accept: */*" -H "Content-Type: application/json" -d "{\"alias\":\"external\",\"baks\":[],\"estOnly\":false,\"name\":\"vLEI-external\",\"noBackers\":true,\"toad\":0}" | jq -curl -s -X POST "http://localhost:5626/registries" -H "accept: */*" -H "Content-Type: application/json" -d "{\"alias\":\"qvi\",\"baks\":[],\"estOnly\":false,\"name\":\"vLEI-qvi\",\"noBackers\":true,\"toad\":0}" | jq -curl -s -X POST "http://localhost:5628/registries" -H "accept: */*" -H "Content-Type: application/json" -d "{\"alias\":\"legal-entity\",\"baks\":[],\"estOnly\":false,\"name\":\"vLEI-legal-entity\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"noBackers\":true,\"toad\":0}" | jq -curl -s -X POST "http://localhost:5630/registries" -H "accept: */*" -H "Content-Type: application/json" -d "{\"alias\":\"person\",\"baks\":[],\"estOnly\":false,\"name\":\"vLEI-person\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\",\"noBackers\":true,\"toad\":0}" | jq -sleep 5 - -# Issue QVI credential vLEI from GLEIF External to QVI -echo 'external issues qvi credential' -curl -s -X POST "http://localhost:5623/credentials/external" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"credentialData\":{\"LEI\":\"6383001AJTYIGC8Y1X37\"},\"recipient\":\"EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX\",\"registry\":\"vLEI-external\",\"schema\":\"EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao\"}" -sleep 8 -echo "qvi retrieves Credentials..." -curl -s -X GET "http://localhost:5626/credentials/qvi?type=received" -H "accept: application/json" -sleep 3 - -# Issue LE credential from QVI to Legal Entity - have to create the edges first -echo 'Issue LE credential from QVI to Legal Entity - have to create the edges first' -QVI_SAID=$(curl -s -X GET "http://localhost:5626/credentials/qvi?type=received" -H "accept: application/json" -H "Content-Type: application/json" | jq '.[0] | .sad.d') -echo $QVI_SAID | jq -f ${KERI_DEMO_SCRIPT_DIR}/data/legal-entity-edges-filter.jq > /tmp/legal-entity-edges.json -LE_EDGES=`cat /tmp/legal-entity-edges.json` -RULES=`cat ${KERI_DEMO_SCRIPT_DIR}/data/rules.json` -curl -s -X POST "http://localhost:5626/credentials/qvi" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"credentialData\":{\"LEI\":\"5493001KJTIIGC8Y1R17\"},\"recipient\":\"EIitNxxiNFXC1HDcPygyfyv3KUlBfS_Zf-ZYOvwjpTuz\",\"registry\":\"vLEI-qvi\",\"schema\":\"ENPXp1vQzRF6JwIuS-mp2U8Uf1MoADoP_GqQ62VsDZWY\",\"source\":$LE_EDGES,\"rules\":$RULES}" | jq - -sleep 8 -echo "LE retrieves Credentials..." -curl -s -X GET "http://localhost:5628/credentials/legal-entity?type=received" -H "accept: application/json" | jq -sleep 3 - -# Issue OOR Authorization credential from LE to QVI -echo 'LE issues OOR authorization credential to person' -LE_SAID=$(curl -s -X GET "http://localhost:5628/credentials/legal-entity?type=received" -H "accept: application/json" -H "Content-Type: application/json" | jq '.[0] | .sad.d') -echo $LE_SAID | jq -f ${KERI_DEMO_SCRIPT_DIR}/data/oor-auth-edges-filter.jq > /tmp/oor-auth-edges.json -OOR_AUTH_EDGES=`cat /tmp/oor-auth-edges.json` - -curl -s -X POST "http://localhost:5628/credentials/legal-entity" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"credentialData\":{\"AID\": \"EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe\", \"LEI\":\"6383001AJTYIGC8Y1X37\", \"personLegalName\": \"John Smith\", \"officialRole\": \"Chief Executive Officer\"},\"recipient\":\"EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX\",\"registry\":\"vLEI-legal-entity\",\"schema\":\"EKA57bKBKxr_kN7iN5i7lMUxpMG-s19dRcmov1iDxz-E\",\"source\":$OOR_AUTH_EDGES,\"rules\":$RULES}" | jq -sleep 8 -echo "QVI retrieves Credentials..." -curl -s -X GET "http://localhost:5626/credentials/qvi?type=received" -H "accept: application/json" | jq -sleep 3 - -# Issue OOR credential from QVI to Person -echo 'qvi issues OOR credential to person' -AUTH_SAID=$(curl -s -X GET "http://localhost:5626/credentials/qvi?type=received&schema=EKA57bKBKxr_kN7iN5i7lMUxpMG-s19dRcmov1iDxz-E" -H "accept: application/json" -H "Content-Type: application/json" | jq '.[0] | .sad.d') -echo "[$QVI_SAID, $AUTH_SAID]" | jq -f ${KERI_DEMO_SCRIPT_DIR}/data/oor-edges-filter.jq > /tmp/oor-edges.json -OOR_EDGES=`cat /tmp/oor-edges.json` - -curl -s -X POST "http://localhost:5626/credentials/qvi" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"credentialData\":{\"LEI\":\"6383001AJTYIGC8Y1X37\", \"personLegalName\": \"John Smith\", \"officialRole\": \"Chief Executive Officer\"},\"recipient\":\"EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe\",\"registry\":\"vLEI-qvi\",\"schema\":\"EBNaNu-M9P5cgrnfl2Fvymy4E_jvxxyjb70PRtiANlJy\",\"source\":$OOR_EDGES,\"rules\":$RULES}" | jq -sleep 8 -echo "Person retrieves Credentials..." -curl -s -X GET "http://localhost:5630/credentials/person?type=received" -H "accept: application/json" | jq -sleep 3 - -echo "iXBRL data attestation from person" -OOR_SAID=$(curl -s -X GET "http://localhost:5630/credentials/person?type=received" -H "accept: application/json" -H "Content-Type: application/json" | jq '.[0] | .sad.d') -echo $OOR_SAID -echo $OOR_SAID | jq -f ${KERI_DEMO_SCRIPT_DIR}/data/xbrl-edges-filter.jq > /tmp/xbrl-edges.json -XBRL_EDGES=`cat /tmp/xbrl-edges.json` -NOW=`date -u +"%Y-%m-%dT%H:%M:%S+00:00"` -echo \"$NOW\" | jq -f ${KERI_DEMO_SCRIPT_DIR}/data/xbrl-data.jq > /tmp/xbrl-data.json -XBRL_DATA=`cat /tmp/xbrl-data.json` -curl -X POST "http://localhost:5630/credentials/person" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"credentialData\":$XBRL_DATA,\"registry\":\"vLEI-person\",\"schema\":\"EMhvwOlyEJ9kN4PrwCpr9Jsv7TxPhiYveZ0oP3lJzdEi\",\"source\":$XBRL_EDGES}" | jq -sleep 4 -echo "Person retrieves attestation..." -curl -s -X GET "http://localhost:5630/credentials/person?type=issued" -H "accept: application/json" -d "{\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq diff --git a/scripts/demo/vLEI/issue-xbrl-attestation.sh b/scripts/demo/vLEI/issue-xbrl-attestation.sh index 628e5828f..8687de97b 100755 --- a/scripts/demo/vLEI/issue-xbrl-attestation.sh +++ b/scripts/demo/vLEI/issue-xbrl-attestation.sh @@ -52,42 +52,66 @@ kli vc registry incept --name legal-entity --alias legal-entity --registry-name kli vc registry incept --name person --passcode DoB26Fj4x9LboAFWJra17O --alias person --registry-name vLEI-person # Issue QVI credential vLEI from GLEIF External to QVI -kli vc issue --name external --alias external --registry-name vLEI-external --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX --data @${KERI_DEMO_SCRIPT_DIR}/data/qvi-data.json -kli vc list --name qvi --alias qvi --poll +kli vc create --name external --alias external --registry-name vLEI-external --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX --data @${KERI_DEMO_SCRIPT_DIR}/data/qvi-data.json +SAID=$(kli vc list --name external --alias external --issued --said --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao) +kli ipex grant --name external --alias external --said "${SAID}" --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX +GRANT=$(kli ipex list --name qvi --alias qvi --poll --said) +kli ipex admit --name qvi --alias qvi --said "${GRANT}" +kli vc list --name qvi --alias qvi # Issue LE credential from QVI to Legal Entity - have to create the edges first QVI_SAID=`kli vc list --name qvi --alias qvi --said --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao` echo \"$QVI_SAID\" | jq -f ${KERI_DEMO_SCRIPT_DIR}/data/legal-entity-edges-filter.jq > /tmp/legal-entity-edges.json kli saidify --file /tmp/legal-entity-edges.json -kli vc issue --name qvi --alias qvi --registry-name vLEI-qvi --schema ENPXp1vQzRF6JwIuS-mp2U8Uf1MoADoP_GqQ62VsDZWY --recipient EIitNxxiNFXC1HDcPygyfyv3KUlBfS_Zf-ZYOvwjpTuz --data @${KERI_DEMO_SCRIPT_DIR}/data/legal-entity-data.json --edges @/tmp/legal-entity-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/rules.json -kli vc list --name legal-entity --alias legal-entity --poll +kli vc create --name qvi --alias qvi --registry-name vLEI-qvi --schema ENPXp1vQzRF6JwIuS-mp2U8Uf1MoADoP_GqQ62VsDZWY --recipient EIitNxxiNFXC1HDcPygyfyv3KUlBfS_Zf-ZYOvwjpTuz --data @${KERI_DEMO_SCRIPT_DIR}/data/legal-entity-data.json --edges @/tmp/legal-entity-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/rules.json +SAID=$(kli vc list --name qvi --alias qvi --issued --said --schema ENPXp1vQzRF6JwIuS-mp2U8Uf1MoADoP_GqQ62VsDZWY) +kli ipex grant --name qvi --alias qvi --said "${SAID}" --recipient EIitNxxiNFXC1HDcPygyfyv3KUlBfS_Zf-ZYOvwjpTuz +GRANT=$(kli ipex list --name legal-entity --alias legal-entity --poll --said) +kli ipex admit --name legal-entity --alias legal-entity --said "${GRANT}" +kli vc list --name legal-entity --alias legal-entity # Issue ECR Authorization credential from Legal Entity to QVI - have to create the edges first LE_SAID=`kli vc list --name legal-entity --alias legal-entity --said` echo \"$LE_SAID\" | jq -f ${KERI_DEMO_SCRIPT_DIR}/data/ecr-auth-edges-filter.jq > /tmp/ecr-auth-edges.json kli saidify --file /tmp/ecr-auth-edges.json -kli vc issue --name legal-entity --alias legal-entity --registry-name vLEI-legal-entity --schema EH6ekLjSr8V32WyFbGe1zXjTzFs9PkTYmupJ9H65O14g --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX --data @${KERI_DEMO_SCRIPT_DIR}/data/ecr-auth-data.json --edges @/tmp/ecr-auth-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/ecr-auth-rules.json -kli vc list --name qvi --alias qvi --poll +kli vc create --name legal-entity --alias legal-entity --registry-name vLEI-legal-entity --schema EH6ekLjSr8V32WyFbGe1zXjTzFs9PkTYmupJ9H65O14g --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX --data @${KERI_DEMO_SCRIPT_DIR}/data/ecr-auth-data.json --edges @/tmp/ecr-auth-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/ecr-auth-rules.json +SAID=$(kli vc list --name legal-entity --alias legal-entity --issued --said --schema EH6ekLjSr8V32WyFbGe1zXjTzFs9PkTYmupJ9H65O14g) +kli ipex grant --name legal-entity --alias legal-entity --said "${SAID}" --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX +GRANT=$(kli ipex list --name qvi --alias qvi --poll --said | tail -1) +kli ipex admit --name qvi --alias qvi --said "${GRANT}" +kli vc list --name qvi --alias qvi # Issue ECR credential from QVI to Person AUTH_SAID=`kli vc list --name qvi --alias qvi --said --schema EH6ekLjSr8V32WyFbGe1zXjTzFs9PkTYmupJ9H65O14g` echo "[\"$QVI_SAID\", \"$AUTH_SAID\"]" | jq -f ${KERI_DEMO_SCRIPT_DIR}/data/ecr-edges-filter.jq > /tmp/ecr-edges.json kli saidify --file /tmp/ecr-edges.json -kli vc issue --name qvi --alias qvi --private --registry-name vLEI-qvi --schema EEy9PkikFcANV1l7EHukCeXqrzT1hNZjGlUk7wuMO5jw --recipient EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe --data @${KERI_DEMO_SCRIPT_DIR}/data/ecr-data.json --edges @/tmp/ecr-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/ecr-rules.json -kli vc list --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --poll +kli vc create --name qvi --alias qvi --private --registry-name vLEI-qvi --schema EEy9PkikFcANV1l7EHukCeXqrzT1hNZjGlUk7wuMO5jw --recipient EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe --data @${KERI_DEMO_SCRIPT_DIR}/data/ecr-data.json --edges @/tmp/ecr-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/ecr-rules.json +SAID=$(kli vc list --name qvi --alias qvi --issued --said --schema EEy9PkikFcANV1l7EHukCeXqrzT1hNZjGlUk7wuMO5jw) +kli ipex grant --name qvi --alias qvi --said "${SAID}" --recipient EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe +GRANT=$(kli ipex list --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --poll --said) +kli ipex admit --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --said "${GRANT}" +kli vc list --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O # Issue OOR Authorization credential from Legal Entity to QVI - have to create the edges first echo \"$LE_SAID\" | jq -f ${KERI_DEMO_SCRIPT_DIR}/data/oor-auth-edges-filter.jq > /tmp/oor-auth-edges.json kli saidify --file /tmp/oor-auth-edges.json -kli vc issue --name legal-entity --alias legal-entity --registry-name vLEI-legal-entity --schema EKA57bKBKxr_kN7iN5i7lMUxpMG-s19dRcmov1iDxz-E --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX --data @${KERI_DEMO_SCRIPT_DIR}/data/oor-auth-data.json --edges @/tmp/oor-auth-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/rules.json -kli vc list --name qvi --alias qvi --poll +kli vc create --name legal-entity --alias legal-entity --registry-name vLEI-legal-entity --schema EKA57bKBKxr_kN7iN5i7lMUxpMG-s19dRcmov1iDxz-E --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX --data @${KERI_DEMO_SCRIPT_DIR}/data/oor-auth-data.json --edges @/tmp/oor-auth-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/rules.json +SAID=$(kli vc list --name legal-entity --alias legal-entity --issued --said --schema EKA57bKBKxr_kN7iN5i7lMUxpMG-s19dRcmov1iDxz-E) +kli ipex grant --name legal-entity --alias legal-entity --said "${SAID}" --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX +GRANT=$(kli ipex list --name qvi --alias qvi --poll --said | tail -1) +kli ipex admit --name qvi --alias qvi --said "${GRANT}" +kli vc list --name qvi --alias qvi # Issue OOR credential from QVI to Person AUTH_SAID=`kli vc list --name qvi --alias qvi --said --schema EKA57bKBKxr_kN7iN5i7lMUxpMG-s19dRcmov1iDxz-E` echo "[\"$QVI_SAID\", \"$AUTH_SAID\"]" | jq -f ${KERI_DEMO_SCRIPT_DIR}/data/oor-edges-filter.jq > /tmp/oor-edges.json kli saidify --file /tmp/oor-edges.json -kli vc issue --name qvi --alias qvi --registry-name vLEI-qvi --schema EBNaNu-M9P5cgrnfl2Fvymy4E_jvxxyjb70PRtiANlJy --recipient EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe --data @${KERI_DEMO_SCRIPT_DIR}/data/oor-data.json --edges @/tmp/oor-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/rules.json -kli vc list --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --poll +kli vc create --name qvi --alias qvi --registry-name vLEI-qvi --schema EBNaNu-M9P5cgrnfl2Fvymy4E_jvxxyjb70PRtiANlJy --recipient EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe --data @${KERI_DEMO_SCRIPT_DIR}/data/oor-data.json --edges @/tmp/oor-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/rules.json +SAID=$(kli vc list --name qvi --alias qvi --issued --said --schema EBNaNu-M9P5cgrnfl2Fvymy4E_jvxxyjb70PRtiANlJy) +kli ipex grant --name qvi --alias qvi --said "${SAID}" --recipient EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe +GRANT=$(kli ipex list --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --poll --said | tail -1) +kli ipex admit --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --said "${GRANT}" +kli vc list --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O # Issue iXBRL data attestation from Person OOR_SAID=`kli vc list --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --said --schema EBNaNu-M9P5cgrnfl2Fvymy4E_jvxxyjb70PRtiANlJy` @@ -96,5 +120,5 @@ kli saidify --file /tmp/xbrl-edges.json NOW=`date -u +"%Y-%m-%dT%H:%M:%S+00:00"` echo \"$NOW\" | jq -f ${KERI_DEMO_SCRIPT_DIR}/data/xbrl-data.jq > /tmp/xbrl-data.json kli saidify --file /tmp/xbrl-data.json -kli vc issue --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --registry-name vLEI-person --schema EMhvwOlyEJ9kN4PrwCpr9Jsv7TxPhiYveZ0oP3lJzdEi --data @/tmp/xbrl-data.json --edges @/tmp/xbrl-edges.json +kli vc create --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --registry-name vLEI-person --schema EMhvwOlyEJ9kN4PrwCpr9Jsv7TxPhiYveZ0oP3lJzdEi --data @/tmp/xbrl-data.json --edges @/tmp/xbrl-edges.json kli vc list --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --issued \ No newline at end of file diff --git a/scripts/demo/vLEI/legal-entity.sh b/scripts/demo/vLEI/legal-entity.sh deleted file mode 100755 index 9af47cac3..000000000 --- a/scripts/demo/vLEI/legal-entity.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash -# DoB2-6Fj4x-9Lbo-AFWJr-a17O -# Create local QAR keystores -curl -s -X POST "http://localhost:5626/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"keep-qar-5626\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpdo1\"}" | jq -curl -s -X POST "http://localhost:5627/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"keep-qar-5627\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpdo2\"}" | jq - -# Create local LAR keystore -curl -s -X POST "http://localhost:5628/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"keep-lar-5628\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpdo3\"}" | jq -curl -s -X POST "http://localhost:5629/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"keep-lar-5629\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpdo4\"}" | jq -sleep 2 - -# Unlock local QAR agents -curl -s -X PUT "http://localhost:5626/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"keep-qar-5626\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5627/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"keep-qar-5627\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq - -# Unlock local LAR agents -curl -s -X PUT "http://localhost:5628/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"keep-lar-5628\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5629/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"keep-lar-5629\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -sleep 2 - -# Create local QAR AIDs -# qar1: EKyS_K3auADxLDhKN2JiT0k6neX_LwfGJQxGg4f7Gp3g -# qar2: EUS3cZM8f55JyMpIAMAirr91369PbEkY2WqI28Uo4uys -curl -s -X POST "http://localhost:5626/ids/qar1" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -curl -s -X POST "http://localhost:5627/ids/qar2" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq - -# Create local LAR AIDs -# lar1: EunqQ85SnNhpJdEpakejQ3pDMBeYEuMaPCyH0gpZnppQ -# lar2: EMRvWr--WHsEjzcb4TST50HhAGy0NnBeHJ6ZMWMZ4zBY -curl -s -X POST "http://localhost:5628/ids/lar1" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -curl -s -X POST "http://localhost:5629/ids/lar2" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -sleep 3 - -# OOBI between local QARs -curl -s -X POST "http://localhost:5626/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"qar2\", \"url\":\"http://127.0.0.1:5642/oobi/EUS3cZM8f55JyMpIAMAirr91369PbEkY2WqI28Uo4uys/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5627/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"qar1\", \"url\":\"http://127.0.0.1:5642/oobi/EKyS_K3auADxLDhKN2JiT0k6neX_LwfGJQxGg4f7Gp3g/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq - -# OOBI between local LARs -curl -s -X POST "http://localhost:5628/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"lar2\", \"url\":\"http://127.0.0.1:5642/oobi/EMRvWr--WHsEjzcb4TST50HhAGy0NnBeHJ6ZMWMZ4zBY/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5629/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"lar1\", \"url\":\"http://127.0.0.1:5642/oobi/EunqQ85SnNhpJdEpakejQ3pDMBeYEuMaPCyH0gpZnppQ/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -sleep 3 - -# Initiate and join QVI multisig AID -curl -s -X POST "http://localhost:5626/groups/QAR/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EKyS_K3auADxLDhKN2JiT0k6neX_LwfGJQxGg4f7Gp3g\",\"EUS3cZM8f55JyMpIAMAirr91369PbEkY2WqI28Uo4uys\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq -curl -s -X PUT "http://localhost:5627/groups/QAR/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EKyS_K3auADxLDhKN2JiT0k6neX_LwfGJQxGg4f7Gp3g\",\"EUS3cZM8f55JyMpIAMAirr91369PbEkY2WqI28Uo4uys\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq - -# Initiate and join LegalEntity multisig AID -curl -s -X POST "http://localhost:5628/groups/LAR/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EunqQ85SnNhpJdEpakejQ3pDMBeYEuMaPCyH0gpZnppQ\",\"EMRvWr--WHsEjzcb4TST50HhAGy0NnBeHJ6ZMWMZ4zBY\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq -curl -s -X PUT "http://localhost:5629/groups/LAR/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EunqQ85SnNhpJdEpakejQ3pDMBeYEuMaPCyH0gpZnppQ\",\"EMRvWr--WHsEjzcb4TST50HhAGy0NnBeHJ6ZMWMZ4zBY\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq diff --git a/scripts/demo/vLEI/root-gar.sh b/scripts/demo/vLEI/root-gar.sh deleted file mode 100755 index 5c7077edc..000000000 --- a/scripts/demo/vLEI/root-gar.sh +++ /dev/null @@ -1,27 +0,0 @@ -# Create local Root GAR keystores -# DoB26Fj4x9LboAFWJra17O -curl -s -X POST "http://localhost:5620/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"rootgar1\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpd00\"}" | jq -curl -s -X POST "http://localhost:5621/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"rootgar2\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\", \"salt\":\"0ACDEyMzQ1Njc4OWdoaWpd01\"}" | jq -sleep 2 - -# Unlock local Root GAR agents -curl -s -X PUT "http://localhost:5620/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"rootgar1\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -curl -s -X PUT "http://localhost:5621/boot" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"rootgar2\",\"passcode\":\"DoB2-6Fj4x-9Lbo-AFWJr-a17O\"}" | jq -sleep 2 - -# Create local Root GAR AIDs -# rootgar1: -curl -s -X POST "http://localhost:5620/ids/rootgar1" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq - -# rootgar2: -curl -s -X POST "http://localhost:5621/ids/rootgar2" -H "accept: */*" -H "Content-Type: application/json" -d "{\"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\",\"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":2,\"icount\":1,\"ncount\":1,\"isith\":1,\"nsith\":1}" | jq -sleep 3 - -# OOBI between local Root GARs -curl -s -X POST "http://localhost:5620/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"rootgar2\", \"url\":\"http://127.0.0.1:5642/oobi/EUwocCDxDjqV0gT1ah6dA1FWDyR4EQyHEayQzeS0h-PA/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -curl -s -X POST "http://localhost:5621/oobi" -H "accept: */*" -H "Content-Type: application/json" -d "{\"oobialias\": \"rootgar1\", \"url\":\"http://127.0.0.1:5642/oobi/EC_y9UOQlOD8LEQDx3rnrJdwo3LVOzA6VdCK67qT2C-g/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\"}" | jq -sleep 3 -# -# # Initiate and join GLEIF Root multisig identifier -curl -s -X POST "http://localhost:5620/groups/RootGAR/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EC_y9UOQlOD8LEQDx3rnrJdwo3LVOzA6VdCK67qT2C-g\",\"EUwocCDxDjqV0gT1ah6dA1FWDyR4EQyHEayQzeS0h-PA\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq -curl -s -X POST "http://localhost:5621/groups/RootGAR/icp" -H "accept: */*" -H "Content-Type: application/json" -d "{\"aids\":[\"EC_y9UOQlOD8LEQDx3rnrJdwo3LVOzA6VdCK67qT2C-g\",\"EUwocCDxDjqV0gT1ah6dA1FWDyR4EQyHEayQzeS0h-PA\"], \"transferable\":true,\"wits\":[\"BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha\", \"BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM\",\"BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX\"],\"toad\":3, \"isith\":2,\"nsith\":2}" | jq diff --git a/setup.py b/setup.py index 332667f5c..7341c6bff 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ from setuptools import find_packages, setup setup( name='keri', - version='1.0.0', # also change in src/keri/__init__.py + version='1.1.0', # also change in src/keri/__init__.py license='Apache Software License 2.0', description='Key Event Receipt Infrastructure', long_description="KERI Decentralized Key Management Infrastructure", @@ -81,12 +81,13 @@ 'multicommand>=1.0.0', 'jsonschema>=4.17.0', 'falcon>=3.1.0', - 'daemonocle>=1.2.3', 'hjson>=3.0.2', 'PyYaml>=6.0', 'apispec>=6.0.0', 'mnemonic>=0.20', - 'PrettyTable>=3.5.0' + 'PrettyTable>=3.5.0', + 'http_sfv>=0.9.8', + 'cryptography>=39.0.2' ], extras_require={ }, diff --git a/src/keri/__init__.py b/src/keri/__init__.py index eac850a35..b2781c6ee 100644 --- a/src/keri/__init__.py +++ b/src/keri/__init__.py @@ -1,5 +1,5 @@ # -*- encoding: utf-8 -*- -__version__ = '1.0.0' # also change in setup.py +__version__ = '1.1.0' # also change in setup.py diff --git a/src/keri/app/agenting.py b/src/keri/app/agenting.py index 0619705a2..5683b8cea 100644 --- a/src/keri/app/agenting.py +++ b/src/keri/app/agenting.py @@ -5,22 +5,251 @@ """ import random -from urllib.parse import urlparse +from urllib.parse import urlparse, urljoin from hio.base import doing from hio.core import http from hio.core.tcp import clienting -from hio.help import decking +from hio.help import decking, Hict from . import httping, forwarding from .. import help from .. import kering -from ..core import eventing, parsing, coring +from ..core import eventing, parsing, coring, serdering +from ..core.coring import CtrDex from ..db import dbing +from ..kering import Roles logger = help.ogler.getLogger() +class Receiptor(doing.DoDoer): + + def __init__(self, hby, msgs=None, gets=None, cues=None): + + self.msgs = msgs if msgs is not None else decking.Deck() + self.gets = gets if gets is not None else decking.Deck() + self.cues = cues if cues is not None else decking.Deck() + self.clienter = httping.Clienter() + + doers = [self.clienter, doing.doify(self.witDo), doing.doify(self.gitDo)] + self.hby = hby + + super(Receiptor, self).__init__(doers=doers) + + def receipt(self, pre, sn=None): + """ Returns a generator for witness receipting + + The returns a generator that will submit the designated event to witnesses for receipts using + the synchronous witness API, the propogate the receipts to each of the other witnesses. + + + Parameters: + pre (str): qualified base64 identifier to gather receipts for + sn: (Optiona[int]): sequence number of event to gather receipts for, latest is used if not provided + + Returns: + list: identifiers of witnesses that returned receipts. + + """ + if pre not in self.hby.prefixes: + raise kering.MissingEntryError(f"{pre} not a valid AID") + + hab = self.hby.habs[pre] + sn = sn if sn is not None else hab.kever.sner.num + wits = hab.kever.wits + + if len(wits) == 0: + return + + msg = hab.makeOwnEvent(sn=sn) + ser = serdering.SerderKERI(raw=msg) + + # If we are a rotation event, may need to catch new witnesses up to current key state + if ser.ked['t'] in (coring.Ilks.rot,): + adds = ser.ked["ba"] + for wit in adds: + yield from self.catchup(ser.pre, wit) + + clients = dict() + doers = [] + for wit in wits: + client, clientDoer = httpClient(hab, wit) + clients[wit] = client + doers.append(clientDoer) + self.extend([clientDoer]) + + rcts = dict() + for wit, client in clients.items(): + httping.streamCESRRequests(client=client, dest=wit, ims=bytearray(msg), path="/receipts") + while not client.responses: + yield self.tock + + rep = client.respond() + if rep.status == 200: + rct = bytearray(rep.body) + hab.psr.parseOne(bytearray(rct)) + rserder = serdering.SerderKERI(raw=rct) + del rct[:rserder.size] + + # pull off the count code + coring.Counter(qb64b=rct, strip=True) + rcts[wit] = rct + else: + logger.error(f"invalid response {rep.status} from witnesses {wit}") + + for wit in rcts.keys(): + ewits = [w for w in rcts.keys() if w != wit] + wigs = [sig for w, sig in rcts.items() if w != wit] + + msg = bytearray() + if ser.ked['t'] in (coring.Ilks.icp, coring.Ilks.dip): # introduce new witnesses + msg.extend(schemes(self.hby.db, eids=ewits)) + elif ser.ked['t'] in (coring.Ilks.rot, coring.Ilks.drt) and \ + ("ba" in ser.ked and wit in ser.ked["ba"]): # Newly added witness, introduce to all + msg.extend(schemes(self.hby.db, eids=ewits)) + + rserder = eventing.receipt(pre=hab.pre, + sn=sn, + said=ser.said) + msg.extend(rserder.raw) + msg.extend(coring.Counter(code=CtrDex.NonTransReceiptCouples, count=len(wigs)).qb64b) + for wig in wigs: + msg.extend(wig) + + client = clients[wit] + + sent = httping.streamCESRRequests(client=client, dest=wit, ims=bytearray(msg)) + while len(client.responses) < sent: + yield self.tock + + self.remove(doers) + + return rcts.keys() + + def get(self, pre, sn=None): + """ Returns a generator for witness querying + + The returns a generator that will request receipts for event identified by pre and sn + + + Parameters: + pre (str): qualified base64 identifier to gather receipts for + sn: (Optiona[int]): sequence number of event to gather receipts for, latest is used if not provided + + Returns: + list: identifiers of witnesses that returned receipts. + + """ + if pre not in self.hby.prefixes: + raise kering.MissingEntryError(f"{pre} not a valid AID") + + hab = self.hby.habs[pre] + sn = sn if sn is not None else hab.kever.sner.num + wits = hab.kever.wits + + if len(wits) == 0: + return + + wit = random.choice(hab.kever.wits) + urls = hab.fetchUrls(eid=wit, scheme=kering.Schemes.http) or hab.fetchUrls(eid=wit, scheme=kering.Schemes.https) + if not urls: + raise kering.MissingEntryError(f"unable to query witness {wit}, no http endpoint") + + base = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https] + url = urljoin(base, f"/receipts?pre={pre}&sn={sn}") + + client = self.clienter.request("GET", url) + while not client.responses: + yield self.tock + + rep = client.respond() + if rep.status == 200: + rct = bytearray(rep.body) + hab.psr.parseOne(bytearray(rct)) + + self.clienter.remove(client) + return rep.status == 200 + + def catchup(self, pre, wit): + """ When adding a new Witness, use this method to catch the witness up to the current state of the KEL + + Parameters: + pre (str): qualified base64 AID of the KEL to send + wit (str): qualified base64 AID of the witness to send the KEL to + + """ + if pre not in self.hby.prefixes: + raise kering.MissingEntryError(f"{pre} not a valid AID") + + hab = self.hby.habs[pre] + + client, clientDoer = httpClient(hab, wit) + self.extend([clientDoer]) + + for fmsg in hab.db.clonePreIter(pre=pre): + httping.streamCESRRequests(client=client, dest=wit, ims=bytearray(fmsg)) + while not client.responses: + yield self.tock + + self.remove([clientDoer]) + + def witDo(self, tymth=None, tock=0.0): + """ + Returns doifiable Doist compatibile generator method (doer dog) to process + .kevery and .tevery escrows. + + Parameters: + tymth (function): injected function wrapper closure returned by .tymen() of + Tymist instance. Calling tymth() returns associated Tymist .tyme. + tock (float): injected initial tock value + + Usage: + add result of doify on this method to doers list + """ + self.wind(tymth) + self.tock = tock + _ = (yield self.tock) + + while True: + while self.msgs: + msg = self.msgs.popleft() + pre = msg["pre"] + sn = msg["sn"] if "sn" in msg else None + + yield from self.receipt(pre, sn) + self.cues.push(msg) + + yield self.tock + + def gitDo(self, tymth=None, tock=0.0): + """ + Returns doifiable Doist compatibile generator method (doer dog) to process + .kevery and .tevery escrows. + + Parameters: + tymth (function): injected function wrapper closure returned by .tymen() of + Tymist instance. Calling tymth() returns associated Tymist .tyme. + tock (float): injected initial tock value + + Usage: + add result of doify on this method to doers list + """ + self.wind(tymth) + self.tock = tock + _ = (yield self.tock) + + while True: + while self.gets: + msg = self.gets.popleft() + pre = msg["pre"] + sn = msg["sn"] if "sn" in msg else None + + yield from self.get(pre, sn) + + yield self.tock + + class WitnessReceiptor(doing.DoDoer): """ Sends messages to all current witnesses of given identifier (from hab) and waits @@ -86,13 +315,13 @@ def receiptDo(self, tymth=None, tock=0.0): continue msg = hab.makeOwnEvent(sn=sn) - ser = coring.Serder(raw=msg) + ser = serdering.SerderKERI(raw=msg) dgkey = dbing.dgKey(ser.preb, ser.saidb) witers = [] for wit in wits: - witer = witnesser(hab, wit) + witer = messenger(hab, wit) witers.append(witer) self.extend([witer]) @@ -106,7 +335,8 @@ def receiptDo(self, tymth=None, tock=0.0): for dmsg in hab.db.cloneDelegation(hab.kever): witer.msgs.append(bytearray(dmsg)) - if "ba" in ser.ked and wit in ser.ked["ba"]: # Newly added witness, must send full KEL to catch up + if ser.ked['t'] in (coring.Ilks.icp, coring.Ilks.dip) or \ + "ba" in ser.ked and wit in ser.ked["ba"]: # Newly added witness, must send full KEL to catch up for fmsg in hab.db.clonePreIter(pre=pre): witer.msgs.append(bytearray(fmsg)) @@ -121,7 +351,7 @@ def receiptDo(self, tymth=None, tock=0.0): # If we started with all our recipts, exit unless told to force resubmit of all receipts if completed and not self.force: - self.cues.append(evt) + self.cues.push(evt) continue # generate all rct msgs to send to all witnesses @@ -144,10 +374,10 @@ def receiptDo(self, tymth=None, tock=0.0): # Now that the witnesses have not met each other, send them each other's receipts if ser.ked['t'] in (coring.Ilks.icp, coring.Ilks.dip): # introduce new witnesses - rctMsg.extend(self.replay(eids=ewits)) + rctMsg.extend(schemes(self.hby.db, eids=ewits)) elif ser.ked['t'] in (coring.Ilks.rot, coring.Ilks.drt) and \ ("ba" in ser.ked and witer.wit in ser.ked["ba"]): # Newly added witness, introduce to all - rctMsg.extend(self.replay(eids=ewits)) + rctMsg.extend(schemes(self.hby.db, eids=ewits)) rserder = eventing.receipt(pre=ser.pre, sn=sn, @@ -169,31 +399,11 @@ def receiptDo(self, tymth=None, tock=0.0): self.remove(witers) - self.cues.append(evt) + self.cues.push(evt) yield self.tock yield self.tock - def replay(self, eids): - msgs = bytearray() - for eid in eids: - for scheme in kering.Schemes: - keys = (eid, scheme) - said = self.hby.db.lans.get(keys=keys) - if said is not None: - serder = self.hby.db.rpys.get(keys=(said.qb64,)) - cigars = self.hby.db.scgs.get(keys=(said.qb64,)) - - if len(cigars) == 1: - (verfer, cigar) = cigars[0] - cigar.verfer = verfer - else: - cigar = None - msgs.extend(eventing.messagize(serder=serder, - cigars=[cigar], - pipelined=True)) - return msgs - class WitnessInquisitor(doing.DoDoer): """ @@ -219,7 +429,7 @@ def __init__(self, hby, reger=None, msgs=None, klas=None, **kwa): """ self.hby = hby self.reger = reger - self.klas = klas if klas is not None else HttpWitnesser + self.klas = klas if klas is not None else HTTPMessenger self.msgs = msgs if msgs is not None else decking.Deck() self.sent = decking.Deck() @@ -242,31 +452,52 @@ def msgDo(self, tymth=None, tock=1.0, **opts): evt = self.msgs.popleft() pre = evt["pre"] + target = evt["target"] src = evt["src"] r = evt["r"] q = evt["q"] - wits = evt["wits"] + wits = evt["wits"] if "wits" in evt else None - hab = self.hby.habs[src] if src in self.hby.habs else None - if hab is None: + if "hab" in evt: + hab = evt["hab"] + elif (hab := self.hby.habByPre(src)) is None: continue if not wits and pre not in self.hby.kevers: logger.error(f"must have KEL for identifier to query {pre}") continue - wits = wits if wits is not None else hab.kevers[pre].wits - if len(wits) == 0: - logger.error("Must be used with an identifier that has witnesses") - continue + if not wits: + ends = hab.endsFor(pre=pre) + if Roles.controller in ends: + end = ends[Roles.controller] + elif Roles.agent in ends: + end = ends[Roles.agent] + elif Roles.witness in ends: + end = ends[Roles.witness] + else: + logger.error(f"unable query: can not find a valid role for {pre}") + continue + + if len(end.items()) == 0: + logger.error(f"must have endpoint to query for pre={pre}") + continue + + ctrl, locs = random.choice(list(end.items())) + if len(locs.items()) == 0: + logger.error(f"must have location in endpoint to query for pre={pre}") + continue + + witer = messengerFrom(hab=hab, pre=ctrl, urls=locs) + else: + wit = random.choice(wits) + witer = messenger(hab, wit) - wit = random.choice(wits) - witer = witnesser(hab, wit) self.extend([witer]) - msg = hab.query(pre, src=wit, route=r, query=q) # Query for remote pre Event + msg = hab.query(target, src=witer.wit, route=r, query=q) # Query for remote pre Event - kel = forwarding.introduce(hab, wit) + kel = forwarding.introduce(hab, witer.wit) if kel: witer.msgs.append(bytearray(kel)) @@ -279,15 +510,16 @@ def msgDo(self, tymth=None, tock=1.0, **opts): yield self.tock - def query(self, src, pre, r="logs", sn=0, anchor=None, wits=None, **kwa): + def query(self, pre, r="logs", sn='0', src=None, hab=None, anchor=None, wits=None, **kwa): """ Create, sign and return a `qry` message against the attester for the prefix Parameters: src (str): qb64 identifier prefix of source of query + hab (Hab): Hab to use instead of src if provided pre (str): qb64 identifier prefix being queried for r (str): query route - sn (int): optional specific sequence number to query for - anchor (Seal) anchor to search for + sn (str): optional specific hex str of sequence number to query for + anchor (Seal): anchored Seal to search for wits (list) witnesses to query Returns: @@ -298,11 +530,19 @@ def query(self, src, pre, r="logs", sn=0, anchor=None, wits=None, **kwa): if anchor is not None: qry["a"] = anchor - self.msgs.append(dict(src=src, pre=pre, r=r, q=qry, wits=wits)) + msg = dict(src=src, pre=pre, target=pre, r=r, q=qry, wits=wits) + if hab is not None: + msg["hab"] = hab - def telquery(self, src, ri, i=None, r="tels", **kwa): + self.msgs.append(msg) + + def telquery(self, ri, src=None, i=None, r="tels", hab=None, pre=None, wits=None, **kwa): qry = dict(ri=ri) - self.msgs.append(dict(src=src, pre=i, r=r, q=qry)) + msg = dict(src=src, pre=pre, target=i, r=r, wits=wits, q=qry) + if hab is not None: + msg["hab"] = hab + + self.msgs.append(msg) class WitnessPublisher(doing.DoDoer): @@ -356,7 +596,7 @@ def sendDo(self, tymth=None, tock=0.0, **opts): witers = [] for wit in wits: - witer = witnesser(hab, wit) + witer = messenger(hab, wit) witers.append(witer) witer.msgs.append(bytearray(msg)) # make a copy so everyone munges their own self.extend([witer]) @@ -371,14 +611,121 @@ def sendDo(self, tymth=None, tock=0.0, **opts): _ = (yield self.tock) self.remove(witers) - self.cues.append(evt) + self.cues.push(evt) + + yield self.tock + + yield self.tock + + def sent(self, said): + """ Check if message with given SAID was sent + + Parameters: + said (str): qb64 SAID of message to check for + """ + + for cue in self.cues: + if cue["said"] == said: + return True + + return False + + +class TCPMessenger(doing.DoDoer): + """ Send events to witnesses for receipting using TCP direct connection + + """ + + def __init__(self, hab, wit, url, msgs=None, sent=None, doers=None, **kwa): + """ + For the current event, gather the current set of witnesses, send the event, + gather all receipts and send them to all other witnesses + + Parameters: + hab: Habitat of the identifier to populate witnesses + + """ + self.hab = hab + self.wit = wit + self.url = url + self.posted = 0 + self.msgs = msgs if msgs is not None else decking.Deck() + self.sent = sent if sent is not None else decking.Deck() + self.parser = None + doers = doers if doers is not None else [] + doers.extend([doing.doify(self.receiptDo)]) + + self.kevery = eventing.Kevery(db=self.hab.db, + **kwa) + + super(TCPMessenger, self).__init__(doers=doers) + + def receiptDo(self, tymth=None, tock=0.0): + """ + Returns doifiable Doist compatible generator method (doer dog) + + Usage: + add result of doify on this method to doers list + """ + self.wind(tymth) + self.tock = tock + _ = (yield self.tock) + + up = urlparse(self.url) + if up.scheme != kering.Schemes.tcp: + raise ValueError(f"invalid scheme {up.scheme} for TcpWitnesser") + + client = clienting.Client(host=up.hostname, port=up.port) + self.parser = parsing.Parser(ims=client.rxbs, + framed=True, + kvy=self.kevery) + + clientDoer = clienting.ClientDoer(client=client) + self.extend([clientDoer, doing.doify(self.msgDo)]) + + while True: + while not self.msgs: + yield self.tock + + msg = self.msgs.popleft() + self.posted += 1 + client.tx(msg) # send to connected remote + + while client.txbs: yield self.tock + self.sent.append(msg) yield self.tock + def msgDo(self, tymth=None, tock=0.0, **opts): + """ + Returns doifiable Doist compatible generator method (doer dog) to process + incoming message stream of .kevery + + Doist Injected Attributes: + g.tock = tock # default tock attributes + g.done = None # default done state + g.opts + + Parameters: + tymth is injected function wrapper closure returned by .tymen() of + Tymist instance. Calling tymth() returns associated Tymist .tyme. + tock is injected initial tock value + opts is dict of injected optional additional parameters + + + Usage: + add result of doify on this method to doers list + """ + yield from self.parser.parsator() # process messages continuously + + @property + def idle(self): + return len(self.sent) == self.posted + -class TCPWitnesser(doing.DoDoer): +class TCPStreamMessenger(doing.DoDoer): """ Send events to witnesses for receipting using TCP direct connection """ @@ -405,7 +752,7 @@ def __init__(self, hab, wit, url, msgs=None, sent=None, doers=None, **kwa): self.kevery = eventing.Kevery(db=self.hab.db, **kwa) - super(TCPWitnesser, self).__init__(doers=doers) + super(TCPStreamMessenger, self).__init__(doers=doers) def receiptDo(self, tymth=None, tock=0.0): """ @@ -472,9 +819,9 @@ def idle(self): return len(self.sent) == self.posted -class HttpWitnesser(doing.DoDoer): +class HTTPMessenger(doing.DoDoer): """ - Interacts with Witnesses on HTTP and SSE for sending events and receiving receipts + Interacts with Recipients on HTTP and SSE for sending events and receiving receipts """ @@ -497,15 +844,15 @@ def __init__(self, hab, wit, url, msgs=None, sent=None, doers=None, **kwa): doers.extend([doing.doify(self.msgDo), doing.doify(self.responseDo)]) up = urlparse(url) - if up.scheme != kering.Schemes.http: - raise ValueError(f"invalid scheme {up.scheme} for HttpWitnesser") + if up.scheme != kering.Schemes.http and up.scheme != kering.Schemes.https: + raise ValueError(f"invalid scheme {up.scheme} for HTTPMessenger") - self.client = http.clienting.Client(hostname=up.hostname, port=up.port) + self.client = http.clienting.Client(scheme=up.scheme, hostname=up.hostname, port=up.port) clientDoer = http.clienting.ClientDoer(client=self.client) doers.extend([clientDoer]) - super(HttpWitnesser, self).__init__(doers=doers, **kwa) + super(HTTPMessenger, self).__init__(doers=doers, **kwa) def msgDo(self, tymth=None, tock=0.0): """ @@ -523,7 +870,7 @@ def msgDo(self, tymth=None, tock=0.0): yield self.tock msg = self.msgs.popleft() - self.posted += httping.streamCESRRequests(client=self.client, ims=msg) + self.posted += httping.streamCESRRequests(client=self.client, dest=self.wit, ims=msg) while self.client.requests: yield self.tock @@ -550,6 +897,65 @@ def idle(self): return len(self.msgs) == 0 and self.posted == len(self.sent) +class HTTPStreamMessenger(doing.DoDoer): + """ + Interacts with Recipients on HTTP and SSE for sending events and receiving receipts + + """ + + def __init__(self, hab, wit, url, msg=b'', headers=None, **kwa): + """ + For the current event, gather the current set of witnesses, send the event, + gather all receipts and send them to all other witnesses + + Parameters: + hab: Habitat of the identifier to populate witnesses + + """ + self.hab = hab + self.wit = wit + self.rep = None + headers = headers if headers is not None else {} + + up = urlparse(url) + if up.scheme != kering.Schemes.http and up.scheme != kering.Schemes.https: + raise ValueError(f"invalid scheme {up.scheme} for HTTPMessenger") + + self.client = http.clienting.Client(scheme=up.scheme, hostname=up.hostname, port=up.port) + clientDoer = http.clienting.ClientDoer(client=self.client) + + headers = Hict([ + ("Content-Type", "application/cesr"), + ("Content-Length", len(msg)), + (httping.CESR_DESTINATION_HEADER, self.wit), + ] + list(headers.items())) + + self.client.request( + method="PUT", + path="/", + headers=headers, + body=bytes(msg) + ) + + doers = [clientDoer] + + super(HTTPStreamMessenger, self).__init__(doers=doers, **kwa) + + def recur(self, tyme, deeds=None): + """ + Returns doifiable Doist compatible generator method (doer dog) + + Usage: + add result of doify on this method to doers list + """ + if self.client.responses: + self.rep = self.client.respond() + self.remove([self.client]) + return True + + return super(HTTPStreamMessenger, self).recur(tyme, deeds) + + def mailbox(hab, cid): for (_, erole, eid), end in hab.db.ends.getItemIter(keys=(cid, kering.Roles.mailbox)): if end.allowed: @@ -566,25 +972,64 @@ def mailbox(hab, cid): return mbx -def witnesser(hab, wit): - """ Create a Witnesser (tcp or http) based on available endpoints +def messenger(hab, pre): + """ Create a Messenger (tcp or http) based on available endpoints + + Parameters: + hab (Habitat): Environment to use to look up witness URLs + pre (str): qb64 identifier prefix of recipient to create a messanger for + + Returns: + Optional(TcpWitnesser, HTTPMessenger): witnesser for ensuring full reciepts + """ + urls = hab.fetchUrls(eid=pre) + return messengerFrom(hab, pre, urls) + + +def messengerFrom(hab, pre, urls): + """ Create a Witnesser (tcp or http) based on provided endpoints Parameters: hab (Habitat): Environment to use to look up witness URLs - wit (str): qb64 identifier prefix of witness to create a witnesser for + pre (str): qb64 identifier prefix of recipient to create a messanger for + urls (dict): map of schemes to urls of available endpoints Returns: - Optional(TcpWitnesser, HttpWitnesser): witnesser for ensuring full reciepts + Optional(TcpWitnesser, HTTPMessenger): witnesser for ensuring full reciepts """ - urls = hab.fetchUrls(eid=wit) - if kering.Schemes.http in urls: - url = urls[kering.Schemes.http] - witer = HttpWitnesser(hab=hab, wit=wit, url=url) + if kering.Schemes.http in urls or kering.Schemes.https in urls: + url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https] + witer = HTTPMessenger(hab=hab, wit=pre, url=url) elif kering.Schemes.tcp in urls: url = urls[kering.Schemes.tcp] - witer = TCPWitnesser(hab=hab, wit=wit, url=url) + witer = TCPMessenger(hab=hab, wit=pre, url=url) else: - raise kering.ConfigurationError(f"unable to find a valid endpoint for witness {wit}") + raise kering.ConfigurationError(f"unable to find a valid endpoint for witness {pre}") + + return witer + + +def streamMessengerFrom(hab, pre, urls, msg, headers=None): + """ Create a Witnesser (tcp or http) based on provided endpoints + + Parameters: + hab (Habitat): Environment to use to look up witness URLs + pre (str): qb64 identifier prefix of recipient to create a messanger for + urls (dict): map of schemes to urls of available endpoints + msg (bytes): bytes of message to send + headers (dict): optional headers to send with HTTP requests + + Returns: + Optional(TcpWitnesser, HTTPMessenger): witnesser for ensuring full reciepts + """ + if kering.Schemes.http in urls or kering.Schemes.https in urls: + url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https] + witer = HTTPStreamMessenger(hab=hab, wit=pre, url=url, msg=msg, headers=headers) + elif kering.Schemes.tcp in urls: + url = urls[kering.Schemes.tcp] + witer = TCPStreamMessenger(hab=hab, wit=pre, url=url) + else: + raise kering.ConfigurationError(f"unable to find a valid endpoint for witness {pre}") return witer @@ -601,12 +1046,34 @@ def httpClient(hab, wit): ClientDoer: Doer for client """ - urls = hab.fetchUrls(eid=wit, scheme=kering.Schemes.http) + urls = hab.fetchUrls(eid=wit, scheme=kering.Schemes.http) or hab.fetchUrls(eid=wit, scheme=kering.Schemes.https) if not urls: raise kering.MissingEntryError(f"unable to query witness {wit}, no http endpoint") - up = urlparse(urls[kering.Schemes.http]) - client = http.clienting.Client(hostname=up.hostname, port=up.port) + url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https] + up = urlparse(url) + client = http.clienting.Client(scheme=up.scheme, hostname=up.hostname, port=up.port) clientDoer = http.clienting.ClientDoer(client=client) return client, clientDoer + + +def schemes(db, eids): + msgs = bytearray() + for eid in eids: + for scheme in kering.Schemes: + keys = (eid, scheme) + said = db.lans.get(keys=keys) + if said is not None: + serder = db.rpys.get(keys=(said.qb64,)) + cigars = db.scgs.get(keys=(said.qb64,)) + + if len(cigars) == 1: + (verfer, cigar) = cigars[0] + cigar.verfer = verfer + else: + cigar = None + msgs.extend(eventing.messagize(serder=serder, + cigars=[cigar], + pipelined=True)) + return msgs diff --git a/src/keri/app/booting.py b/src/keri/app/booting.py deleted file mode 100644 index d5fc7f31c..000000000 --- a/src/keri/app/booting.py +++ /dev/null @@ -1,441 +0,0 @@ -# -*- encoding: utf-8 -*- -""" -keri.app.booting module - -""" -import json -import secrets -import string - -import falcon -import hio.core.tcp -import pysodium -from falcon import media -from hio.base import doing -from hio.core import http -from hio.help import decking - -from keri.app import specing, configing, habbing, kiwiing, httping, keeping -from keri.core import coring -from keri.vdr import credentialing - -DEFAULT_PASSCODE_SIZE = 21 -PASSCODE_CHARS = string.ascii_lowercase + string.ascii_uppercase + '123456789' - - -class Servery(doing.DoDoer): - """ Http Server Manager """ - - def __init__(self, port, keypath=None, certpath=None, cafilepath=None): - """ Servery init - - Returns a Servery capable of starting and stopping a single HTTP server on the same port - - Parameters: - port (int): port to listen on for all HTTP server instances - """ - doers = [doing.doify(self.serverDo)] - self.msgs = decking.Deck() - - self.port = port - self.keypath = keypath - self.certpath = certpath - self.cafilepath = cafilepath - self.server = None - self.serverDoer = None - self.currentDoers = None - - super(Servery, self).__init__(doers=doers) - - def serverDo(self, tymth, tock=0.0): - """ - Process cues from Verifier coroutine - - Parameters: - tymth is injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock is injected initial tock value - - """ - self.wind(tymth) - self.tock = tock - yield self.tock - - while True: - while self.msgs: - yield 1.0 - - msg = self.msgs.popleft() - app = msg["app"] - doers = msg["doers"] - - if self.serverDoer: - self.remove([self.serverDoer]) - - if self.server: - self.server.close() - - if self.currentDoers: - self.remove(self.currentDoers) - - self.currentDoers = doers - yield 1.0 - - if self.keypath is not None and self.certpath is not None and self.cafilepath is not None: - servant = hio.core.tcp.ServerTls(certify=False, - keypath=self.keypath, - certpath=self.certpath, - cafilepath=self.cafilepath, - port=self.port) - else: - servant = None - - self.server = http.Server(port=self.port, app=app, servant=servant) - self.serverDoer = http.ServerDoer(server=self.server) - - self.extend(self.currentDoers) - self.extend([self.serverDoer]) - - yield self.tock - - -class PasscodeEnd: - """ Resource class for passcode manipulation """ - - @staticmethod - def on_get(req, rep): - """ GET endpoint for passcode resource - - Args: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - - --- - summary: Generate random 22 digit passcode for use in securing and encrypting keystore - description: Generate random 22 digit passcode for use in securing and encrypting keystore - tags: - - Passcode - responses: - 200: - description: Randomly generated 22 character passcode formatted as xxxx-xxxxx-xxxx-xxxxx-xxxx - - """ - - size = DEFAULT_PASSCODE_SIZE - if "size" in req.params: - size = int(req.params["size"]) - - code = [] - for x in range(size): - code.append(PASSCODE_CHARS[secrets.randbelow(len(PASSCODE_CHARS))]) - - code = "".join(code) - body = dict( - passcode=f"{code}" - ) - - rep.status = falcon.HTTP_200 - rep.content_type = "application/json" - rep.data = json.dumps(body).encode("utf-8") - - -class BootEnd(doing.DoDoer): - """ Resource class for boot a cloud agent """ - - def __init__(self, servery, base="", temp=False, configFile=None, configDir=None, headDirPath=None, **kwa): - """ Provides endpoints for initializing and unlocking an agent - - Parameters: - servery (Servery): HTTP server manager for stopping and restarting HTTP servers - base (str): optional directory path segment inserted before name - that allows further hierarchical differentiation of databases. - "" means optional. - temp (bool): True for testing: - temporary storage of databases and config file - weak resources for stretch of salty key - configFile (str): name of config file to load - configDir (str): name of base for directory to load - headDirPath (str): root path - - """ - self.servery = servery - self.base = base - self.temp = temp - self.configFile = configFile - self.configDir = configDir - self.headDirPath = headDirPath - self.msgs = decking.Deck() - self.bootConfig = dict( - configFile=configFile, - configDir=configDir, - headDirPath=headDirPath - ) | kwa - self._kiwinits = kwa - - doers = [] - super(BootEnd, self).__init__(doers=doers) - - def on_get_name(self, _, rep, name=None): - """ GET endpoint for - - Get keystore status - - Args: - _: falcon.Request HTTP request - rep: falcon.Response HTTP response - name: Keystore name - - --- - summary: Query KERI environment for keystore name - tags: - - Boot - parameters: - - in: path - name: name - schema: - type: string - required: true - description: predetermined name of keep keystore - example: alice - responses: - 202: - description: Keystore exists - 404: - description: No keystore exists - - """ - if name is None: - rep.status = falcon.HTTP_400 - rep.text = "Invalid request" - return - - ks = keeping.Keeper(name=name, - base=self.base, - temp=False, - reopen=True, - headDirPath=self.headDirPath) - - aeid = ks.gbls.get('aeid') - if aeid is None: - ks.close() - rep.status = falcon.HTTP_404 - return - - ks.close() - rep.status = falcon.HTTP_202 - - def on_post(self, req, rep): - """ POST endpoint for creating a new environment (keystore and database) - - Post creates a new database with aeid encryption key generated from passcode. Fails - if database already exists. - - Args: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - - --- - summary: Create KERI environment (database and keystore) - description: Creates the directories for database and keystore for vacuous KERI instance - using name and aeid key or passcode to encrypt datastore. Fails if directory - already exists. - tags: - - Boot - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: - type: string - description: human readable nickname for this agent - example: alice - passcode: - type: string - description: passcode for encrypting and securing this agent - example: RwyY-KleGM-jbe1-cUiSz-p3Ce - responses: - 200: - description: JSON object containing status message - - """ - body = req.get_media() - - bran = None - if "passcode" in body: - bran = body["passcode"] - bran = bran.replace("-", "") - name = body["name"] - - kwa = dict() - if "salt" in body: - kwa["salt"] = body["salt"] - else: - kwa["salt"] = coring.Salter(raw=pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES)).qb64 - - kwa["bran"] = bran - kwa["aeid"] = body["aeid"] if "aeid" in body else None - kwa["seed"] = body["seed"] if "seed" in body else None - - cf = None - if self.configFile is not None: - cf = configing.Configer(name=self.configFile, - base=self.base, - headDirPath=self.configDir, - temp=self.temp, - reopen=True, - clear=False) - - hby = habbing.Habery(name=name, base=self.base, temp=self.temp, cf=cf, headDirPath=self.headDirPath, **kwa) - rgy = credentialing.Regery(hby=hby, name=name, base=self.base) - - hby.close() - rgy.close() - - rep.status = falcon.HTTP_200 - body = dict(name=name, msg="Agent and keystore created") - rep.content_type = "application/json" - rep.data = json.dumps(body).encode("utf-8") - - def on_put(self, req, rep): - """ PUT endpoint for unlocking an environment (keystore and database) - - Put unlocks a database with aeid encryption key generated from passcode. - - Args: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - - --- - summary: Unlock keystore with aeid encryption key generated from passcode. - description: Unlock keystore with aeid encryption key generated from passcode.. - tags: - - Boot - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: - type: string - description: human readable nickname for this agent - example: alice - passcode: - type: string - description: passcode for unlocking the agent and decrypting the keystore - example: RwyY-KleGM-jbe1-cUiSz-p3Ce - responses: - 200: - description: JSON object containing status message - - """ - body = req.get_media() - - bran = None - if "passcode" in body: - bran = body["passcode"] - bran = bran.replace("-", "") - name = body["name"] - - ks = keeping.Keeper(name=name, - base=self.base, - temp=False, - reopen=True, - headDirPath=self.headDirPath) - aeid = ks.gbls.get('aeid') - if aeid is None: - rep.status = falcon.HTTP_400 - rep.text = "Keystore must already exist, exiting" - return - - ks.close() - - if self.configFile is not None: - cf = configing.Configer(name=self.configFile, - base=self.base, - headDirPath=self.configDir, - temp=self.temp, - reopen=True, - clear=False) - else: - cf = None - - hby = habbing.Habery(name=name, base=self.base, bran=bran, cf=cf, headDirPath=self.headDirPath) - rgy = credentialing.Regery(hby=hby, name=name, base=self.base) - - kiwiing.setup(hby=hby, rgy=rgy, servery=self.servery, bootConfig=self.bootConfig, **self._kiwinits) - - rep.status = falcon.HTTP_200 - body = dict(name=name, msg="Agent unlocked") - rep.content_type = "application/json" - rep.data = json.dumps(body).encode("utf-8") - - -def setup(servery, controller="", configFile=None, configDir=None, insecure=True, path="", - headDirPath=None): - """ Set up an agent in bootloader mode """ - app = falcon.App(middleware=falcon.CORSMiddleware( - allow_origins='*', allow_credentials='*', expose_headers=['cesr-attachment', 'cesr-date', 'content-type'])) - if not insecure: - app.add_middleware(httping.SignatureValidationComponent(hby=None, pre=controller)) - app.req_options.media_handlers.update(media.Handlers()) - app.resp_options.media_handlers.update(media.Handlers()) - - kwargs = dict( - controller=controller, - insecure=insecure, - staticPath=path, - ) - - ends = loadEnds(app=app, configFile=configFile, configDir=configDir, path=path, servery=servery, - headDirPath=headDirPath, **kwargs) - - servery.msgs.append(dict(app=app, doers=ends)) - - -def loadEnds(app, servery, *, configFile=None, configDir=None, base="", temp=False, headDirPath=None, path, **kwargs): - """ - Load endpoints for KIWI admin interface into the provided Falcon app - - Parameters: - app (falcon.App): falcon.App to register handlers with: - servery (Servery): HTTP server manager for stopping and restarting HTTP servers - base (str): optional directory path segment inserted before name - that allows further differentiation with a hierarchy. "" means - optional. - temp (bool): assign to .temp - True then open in temporary directory, clear on close - Otherwise then open persistent directory, do not clear on close - configFile: (str) file name override for configuration data - configDir: (str) directory override for configuration data - headDirPath: (str) optional path - path (str): directory location of UI web app files to be served with this API server - - Returns: - list: doers from registering endpoints - - """ - sink = http.serving.StaticSink(staticDirPath=path) - app.add_sink(sink, prefix=sink.DefaultStaticSinkBasePath) - - swagsink = http.serving.StaticSink(staticDirPath="./static") - app.add_sink(swagsink, prefix="/swaggerui") - - passcodeEnd = PasscodeEnd() - app.add_route("/codes", passcodeEnd) - - bootEnd = BootEnd(configFile=configFile, configDir=configDir, base=base, temp=temp, servery=servery, - headDirPath=headDirPath, **kwargs) - app.add_route("/boot", bootEnd) - app.add_route("/boot/{name}", bootEnd, suffix="name") - - resources = [passcodeEnd, bootEnd] - - app.add_route("/spec.yaml", specing.SpecResource(app=app, title='KERI Interactive Web Interface API', - resources=resources)) - - return [bootEnd] diff --git a/src/keri/app/challenging.py b/src/keri/app/challenging.py index 86bccb4d1..3845815cc 100644 --- a/src/keri/app/challenging.py +++ b/src/keri/app/challenging.py @@ -4,8 +4,7 @@ """ -from hio.base import doing -from hio.help import decking +from keri.core import coring def loadHandlers(db, signaler, exc): @@ -21,56 +20,38 @@ def loadHandlers(db, signaler, exc): exc.addHandler(chacha) -class ChallengeHandler(doing.Doer): - """ Handle challange response peer to peer `exn` message """ +class ChallengeHandler: + """ Handle challenge response peer to peer `exn` message """ resource = "/challenge/response" - persist = True def __init__(self, db, signaler): - """ Initialize peer to peer challange response messsage """ + """ Initialize peer to peer challenge response messsage """ self.db = db - self.msgs = decking.Deck() - self.cues = decking.Deck() self.signaler = signaler super(ChallengeHandler, self).__init__() - def do(self, tymth, *, tock=0.0, **opts): - """ Do override to process incoming challenge responses + def handle(self, serder, attachments=None): + """ Do route specific processsing of Challenge response messages Parameters: - tymth (function): injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock (float): injected initial tock value + serder (Serder): Serder of the exn challenge response message + attachments (list): list of tuples of pather, CESR SAD path attachments to the exn event """ - # start enter context - self.wind(tymth) - self.tock = tock - yield self.tock + payload = serder.ked['a'] + signer = serder.pre + words = payload["words"] - while True: + msg = dict( + signer=signer, + said=serder.said, + words=words + ) - while self.msgs: - msg = self.msgs.popleft() - payload = msg["payload"] - signer = msg["pre"] - words = payload["words"] + # Notify controller of sucessful challenge + self.signaler.push(msg, topic="/challenge") - serder = msg["serder"] - - msg = dict( - signer=signer.qb64, - said=serder.said, - words=words - ) - - # Notify controller of sucessful challenge - self.signaler.push(msg, topic="/challenge") - - # Log signer against event to track successful challenges with signed response - self.db.reps.add(keys=(signer.qb64,), val=serder.saider) - - yield self.tock - yield self.tock + # Log signer against event to track successful challenges with signed response + self.db.reps.add(keys=(signer,), val=coring.Saider(qb64=serder.said)) diff --git a/src/keri/app/cli/commands/agent/demo.py b/src/keri/app/cli/commands/agent/demo.py deleted file mode 100644 index 6741be43c..000000000 --- a/src/keri/app/cli/commands/agent/demo.py +++ /dev/null @@ -1,47 +0,0 @@ -import argparse - -from keri.app import booting -from keri.app.cli.commands.agent import start - -parser = argparse.ArgumentParser(description="Run a demo collection of multisig agents") -parser.set_defaults(handler=lambda args: demo(args)) -parser.add_argument('--config-file', - dest="configFile", - action='store', - default="demo-witness-oobis", - help="configuration filename") - - -def demo(args): - print("\n******* Starting Multisig Delegation Agents on ports 5623, 5723, 5823, 5923 " - ".******\n\n") - - # kli agent start --config-dir ./scripts --config-file demo-witness-oobis --insecure --tcp 5621 -a 5623 - servery0 = booting.Servery(port=5623) - booting.setup(servery=servery0, controller="E59KmDbpjK0tRf9Rmc7OlueZVz7LB94DdD3cjQVvPcng", - configFile=args.configFile, - configDir="./scripts", insecure=True, - path=start.STATIC_DIR_PATH) - - # kli agent start --config-dir ./scripts --config-file demo-witness-oobis --insecure --tcp 5721 -a 5723 - servery1 = booting.Servery(port=5723) - booting.setup(servery=servery1, controller="E59KmDbpjK0tRf9Rmc7OlueZVz7LB94DdD3cjQVvPcng", - configFile=args.configFile, - configDir="./scripts", insecure=True, - path=start.STATIC_DIR_PATH) - - # kli agent start --config-dir ./scripts --config-file demo-witness-oobis --insecure --tcp 5821 -a 5823 - servery2 = booting.Servery(port=5823) - booting.setup(servery=servery2, controller="E59KmDbpjK0tRf9Rmc7OlueZVz7LB94DdD3cjQVvPcng", - configFile=args.configFile, - configDir="./scripts", insecure=True, - path=start.STATIC_DIR_PATH) - - # kli agent start --config-dir ./scripts --config-file demo-witness-oobis --insecure --tcp 5921 -a 5923 - servery3 = booting.Servery(port=5923) - booting.setup(servery=servery3, controller="E59KmDbpjK0tRf9Rmc7OlueZVz7LB94DdD3cjQVvPcng", - configFile=args.configFile, - configDir="./scripts", insecure=True, - path=start.STATIC_DIR_PATH) - - return [servery0, servery1, servery2, servery3] diff --git a/src/keri/app/cli/commands/agent/start.py b/src/keri/app/cli/commands/agent/start.py deleted file mode 100644 index 8e6d5cf81..000000000 --- a/src/keri/app/cli/commands/agent/start.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- encoding: utf-8 -*- -""" -KERI -keri.kli.agent module - -Witness command line interface -""" - -import argparse -import logging -import os - -import sys - -from keri import help -from keri.app import booting - -WEB_DIR_PATH = os.path.dirname( - os.path.abspath( - sys.modules.get(__name__).__file__)) -STATIC_DIR_PATH = os.path.join(WEB_DIR_PATH, 'static') - -d = "Runs KERI Agent controller.\n" -d += "Example:\nagent -t 5621\n" -parser = argparse.ArgumentParser(description=d) -parser.set_defaults(handler=lambda args: launch(args)) -parser.add_argument('-T', '--tcp', - action='store', - default=5621, - help="Local port number the HTTP server listens on. Default is 5621.") -parser.add_argument('-a', '--admin-http-port', - action='store', - default=5623, - help="Admin port number the HTTP server listens on. Default is 5623.") -parser.add_argument('--config-file', - dest="configFile", - action='store', - default="", - help="configuration filename") -parser.add_argument("--config-dir", - dest="configDir", - action="store", - default=None, - help="directory override for configuration data") - -parser.add_argument('-c', '--controller', - action='store', - default="E59KmDbpjK0tRf9Rmc7OlueZVz7LB94DdD3cjQVvPcng", - help="Identifier prefix to accept control messages from.") -parser.add_argument("-I", '--insecure', - action='store_true', - help="Run admin HTTP server without checking signatures on controlling requests") -parser.add_argument("-p", "--path", - action="store", - default=STATIC_DIR_PATH, - help="Location of the KIWI app bundle for this agent") -parser.add_argument("--keypath", action="store", required=False, default=None) -parser.add_argument("--certpath", action="store", required=False, default=None) -parser.add_argument("--cafilepath", action="store", required=False, default=None) - - -def launch(args): - """ Launch the agent - - Args: - args (Namespace) parsed command line argument object: - - Returns: - - """ - help.ogler.level = logging.INFO - help.ogler.reopen(name="keri", temp=True, clear=True) - - print("\n******* Starting agent listening: http/{}, tcp/{} " - ".******\n\n".format(args.admin_http_port, args.tcp)) - - servery = booting.Servery(port=int(args.admin_http_port), keypath=args.keypath, certpath=args.certpath, - cafilepath=args.cafilepath) # Manager of HTTP server environments - booting.setup(servery=servery, controller=args.controller, configFile=args.configFile, - configDir=args.configDir, insecure=args.insecure, path=args.path) - return [servery] diff --git a/src/keri/app/cli/commands/agent/vlei.py b/src/keri/app/cli/commands/agent/vlei.py deleted file mode 100644 index ca2c43bac..000000000 --- a/src/keri/app/cli/commands/agent/vlei.py +++ /dev/null @@ -1,98 +0,0 @@ -import argparse - -from keri.app import booting -from keri.app.cli.commands.agent import start - -parser = argparse.ArgumentParser(description="Run a demo collection of agents for the vLEI scenario") -parser.set_defaults(handler=lambda args: vlei(args)) -parser.add_argument('--config-file', - dest="configFile", - action='store', - default="demo-witness-oobis", - help="configuration filename") - - -def vlei(args): - print("\n******* Starting Agents for vLEI scenairo testing on ports:" - "\n\n" - " RootGARs: 5620, 5621\n" - " ExtGARs: 5622, 5623\n" - " IntGARs: 5624, 5625\n" - " QARs: 5626, 5627\n" - " LARs: 5628, 5629\n" - " Person: 5630\n\n" - "*******\n") - - # RootGAR1 - rootGAR1 = booting.Servery(port=5620) - booting.setup(servery=rootGAR1, controller="E59KmDbpjK0tRf9Rmc7OlueZVz7LB94DdD3cjQVvPcng", - configFile="vlei-root-oobis-schema", - configDir="./scripts", insecure=True, - path=start.STATIC_DIR_PATH) - # RootGAR2 - rootGAR2 = booting.Servery(port=5621) - booting.setup(servery=rootGAR2, controller="E59KmDbpjK0tRf9Rmc7OlueZVz7LB94DdD3cjQVvPcng", - configFile="vlei-root-oobis-schema", - configDir="./scripts", insecure=True, - path=start.STATIC_DIR_PATH) - - # ExtGAR1 - extGAR1 = booting.Servery(port=5622) - booting.setup(servery=extGAR1, controller="E59KmDbpjK0tRf9Rmc7OlueZVz7LB94DdD3cjQVvPcng", - configFile="vlei-gar-oobis-schema", - configDir="./scripts", insecure=True, - path=start.STATIC_DIR_PATH) - # ExtGAR2 - extGAR2 = booting.Servery(port=5623) - booting.setup(servery=extGAR2, controller="E59KmDbpjK0tRf9Rmc7OlueZVz7LB94DdD3cjQVvPcng", - configFile="vlei-gar-oobis-schema", - configDir="./scripts", insecure=True, - path=start.STATIC_DIR_PATH) - - # IntGAR1 - intGAR1 = booting.Servery(port=5624) - booting.setup(servery=intGAR1, controller="E59KmDbpjK0tRf9Rmc7OlueZVz7LB94DdD3cjQVvPcng", - configFile="vlei-gar-oobis-schema", - configDir="./scripts", insecure=True, - path=start.STATIC_DIR_PATH) - # IntGAR2 - intGAR2 = booting.Servery(port=5625) - booting.setup(servery=intGAR2, controller="E59KmDbpjK0tRf9Rmc7OlueZVz7LB94DdD3cjQVvPcng", - configFile="vlei-gar-oobis-schema", - configDir="./scripts", insecure=True, - path=start.STATIC_DIR_PATH) - - # QAR1 - qar1 = booting.Servery(port=5626) - booting.setup(servery=qar1, controller="E59KmDbpjK0tRf9Rmc7OlueZVz7LB94DdD3cjQVvPcng", - configFile="vlei-qar-oobis-schema", - configDir="./scripts", insecure=True, - path=start.STATIC_DIR_PATH) - # QAR2 - qar2 = booting.Servery(port=5627) - booting.setup(servery=qar2, controller="E59KmDbpjK0tRf9Rmc7OlueZVz7LB94DdD3cjQVvPcng", - configFile="vlei-qar-oobis-schema", - configDir="./scripts", insecure=True, - path=start.STATIC_DIR_PATH) - - # LAR1 - lar1 = booting.Servery(port=5628) - booting.setup(servery=lar1, controller="E59KmDbpjK0tRf9Rmc7OlueZVz7LB94DdD3cjQVvPcng", - configFile="vlei-qar-oobis-schema", - configDir="./scripts", insecure=True, - path=start.STATIC_DIR_PATH) - # LAR2 - lar2 = booting.Servery(port=5629) - booting.setup(servery=lar2, controller="E59KmDbpjK0tRf9Rmc7OlueZVz7LB94DdD3cjQVvPcng", - configFile="vlei-qar-oobis-schema", - configDir="./scripts", insecure=True, - path=start.STATIC_DIR_PATH) - - # Person - person = booting.Servery(port=5630) - booting.setup(servery=person, controller="E59KmDbpjK0tRf9Rmc7OlueZVz7LB94DdD3cjQVvPcng", - configFile="vlei-qar-oobis-schema", - configDir="./scripts", insecure=True, - path=start.STATIC_DIR_PATH) - - return [rootGAR1, rootGAR2, extGAR1, extGAR2, intGAR1, intGAR2, qar1, qar2, lar1, lar2, person] diff --git a/src/keri/app/cli/commands/challenge/respond.py b/src/keri/app/cli/commands/challenge/respond.py index b55094747..75c31094a 100644 --- a/src/keri/app/cli/commands/challenge/respond.py +++ b/src/keri/app/cli/commands/challenge/respond.py @@ -9,6 +9,7 @@ from keri.app import habbing, forwarding, connecting from keri.app.cli.common import existing +from keri.app.habbing import GroupHab from keri.peer import exchanging parser = argparse.ArgumentParser(description='Respond to a list of challenge words by signing and sending an EXN ' @@ -75,7 +76,7 @@ def __init__(self, name, base, bran, alias, words: list, recp: str): self.recp = recp self.hby = existing.setupHby(name=name, base=base, bran=bran) - self.postman = forwarding.Postman(hby=self.hby) + self.postman = forwarding.Poster(hby=self.hby) self.hbyDoer = habbing.HaberyDoer(habery=self.hby) # setup doer self.org = connecting.Organizer(hby=self.hby) doers = [self.hbyDoer, self.postman, doing.doify(self.respondDo)] @@ -106,11 +107,11 @@ def respondDo(self, tymth, tock=0.0, **opts): recp = recp[0]['id'] payload = dict(i=hab.pre, words=self.words) - exn = exchanging.exchange(route="/challenge/response", payload=payload) - ims = hab.endorse(serder=exn, last=True, pipelined=False) + exn, _ = exchanging.exchange(route="/challenge/response", payload=payload, sender=hab.pre) + ims = hab.endorse(serder=exn, last=False, pipelined=False) del ims[:exn.size] - senderHab = hab.mhab if hab.group else hab + senderHab = hab.mhab if isinstance(hab, GroupHab) else hab self.postman.send(src=senderHab.pre, dest=recp, topic="challenge", serder=exn, attachment=ims) while not self.postman.cues: yield self.tock diff --git a/src/keri/app/cli/commands/challenge/verify.py b/src/keri/app/cli/commands/challenge/verify.py index ed1c961dc..db27501ee 100644 --- a/src/keri/app/cli/commands/challenge/verify.py +++ b/src/keri/app/cli/commands/challenge/verify.py @@ -76,7 +76,7 @@ def __init__(self, name, alias, base, bran, words, generate, strength, out, sign alias = existing.aliasInput(self.hby) self.hab = self.hby.habByName(alias) - self.exc = exchanging.Exchanger(db=self.hby.db, handlers=[]) + self.exc = exchanging.Exchanger(hby=self.hby, handlers=[]) self.org = connecting.Organizer(hby=self.hby) signaler = signaling.Signaler() @@ -84,7 +84,7 @@ def __init__(self, name, alias, base, bran, words, generate, strength, out, sign self.mbd = indirecting.MailboxDirector(hby=self.hby, topics=['/challenge'], exc=self.exc) - doers = [self.mbd, self.exc, doing.doify(self.verifyDo)] + doers = [self.mbd, doing.doify(self.verifyDo)] super(VerifyDoer, self).__init__(doers=doers) diff --git a/src/keri/app/cli/commands/delegate/confirm.py b/src/keri/app/cli/commands/delegate/confirm.py index 05ef5d69a..c5f90b481 100644 --- a/src/keri/app/cli/commands/delegate/confirm.py +++ b/src/keri/app/cli/commands/delegate/confirm.py @@ -10,10 +10,12 @@ from hio.base import doing from keri import help -from keri.app import habbing, indirecting, agenting, grouping, forwarding +from keri.app import habbing, indirecting, agenting, grouping, forwarding, delegating, notifying from keri.app.cli.common import existing -from keri.core import coring +from keri.app.habbing import GroupHab +from keri.core import coring, serdering from keri.db import dbing +from keri.peer import exchanging logger = help.ogler.getLogger() @@ -56,9 +58,17 @@ def __init__(self, name, base, alias, bran, interact=False, auto=False): hby = existing.setupHby(name=name, base=base, bran=bran) self.hbyDoer = habbing.HaberyDoer(habery=hby) # setup doer self.witq = agenting.WitnessInquisitor(hby=hby) - self.postman = forwarding.Postman(hby=hby) + self.postman = forwarding.Poster(hby=hby) self.counselor = grouping.Counselor(hby=hby) - self.mbx = indirecting.MailboxDirector(hby=hby, topics=['/receipt', '/multisig', '/replay', '/delegate']) + self.notifier = notifying.Notifier(hby=hby) + self.mux = grouping.Multiplexor(hby=hby, notifier=self.notifier) + + exc = exchanging.Exchanger(hby=hby, handlers=[]) + delegating.loadHandlers(hby=hby, exc=exc, notifier=self.notifier) + grouping.loadHandlers(exc=exc, mux=self.mux) + + self.mbx = indirecting.MailboxDirector(hby=hby, topics=['/receipt', '/multisig', '/replay', '/delegate'], + exc=exc) doers = [self.hbyDoer, self.witq, self.postman, self.counselor, self.mbx] self.toRemove = list(doers) doers.extend([doing.doify(self.confirmDo)]) @@ -91,12 +101,12 @@ def confirmDo(self, tymth, tock=0.0): eraw = self.hby.db.getEvt(dgkey) if eraw is None: continue - eserder = coring.Serder(raw=bytes(eraw)) # escrowed event + eserder = serdering.SerderKERI(raw=bytes(eraw)) # escrowed event - ilk = eserder.ked["t"] + ilk = eserder.sad["t"] if ilk in (coring.Ilks.dip,): typ = "inception" - delpre = eserder.ked["di"] + delpre = eserder.sad["di"] elif ilk in (coring.Ilks.drt,): typ = "rotation" @@ -118,9 +128,8 @@ def confirmDo(self, tymth, tock=0.0): if not approve: continue - if hab.group: - smids, rmids = hab.members() - aids = smids + if isinstance(hab, GroupHab): + aids = hab.smids seqner = coring.Seqner(sn=eserder.sn) anchor = dict(i=eserder.ked["i"], s=seqner.snh, d=eserder.said) if self.interact: @@ -128,14 +137,10 @@ def confirmDo(self, tymth, tock=0.0): else: print("Confirm does not support rotation for delegation approval with group multisig") continue - # self.counselor.rotate(ghab=hab, mids=self.aids, isith=self.isith, toad=self.toad, - # cuts=list(self.cuts), adds=list(self.adds), - # data=self.data) - - serder = coring.Serder(raw=msg) - exn, atc = grouping.multisigInteractExn(hab, serder.sner.num, aids, [anchor]) - others = list(oset(smids + (rmids or []))) + serder = serdering.SerderKERI(raw=msg) + exn, atc = grouping.multisigInteractExn(ghab=hab, aids=aids, ixn=bytearray(msg)) + others = list(oset(hab.smids + (hab.rmids or []))) others.remove(hab.mhab.pre) for recpt in others: # send notification to other participants as a signalling mechanism @@ -145,8 +150,7 @@ def confirmDo(self, tymth, tock=0.0): prefixer = coring.Prefixer(qb64=hab.pre) seqner = coring.Seqner(sn=serder.sn) saider = coring.Saider(qb64b=serder.saidb) - self.counselor.start(smids=aids, mid=hab.mhab.pre, prefixer=prefixer, seqner=seqner, - saider=saider) + self.counselor.start(ghab=hab, prefixer=prefixer, seqner=seqner, saider=saider) while True: saider = self.hby.db.cgms.get(keys=(prefixer.qb64, seqner.qb64)) @@ -183,7 +187,7 @@ def confirmDo(self, tymth, tock=0.0): print(f'\tDelegate {eserder.pre} {typ} Anchored at Seq. No. {hab.kever.sner.num}') # wait for confirmation of fully commited event - wits = [werfer.qb64 for werfer in eserder.werfers] + wits = [werfer.qb64 for werfer in eserder.berfers] self.witq.query(src=hab.pre, pre=eserder.pre, sn=eserder.sn, wits=wits) while eserder.pre not in self.hby.kevers: diff --git a/src/keri/app/cli/commands/delegate/request.py b/src/keri/app/cli/commands/delegate/request.py index eea0b57d5..85d6cbd0f 100644 --- a/src/keri/app/cli/commands/delegate/request.py +++ b/src/keri/app/cli/commands/delegate/request.py @@ -12,7 +12,8 @@ from keri import help from keri.app import habbing, indirecting, agenting, grouping, forwarding, delegating from keri.app.cli.common import existing -from keri.core import coring +from keri.app.habbing import GroupHab +from keri.core import coring, serdering from keri.db import dbing logger = help.ogler.getLogger() @@ -50,7 +51,7 @@ def __init__(self, name, base, alias, bran): hby = existing.setupHby(name=name, base=base, bran=bran) self.hbyDoer = habbing.HaberyDoer(habery=hby) # setup doer self.witq = agenting.WitnessInquisitor(hby=hby) - self.postman = forwarding.Postman(hby=hby) + self.postman = forwarding.Poster(hby=hby) self.counselor = grouping.Counselor(hby=hby) doers = [self.hbyDoer, self.postman] self.toRemove = list(doers) @@ -85,20 +86,18 @@ def requestDo(self, tymth, tock=0.0): (seqner, saider) = esc[0] evt = hab.makeOwnEvent(sn=seqner.sn) - srdr = coring.Serder(raw=evt) - del evt[:srdr.size] delpre = hab.kever.delegator # get the delegator identifier - if hab.group: + if isinstance(hab, GroupHab): phab = hab.mhab - smids, _ = hab.members() else: phab = self.hby.habByName(f"{self.alias}-proxy") - smids = [] - exn, atc = delegating.delegateRequestExn(hab.mhab, delpre=delpre, ked=srdr.ked, aids=smids) + exn, atc = delegating.delegateRequestExn(hab.mhab, delpre=delpre, evt=bytes(evt), aids=hab.smids) # delegate AID ICP and exn of delegation request EXN + srdr = serdering.SerderKERI(raw=evt) # coring.Serder(raw=evt) + del evt[:srdr.size] self.postman.send(src=phab.pre, dest=delpre, topic="delegate", serder=srdr, attachment=evt) self.postman.send(src=phab.pre, dest=hab.kever.delegator, topic="delegate", serder=exn, attachment=atc) diff --git a/src/keri/app/cli/commands/did/generate.py b/src/keri/app/cli/commands/did/generate.py index ab6791d6b..9e0cb28d1 100644 --- a/src/keri/app/cli/commands/did/generate.py +++ b/src/keri/app/cli/commands/did/generate.py @@ -69,22 +69,24 @@ def generate(tymth, tock=0.0, **opts): elif role in (kering.Roles.witness,): if not hab.kever.wits: - print(f"{alias} identfier {hab.pre} does not have any witnesses.") + print(f"{alias} identifier {hab.pre} does not have any witnesses.") sys.exit(-1) - wit = random.choice(hab.kever.wits) - urls = hab.fetchUrls(eid=wit, scheme=kering.Schemes.http) + wit = random.choice(hab.kever.wits) + urls = hab.fetchUrls(eid=wit, scheme=kering.Schemes.http) or hab.fetchUrls(eid=wit, scheme=kering.Schemes.https) if not urls: raise kering.ConfigurationError(f"unable to query witness {wit}, no http endpoint") - up = urlparse(urls[kering.Schemes.http]) - enc = urllib.parse.quote_plus(f"http://{up.hostname}:{up.port}/oobi/{hab.pre}/witness") + url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https] + up = urlparse(url) + enc = urllib.parse.quote_plus(f"{up.scheme}://{up.hostname}:{up.port}/oobi/{hab.pre}/witness") print(f"did:keri:{hab.pre}?oobi={enc}") elif role in (kering.Roles.controller,): - urls = hab.fetchUrls(eid=hab.pre, scheme=kering.Schemes.http) + urls = hab.fetchUrls(eid=hab.pre, scheme=kering.Schemes.http) or hab.fetchUrls(eid=hab.pre, scheme=kering.Schemes.https) if not urls: print(f"{alias} identifier {hab.pre} does not have any controller endpoints") return - up = urlparse(urls[kering.Schemes.http]) - enc = urllib.parse.quote_plus(f"http://{up.hostname}:{up.port}/oobi/{hab.pre}/controller") + url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https] + up = urlparse(url) + enc = urllib.parse.quote_plus(f"{up.scheme}://{up.hostname}:{up.port}/oobi/{hab.pre}/controller") print(f"did:keri:{hab.pre}?oobi={enc}") diff --git a/src/keri/app/cli/commands/ends/add.py b/src/keri/app/cli/commands/ends/add.py new file mode 100644 index 000000000..e672abfda --- /dev/null +++ b/src/keri/app/cli/commands/ends/add.py @@ -0,0 +1,124 @@ +# -*- encoding: utf-8 -*- +""" +KERI +keri.kli.commands module + +""" +import argparse + +from hio import help +from hio.base import doing + +from keri import kering +from keri.app import habbing, grouping, indirecting, forwarding +from keri.app.agenting import WitnessPublisher +from keri.app.cli.common import existing +from keri.app.notifying import Notifier +from keri.core import parsing +from keri.peer import exchanging + +logger = help.ogler.getLogger() + +parser = argparse.ArgumentParser(description='Add new endpoint role authorization.') +parser.set_defaults(handler=lambda args: add_end(args), + transferable=True) +parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True) +parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore', + required=False, default="") +parser.add_argument('--alias', '-a', help='human readable alias for the new identifier prefix', required=True) +parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)', + dest="bran", default=None) # passcode => bran +parser.add_argument("--role", "-r", help="KERI enpoint authorization role.", + required=True) +parser.add_argument("--eid", "-e", help="qualified base64 of AID to authorize with new role for the AID identified " + "by alias", + required=True) +parser.add_argument("--time", help="timestamp for the end auth", required=False, default=None) + + +def add_end(args): + """ Command line tool for adding endpoint role authorizations + + """ + ld = RoleDoer(name=args.name, + base=args.base, + alias=args.alias, + bran=args.bran, + role=args.role, + eid=args.eid, + timestamp=args.time) + return [ld] + + +class RoleDoer(doing.DoDoer): + + def __init__(self, name, base, alias, bran, role, eid, timestamp=None): + self.role = role + self.eid = eid + self.timestamp = timestamp + + self.hby = existing.setupHby(name=name, base=base, bran=bran) + self.hab = self.hby.habByName(alias) + self.witpub = WitnessPublisher(hby=self.hby) + self.postman = forwarding.Poster(hby=self.hby) + notifier = Notifier(self.hby) + mux = grouping.Multiplexor(self.hby, notifier=notifier) + exc = exchanging.Exchanger(hby=self.hby, handlers=[]) + grouping.loadHandlers(exc, mux) + + mbx = indirecting.MailboxDirector(hby=self.hby, topics=["/receipt", "/multisig", "/replay"], exc=exc) + + if self.hab is None: + raise kering.ConfigurationError(f"unknown alias={alias}") + + self.toRemove = [self.witpub, self.postman, mbx] + + super(RoleDoer, self).__init__(doers=self.toRemove + [doing.doify(self.roleDo)]) + + def roleDo(self, tymth, tock=0.0): + """ Export any end reply messages previous saved for the provided AID + + Parameters: + tymth (function): injected function wrapper closure returned by .tymen() of + Tymist instance. Calling tymth() returns associated Tymist .tyme. + tock (float): injected initial tock value + + Returns: doifiable Doist compatible generator method + + """ + # enter context + self.wind(tymth) + self.tock = tock + _ = (yield self.tock) + + data = dict(cid=self.hab.pre, role=self.role, eid=self.eid) + + route = "/end/role/add" + msg = self.hab.reply(route=route, data=data, stamp=self.timestamp) + + parsing.Parser().parse(ims=bytes(msg), kvy=self.hab.kvy, rvy=self.hab.rvy) + + if isinstance(self.hab, habbing.GroupHab): + smids = self.hab.db.signingMembers(pre=self.hab.pre) + smids.remove(self.hab.mhab.pre) + + for recp in smids: # this goes to other participants only as a signaling mechanism + exn, atc = grouping.multisigRpyExn(ghab=self.hab, rpy=msg) + self.postman.send(src=self.hab.mhab.pre, + dest=recp, + topic="multisig", + serder=exn, + attachment=atc) + + while not self.hab.loadEndRole(cid=self.hab.pre, role=self.role, eid=self.eid): + yield self.tock + + self.witpub.msgs.append(dict(pre=self.hab.pre, msg=bytes(msg))) + + while not self.witpub.cues: + yield self.tock + + print(f"End role authorization added for role {self.role}") + + self.remove(self.toRemove) + return diff --git a/src/keri/app/cli/commands/ends/export.py b/src/keri/app/cli/commands/ends/export.py index 0300db2da..e1d54a2f3 100644 --- a/src/keri/app/cli/commands/ends/export.py +++ b/src/keri/app/cli/commands/ends/export.py @@ -15,7 +15,7 @@ logger = help.ogler.getLogger() -parser = argparse.ArgumentParser(description='List credentials and check mailboxes for any newly issued credentials') +parser = argparse.ArgumentParser(description='Export end points') parser.set_defaults(handler=lambda args: export_ends(args), transferable=True) parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True) @@ -83,9 +83,5 @@ def exportDo(self, tymth, tock=0.0): cigars=[cigar], sigers=sigers, pipelined=True)) - # - # print( - # f"Current {'issued' if self.issued else 'received'} credentials for {self.hab.name} (" - # f"{self.hab.pre}):\n") return True diff --git a/src/keri/app/cli/commands/ends/list.py b/src/keri/app/cli/commands/ends/list.py new file mode 100644 index 000000000..533e39d95 --- /dev/null +++ b/src/keri/app/cli/commands/ends/list.py @@ -0,0 +1,76 @@ +# -*- encoding: utf-8 -*- +""" +KERI +keri.kli.commands module + +""" +import argparse +import json + +from hio import help +from hio.base import doing + +from keri import kering +from keri.app import indirecting, habbing, forwarding, grouping +from keri.app.cli.common import existing +from keri.core import eventing, parsing, coring + +logger = help.ogler.getLogger() + +parser = argparse.ArgumentParser(description='Add new endpoint role authorization.') +parser.set_defaults(handler=lambda args: add_end(args), + transferable=True) +parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True) +parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore', + required=False, default="") +parser.add_argument('--alias', '-a', help='human readable alias for the new identifier prefix', required=True) +parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)', + dest="bran", default=None) # passcode => bran +parser.add_argument("--aid", help="qualified base64 of AID to export rpy messages for all endpoints.", + required=True) + + +def add_end(args): + """ Command line tool for adding endpoint role authorizations + + """ + ld = RoleDoer(name=args.name, + base=args.base, + alias=args.alias, + bran=args.bran, + aid=args.aid) + return [ld] + + +class RoleDoer(doing.DoDoer): + + def __init__(self, name, base, alias, bran, aid): + self.hby = existing.setupHby(name=name, base=base, bran=bran) + self.hab = self.hby.habByName(alias) + if self.hab is None: + raise kering.ConfigurationError(f"unknown alias={alias}") + + self.aid = aid + doers = [doing.doify(self.roleDo)] + + super(RoleDoer, self).__init__(doers=doers) + + def roleDo(self, tymth, tock=0.0): + """ Export any end reply messages previous saved for the provided AID + + Parameters: + tymth (function): injected function wrapper closure returned by .tymen() of + Tymist instance. Calling tymth() returns associated Tymist .tyme. + tock (float): injected initial tock value + + Returns: doifiable Doist compatible generator method + + """ + # enter context + self.wind(tymth) + self.tock = tock + _ = (yield self.tock) + + ends = self.hab.endsFor(self.aid) + print(json.dumps(ends, indent=1)) + return diff --git a/src/keri/app/cli/commands/escrow.py b/src/keri/app/cli/commands/escrow.py index 994a6253e..24d78f335 100644 --- a/src/keri/app/cli/commands/escrow.py +++ b/src/keri/app/cli/commands/escrow.py @@ -127,32 +127,24 @@ def escrows(tymth, tock=0.0, **opts): if (not escrow) or escrow == "missing-registry-escrow": creds = list() for (said,), dater in reger.mre.getItemIter(): - creder, sadsigers, sadcigars = reger.cloneCred(said) - creds.append(creder.crd) + creder, *_ = reger.cloneCred(said) + creds.append(creder.sad) escrows["missing-registry-escrow"] = creds - if (not escrow) or escrow == "missing-issuer-escrow": - creds = list() - for (said,), dater in reger.mie.getItemIter(): - creder, sadsigers, sadcigars = reger.cloneCred(said) - creds.append(creder.crd) - - escrows["missing-issuer-escrow"] = creds - if (not escrow) or escrow == "broken-chain-escrow": creds = list() for (said,), dater in reger.mce.getItemIter(): - creder, sadsigers, sadcigars = reger.cloneCred(said) - creds.append(creder.crd) + creder, *_ = reger.cloneCred(said) + creds.append(creder.sad) escrows["broken-chain-escrow"] = creds if (not escrow) or escrow == "missing-schema-escrow": creds = list() for (said,), dater in reger.mse.getItemIter(): - creder, sadsigers, sadcigars = reger.cloneCred(said) - creds.append(creder.crd) + creder, *_ = reger.cloneCred(said) + creds.append(creder.sad) escrows["missing-schema-escrow"] = creds @@ -163,6 +155,5 @@ def escrows(tymth, tock=0.0, **opts): pass except ConfigurationError as e: - print(e) print(f"identifier prefix for {name} does not exist, incept must be run first", ) return -1 diff --git a/src/keri/app/cli/commands/export.py b/src/keri/app/cli/commands/export.py index fb1132828..7fe7dfff9 100644 --- a/src/keri/app/cli/commands/export.py +++ b/src/keri/app/cli/commands/export.py @@ -11,7 +11,7 @@ from hio.base import doing from keri.app.cli.common import existing -from keri.core import coring +from keri.core import coring, serdering logger = help.ogler.getLogger() @@ -92,7 +92,7 @@ def outputKEL(self, pre): if f is not None: f.write(msg.decode("utf-8")) else: - serder = coring.Serder(raw=msg) + serder = serdering.SerderKERI(raw=msg) atc = msg[serder.size:] sys.stdout.write(serder.raw.decode("utf-8")) sys.stdout.write(atc.decode("utf-8")) @@ -110,7 +110,7 @@ def outputEnds(self, pre): if f is not None: f.write(msg.decode("utf-8")) else: - serder = coring.Serder(raw=msg) + serder = serdering.SerderKERI(raw=msg) atc = msg[serder.size:] sys.stdout.write(serder.raw.decode("utf-8")) sys.stdout.write(atc.decode("utf-8")) diff --git a/src/keri/app/cli/commands/incept.py b/src/keri/app/cli/commands/incept.py index aaec81b32..fbc59560a 100644 --- a/src/keri/app/cli/commands/incept.py +++ b/src/keri/app/cli/commands/incept.py @@ -11,17 +11,20 @@ from keri.app import habbing, agenting, indirecting, configing, delegating, forwarding from keri.app.cli.common import existing, incepting, config +from keri.core import coring logger = help.ogler.getLogger() parser = argparse.ArgumentParser(description='Initialize a prefix') -parser.set_defaults(handler=lambda args: handler(args), - transferable=True) +parser.set_defaults(handler=lambda args: handler(args)) parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True) parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore', required=False, default="") parser.add_argument('--alias', '-a', help='human readable alias for the new identifier prefix', required=True) parser.add_argument("--config", "-c", help="directory override for configuration data") +parser.add_argument("--receipt-endpoint", help="Attempt to connect to witness receipt endpoint for witness receipts.", + dest="endpoint", action='store_true') +parser.add_argument("--proxy", help="alias for delegation communication proxy", default="") parser.add_argument('--file', '-f', help='Filename to use to create the identifier', default="", required=False) @@ -38,12 +41,12 @@ class InceptOptions: """ Options loaded from file parameter. """ - transferable: bool - wits: list - icount: int - isith: int | str | list - ncount: int - nsith: int | str | list = '0' + transferable: bool | None + wits: list | None + icount: int | None + isith: int | str | list | None + ncount: int | None + nsith: int | str | list | None = '0' toad: int = 0 delpre: str = None estOnly: bool = False @@ -63,10 +66,13 @@ def handler(args): bran = args.bran alias = args.alias config_dir = args.config + endpoint = args.endpoint + proxy = args.proxy kwa = mergeArgsWithFile(args).__dict__ - icpDoer = InceptDoer(name=name, base=base, alias=alias, bran=bran, config=config_dir, **kwa) + icpDoer = InceptDoer(name=name, base=base, alias=alias, bran=bran, endpoint=endpoint, proxy=proxy, + cnfg=config_dir, **kwa) doers = [icpDoer] return doers @@ -92,8 +98,7 @@ def mergeArgsWithFile(args): incept_opts = config.loadFileOptions(args.file, InceptOptions) if args.file != '' else emptyOptions() - if args.transferable is not None: - incept_opts.transferable = args.transferable + incept_opts.transferable = True if args.transferable else incept_opts.transferable if len(args.wits) > 0: incept_opts.wits = args.wits if args.icount is not None: @@ -120,23 +125,23 @@ class InceptDoer(doing.DoDoer): """ DoDoer for creating a new identifier prefix and Hab with an alias. """ - def __init__(self, name, base, alias, bran, config=None, **kwa): + def __init__(self, name, base, alias, bran, endpoint, proxy=None, cnfg=None, **kwa): cf = None if config is not None: cf = configing.Configer(name=name, base="", - headDirPath=config, + headDirPath=cnfg, temp=False, reopen=True, clear=False) - + self.endpoint = endpoint + self.proxy = proxy hby = existing.setupHby(name=name, base=base, bran=bran, cf=cf) self.hbyDoer = habbing.HaberyDoer(habery=hby) # setup doer - self.swain = delegating.Boatswain(hby=hby) - self.postman = forwarding.Postman(hby=hby) + self.swain = delegating.Sealer(hby=hby) + self.postman = forwarding.Poster(hby=hby) self.mbx = indirecting.MailboxDirector(hby=hby, topics=['/receipt', "/replay", "/reply"]) - self.witDoer = None doers = [self.hbyDoer, self.postman, self.mbx, self.swain, doing.doify(self.inceptDo)] self.inits = kwa @@ -159,20 +164,24 @@ def inceptDo(self, tymth, tock=0.0): _ = (yield self.tock) hab = self.hby.makeHab(name=self.alias, **self.inits) - self.witDoer = agenting.WitnessReceiptor(hby=self.hby) - self.extend([self.witDoer]) + witDoer = agenting.WitnessReceiptor(hby=self.hby) + receiptor = agenting.Receiptor(hby=self.hby) + self.extend([witDoer, receiptor]) if hab.kever.delegator: - self.swain.msgs.append(dict(alias=self.alias, pre=hab.pre, sn=0)) + self.swain.delegation(pre=hab.pre, sn=0, proxy=self.hby.habByName(self.proxy)) print("Waiting for delegation approval...") - while not self.swain.cues: + while not self.swain.complete(hab.kever.prefixer, coring.Seqner(sn=hab.kever.sn)): yield self.tock - if hab.kever.wits: + elif hab.kever.wits: print("Waiting for witness receipts...") - self.witDoer.msgs.append(dict(pre=hab.pre)) - while not self.witDoer.cues: - _ = yield self.tock + if self.endpoint: + yield from receiptor.receipt(hab.pre, sn=0) + else: + witDoer.msgs.append(dict(pre=hab.pre)) + while not witDoer.cues: + _ = yield self.tock if hab.kever.delegator: yield from self.postman.sendEvent(hab=hab, fn=hab.kever.sn) @@ -182,7 +191,7 @@ def inceptDo(self, tymth, tock=0.0): print(f'\tPublic key {idx + 1}: {verfer.qb64}') print() - toRemove = [self.hbyDoer, self.witDoer, self.mbx, self.swain, self.postman] + toRemove = [self.hbyDoer, witDoer, self.mbx, self.swain, self.postman, receiptor] self.remove(toRemove) return diff --git a/src/keri/app/cli/commands/interact.py b/src/keri/app/cli/commands/interact.py index 004b4458a..e768d2994 100644 --- a/src/keri/app/cli/commands/interact.py +++ b/src/keri/app/cli/commands/interact.py @@ -21,7 +21,7 @@ parser.add_argument('--alias', '-a', help='human readable alias for the new identifier prefix', required=True) parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)', dest="bran", default=None) # passcode => bran -parser.add_argument('--data', '-d', help='Anchor data, \'@\' allowed', default=[], action="store", required=False) +parser.add_argument('--data', '-d', help='Anchor data, \'@\' allowed', default=None, action="store", required=False) def interact(args): @@ -78,7 +78,7 @@ def __init__(self, name, base, bran, alias, data: list = None): self.hby = existing.setupHby(name=name, base=base, bran=bran) self.hbyDoer = habbing.HaberyDoer(habery=self.hby) # setup doer - self.mbx = indirecting.MailboxDirector(hby=self.hby, topics="/receipt") + self.mbx = indirecting.MailboxDirector(hby=self.hby, topics=['/receipt', "/replay", "/reply"]) doers = [self.hbyDoer, self.mbx, doing.doify(self.interactDo)] super(InteractDoer, self).__init__(doers=doers) diff --git a/src/keri/app/cli/commands/agent/__init__.py b/src/keri/app/cli/commands/ipex/__init__.py similarity index 100% rename from src/keri/app/cli/commands/agent/__init__.py rename to src/keri/app/cli/commands/ipex/__init__.py diff --git a/src/keri/app/cli/commands/ipex/admit.py b/src/keri/app/cli/commands/ipex/admit.py new file mode 100644 index 000000000..f45918aa2 --- /dev/null +++ b/src/keri/app/cli/commands/ipex/admit.py @@ -0,0 +1,155 @@ +# -*- encoding: utf-8 -*- +""" +keri.app.cli.commands module + +""" +import argparse + +from hio.base import doing + +from keri.app import connecting, habbing, grouping, indirecting, agenting, forwarding +from keri.app.cli.common import existing +from keri.app.notifying import Notifier +from keri.core import parsing, coring, eventing +from keri.peer import exchanging +from keri.vc import protocoling +from keri.vdr import credentialing, verifying +from keri.vdr import eventing as teventing + + +parser = argparse.ArgumentParser(description='Accept a credential being issued or presented in response to an IPEX ' + 'grant') +parser.set_defaults(handler=lambda args: handler(args)) +parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True) +parser.add_argument('--alias', '-a', help='human readable alias for the identifier to whom the credential was issued', + required=True) +parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore', + required=False, default="") +parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)', + dest="bran", default=None) # passcode => bran + +parser.add_argument("--said", "-s", help="SAID of the exn grant message to admit", required=True) +parser.add_argument("--message", "-m", help="optional human readable message to " + "send to recipient", required=False, default="") + + +def handler(args): + ed = AdmitDoer(name=args.name, + alias=args.alias, + base=args.base, + bran=args.bran, + said=args.said, + message=args.message) + return [ed] + + +class AdmitDoer(doing.DoDoer): + + def __init__(self, name, alias, base, bran, said, message): + self.said = said + self.message = message + self.hby = existing.setupHby(name=name, base=base, bran=bran) + self.hab = self.hby.habByName(alias) + self.rgy = credentialing.Regery(hby=self.hby, name=name, base=base) + self.org = connecting.Organizer(hby=self.hby) + self.witq = agenting.WitnessInquisitor(hby=self.hby) + + self.kvy = eventing.Kevery(db=self.hby.db) + self.tvy = teventing.Tevery(db=self.hby.db, reger=self.rgy.reger) + self.vry = verifying.Verifier(hby=self.hby, reger=self.rgy.reger) + + self.psr = parsing.Parser(kvy=self.kvy, tvy=self.tvy, vry=self.vry) + + notifier = Notifier(self.hby) + mux = grouping.Multiplexor(self.hby, notifier=notifier) + + self.exc = exchanging.Exchanger(hby=self.hby, handlers=[]) + grouping.loadHandlers(self.exc, mux) + protocoling.loadHandlers(self.hby, exc=self.exc, notifier=notifier) + + mbx = indirecting.MailboxDirector(hby=self.hby, + topics=["/receipt", "/multisig", "/replay", "/credential"], + exc=self.exc, kvy=self.kvy, tvy=self.tvy, verifier=self.vry) + + self.toRemove = [mbx, self.witq] + super(AdmitDoer, self).__init__(doers=self.toRemove + [doing.doify(self.admitDo)]) + + def admitDo(self, tymth, tock=0.0): + """ Admit credential by accepting into database and sending /ipex/admit exn message + + Parameters: + tymth (function): injected function wrapper closure returned by .tymen() of + Tymist instance. Calling tymth() returns associated Tymist .tyme. + tock (float): injected initial tock value + + Returns: doifiable Doist compatible generator method + + """ + # enter context + self.wind(tymth) + self.tock = tock + _ = (yield self.tock) + + grant, pathed = exchanging.cloneMessage(self.hby, self.said) + if grant is None: + raise ValueError(f"exn message said={self.said} not found") + + route = grant.ked['r'] + if route != "/ipex/grant": + raise ValueError(f"exn said={self.said} is not a grant message, route={route}") + + embeds = grant.ked['e'] + acdc = embeds["acdc"] + issr = acdc['i'] + + # Lets get the latest KEL and Registry if needed + self.witq.query(src=self.hab.pre, pre=issr) + if "ri" in acdc: + self.witq.telquery(src=self.hab.pre, wits=self.hab.kevers[issr].wits, ri=acdc["ri"], i=acdc["d"]) + + for label in ("anc", "iss", "acdc"): + ked = embeds[label] + sadder = coring.Sadder(ked=ked) + ims = bytearray(sadder.raw) + pathed[label] + self.psr.parseOne(ims=ims) + + said = acdc["d"] + while not self.rgy.reger.saved.get(keys=said): + yield self.tock + + recp = grant.ked['i'] + exn, atc = protocoling.ipexAdmitExn(hab=self.hab, message=self.message, grant=grant) + msg = bytearray(exn.raw) + msg.extend(atc) + + parsing.Parser().parseOne(ims=bytes(msg), exc=self.exc) + + if isinstance(self.hab, habbing.GroupHab): + wexn, watc = grouping.multisigExn(self.hab, exn=msg) + + smids = self.hab.db.signingMembers(pre=self.hab.pre) + smids.remove(self.hab.mhab.pre) + + for recp in smids: # this goes to other participants only as a signaling mechanism + postman = forwarding.StreamPoster(hby=self.hby, hab=self.hab.mhab, recp=recp, topic="multisig") + postman.send(serder=wexn, + attachment=watc) + doer = doing.DoDoer(doers=postman.deliver()) + self.extend([doer]) + + while not self.exc.complete(said=wexn.said): + yield self.tock + + if self.exc.lead(self.hab, said=exn.said): + print(f"Sending admit message to {recp}") + postman = forwarding.StreamPoster(hby=self.hby, hab=self.hab, recp=recp, topic="credential") + postman.send(serder=exn, + attachment=atc) + + doer = doing.DoDoer(doers=postman.deliver()) + self.extend([doer]) + + while not doer.done: + yield self.tock + + self.remove(self.toRemove) diff --git a/src/keri/app/cli/commands/ipex/agree.py b/src/keri/app/cli/commands/ipex/agree.py new file mode 100644 index 000000000..5e04b49ee --- /dev/null +++ b/src/keri/app/cli/commands/ipex/agree.py @@ -0,0 +1,26 @@ +# -*- encoding: utf-8 -*- +""" +keri.app.cli.commands module + +""" +import argparse + +from hio.base import doing + +from keri.core import coring + +parser = argparse.ArgumentParser(description='Reply to IPEX offer message acknowledged willingness to accept offered ' + 'credential') +parser.set_defaults(handler=lambda args: handler(args)) + + +def handler(_): + return [doing.doify(nonce)] + + +def nonce(tymth, tock=0.0): + """ nonce + """ + _ = (yield tock) + + print(coring.randomNonce()) diff --git a/src/keri/app/cli/commands/ipex/apply.py b/src/keri/app/cli/commands/ipex/apply.py new file mode 100644 index 000000000..b5d3d8ef9 --- /dev/null +++ b/src/keri/app/cli/commands/ipex/apply.py @@ -0,0 +1,25 @@ +# -*- encoding: utf-8 -*- +""" +keri.app.cli.commands module + +""" +import argparse + +from hio.base import doing + +from keri.core import coring + +parser = argparse.ArgumentParser(description='Request a credential from another party by initiating an IPEX exchange') +parser.set_defaults(handler=lambda args: handler(args)) + + +def handler(_): + return [doing.doify(nonce)] + + +def nonce(tymth, tock=0.0): + """ nonce + """ + _ = (yield tock) + + print(coring.randomNonce()) diff --git a/src/keri/app/cli/commands/ipex/grant.py b/src/keri/app/cli/commands/ipex/grant.py new file mode 100644 index 000000000..ba9dcb232 --- /dev/null +++ b/src/keri/app/cli/commands/ipex/grant.py @@ -0,0 +1,167 @@ +# -*- encoding: utf-8 -*- +""" +keri.app.cli.commands module + +""" +import argparse + +from hio.base import doing + +from keri.app import forwarding, connecting, habbing, grouping, indirecting, signing +from keri.app.cli.common import existing +from keri.app.notifying import Notifier +from keri.core import coring, parsing, serdering +from keri.peer import exchanging +from keri.vc import protocoling +from keri.vdr import credentialing + +parser = argparse.ArgumentParser(description='Reply to IPEX agree message or initiate an IPEX exchange with a ' + 'credential issuance or presentation') +parser.set_defaults(handler=lambda args: handler(args)) +parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True) +parser.add_argument('--alias', '-a', help='human readable alias for the identifier to whom the credential was issued', + required=True) +parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore', + required=False, default="") +parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)', + dest="bran", default=None) # passcode => bran + +parser.add_argument("--recipient", "-r", help="alias or qb64 identifier prefix of the self.recp of " + "the credential", required=True) +parser.add_argument("--said", "-s", help="SAID of the credential to grant", required=True) +parser.add_argument("--message", "-m", help="optional human readable message to " + "send to recipient", required=False, default="") +parser.add_argument("--time", help="timestamp for the revocation", required=False, default=None) + + +def handler(args): + ed = GrantDoer(name=args.name, + alias=args.alias, + base=args.base, + bran=args.bran, + said=args.said, + recp=args.recipient, + message=args.message, + timestamp=args.time) + return [ed] + + +class GrantDoer(doing.DoDoer): + + def __init__(self, name, alias, base, bran, said, recp, message, timestamp): + self.said = said + self.recp = recp + self.message = message + self.timestamp = timestamp + self.hby = existing.setupHby(name=name, base=base, bran=bran) + self.hab = self.hby.habByName(alias) + self.rgy = credentialing.Regery(hby=self.hby, name=name, base=base) + self.org = connecting.Organizer(hby=self.hby) + notifier = Notifier(self.hby) + mux = grouping.Multiplexor(self.hby, notifier=notifier) + + self.exc = exchanging.Exchanger(hby=self.hby, handlers=[]) + grouping.loadHandlers(self.exc, mux) + protocoling.loadHandlers(self.hby, exc=self.exc, notifier=notifier) + + mbx = indirecting.MailboxDirector(hby=self.hby, + topics=["/receipt", "/multisig", "/replay", "/credential"], + exc=self.exc) + + self.toRemove = [mbx] + super(GrantDoer, self).__init__(doers=self.toRemove + [doing.doify(self.grantDo)]) + + def grantDo(self, tymth, tock=0.0): + """ Grant credential by creating /ipex/grant exn message + + Parameters: + tymth (function): injected function wrapper closure returned by .tymen() of + Tymist instance. Calling tymth() returns associated Tymist .tyme. + tock (float): injected initial tock value + + Returns: doifiable Doist compatible generator method + + """ + # enter context + self.wind(tymth) + self.tock = tock + _ = (yield self.tock) + + creder, prefixer, seqner, saider = self.rgy.reger.cloneCred(said=self.said) + if creder is None: + raise ValueError(f"invalid credential SAID to grant={self.said}") + + acdc = signing.serialize(creder, prefixer, seqner, saider) + + if self.recp is None: + recp = creder.attrib['i'] if 'i' in creder.attrib else None + elif self.recp in self.hby.kevers: + recp = self.recp + else: + recp = self.org.find("alias", self.recp) + if len(recp) != 1: + raise ValueError(f"invalid recipient {self.recp}") + recp = recp[0]['id'] + + if recp is None: + raise ValueError("unable to find recipient") + + iss = self.rgy.reger.cloneTvtAt(creder.said) + + iserder = serdering.SerderKERI(raw=bytes(iss)) # coring.Serder(raw=bytes(iss)) + seqner = coring.Seqner(sn=iserder.sn) + + serder = self.hby.db.findAnchoringSealEvent(creder.sad['i'], + seal=dict(i=iserder.pre, s=seqner.snh, d=iserder.said)) + anc = self.hby.db.cloneEvtMsg(pre=serder.pre, fn=0, dig=serder.said) + + exn, atc = protocoling.ipexGrantExn(hab=self.hab, recp=recp, message=self.message, acdc=acdc, iss=iss, anc=anc, + dt=self.timestamp) + msg = bytearray(exn.raw) + msg.extend(atc) + + parsing.Parser().parseOne(ims=bytes(msg), exc=self.exc) + + sender = self.hab + if isinstance(self.hab, habbing.GroupHab): + sender = self.hab.mhab + wexn, watc = grouping.multisigExn(self.hab, exn=msg) + + smids = self.hab.db.signingMembers(pre=self.hab.pre) + smids.remove(self.hab.mhab.pre) + + for part in smids: # this goes to other participants + postman = forwarding.StreamPoster(hby=self.hby, hab=self.hab.mhab, recp=part, topic="multisig") + postman.send(serder=wexn, + attachment=watc) + doer = doing.DoDoer(doers=postman.deliver()) + self.extend([doer]) + + while not self.exc.complete(said=exn.said): + yield self.tock + + if self.exc.lead(self.hab, said=exn.said): + print(f"Sending message {exn.said} to {recp}") + postman = forwarding.StreamPoster(hby=self.hby, hab=sender, recp=recp, topic="credential") + + sources = self.rgy.reger.sources(self.hby.db, creder) + credentialing.sendArtifacts(self.hby, self.rgy.reger, postman, creder, recp) + for source, atc in sources: + credentialing.sendArtifacts(self.hby, self.rgy.reger, postman, source, recp) + postman.send(serder=source, attachment=atc) + + atc = exchanging.serializeMessage(self.hby, exn.said) + del atc[:exn.size] + postman.send(serder=exn, + attachment=atc) + + doer = doing.DoDoer(doers=postman.deliver()) + self.extend([doer]) + + while not doer.done: + yield self.tock + + print(f"... grant message sent") + self.remove([postman]) + + self.remove(self.toRemove) diff --git a/src/keri/app/cli/commands/ipex/list.py b/src/keri/app/cli/commands/ipex/list.py new file mode 100644 index 000000000..4f59d9816 --- /dev/null +++ b/src/keri/app/cli/commands/ipex/list.py @@ -0,0 +1,267 @@ +# -*- encoding: utf-8 -*- +""" +KERI +keri.kli.commands module + +""" + +import argparse +import datetime +import os +import sys + +from hio import help +from hio.base import doing + +from keri import kering +from keri.app import indirecting, notifying, connecting +from keri.app.cli.common import existing, terming +from keri.core import scheming +from keri.help import helping +from keri.peer import exchanging +from keri.vc import protocoling +from keri.vc.protocoling import Ipex +from keri.vdr import credentialing, verifying + +logger = help.ogler.getLogger() + +parser = argparse.ArgumentParser(description='List notifications related to IPEX protocol messages') +parser.set_defaults(handler=lambda args: listNotes(args), + transferable=True) +parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True) +parser.add_argument('--alias', '-a', help='human readable alias for the identifier to whom the credential was issued', + default=None) +parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore', + required=False, default="") +parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)', + dest="bran", default=None) # passcode => bran + +parser.add_argument("--verbose", "-V", help="print full JSON of all credentials", action="store_true") +parser.add_argument("--said", "-s", help="display only the SAID of found exn message, one per line.", + action="store_true") +parser.add_argument("--type", "-t", help="message type to list, options are (apply, offer, agree, grant, submit)") +parser.add_argument("--poll", "-P", help="Poll mailboxes for any IPEX messages", action="store_true") +parser.add_argument("--sent", help="Show messages sent by a local identifier, default is messages received.", + action="store_true") + + +def listNotes(args): + """ Command line list credential registries handler + + """ + ld = ListDoer(name=args.name, + alias=args.alias, + base=args.base, + bran=args.bran, + poll=args.poll, + verbose=args.verbose, + said=args.said, + typ=args.type, + sent=args.sent) + return [ld] + + +class ListDoer(doing.DoDoer): + + def __init__(self, name, alias, base, bran, poll=False, verbose=False, said=False, typ=None, sent=False): + self.poll = poll + self.type = typ + self.verbose = verbose + self.said = said + self.sent = sent + self.notes = [] + + self.hby = existing.setupHby(name=name, base=base, bran=bran) + if alias is None: + alias = existing.aliasInput(self.hby) + + self.hab = self.hby.habByName(alias) + self.notifier = notifying.Notifier(hby=self.hby) + self.org = connecting.Organizer(hby=self.hby) + self.rgy = credentialing.Regery(hby=self.hby, name=name, base=base) + self.vry = verifying.Verifier(hby=self.hby, reger=self.rgy.reger) + self.exc = exchanging.Exchanger(hby=self.hby, handlers=[]) + protocoling.loadHandlers(self.hby, self.exc, self.notifier) + self.mbx = indirecting.MailboxDirector(hby=self.hby, topics=['/replay', '/reply', '/credential'], + exc=self.exc, verifier=self.vry) + + self.doers = [self.mbx] + + super(ListDoer, self).__init__(doers=self.doers + [doing.doify(self.listDo)]) + + def listDo(self, tymth, tock=0.0): + """ Check for any credential messages in mailboxes and list all held credentials + + Parameters: + tymth (function): injected function wrapper closure returned by .tymen() of + Tymist instance. Calling tymth() returns associated Tymist .tyme. + tock (float): injected initial tock value + + Returns: doifiable Doist compatible generator method + + """ + # enter context + self.wind(tymth) + self.tock = tock + _ = (yield self.tock) + + if self.poll: + end = helping.nowUTC() + datetime.timedelta(seconds=5) + if not self.said: + sys.stdout.write(f"Checking mailboxes for any ipex messages") + sys.stdout.flush() + while helping.nowUTC() < end: + if not self.said: + sys.stdout.write(".") + sys.stdout.flush() + if "/credential" in self.mbx.times: + end = self.mbx.times['/credential'] + datetime.timedelta(seconds=5) + yield 1.0 + if not self.said: + print() + + if not self.said: + direction = "Sent" if self.sent else "Received" + print(f"\n{direction} IPEX Messages:") + + self.notes = [] + + q = "/exn/ipex" + if self.type is not None: + q = f"/exn/ipex/{self.type}" + + for keys, notice in self.notifier.noter.notes.getItemIter(): + if notice.pad['a']['r'].startswith(q): + self.notes.append(notice) + + for note in self.notes: + attrs = note.attrs + said = attrs['d'] + exn, pathed = exchanging.cloneMessage(self.hby, said) + if exn is None: + continue + + sender = exn.ked['i'] + if (sender in self.hby.habs and not self.sent) or (sender not in self.hby.habs and self.sent): + continue + + if self.said: + print(exn.said) + else: + print() + match exn.ked['r']: + case "/ipex/agree": + self.agree(note, exn, attrs) + case "/ipex/apply": + self.apply(note, exn, attrs) + case "/ipex/offer": + self.offer(note, exn, attrs) + case "/ipex/grant": + self.grant(exn) + case "/ipex/admit": + self.admit(note, exn, attrs) + case "/ipex/spurn": + self.spurn(note, exn, attrs) + case _: + print("Unknown Type") + print() + + self.remove(self.doers) + + def grant(self, exn): + print(f"GRANT - SAID: {exn.said}") + sad = exn.ked['e']["acdc"] + iss = exn.ked['e']['iss'] + + schema = sad['s'] + scraw = self.mbx.verifier.resolver.resolve(schema) + if not scraw: + raise kering.ConfigurationError("Credential schema {} not found".format(schema)) + + schemer = scheming.Schemer(raw=scraw) + response = self.hby.db.erpy.get(keys=(exn.said,)) + + if response is None: + accepted = f"No {terming.Colors.FAIL}{terming.Symbols.FAILED}{terming.Colors.ENDC}" + responseType = None + else: + accepted = f"Yes {terming.Colors.OKGREEN}{terming.Symbols.CHECKMARK}{terming.Colors.ENDC}" + rexn, _ = exchanging.cloneMessage(self.hby, response.qb64) + responseType = humanResponse(rexn.ked['r']) + + print(f"Credential {sad['d']}:") + print(f" Type: {schemer.sed['title']}") + print( + f" Status: Issued {terming.Colors.OKGREEN}{terming.Symbols.CHECKMARK}{terming.Colors.ENDC}") + print(f" Issued by {sad['i']}") + print(f" Issued on {iss['dt']}") + print(f" Already responded? {accepted}") + if response is not None: + print(f" Response: {responseType} ({response.qb64})") + + def apply(self, note, exn, pathed): + pass + + def offer(self, note, exn, pathed): + pass + + def agree(self, note, exn, pathed): + pass + + def spurn(self, note, exn, pathed): + print(f"SPURN - SAID: {exn.said}") + dig = exn.ked['p'] + spurned, _ = exchanging.cloneMessage(self.hby, said=dig) + + sroute = spurned.ked['r'] + sverb = os.path.basename(os.path.normpath(sroute)) + + print(f"Spurned message type: {sverb.capitalize()}") + print(f"Spurned message SAID: {spurned.said}") + + if sverb in (Ipex.grant, Ipex.offer): + sad = spurned.ked['e']["acdc"] + + schema = sad['s'] + scraw = self.mbx.verifier.resolver.resolve(schema) + if not scraw: + raise kering.ConfigurationError("Credential schema {} not found".format(schema)) + + schemer = scheming.Schemer(raw=scraw) + print(f"Spurned Credential {sad['d']}:") + print(f" Type: {schemer.sed['title']}") + + def admit(self, note, exn, pathed): + print(f"ADMIT - SAID: {exn.said}") + dig = exn.ked['p'] + + admitted, _ = exchanging.cloneMessage(self.hby, said=dig) + sad = admitted.ked['e']["acdc"] + + schema = sad['s'] + scraw = self.mbx.verifier.resolver.resolve(schema) + if not scraw: + raise kering.ConfigurationError("Credential schema {} not found".format(schema)) + + schemer = scheming.Schemer(raw=scraw) + + print(f"Admitted message SAID: {admitted.said}") + + print(f"Credential {sad['d']}:") + print(f" Type: {schemer.sed['title']}") + print(f" Status: Accepted {terming.Colors.OKGREEN}{terming.Symbols.CHECKMARK}{terming.Colors.ENDC}") + + def deleteNote(self, keys): + yn = input(f"\n Delete the notification [Y|n]?") + if yn in ('', 'y', 'Y'): + self.notifier.noter.notes.rem(keys=keys) + + +def humanResponse(route): + verb = os.path.basename(os.path.normpath(route)) + match verb: + case "admit": + return f"{terming.Colors.OKGREEN}Admit{terming.Colors.ENDC}" + case "spurn": + return f"{terming.Colors.FAIL}Spurn{terming.Colors.ENDC}" + return verb.capitalize() diff --git a/src/keri/app/cli/commands/ipex/offer.py b/src/keri/app/cli/commands/ipex/offer.py new file mode 100644 index 000000000..a297b234f --- /dev/null +++ b/src/keri/app/cli/commands/ipex/offer.py @@ -0,0 +1,26 @@ +# -*- encoding: utf-8 -*- +""" +keri.app.cli.commands module + +""" +import argparse + +from hio.base import doing + +from keri.core import coring + +parser = argparse.ArgumentParser(description='Reply to IPEX apply message or initiate an IPEX exchange with an offer' + ' for a credential with certain characteristics') +parser.set_defaults(handler=lambda args: handler(args)) + + +def handler(_): + return [doing.doify(nonce)] + + +def nonce(tymth, tock=0.0): + """ nonce + """ + _ = (yield tock) + + print(coring.randomNonce()) diff --git a/src/keri/app/cli/commands/ipex/spurn.py b/src/keri/app/cli/commands/ipex/spurn.py new file mode 100644 index 000000000..3d4ecebe8 --- /dev/null +++ b/src/keri/app/cli/commands/ipex/spurn.py @@ -0,0 +1,142 @@ +# -*- encoding: utf-8 -*- +""" +keri.app.cli.commands module + +""" +import argparse +import os + +from hio.base import doing + +from keri.app import forwarding, connecting, habbing, grouping, indirecting +from keri.app.cli.common import existing +from keri.app.notifying import Notifier +from keri.core import parsing, coring, eventing +from keri.peer import exchanging +from keri.vc import protocoling +from keri.vc.protocoling import Ipex +from keri.vdr import credentialing, verifying +from keri.vdr import eventing as teventing + + +parser = argparse.ArgumentParser(description='Reject an IPEX apply, offer, agree or grant message') +parser.set_defaults(handler=lambda args: handler(args)) +parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True) +parser.add_argument('--alias', '-a', help='human readable alias for the identifier to whom the credential was issued', + required=True) +parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore', + required=False, default="") +parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)', + dest="bran", default=None) # passcode => bran + +parser.add_argument("--said", "-s", help="SAID of the exn IPEX message to spurn", required=True) +parser.add_argument("--message", "-m", help="optional human readable message to " + "send to recipient", required=False, default="") + + +def handler(args): + ed = SpurnDoer(name=args.name, + alias=args.alias, + base=args.base, + bran=args.bran, + said=args.said, + message=args.message) + return [ed] + + +class SpurnDoer(doing.DoDoer): + + def __init__(self, name, alias, base, bran, said, message): + self.said = said + self.message = message + self.hby = existing.setupHby(name=name, base=base, bran=bran) + self.hab = self.hby.habByName(alias) + self.rgy = credentialing.Regery(hby=self.hby, name=name, base=base) + self.org = connecting.Organizer(hby=self.hby) + + kvy = eventing.Kevery(db=self.hby.db) + tvy = teventing.Tevery(db=self.hby.db, reger=self.rgy.reger) + vry = verifying.Verifier(hby=self.hby, reger=self.rgy.reger) + + self.psr = parsing.Parser(kvy=kvy, tvy=tvy, vry=vry) + + notifier = Notifier(self.hby) + mux = grouping.Multiplexor(self.hby, notifier=notifier) + + self.exc = exchanging.Exchanger(hby=self.hby, handlers=[]) + grouping.loadHandlers(self.exc, mux) + protocoling.loadHandlers(self.hby, exc=self.exc, notifier=notifier) + + mbx = indirecting.MailboxDirector(hby=self.hby, + topics=["/receipt", "/multisig", "/replay", "/credential"], + exc=self.exc) + + self.toRemove = [mbx] + super(SpurnDoer, self).__init__(doers=self.toRemove + [doing.doify(self.spurnDo)]) + + def spurnDo(self, tymth, tock=0.0): + """ Sprun any IPEX message + + Parameters: + tymth (function): injected function wrapper closure returned by .tymen() of + Tymist instance. Calling tymth() returns associated Tymist .tyme. + tock (float): injected initial tock value + + Returns: doifiable Doist compatible generator method + + """ + # enter context + self.wind(tymth) + self.tock = tock + _ = (yield self.tock) + + ipex, pathed = exchanging.cloneMessage(self.hby, self.said) + if ipex is None: + raise ValueError(f"exn message said={self.said} not found") + + route = ipex.ked['r'] + verb = os.path.basename(os.path.normpath(route)) + + if verb not in (Ipex.apply, Ipex.offer, Ipex.agree, Ipex.grant): + raise ValueError(f"exn said={self.said} is not a spurnable message, route={route}") + + recp = ipex.ked['i'] + exn, atc = protocoling.ipexSpurnExn(hab=self.hab, message=self.message, spurned=ipex) + msg = bytearray(exn.raw) + msg.extend(atc) + + parsing.Parser().parseOne(ims=bytes(msg), exc=self.exc) + + spurn, _ = exchanging.cloneMessage(self.hby, exn.said) + if spurn is None: + raise ValueError(f"Invalid spurn evt={exn.ked}, not saved") + + if isinstance(self.hab, habbing.GroupHab): + wexn, watc = grouping.multisigExn(self.hab, exn=msg) + + smids = self.hab.db.signingMembers(pre=self.hab.pre) + smids.remove(self.hab.mhab.pre) + + for recp in smids: # this goes to other participants only as a signaling mechanism + postman = forwarding.StreamPoster(hby=self.hby, hab=self.hab.mhab, recp=recp, topic="multisig") + postman.send(serder=wexn, + attachment=watc) + doer = doing.DoDoer(doers=postman.deliver()) + self.extend([doer]) + + while not self.exc.complete(said=wexn.said): + yield self.tock + + if self.exc.lead(self.hab, said=exn.said): + print("Sending spurn message...") + postman = forwarding.StreamPoster(hby=self.hby, hab=self.hab, recp=recp, topic="credential") + postman.send(serder=exn, + attachment=atc) + + doer = doing.DoDoer(doers=postman.deliver()) + self.extend([doer]) + + while not doer.done: + yield self.tock + + self.remove(self.toRemove) diff --git a/src/keri/app/cli/commands/kevers.py b/src/keri/app/cli/commands/kevers.py index aac0503e6..3eaf42aca 100644 --- a/src/keri/app/cli/commands/kevers.py +++ b/src/keri/app/cli/commands/kevers.py @@ -13,7 +13,7 @@ from keri.app import indirecting from keri.app.cli.common import displaying, existing -from keri.core import coring +from keri.core import coring, serdering from keri.help import helping logger = help.ogler.getLogger() @@ -84,7 +84,7 @@ def kevers(self, tymth, tock=0.0, **opts): cloner = self.hby.db.clonePreIter(pre=self.prefix, fn=0) # create iterator at 0 for msg in cloner: - srdr = coring.Serder(raw=msg) + srdr = serdering.SerderKERI(raw=msg) print(srdr.pretty()) print() diff --git a/src/keri/app/cli/commands/local/watch.py b/src/keri/app/cli/commands/local/watch.py index b94a06c68..6529f80e9 100644 --- a/src/keri/app/cli/commands/local/watch.py +++ b/src/keri/app/cli/commands/local/watch.py @@ -13,6 +13,7 @@ from hio.base import doing from keri.app import agenting, indirecting, habbing, forwarding from keri.app.cli.common import existing, terming +from keri.app.habbing import GroupHab from keri.core import coring logger = help.ogler.getLogger() @@ -57,8 +58,8 @@ def __init__(self, name, base, bran, **kwa): self.hbyDoer = habbing.HaberyDoer(habery=self.hby) # setup doer self.cues = help.decking.Deck() - self.mbd = indirecting.MailboxDirector(hby=self.hby, topics=["/replay", "/receipt", "reply"]) - self.postman = forwarding.Postman(hby=self.hby) + self.mbd = indirecting.MailboxDirector(hby=self.hby, topics=["/replay", "/receipt", "/reply"]) + self.postman = forwarding.Poster(hby=self.hby) doers.extend([self.hbyDoer, self.mbd, self.postman, doing.doify(self.cueDo)]) self.toRemove = list(doers) @@ -95,7 +96,7 @@ def watchDo(self, tymth, tock=0.0, **opts): hab.db.ksns.rem((saider.qb64,)) hab.db.ksns.rem((saider.qb64,)) - witer = agenting.witnesser(hab, wit) + witer = agenting.messenger(hab, wit) self.extend([witer]) msg = hab.query(pre=hab.pre, src=wit, route="ksn") @@ -148,7 +149,7 @@ def watchDo(self, tymth, tock=0.0, **opts): elif len(ahds) > 0: # Only group habs can be behind their witnesses - if not hab.group: + if not isinstance(hab, GroupHab): print("ERROR: Single sig AID behind witnesses, aborting for this AID") continue @@ -218,11 +219,11 @@ def diffState(wit, preksn, witksn): witstate = WitnessState() witstate.wit = wit - mysn = preksn.sner.num - mydig = preksn.ked['d'] - witstate.sn = coring.Number(num=witksn.ked["f"]).num - witstate.dig = witksn.ked['d'] - + mysn = int(preksn.s, 16) + mydig = preksn.d + witstate.sn = int(witksn.f, 16) + witstate.dig = witksn.d + # At the same sequence number, check the DIGs if mysn == witstate.sn: if mydig == witstate.dig: diff --git a/src/keri/app/cli/commands/mailbox/debug.py b/src/keri/app/cli/commands/mailbox/debug.py index 1d6f33e20..de7f425ba 100644 --- a/src/keri/app/cli/commands/mailbox/debug.py +++ b/src/keri/app/cli/commands/mailbox/debug.py @@ -12,6 +12,7 @@ from keri import kering from keri.app import agenting, indirecting, habbing, httping from keri.app.cli.common import displaying, existing +from keri.app.habbing import GroupHab from keri.core import coring from keri.kering import ConfigurationError @@ -99,12 +100,12 @@ def readDo(self, tymth, tock=0.0): print() q = dict(pre=hab.pre, topics=topics) - if hab.group: + if isinstance(hab, GroupHab): msg = hab.mhab.query(pre=hab.pre, src=self.witness, route="mbx", query=q) else: msg = hab.query(pre=hab.pre, src=self.witness, route="mbx", query=q) - httping.createCESRRequest(msg, client) + httping.createCESRRequest(msg, client, dest=self.witness) while client.requests: yield self.tock diff --git a/src/keri/app/cli/commands/multisig/continue.py b/src/keri/app/cli/commands/multisig/continue.py index 2741f3a0d..2a2508f97 100644 --- a/src/keri/app/cli/commands/multisig/continue.py +++ b/src/keri/app/cli/commands/multisig/continue.py @@ -11,6 +11,7 @@ from keri.app import indirecting, grouping, agenting from keri.app.cli.common import existing, displaying +from keri.app.habbing import GroupHab logger = help.ogler.getLogger() @@ -62,7 +63,7 @@ def recover(self, tymth, tock=0.0, **opts): raise ValueError(f"no escrowed events for {self.alias} ({hab.pre})") (seqner, saider) = esc[0] - src = hab.mhab.pre if hab.group else hab.pre + src = hab.mhab.pre if isinstance(hab, GroupHab) else hab.pre anchor = dict(i=hab.pre, s=seqner.snh, d=saider.qb64) self.witq.query(src=src, pre=hab.kever.delegator, anchor=anchor) diff --git a/src/keri/app/cli/commands/multisig/incept.py b/src/keri/app/cli/commands/multisig/incept.py index 4a12f84b8..1c589ab53 100644 --- a/src/keri/app/cli/commands/multisig/incept.py +++ b/src/keri/app/cli/commands/multisig/incept.py @@ -16,7 +16,9 @@ from keri import help, kering from keri.app import indirecting, grouping, habbing, forwarding from keri.app.cli.common import existing, displaying -from keri.core import coring +from keri.app.notifying import Notifier +from keri.core import coring, serdering +from keri.peer import exchanging logger = help.ogler.getLogger() @@ -87,9 +89,14 @@ def __init__(self, name, base, alias, bran, group, wait, **kwa): if "delpre" in self.inits: topics.append('/delegate') - self.mbx = indirecting.MailboxDirector(hby=self.hby, topics=topics) + notifier = Notifier(self.hby) + mux = grouping.Multiplexor(self.hby, notifier=notifier) + exc = exchanging.Exchanger(hby=self.hby, handlers=[]) + grouping.loadHandlers(exc, mux) + + self.mbx = indirecting.MailboxDirector(hby=self.hby, topics=topics, exc=exc) self.counselor = grouping.Counselor(hby=self.hby) - self.postman = forwarding.Postman(hby=self.hby) + self.postman = forwarding.Poster(hby=self.hby) doers = [self.hbyDoer, self.mbx, self.counselor, self.postman] self.toRemove = list(doers) @@ -131,14 +138,15 @@ def inceptDo(self, tymth, tock=0.0): ghab = self.hby.makeGroupHab(group=self.group, mhab=hab, smids=smids, rmids=rmids, **self.inits) - evt = grouping.getEscrowedEvent(db=self.hby.db, pre=ghab.pre, sn=0) - serder = coring.Serder(raw=evt) + icp = ghab.makeOwnInception(allowPartiallySigned=True) # Create a notification EXN message to send to the other agents exn, ims = grouping.multisigInceptExn(ghab.mhab, - aids=ghab.smids, - ked=serder.ked) + smids=ghab.smids, + rmids=ghab.rmids, + icp=icp) others = list(oset(smids + (rmids or []))) + others.remove(ghab.mhab.pre) for recpt in others: # this goes to other participants only as a signaling mechanism @@ -153,7 +161,7 @@ def inceptDo(self, tymth, tock=0.0): seqner = coring.Seqner(sn=0) saider = coring.Saider(qb64=prefixer.qb64) self.counselor.start(prefixer=prefixer, seqner=seqner, saider=saider, - mid=hab.pre, smids=smids, rmids=rmids) + ghab=ghab) else: prefixer = coring.Prefixer(ghab.pre) diff --git a/src/keri/app/cli/commands/multisig/interact.py b/src/keri/app/cli/commands/multisig/interact.py index a909a06fe..77b98ff73 100644 --- a/src/keri/app/cli/commands/multisig/interact.py +++ b/src/keri/app/cli/commands/multisig/interact.py @@ -13,7 +13,9 @@ from keri import kering from keri.app import grouping, indirecting, habbing, forwarding from keri.app.cli.common import existing, displaying, config -from keri.core import coring +from keri.app.notifying import Notifier +from keri.core import coring, serdering +from keri.peer import exchanging logger = help.ogler.getLogger() @@ -26,7 +28,7 @@ parser.add_argument('--alias', '-a', help='human readable alias for the local identifier prefix', required=True) parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)', dest="bran", default=None) # passcode => bran -parser.add_argument('--data', '-d', help='Anchor data, \'@\' allowed', default=None, action="store", required=True) +parser.add_argument('--data', '-d', help='Anchor data, \'@\' allowed', default=[], action="store", required=True) parser.add_argument("--aids", "-g", help="List of other participant qb64 identifiers to include in interaction event", action="append", required=False, default=None) @@ -68,14 +70,19 @@ def __init__(self, name, alias, aids, base, bran, data): self.base = base self.bran = bran self.alias = alias - self.aids=aids + self.aids = aids self.data = data self.hby = existing.setupHby(name=name, base=base, bran=bran) self.hbyDoer = habbing.HaberyDoer(habery=self.hby) # setup doer - self.postman = forwarding.Postman(hby=self.hby) + self.postman = forwarding.Poster(hby=self.hby) - mbd = indirecting.MailboxDirector(hby=self.hby, topics=['/receipt', '/multisig']) + notifier = Notifier(self.hby) + mux = grouping.Multiplexor(self.hby, notifier=notifier) + exc = exchanging.Exchanger(hby=self.hby, handlers=[]) + grouping.loadHandlers(exc, mux) + + mbd = indirecting.MailboxDirector(hby=self.hby, topics=['/receipt', '/multisig'], exc=exc) self.counselor = grouping.Counselor(hby=self.hby) doers = [self.hbyDoer, self.postman, mbd, self.counselor] @@ -93,9 +100,6 @@ def interactDo(self, tymth, tock=0.0): Tymist instance. Calling tymth() returns associated Tymist .tyme. tock (float): injected initial tock value - ToDo: NRR - confirm only needs ghab.smids or do we need to add self.rmids to - """ # enter context self.wind(tymth) @@ -106,26 +110,22 @@ def interactDo(self, tymth, tock=0.0): if ghab is None: raise kering.ConfigurationError(f"invalid alias {self.alias} specified for database {self.hby.name}") - smids, rmids = ghab.members() - aids = self.aids if self.aids is not None else smids + aids = self.aids if self.aids is not None else ghab.smids - rmids = None # need to fix ixn = ghab.interact(data=self.data) + serder = serdering.SerderKERI(raw=ixn) - serder = coring.Serder(raw=ixn) - exn, atc = grouping.multisigInteractExn(ghab, serder.sner.num, aids, self.data) - others = list(aids) - + exn, ims = grouping.multisigInteractExn(ghab=ghab, aids=aids, ixn=ixn) + others = list(oset(ghab.smids + (ghab.rmids or []))) others.remove(ghab.mhab.pre) for recpt in others: # send notification to other participants as a signalling mechanism - self.postman.send(src=ghab.mhab.pre, dest=recpt, topic="multisig", serder=exn, attachment=atc) + self.postman.send(src=ghab.mhab.pre, dest=recpt, topic="multisig", serder=exn, attachment=ims) prefixer = coring.Prefixer(qb64=ghab.pre) seqner = coring.Seqner(sn=serder.sn) saider = coring.Saider(qb64b=serder.saidb) - self.counselor.start(prefixer=prefixer, seqner=seqner, saider=saider, - mid=ghab.mhab.pre, smids=aids, rmids=rmids) + self.counselor.start(prefixer=prefixer, seqner=seqner, saider=saider, ghab=ghab) while True: saider = self.hby.db.cgms.get(keys=(prefixer.qb64, seqner.qb64)) diff --git a/src/keri/app/cli/commands/multisig/join.py b/src/keri/app/cli/commands/multisig/join.py index e79a35422..dfca28c15 100644 --- a/src/keri/app/cli/commands/multisig/join.py +++ b/src/keri/app/cli/commands/multisig/join.py @@ -10,12 +10,13 @@ from hio.base import doing from prettytable import PrettyTable -from keri import help -from keri.app import habbing, indirecting, agenting, notifying, grouping, connecting +from keri import help, kering +from keri.app import habbing, indirecting, agenting, notifying, grouping, connecting, forwarding from keri.app.cli.common import existing, displaying -from keri.core import coring, eventing -from keri.db import basing +from keri.core import coring, eventing, scheming, parsing, routing, serdering from keri.peer import exchanging +from keri.vc import proving +from keri.vdr import verifying, credentialing logger = help.ogler.getLogger() @@ -59,22 +60,34 @@ def __init__(self, name, base, bran): bran (str): passcode to unlock keystore """ - hby = existing.setupHby(name=name, base=base, bran=bran) - self.hbyDoer = habbing.HaberyDoer(habery=hby) # setup doer - self.witq = agenting.WitnessInquisitor(hby=hby) - self.org = connecting.Organizer(hby=hby) - self.notifier = notifying.Notifier(hby=hby) - self.exc = exchanging.Exchanger(db=hby.db, handlers=[]) - grouping.loadHandlers(hby=hby, exc=self.exc, notifier=self.notifier) - self.counselor = grouping.Counselor(hby=hby) - self.mbx = indirecting.MailboxDirector(hby=hby, exc=self.exc, topics=['/receipt', '/multisig', '/replay', - '/delegate']) - - doers = [self.hbyDoer, self.witq, self.exc, self.mbx, self.counselor] + self.hby = existing.setupHby(name=name, base=base, bran=bran) + self.rgy = credentialing.Regery(hby=self.hby, name=name, base=base) + self.hbyDoer = habbing.HaberyDoer(habery=self.hby) # setup doer + self.witq = agenting.WitnessInquisitor(hby=self.hby) + self.org = connecting.Organizer(hby=self.hby) + self.notifier = notifying.Notifier(hby=self.hby) + self.exc = exchanging.Exchanger(hby=self.hby, handlers=[]) + self.verifier = verifying.Verifier(hby=self.hby, reger=self.rgy.reger) + self.rvy = routing.Revery(db=self.hby.db, lax=True) + self.hby.kvy.registerReplyRoutes(self.rvy.rtr) + self.psr = parsing.Parser(kvy=self.hby.kvy, tvy=self.rgy.tvy, rvy=self.rvy, vry=self.verifier, exc=self.exc) + + mux = grouping.Multiplexor(hby=self.hby, notifier=self.notifier) + grouping.loadHandlers(exc=self.exc, mux=mux) + self.counselor = grouping.Counselor(hby=self.hby) + + self.registrar = credentialing.Registrar(hby=self.hby, rgy=self.rgy, counselor=self.counselor) + self.credentialer = credentialing.Credentialer(hby=self.hby, rgy=self.rgy, registrar=self.registrar, + verifier=self.verifier) + + self.mbx = indirecting.MailboxDirector(hby=self.hby, exc=self.exc, topics=['/receipt', '/multisig', '/replay', + '/delegate']) + self.postman = forwarding.Poster(hby=self.hby) + + doers = [self.hbyDoer, self.witq, self.mbx, self.counselor, self.registrar, self.credentialer, self.postman] self.toRemove = list(doers) doers.extend([doing.doify(self.confirmDo)]) - self.hby = hby super(ConfirmDoer, self).__init__(doers=doers) def confirmDo(self, tymth, tock=0.0): @@ -94,52 +107,49 @@ def confirmDo(self, tymth, tock=0.0): print("Waiting for group multisig events...") while True: - notes = list(self.notifier.noter.notes.getItemIter(keys=b'',)) - for keys, notice in notes: + for keys, notice in self.notifier.noter.notes.getItemIter(): attrs = notice.attrs route = attrs['r'] - if route == '/multisig/icp/init': - done = yield from self.incept(attrs) - if done: - self.notifier.noter.notes.rem(keys=keys) - - else: - delete = input(f"\nDelete event [Y|n]? ") - if delete: - self.notifier.noter.notes.rem(keys=keys) - - if route == '/multisig/rot': - done = yield from self.rotate(attrs) - if done: - self.notifier.noter.notes.rem(keys=keys) - - else: - delete = input(f"\nDelete rotation event [Y|n]? ") - if delete: - self.notifier.noter.notes.rem(keys=keys) + match route: + case '/multisig/icp': + done = yield from self.incept(attrs) + case '/multisig/ixn': + done = yield from self.interact(attrs) + case '/multisig/rot': + done = yield from self.rotate(attrs) + case '/multisig/rpy': + done = yield from self.rpy(attrs) + case '/multisig/vcp': + done = yield from self.vcp(attrs) + case '/multisig/iss': + done = yield from self.iss(attrs) + case '/multisig/rev': + done = yield from self.rev(attrs) + case '/multisig/exn': + done = yield from self.exn(attrs) + case _: + continue + + if done: + self.notifier.noter.notes.rem(keys=keys) - if route == '/multisig/ixn': - done = yield from self.interact(attrs) - if done: + else: + delete = input(f"\nDelete event [Y|n]? ") + if delete in ("Y", "y"): self.notifier.noter.notes.rem(keys=keys) - else: - delete = input(f"\nDelete event [Y|n]? ") - if delete: - self.notifier.noter.notes.rem(keys=keys) - yield self.tock - yield self.tock def incept(self, attrs): - """Incept group multisig + """ Incept group multisig - ToDo: NRR - Add rmids """ - smids = attrs["aids"] # change body mids for group member ids + if True: + return True + + smids = attrs["smids"] # change body mids for group member ids rmids = attrs["rmids"] if "rmids" in attrs else None ked = attrs["ked"] @@ -185,112 +195,20 @@ def incept(self, attrs): ghab = self.hby.makeGroupHab(group=alias, mhab=mhab, smids=smids, rmids=rmids, **inits) except ValueError as e: - print(f"{e.args[0]}") return False prefixer = coring.Prefixer(qb64=ghab.pre) seqner = coring.Seqner(sn=0) saider = coring.Saider(qb64=prefixer.qb64) - yield from self.startCounselor(smids, rmids, ghab, prefixer, seqner, saider) + yield from self.startCounselor(ghab, prefixer, seqner, saider) print() displaying.printIdentifier(self.hby, ghab.pre) return True - def rotate(self, attrs): - gid = attrs["i"] - smids = attrs["smids"] # change attrs["aids"]" to "smids" - rmids = attrs["rmids"] # change attrs["aids"]" to "smids" - ked = attrs["ked"] - both = list(set(smids + (rmids or []))) - - mid = None - for smid in smids: - if smid in self.hby.habs: - mid = smid - break - - if mid is None: - print(f"Unable to join {gid}, no local AIDs in signing authority group {smids}") - return True # return True here so the event is deleted, there is no recourse here - - mhab = self.hby.habs[mid] - - if gid not in self.hby.kevers: - print(f"Unable to join {gid}, current KEL not available") - return False - - kever = self.hby.kevers[gid] - serder = coring.Serder(ked=ked) - seqner = coring.Seqner(sn=serder.sn) - - if gid not in self.hby.habs: - print(f"\nRequest to add local AID '{mhab.name}' to multisig AID {gid} in rotation to {serder.sner.num}:") - self.showEvent(mhab, both, ked) - yn = input(f"Join [Y|n]? ") - approve = yn in ('', 'y', 'Y') - if not approve: - return False - - print(f"Please enter an alias for new AID:") - alias = input(f"Alias: ") - ghab = habbing.GroupHab(ks=self.hby.ks, db=self.hby.db, cf=self.hby.cf, mgr=self.hby.mgr, - rtr=self.hby.rtr, rvy=self.hby.rvy, kvy=self.hby.kvy, psr=self.hby.psr, - name=alias, pre=gid, temp=self.hby.temp, smids=smids) - ghab.mhab = mhab - habord = basing.HabitatRecord(hid=ghab.pre, - mid=mhab.pre, - smids=smids, - rmids=rmids) - self.hby.db.habs.put(keys=alias, val=habord) - self.hby.prefixes.add(gid) - ghab.inited = True - self.hby.habs[ghab.pre] = ghab - - if serder.sner.num <= kever.sner.num: - print(f"Caught up to existing rotation event for AID {gid} to sequence number {serder.sner.num}") - return True # return True here so event is deleted, we will never process this event - elif serder.sner.num != kever.sner.num + 1: - print(f"Unable to joid {gid}, current KEL out of date") - return False - - local = False - - else: - ghab = self.hby.habs[gid] - local = True - print(f"\nRequest to rotate {ghab.name} to {serder.sner.num}:") - yn = input(f"\nJoin [Y|n]? ") - approve = yn in ('', 'y', 'Y') - - if approve: - isith = serder.ked['kt'] - nsith = serder.ked['nt'] - toad = serder.ked['bt'] - cuts = serder.ked['br'] - adds = serder.ked['ba'] - data = serder.ked['a'] - - self.counselor.rotate(ghab=ghab, smids=smids, rmids=rmids, - isith=isith, nsith=nsith, toad=toad, - cuts=list(cuts), adds=list(adds), - data=data, local=local) - - while True: - saider = self.hby.db.cgms.get(keys=(ghab.pre, seqner.qb64)) - if saider is not None: - break - - yield self.tock - - print() - displaying.printIdentifier(self.hby, ghab.pre) - return True - def interact(self, attrs): pre = attrs["gid"] - sn = attrs["sn"] smids = attrs["aids"] # change attrs["aids"]" to "smids" rmids = attrs["rmids"] if "rmids" in attrs else None data = attrs["data"] @@ -307,11 +225,7 @@ def interact(self, attrs): print(f"Local AID {ghab.mhab.pre} not a requested signer in {both}") return False - if sn <= ghab.kever.sner.num: - print(f"Discarding stale interaction event for AID {pre} to sequence number {sn}") - return True # return True here so event is deleted, we will never process this event - - print(f"\nGroup Multisig Interaction for {ghab.name} ({ghab.pre}) proposed:") + print(f"Group Multisig Interaction for {ghab.name} ({ghab.pre}) proposed:") print(f"Data:") print(json.dumps(data, indent=2)) yn = input(f"\nJoin [Y|n]? ") @@ -319,21 +233,19 @@ def interact(self, attrs): if approve: ixn = ghab.interact(data=data) - serder = coring.Serder(raw=ixn) - print(serder.pretty()) + serder = serdering.SerderKERI(raw=ixn) prefixer = coring.Prefixer(qb64=ghab.pre) seqner = coring.Seqner(sn=serder.sn) saider = coring.Saider(qb64b=serder.saidb) - yield from self.startCounselor(smids, rmids, ghab, prefixer, seqner, saider) + yield from self.startCounselor(ghab, prefixer, seqner, saider) print() displaying.printIdentifier(self.hby, ghab.pre) return True - def startCounselor(self, smids, rmids, hab, prefixer, seqner, saider): - self.counselor.start(prefixer=prefixer, seqner=seqner, saider=saider, - mid=hab.mhab.pre, smids=smids, rmids=rmids) + def startCounselor(self, hab, prefixer, seqner, saider): + self.counselor.start(prefixer=prefixer, seqner=seqner, saider=saider, ghab=hab) while True: saider = self.hby.db.cgms.get(keys=(prefixer.qb64, seqner.qb64)) @@ -347,7 +259,122 @@ def showEvent(self, hab, mids, ked): print("Participants:") thold = coring.Tholder(sith=ked["kt"]) + self.printMemberTable(mids, hab, thold) + + print() + print("Configuration:") + + tab = PrettyTable() + tab.field_names = ["Name", "Value"] + tab.align["Name"] = "l" + + if "di" in ked: + m = self.org.get(ked["di"]) + alias = m['alias'] if m else "Unknown Delegator" + tab.add_row(["Delegator", f"{alias} ({ked['di']}))"]) + + if not thold.weighted: + tab.add_row(["Signature Threshold", thold.num]) + + tab.add_row(["Establishment Only", eventing.TraitCodex.EstOnly in ked["c"]]) + tab.add_row(["Do Not Delegate", eventing.TraitCodex.DoNotDelegate in ked["c"]]) + tab.add_row(["Witness Threshold", ked["bt"]]) + tab.add_row(["Witnesses", "\n".join(ked["b"])]) + + print(tab) + + def rotate(self, attrs): + """ Rotate group multisig + + """ + smids = attrs["smids"] + rmids = attrs["rmids"] + ked = attrs["ked"] + + both = list(set(smids + (rmids or []))) + + mhab = None + for mid in both: + if mid in self.hby.habs: + mhab = self.hby.habs[mid] + break + + if mhab is None: + print("Invalid multisig group rotation request, signing member list must contain a local identifier'") + return False + + print() + print("Group Multisig Rotation proposed:") + self.showRotation(mhab, smids, rmids, ked) + yn = input(f"\nJoin [Y|n]? ") + approve = yn in ('', 'y', 'Y') + + if approve: + pre = ked['i'] + if pre in self.hby.habs: + ghab = self.hby.habs[pre] + else: + while True: + alias = input(f"\nEnter alias for new AID: ") + if self.hby.habByName(alias) is not None: + print(f"AID alias {alias} is already in use, please try again") + else: + break + + ghab = self.hby.joinGroupHab(pre, group=alias, mhab=mhab, smids=smids, rmids=rmids) + + try: + serder = serdering.SerderKERI(sad=ked) + rot = ghab.rotate(serder=serder) + except ValueError as e: + return False + + serder = serdering.SerderKERI(raw=rot) + prefixer = coring.Prefixer(qb64=ghab.pre) + seqner = coring.Seqner(sn=serder.sn) + + yield from self.startCounselor(ghab, prefixer, seqner, coring.Saider(qb64=serder.said)) + + print() + displaying.printIdentifier(self.hby, ghab.pre) + + return True + + def showRotation(self, hab, smids, rmids, ked): + print() + print("Signing Members") + thold = coring.Tholder(sith=ked["kt"]) + self.printMemberTable(smids, hab, thold) + + print() + print("Rotation Members") + nthold = coring.Tholder(sith=ked["nt"]) + self.printMemberTable(rmids, hab, nthold) + + print() + print("Configuration:") + + tab = PrettyTable() + tab.field_names = ["Name", "Value"] + tab.align["Name"] = "l" + + if "di" in ked: + m = self.org.get(ked["di"]) + alias = m['alias'] if m else "Unknown Delegator" + tab.add_row(["Delegator", f"{alias} ({ked['di']}))"]) + + if not thold.weighted: + tab.add_row(["Signature Threshold", thold.num]) + + tab.add_row(["Witness Threshold", ked["bt"]]) + if "ba" in ked and ked["ba"]: + tab.add_row(["Added Witnesses", "\n".join(ked["ba"])]) + if "br" in ked and ked["br"]: + tab.add_row(["Removed Witnesses", "\n".join(ked["br"])]) + + print(tab) + def printMemberTable(self, mids, hab, thold): tab = PrettyTable() fields = ["Local", "Name", "AID"] @@ -373,25 +400,440 @@ def showEvent(self, hab, mids, ked): tab.add_row(row) print(tab) - print() - print("Configuration:") - tab = PrettyTable() - tab.field_names = ["Name", "Value"] - tab.align["Name"] = "l" + def rpy(self, attrs): + """ Handle reply messages - if "di" in ked: - m = self.org.get(ked["di"]) - alias = m['alias'] if m else "Unknown Delegator" - tab.add_row(["Delegator", f"{alias} ({ked['di']}))"]) + Parameters: + attrs (dict): attributes of the reply message - if not thold.weighted: - tab.add_row(["Signature Threshold", thold.num]) + Returns: + + """ + said = attrs["d"] + exn, pathed = exchanging.cloneMessage(self.hby, said=said) + + sender = exn.ked['i'] + payload = exn.ked['a'] + gid = payload["gid"] + hab = self.hby.habs[gid] if gid in self.hby.habs else None + if hab is None: + raise ValueError(f"credential issuer not a valid AID={gid}") + + contact = self.org.get(sender) + senderAlias = contact['alias'] + + embeds = exn.ked['e'] + rpy = embeds['rpy'] + cid = rpy['a']['cid'] + eid = rpy['a']['eid'] + role = rpy['a']['role'] + + if cid == gid: + controller = hab.name + else: + raise ValueError(f"Endpoint role authorization request for wrong controller {gid} != {cid}") - if 'c' in ked: - tab.add_row(["Establishment Only", eventing.TraitCodex.EstOnly in ked["c"]]) - tab.add_row(["Do Not Delegate", eventing.TraitCodex.DoNotDelegate in ked["c"]]) - tab.add_row(["Witness Threshold", ked["bt"]]) - tab.add_row(["Witnesses", "\n".join(ked["b"])]) + endpoint = self.org.get(eid) + if endpoint is None or 'alias' not in endpoint: + endpointAlias = "Unknown Endpoint" + else: + endpointAlias = endpoint['alias'] - print(tab) + print(f"\nEndpoint Role Authorization (from {senderAlias}):") + print(f" Controller: {controller} ({cid})") + print(f" Role: {role.capitalize()}") + print(f" Endpoint Provider: {endpointAlias} ({eid})") + + yn = input(f"\nApprove [Y|n]? ") + approve = yn in ('', 'y', 'Y') + + if approve: + # Create and parse the event with "their" signatures + rserder = serdering.SerderKERI(sad=rpy) + anc = bytearray(rserder.raw) + pathed["rpy"] + self.psr.parseOne(ims=bytes(anc)) + + # Now sign the event and parse it with our signatures + anc = hab.endorse(rserder) + self.psr.parseOne(ims=bytes(anc)) + + smids = hab.db.signingMembers(pre=hab.pre) + smids.remove(hab.mhab.pre) + + for recp in smids: # this goes to other participants only as a signaling mechanism + exn, atc = grouping.multisigRpyExn(ghab=hab, rpy=anc) + self.postman.send(src=hab.mhab.pre, + dest=recp, + topic="multisig", + serder=exn, + attachment=atc) + + while not hab.loadEndRole(cid=cid, role=role, eid=eid): + self.rgy.processEscrows() + self.rvy.processEscrowReply() + yield self.tock + + print(f"End role authorization added for role {role}") + + yield self.tock + + def vcp(self, attrs): + """ Handle issue messages + + Parameters: + attrs (dict): attributes of the reply message + + Returns: + + """ + said = attrs["d"] + exn, pathed = exchanging.cloneMessage(self.hby, said=said) + + sender = exn.ked['i'] + payload = exn.ked['a'] + usage = payload["usage"] + gid = payload["gid"] + hab = self.hby.habs[gid] if gid in self.hby.habs else None + if hab is None: + raise ValueError(f"credential issuer not a valid AID={gid}") + + contact = self.org.get(sender) + senderAlias = contact['alias'] + + embeds = exn.ked['e'] + print(f"\nGroup Credential Regitry Creation (from {senderAlias}):") + print(f"Usage: {usage}:\n") + + yn = input(f"\nApprove [Y|n]? ") + approve = yn in ('', 'y', 'Y') + + if approve: + # Create and parse the event with "their" signatures + registryName = input("Name for Registry: ") + anc = embeds["anc"] + aserder = serdering.SerderKERI(sad=anc) + anc = bytearray(aserder.raw) + pathed["anc"] + self.psr.parseOne(ims=bytes(anc)) + + # Now sign the event and parse it with our signatures + sigers = hab.sign(aserder.raw) + anc = eventing.messagize(serder=aserder, sigers=sigers) + self.psr.parseOne(ims=bytes(anc)) + + vcp = embeds["vcp"] + vserder = serdering.SerderKERI(sad=vcp) + try: + self.rgy.tvy.processEvent(serder=vserder) + except kering.MissingAnchorError: + pass + + self.rgy.makeRegistry(name=registryName, prefix=hab.pre, vcp=vserder) + self.registrar.incept(vserder, aserder) + + smids = hab.db.signingMembers(pre=hab.pre) + smids.remove(hab.mhab.pre) + + for recp in smids: # this goes to other participants only as a signaling mechanism + exn, atc = grouping.multisigRegistryInceptExn(ghab=hab, vcp=vserder.raw, anc=anc, usage=usage) + self.postman.send(src=hab.mhab.pre, + dest=recp, + topic="multisig", + serder=exn, + attachment=atc) + + while not self.registrar.complete(vserder.pre, sn=0): + self.rgy.processEscrows() + self.verifier.processEscrows() + yield self.tock + + print(f"Registry {vserder.pre} created.") + + yield self.tock + + def iss(self, attrs): + """ Handle issue messages + + Parameters: + attrs (dict): attributes of the reply message + + Returns: + + """ + said = attrs["d"] + exn, pathed = exchanging.cloneMessage(self.hby, said=said) + + sender = exn.ked['i'] + + contact = self.org.get(sender) + senderAlias = contact['alias'] + + embeds = exn.ked['e'] + acdc = embeds["acdc"] + schema = acdc['s'] + scraw = self.verifier.resolver.resolve(schema) + if not scraw: + raise kering.ConfigurationError("Credential schema {} not found".format(schema)) + + schemer = scheming.Schemer(raw=scraw) + + issr = acdc["i"] + hab = self.hby.habs[issr] if issr in self.hby.habs else None + if hab is None: + raise ValueError(f"credential issuer not a valid AID={issr}") + + print(f"\nGroup Credential Issuance Proposed (from {senderAlias}):") + print(f"Credential {acdc['d']}:") + print(f" Type: {schemer.sed['title']}") + print(f" Issued By: {hab.name} ({hab.pre})") + + if "i" in acdc["a"]: + isse = acdc['a']['i'] + contact = self.org.get(isse) + if contact is not None and "alias" in contact: + print(f" Issued To: {contact['alias']} ({isse})") + else: + print(f" Issued To: Unknown AID ({isse})") + + print(" Data:") + for k, v in acdc['a'].items(): + if k not in ('d', 'i'): + print(f" {k}: {v}") + + yn = input(f"\nApprove [Y|n]? ") + approve = yn in ('', 'y', 'Y') + + if approve: + # Create and parse the event with "their" signatures + anc = embeds["anc"] + aserder = serdering.SerderKERI(sad=anc) + anc = bytearray(aserder.raw) + pathed["anc"] + self.psr.parseOne(ims=bytes(anc)) + + # Now sign the event and parse it with our signatures + sigers = hab.sign(aserder.raw) + anc = eventing.messagize(serder=aserder, sigers=sigers) + self.psr.parseOne(ims=bytes(anc)) + + iss = embeds["iss"] + iserder = serdering.SerderKERI(sad=iss) + try: + self.rgy.tvy.processEvent(serder=iserder) + except kering.MissingAnchorError: + pass + + acdc = embeds["acdc"] + creder = serdering.SerderACDC(sad=acdc) + acdc = bytearray(creder.raw) + pathed["acdc"] + self.psr.parseOne(ims=bytes(acdc)) + + self.credentialer.issue(creder, iserder) + self.registrar.issue(creder, iserder, aserder) + + smids = hab.db.signingMembers(pre=hab.pre) + smids.remove(hab.mhab.pre) + + for recp in smids: # this goes to other participants only as a signaling mechanism + exn, atc = grouping.multisigIssueExn(ghab=hab, acdc=acdc, iss=iserder.raw, anc=anc) + self.postman.send(src=hab.mhab.pre, + dest=recp, + topic="multisig", + serder=exn, + attachment=atc) + + while not self.credentialer.complete(said=creder.said): + self.rgy.processEscrows() + self.verifier.processEscrows() + yield self.tock + + print(f"Credential {creder.said} complete.") + + yield self.tock + + def rev(self, attrs): + """ Handle revocation messages + + Parameters: + attrs (dict): attributes of the reply message + + Returns: + + """ + said = attrs["d"] + exn, pathed = exchanging.cloneMessage(self.hby, said=said) + + sender = exn.ked['i'] + payload = exn.ked['a'] + said = payload['said'] + + creder = self.verifier.reger.creds.get(keys=(said,)) + if creder is None: + print(f"invalid credential SAID {said}") + return + + contact = self.org.get(sender) + senderAlias = contact['alias'] + + embeds = exn.ked['e'] + scraw = self.verifier.resolver.resolve(creder.schema) + if not scraw: + raise kering.ConfigurationError("Credential schema {} not found".format(creder.schema)) + + schemer = scheming.Schemer(raw=scraw) + + hab = self.hby.habs[creder.issuer] + if hab is None: + raise ValueError(f"credential issuer not a valid AID={creder.issuer}") + + print(f"\nGroup Credential Revocation Proposed (from {senderAlias}):") + print(f"Credential {creder.said}:") + print(f" Type: {schemer.sed['title']}") + print(f" Issued By: {hab.name} ({hab.pre})") + + if "i" in creder.attrib: + isse = creder.attrib['i'] + contact = self.org.get(isse) + if contact is not None and "alias" in contact: + print(f" Issued To: {contact['alias']} ({isse})") + else: + print(f" Issued To: Unknown AID ({isse})") + + yn = input(f"\nApprove Revocation [Y|n]? ") + approve = yn in ('', 'y', 'Y') + + if approve: + # Create and parse the event with "their" signatures + anc = embeds["anc"] + aserder = serdering.SerderKERI(sad=anc) + anc = bytearray(aserder.raw) + pathed["anc"] + self.psr.parseOne(ims=bytes(anc)) + + # Now sign the event and parse it with our signatures + sigers = hab.sign(aserder.raw) + anc = eventing.messagize(serder=aserder, sigers=sigers) + self.psr.parseOne(ims=bytes(anc)) + + rev = embeds["rev"] + rserder = serdering.SerderKERI(sad=rev) + try: + self.rgy.tvy.processEvent(serder=rserder) + except kering.MissingAnchorError: + pass + + self.registrar.revoke(creder, rserder, aserder) + + smids = hab.db.signingMembers(pre=hab.pre) + smids.remove(hab.mhab.pre) + + for recp in smids: # this goes to other participants only as a signaling mechanism + exn, atc = grouping.multisigRevokeExn(ghab=hab, said=creder.said, rev=rserder.raw, anc=anc) + self.postman.send(src=hab.mhab.pre, + dest=recp, + topic="multisig", + serder=exn, + attachment=atc) + + while not self.registrar.complete(creder.said, sn=1): + self.rgy.processEscrows() + yield self.tock + + print(f"Credential {creder.said} revoked.") + if hab.witnesser() and 'i' in creder.attrib: + recp = creder.attrib['i'] + msgs = [] + for msg in self.hby.db.clonePreIter(pre=creder.issuer): + serder = serdering.SerderKERI(raw=msg) + atc = msg[serder.size:] + msgs.append((serder, atc)) + for msg in self.rgy.reger.clonePreIter(pre=creder.said): + serder = serdering.SerderKERI(raw=msg) + atc = msg[serder.size:] + msgs.append((serder, atc)) + + for (serder, atc) in msgs: + self.postman.send(src=hab.mhab.pre, dest=recp, topic="credential", serder=serder, + attachment=atc) + + last = msgs[-1][0] + while not self.postman.sent(said=last.said): + yield self.tock + + yield self.tock + + def exn(self, attrs): + """ Handle exn messages + + Parameters: + attrs (dict): attributes of the reply message + + Returns: + + """ + said = attrs["d"] + exn, pathed = exchanging.cloneMessage(self.hby, said=said) + embeds = exn.ked['e'] + sender = exn.ked['i'] + + contact = self.org.get(sender) + senderAlias = contact['alias'] + + eexn = embeds['exn'] + + group = eexn["i"] + hab = self.hby.habs[group] if group in self.hby.habs else None + if hab is None: + raise ValueError(f"message sender not a valid AID={group}") + + print(f"Group Peer-2-Peer Message proposal (from {senderAlias}):") + print(f" Message Type: {eexn['r']}") + print(f" Sending From: {hab.name} ({hab.pre})") + recp = eexn['a']['i'] + contact = self.org.get(recp) + if contact is not None and "alias" in contact: + print(f" Sending To: {contact['alias']} ({recp})") + else: + print(f" Sending To: Unknown AID ({recp})") + + yn = input(f"\nApprove [Y|n]? ") + approve = yn in ('', 'y', 'Y') + + if approve: + eserder = serdering.SerderKERI(sad=eexn) + anc = bytearray(eserder.raw) + pathed["exn"] + self.psr.parseOne(ims=bytes(anc)) + + msg = hab.endorse(serder=eserder, last=False, pipelined=False) + msg = msg + pathed["exn"] + self.psr.parseOne(ims=bytes(msg)) + + smids = hab.db.signingMembers(pre=hab.pre) + smids.remove(hab.mhab.pre) + + for smid in smids: # this goes to other participants only as a signaling mechanism + rexn, atc = grouping.multisigExn(ghab=hab, exn=msg) + self.postman.send(src=hab.mhab.pre, + dest=smid, + topic="multisig", + serder=rexn, + attachment=atc) + + while not self.exc.complete(said=eserder.said): + self.exc.processEscrow() + yield self.tock + + if self.exc.lead(hab.mhab, said=exn.said): + print(f"Sending message {eserder.said} to {recp}") + atc = exchanging.serializeMessage(self.hby, eserder.said) + del atc[:eserder.size] + self.postman.send(src=hab.mhab.pre, + dest=recp, + topic="credential", + serder=eserder, + attachment=atc) + + while not self.postman.sent(said=eserder.said): + yield self.tock + + print("... grant message sent") + + yield self.tock diff --git a/src/keri/app/cli/commands/multisig/rotate.py b/src/keri/app/cli/commands/multisig/rotate.py index 802ae4bb2..668a78267 100644 --- a/src/keri/app/cli/commands/multisig/rotate.py +++ b/src/keri/app/cli/commands/multisig/rotate.py @@ -5,6 +5,7 @@ """ import argparse +from ordered_set import OrderedSet as oset from hio import help from hio.base import doing @@ -12,7 +13,10 @@ from keri import kering from keri.app import grouping, indirecting, habbing, forwarding from keri.app.cli.common import rotating, existing, displaying, config -from keri.core import coring +from keri.app.notifying import Notifier +from keri.core import coring, serdering +from keri.db import dbing +from keri.peer import exchanging logger = help.ogler.getLogger() @@ -83,10 +87,14 @@ def __init__(self, name, base, bran, alias, smids=None, rmids=None, isith=None, self.hby = existing.setupHby(name=name, base=base, bran=bran) self.hbyDoer = habbing.HaberyDoer(habery=self.hby) # setup doer + notifier = Notifier(self.hby) + mux = grouping.Multiplexor(self.hby, notifier=notifier) + exc = exchanging.Exchanger(hby=self.hby, handlers=[]) + grouping.loadHandlers(exc, mux) - mbd = indirecting.MailboxDirector(hby=self.hby, topics=['/receipt', '/multisig']) + mbd = indirecting.MailboxDirector(hby=self.hby, topics=['/receipt', '/multisig'], exc=exc) self.counselor = grouping.Counselor(hby=self.hby) - self.postman = forwarding.Postman(hby=self.hby) + self.postman = forwarding.Poster(hby=self.hby) doers = [mbd, self.hbyDoer, self.counselor, self.postman] self.toRemove = list(doers) @@ -113,12 +121,11 @@ def rotateDo(self, tymth, tock=0.0, **opts): if ghab is None: raise kering.ConfigurationError(f"Alias {self.alias} is invalid") - smids, rmids = ghab.members() if self.smids is None: - self.smids = smids + self.smids = ghab.smids if self.rmids is None: - self.rmids = rmids + self.rmids = self.smids if self.wits: if self.adds or self.cuts: @@ -129,11 +136,95 @@ def rotateDo(self, tymth, tock=0.0, **opts): self.cuts = set(ewits) - set(self.wits) self.adds = set(self.wits) - set(ewits) + smids = [] + merfers = [] + for smid in self.smids: + match smid.split(':'): + case [mid]: # Only prefix provided, assume latest event + if mid not in self.hby.kevers: + raise kering.ConfigurationError(f"unknown signing member {mid}") + + mkever = self.hby.kevers[mid] # get key state for given member + merfers.append(mkever.verfers[0]) + smids.append(mid) + + case [mid, sn]: + if mid not in self.hby.kevers: + raise kering.ConfigurationError(f"unknown signing member {mid}") + + dig = self.hby.db.getKeLast(dbing.snKey(mid, int(sn))) + if dig is None: + raise kering.ConfigurationError(f"non-existant event {sn} for signing member {mid}") + + evt = self.hby.db.getEvt(dbing.dgKey(mid, bytes(dig))) + serder = serdering.SerderKERI(raw=bytes(evt)) + if not serder.estive: + raise kering.ConfigurationError(f"invalid event {sn} for signing member {mid}") + + merfers.append(serder.verfers[0]) + smids.append(mid) + + case _: + raise kering.ConfigurationError(f"invalid smid representation {smid}") + + migers = [] + rmids = [] + for rmid in self.rmids: + match rmid.split(':'): + case [mid]: # Only prefix provided, assume latest event + if mid not in self.hby.kevers: + raise kering.ConfigurationError(f"unknown rotation member {mid}") + + mkever = self.hby.kevers[mid] # get key state for given member + migers.append(mkever.ndigers[0]) + rmids.append(mid) + + case [mid, sn]: + if mid not in self.hby.kevers: + raise kering.ConfigurationError(f"unknown rotation member {mid}") + + dig = self.hby.db.getKeLast(dbing.snKey(mid, int(sn))) + if dig is None: + raise kering.ConfigurationError(f"non-existant event {sn} for rotation member {mid}") + + evt = self.hby.db.getEvt(dbing.dgKey(mid, bytes(dig))) + serder = serdering.SerderKERI(raw=bytes(evt)) + if not serder.estive: + raise kering.ConfigurationError(f"invalid event {sn} for rotation member {mid}") + + migers.append(serder.ndigers[0]) + rmids.append(mid) + + case _: + raise kering.ConfigurationError(f"invalid rmid representation {rmid}") + + if ghab.mhab.pre not in smids: + raise kering.ConfigurationError(f"{ghab.mhab.pre} not in signing members {smids} for this event") + + prefixer = coring.Prefixer(qb64=ghab.pre) seqner = coring.Seqner(sn=ghab.kever.sn+1) - self.counselor.rotate(ghab=ghab, smids=self.smids, rmids=self.rmids, - isith=self.isith, nsith=self.nsith, toad=self.toad, - cuts=list(self.cuts), adds=list(self.adds), - data=self.data) + rot = ghab.rotate(isith=self.isith, nsith=self.nsith, + toad=self.toad, cuts=list(self.cuts), adds=list(self.adds), data=self.data, + verfers=merfers, digers=migers) + + rserder = serdering.SerderKERI(raw=rot) + # Create a notification EXN message to send to the other agents + exn, ims = grouping.multisigRotateExn(ghab=ghab, + smids=smids, + rmids=rmids, + rot=bytearray(rot)) + others = list(oset(smids + (rmids or []))) + + others.remove(ghab.mhab.pre) + + for recpt in others: # Send event AND notification message to others + self.postman.send(src=ghab.mhab.pre, + dest=recpt, + topic="multisig", + serder=exn, + attachment=bytearray(ims)) + + self.counselor.start(ghab=ghab, prefixer=prefixer, seqner=seqner, saider=coring.Saider(qb64=rserder.said)) while True: saider = self.hby.db.cgms.get(keys=(ghab.pre, seqner.qb64)) @@ -142,11 +233,6 @@ def rotateDo(self, tymth, tock=0.0, **opts): yield self.tock - habord = self.hby.db.habs.get(keys=ghab.name) - habord.smids = self.smids - habord.rmids = self.rmids - self.hby.db.habs.pin(keys=ghab.name, val=habord) - if ghab.kever.delegator: yield from self.postman.sendEvent(hab=ghab, fn=ghab.kever.sn) diff --git a/src/keri/app/cli/commands/multisig/update.py b/src/keri/app/cli/commands/multisig/update.py index 3efc7f357..5d7284ea8 100644 --- a/src/keri/app/cli/commands/multisig/update.py +++ b/src/keri/app/cli/commands/multisig/update.py @@ -13,6 +13,7 @@ from keri.app import agenting, indirecting, habbing from keri.app.cli.common import displaying from keri.app.cli.common import existing +from keri.app.habbing import GroupHab logger = help.ogler.getLogger() @@ -51,7 +52,7 @@ def __init__(self, name, alias, base, bran, wit, sn, said, **kwa): self.hbyDoer = habbing.HaberyDoer(habery=self.hby) # setup doer hab = self.hby.habByName(alias) - if not hab.group: + if not isinstance(hab, GroupHab): raise kering.ConfigurationError("only group habs can be updated from witnesses.") self.hab = hab @@ -61,7 +62,7 @@ def __init__(self, name, alias, base, bran, wit, sn, said, **kwa): self.said = said self.cues = help.decking.Deck() - self.mbd = indirecting.MailboxDirector(hby=self.hby, topics=["/replay", "/receipt"]) + self.mbd = indirecting.MailboxDirector(hby=self.hby, topics=["/replay", "/receipt", "/reply"]) self.witq = agenting.WitnessInquisitor(hby=self.hby) doers.extend([self.hbyDoer, self.mbd, self.witq]) @@ -90,7 +91,7 @@ def updateDo(self, tymth, tock=0.0, **opts): self.hab.db.ksns.rem((saider.qb64,)) self.hab.db.ksns.rem((saider.qb64,)) - witer = agenting.witnesser(self.hab, self.wit) + witer = agenting.messenger(self.hab, self.wit) self.extend([witer]) msg = self.hab.query(pre=self.hab.pre, src=self.wit, route="ksn") diff --git a/src/keri/app/cli/commands/oobi/generate.py b/src/keri/app/cli/commands/oobi/generate.py index 5ce3513de..f2ac4ba68 100644 --- a/src/keri/app/cli/commands/oobi/generate.py +++ b/src/keri/app/cli/commands/oobi/generate.py @@ -62,20 +62,22 @@ def generate(tymth, tock=0.0, **opts): hab = hby.habByName(name=alias) if role in (kering.Roles.witness,): if not hab.kever.wits: - print(f"{alias} identfier {hab.pre} does not have any witnesses.") + print(f"{alias} identifier {hab.pre} does not have any witnesses.") sys.exit(-1) for wit in hab.kever.wits: - urls = hab.fetchUrls(eid=wit, scheme=kering.Schemes.http) + urls = hab.fetchUrls(eid=wit, scheme=kering.Schemes.http) or hab.fetchUrls(eid=wit, scheme=kering.Schemes.https) if not urls: raise kering.ConfigurationError(f"unable to query witness {wit}, no http endpoint") - up = urlparse(urls[kering.Schemes.http]) - print(f"http://{up.hostname}:{up.port}/oobi/{hab.pre}/witness") + url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https] + up = urlparse(url) + print(f"{up.scheme}://{up.hostname}:{up.port}/oobi/{hab.pre}/witness") elif role in (kering.Roles.controller,): - urls = hab.fetchUrls(eid=hab.pre, scheme=kering.Schemes.http) + urls = hab.fetchUrls(eid=hab.pre, scheme=kering.Schemes.http) or hab.fetchUrls(eid=hab.pre, scheme=kering.Schemes.https) if not urls: print(f"{alias} identifier {hab.pre} does not have any controller endpoints") return - up = urlparse(urls[kering.Schemes.http]) - print(f"http://{up.hostname}:{up.port}/oobi/{hab.pre}/controller") + url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https] + up = urlparse(url) + print(f"{up.scheme}://{up.hostname}:{up.port}/oobi/{hab.pre}/controller") diff --git a/src/keri/app/cli/commands/passcode/remove.py b/src/keri/app/cli/commands/passcode/remove.py index 31f96e093..82b6b04f7 100644 --- a/src/keri/app/cli/commands/passcode/remove.py +++ b/src/keri/app/cli/commands/passcode/remove.py @@ -47,6 +47,5 @@ def remove(tymth, tock=0.0, **opts): print("Passcode removed and keystore unencrypted.") except ConfigurationError as e: - print(e) print(f"identifier prefix for {name} does not exist, incept must be run first", ) return -1 diff --git a/src/keri/app/cli/commands/passcode/set.py b/src/keri/app/cli/commands/passcode/set.py index 287cf9336..a31f978bb 100644 --- a/src/keri/app/cli/commands/passcode/set.py +++ b/src/keri/app/cli/commands/passcode/set.py @@ -70,6 +70,5 @@ def set_passcode(tymth, tock=0.0, **opts): print("Passcode reset and keystore re-encrypted.") except ConfigurationError as e: - print(e) print(f"identifier prefix for {name} does not exist, incept must be run first", ) return -1 diff --git a/src/keri/app/cli/commands/query.py b/src/keri/app/cli/commands/query.py new file mode 100644 index 000000000..dc9a1b220 --- /dev/null +++ b/src/keri/app/cli/commands/query.py @@ -0,0 +1,104 @@ +# -*- encoding: utf-8 -*- +""" +keri.kli.commands module + +""" +import argparse +import datetime +import json + +from hio import help +from hio.base import doing +from hio.help import decking + +from keri.app import indirecting, habbing, querying +from keri.app.cli.common import displaying +from keri.app.cli.common import existing +from keri.help import helping + +logger = help.ogler.getLogger() + +parser = argparse.ArgumentParser(description='Request KEL from Witness') +parser.set_defaults(handler=lambda args: query(args), + transferable=True) +parser.add_argument('--name', '-n', help='Human readable reference', required=True) +parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore', + required=False, default="") +parser.add_argument('--alias', '-a', help='human readable alias for the new identifier prefix', required=True) +parser.add_argument('--prefix', help='QB64 identifier to query', default="", required=True) + +# Authentication for keystore +parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)', + dest="bran", default=None) # passcode => bran +parser.add_argument('--aeid', help='qualified base64 of non-transferable identifier prefix for authentication ' + 'and encryption of secrets in keystore', default=None) +parser.add_argument('--anchor', help='JSON file containing the anchor to search for', default=None, required=False) + + +def query(args): + name = args.name + + qryDoer = LaunchDoer(name=name, alias=args.alias, base=args.base, bran=args.bran, pre=args.prefix, + anchor=args.anchor) + return [qryDoer] + + +class LaunchDoer(doing.DoDoer): + + def __init__(self, name, alias, base, bran, pre, anchor, **kwa): + doers = [] + self.hby = existing.setupHby(name=name, base=base, bran=bran) + self.hbyDoer = habbing.HaberyDoer(habery=self.hby) # setup doer + hab = self.hby.habByName(alias) + + self.hab = hab + self.logs = decking.Deck() + + self.pre = pre + self.anchor = anchor + self.loaded = False + + self.mbd = indirecting.MailboxDirector(hby=self.hby, topics=["/replay", "/receipt", "/reply"]) + doers.extend([self.hbyDoer, self.mbd]) + + self.toRemove = list(doers) + doers.extend([doing.doify(self.queryDo)]) + super(LaunchDoer, self).__init__(doers=doers, **kwa) + + def queryDo(self, tymth, tock=0.0, **opts): + """ + Returns: doifiable Doist compatible generator method + Usage: + add result of doify on this method to doers list + """ + # enter context + self.wind(tymth) + self.tock = tock + _ = (yield self.tock) + + end = helping.nowUTC() + datetime.timedelta(seconds=10) + + if self.anchor is not None: + f = open(self.anchor) + anchor = json.load(f) + print(f"Checking for anchor {anchor}...") + doer = querying.AnchorQuerier(hby=self.hby, hab=self.hab, pre=self.pre, anchor=anchor) + else: + print(f"Checking for updates...") + doer = querying.QueryDoer(hby=self.hby, hab=self.hab, pre=self.pre, kvy=self.mbd.kvy) + + self.extend([doer]) + + while helping.nowUTC() < end: + if doer.done: + break + yield 1.0 + + self.remove([doer]) + print("\n") + + displaying.printExternal(self.hby, self.pre) + + self.remove(self.toRemove) + + return diff --git a/src/keri/app/cli/commands/rollback.py b/src/keri/app/cli/commands/rollback.py index 31a53784f..f7d573f45 100644 --- a/src/keri/app/cli/commands/rollback.py +++ b/src/keri/app/cli/commands/rollback.py @@ -11,8 +11,8 @@ from keri import kering from keri.app.cli.common import displaying, existing -from keri.core import coring -from keri.db import dbing +from keri.core import coring, serdering +from keri.db import dbing, basing from keri.help import helping from keri.kering import ConfigurationError @@ -64,12 +64,12 @@ def rollback(tymth, tock=0.0, **opts): raise kering.ValidationError(f"top event at sequence number {hab.kever.sn} has been published to " f"{len(wigs)} witnesses, unable to rollback.") - state = hby.db.states.get(keys=serder.pre) + ked = hby.db.states.getDict(keys=serder.pre) pdig = hby.db.getKeLast(dbing.snKey(serder.preb, serder.sn - 1)) pDgKey = dbing.dgKey(serder.preb, bytes(pdig)) # get message raw = hby.db.getEvt(key=pDgKey) - pserder = coring.Serder(raw=bytes(raw)) + pserder = serdering.SerderKERI(raw=bytes(raw)) dgkey = dbing.dgKey(serder.preb, serder.saidb) hby.db.delEvt(dgkey) @@ -80,19 +80,21 @@ def rollback(tymth, tock=0.0, **opts): hby.db.delKes(dbing.snKey(serder.preb, serder.sn)) seqner = coring.Number(num=serder.sn - 1) - fner = coring.Number(numh=state.ked['f']) + fner = coring.Number(numh=ked['f']) fner = coring.Number(num=fner.num - 1) # Update the only items in state that will change after rolling back an ixn - state.ked['s'] = seqner.numh - state.ked['et'] = pserder.ked['t'] - state.ked['p'] = pserder.ked['p'] - state.ked['d'] = pserder.said - state.ked['f'] = fner.numh - state.ked['dt'] = helping.nowIso8601() - - state = coring.Serder(ked=state.ked) - hby.db.states.pin(keys=hab.pre, val=state) + ked['s'] = seqner.numh + ked['et'] = pserder.ked['t'] + ked['p'] = pserder.ked['p'] + ked['d'] = pserder.said + ked['f'] = fner.numh + ked['dt'] = helping.nowIso8601() + + state = serdering.SerderKERI(ked=ked) # This is wrong key state is not Serder anymore + hby.db.states.pin(keys=hab.pre, + val=helping.datify(basing.KeyStateRecord, + state.ked)) # Refresh all habs to reload this one hby.db.reload() diff --git a/src/keri/app/cli/commands/rotate.py b/src/keri/app/cli/commands/rotate.py index bab5d1eb5..925bc9b3b 100644 --- a/src/keri/app/cli/commands/rotate.py +++ b/src/keri/app/cli/commands/rotate.py @@ -10,6 +10,7 @@ from keri import kering from keri.app.cli.common import rotating, existing, config +from keri.core import coring from ... import habbing, agenting, indirecting, delegating, forwarding parser = argparse.ArgumentParser(description='Rotate keys') @@ -23,6 +24,8 @@ parser.add_argument('--file', '-f', help='file path of config options (JSON) for rotation', default="", required=False) parser.add_argument('--next-count', '-C', help='Count of pre-rotated keys (signing keys after next rotation).', default=None, type=int, required=False) +parser.add_argument("--receipt-endpoint", help="Attempt to connect to witness receipt endpoint for witness receipts.", + dest="endpoint", action='store_true') rotating.addRotationArgs(parser) @@ -50,7 +53,7 @@ def rotate(args): """ opts = mergeArgsWithFile(args) - rotDoer = RotateDoer(name=args.name, base=args.base, alias=args.alias, + rotDoer = RotateDoer(name=args.name, base=args.base, alias=args.alias, endpoint=args.endpoint, bran=args.bran, wits=opts.wits, cuts=opts.witsCut, adds=opts.witsAdd, isith=opts.isith, nsith=opts.nsith, @@ -109,7 +112,7 @@ class RotateDoer(doing.DoDoer): to all appropriate witnesses """ - def __init__(self, name, base, bran, alias, isith=None, nsith=None, count=None, + def __init__(self, name, base, bran, alias, endpoint=False, isith=None, nsith=None, count=None, toad=None, wits=None, cuts=None, adds=None, data: list = None): """ Returns DoDoer with all registered Doers needed to perform rotation. @@ -131,6 +134,7 @@ def __init__(self, name, base, bran, alias, isith=None, nsith=None, count=None, self.count = count self.toad = toad self.data = data + self.endpoint = endpoint self.wits = wits if wits is not None else [] self.cuts = cuts if cuts is not None else [] @@ -138,8 +142,8 @@ def __init__(self, name, base, bran, alias, isith=None, nsith=None, count=None, self.hby = existing.setupHby(name=name, base=base, bran=bran) self.hbyDoer = habbing.HaberyDoer(habery=self.hby) # setup doer - self.swain = delegating.Boatswain(hby=self.hby) - self.postman = forwarding.Postman(hby=self.hby) + self.swain = delegating.Sealer(hby=self.hby) + self.postman = forwarding.Poster(hby=self.hby) self.mbx = indirecting.MailboxDirector(hby=self.hby, topics=["/receipt"]) doers = [self.hbyDoer, self.mbx, self.swain, self.postman, doing.doify(self.rotateDo)] @@ -159,6 +163,9 @@ def rotateDo(self, tymth, tock=0.0): if hab is None: raise kering.ConfigurationError(f"Alias {self.alias} is invalid") + receiptor = agenting.Receiptor(hby=self.hby) + self.extend([receiptor]) + if self.wits: if self.adds or self.cuts: raise kering.ConfigurationError("you can only specify witnesses or cuts and add") @@ -167,25 +174,37 @@ def rotateDo(self, tymth, tock=0.0): # wits= [a,b,c] wits=[b, z] self.cuts = set(ewits) - set(self.wits) self.adds = set(self.wits) - set(ewits) + if self.endpoint: + for wit in self.adds: + yield from receiptor.catchup(hab.pre, wit) hab.rotate(isith=self.isith, nsith=self.nsith, ncount=self.count, toad=self.toad, cuts=list(self.cuts), adds=list(self.adds), data=self.data) if hab.kever.delegator: - self.swain.msgs.append(dict(alias=self.alias, pre=hab.pre, sn=hab.kever.sn)) + self.swain.delegation(pre=hab.pre, sn=hab.kever.sn) print("Waiting for delegation approval...") - while not self.swain.cues: + while not self.swain.complete(hab.kever.prefixer, coring.Seqner(sn=hab.kever.sn)): + yield self.tock + + elif hab.kever.wits: + if self.endpoint: + yield from receiptor.receipt(hab.pre, sn=hab.kever.sn) + else: + for wit in self.adds: + self.mbx.addPoller(hab, witness=wit) + + print("Waiting for witness receipts...") + witDoer = agenting.WitnessReceiptor(hby=self.hby) + self.extend(doers=[witDoer]) yield self.tock - witDoer = agenting.WitnessReceiptor(hby=self.hby) - self.extend(doers=[witDoer]) - yield self.tock + witDoer.msgs.append(dict(pre=hab.pre)) + while not witDoer.cues: + _ = yield self.tock - if hab.kever.wits: - witDoer.msgs.append(dict(pre=hab.pre)) - while not witDoer.cues: - _ = yield self.tock + self.remove([witDoer]) if hab.kever.delegator: yield from self.postman.sendEvent(hab=hab, fn=hab.kever.sn) @@ -195,7 +214,7 @@ def rotateDo(self, tymth, tock=0.0): for idx, verfer in enumerate(hab.kever.verfers): print(f'\tPublic key {idx + 1}: {verfer.qb64}') - toRemove = [self.hbyDoer, witDoer, self.swain, self.mbx, self.postman] + toRemove = [self.hbyDoer, self.swain, self.mbx, self.postman, receiptor] self.remove(toRemove) return diff --git a/src/keri/app/cli/commands/wallet/__init__.py b/src/keri/app/cli/commands/ssh/__init__.py similarity index 100% rename from src/keri/app/cli/commands/wallet/__init__.py rename to src/keri/app/cli/commands/ssh/__init__.py diff --git a/src/keri/app/cli/commands/ssh/export.py b/src/keri/app/cli/commands/ssh/export.py new file mode 100644 index 000000000..0f0eff8da --- /dev/null +++ b/src/keri/app/cli/commands/ssh/export.py @@ -0,0 +1,87 @@ +# -*- encoding: utf-8 -*- +""" +KERI +keri.kli.commands module + +""" +import argparse +import os +import stat +from pathlib import Path + +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ed25519 +from hio import help +from hio.base import doing + +from keri.app.cli.common import existing +from keri.kering import ConfigurationError + +logger = help.ogler.getLogger() + +parser = argparse.ArgumentParser(description='Export keys of specified identifier for use with SSH') +parser.set_defaults(handler=lambda args: handler(args), + transferable=True) +parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True) +parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore', + required=False, default="") +parser.add_argument('--alias', '-a', help='human readable alias for the new identifier prefix', default=None) +parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)', + dest="bran", default=None) # passcode => bran + +parser.add_argument("--private", help="export private key instead of public key", action="store_true") +parser.add_argument("--username", help="override file name for the key to export", default=None) + + +def handler(args): + kwa = dict(args=args) + return [doing.doify(export, **kwa)] + + +def export(tymth, tock=0.0, **opts): + """ Command line status handler + + """ + _ = (yield tock) + args = opts["args"] + name = args.name + alias = args.alias + base = args.base + bran = args.bran + private = args.private + filename = args.username if args.username else alias + home = str(Path.home()) + + try: + with existing.existingHby(name=name, base=base, bran=bran) as hby: + if alias is None: + alias = existing.aliasInput(hby) + + hab = hby.habByName(alias) + if private: + signer = hab.ks.pris.get(hab.kever.verfers[0].qb64, + decrypter=hab.mgr.decrypter) + sigkey = ed25519.Ed25519PrivateKey.from_private_bytes(signer.raw) + pem = sigkey.private_bytes(encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.OpenSSH, + encryption_algorithm=serialization.NoEncryption()) + + f = open(os.path.join(home, ".ssh", filename), "w") + for line in pem.splitlines(keepends=True): + f.write(line.decode("utf-8")) + f.close() + os.chmod(os.path.join(home, ".ssh", filename), stat.S_IRUSR | stat.S_IWUSR) + + else: + verkey = ed25519.Ed25519PublicKey.from_public_bytes(hab.kever.verfers[0].raw) + pem = verkey.public_bytes(encoding=serialization.Encoding.OpenSSH, + format=serialization.PublicFormat.OpenSSH) + + f = open(os.path.join(home, ".ssh", f"{filename}.pub"), "w") + for line in pem.splitlines(keepends=True): + f.write(line.decode("utf-8")) + + + except ConfigurationError as e: + print(f"identifier prefix for {name} does not exist, incept must be run first", ) + return -1 diff --git a/src/keri/app/cli/commands/status.py b/src/keri/app/cli/commands/status.py index c3f33fd9b..787bae9ad 100644 --- a/src/keri/app/cli/commands/status.py +++ b/src/keri/app/cli/commands/status.py @@ -10,7 +10,7 @@ from hio.base import doing from keri.app.cli.common import displaying, existing -from keri.core import coring +from keri.core import coring, serdering from keri.kering import ConfigurationError logger = help.ogler.getLogger() @@ -60,7 +60,7 @@ def status(tymth, tock=0.0, **opts): cloner = hab.db.clonePreIter(pre=hab.pre, fn=0) # create iterator at 0 for msg in cloner: - srdr = coring.Serder(raw=msg) + srdr = serdering.SerderKERI(raw=msg) print(srdr.pretty(size=10000)) print() diff --git a/src/keri/app/cli/commands/vc/issue.py b/src/keri/app/cli/commands/vc/create.py similarity index 73% rename from src/keri/app/cli/commands/vc/issue.py rename to src/keri/app/cli/commands/vc/create.py index c834c12a7..e30b632dc 100644 --- a/src/keri/app/cli/commands/vc/issue.py +++ b/src/keri/app/cli/commands/vc/create.py @@ -5,9 +5,11 @@ from hio.base import doing from keri import kering -from keri.app import indirecting, habbing, grouping, connecting +from keri.app import indirecting, habbing, grouping, connecting, forwarding, signing, notifying from keri.app.cli.common import existing -from keri.vc import proving +from keri.core import coring, eventing, serdering +from keri.help import helping +from keri.peer import exchanging from keri.vdr import credentialing, verifying logger = help.ogler.getLogger() @@ -29,8 +31,6 @@ parser.add_argument('--data', '-d', help='Credential data, \'@\' allowed', default=None, action="store", required=False) parser.add_argument('--credential', help='Full credential, \'@\' allowed', default=None, action="store", required=False) -parser.add_argument('--out', '-o', help='Name of file for credential output', default="credential.json", action="store", - required=False) parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore', required=False, default="") parser.add_argument('--alias', '-a', help='human readable alias for the new identifier prefix', required=True) @@ -38,6 +38,7 @@ action="store_true") parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)', dest="bran", default=None) # passcode => bran +parser.add_argument("--time", help="timestamp for the credential creation", required=False, default=None) def issueCredential(args): @@ -97,7 +98,7 @@ def issueCredential(args): edges=edges, rules=rules, credential=credential, - out=args.out, + timestamp=args.time, private=args.private) doers = [issueDoer] @@ -111,7 +112,7 @@ class CredentialIssuer(doing.DoDoer): """ def __init__(self, name, alias, base, bran, registryName=None, schema=None, edges=None, recipient=None, data=None, - rules=None, credential=None, out=None, private=False): + rules=None, credential=None, timestamp=None, private=False): """ Create DoDoer for issuing a credential and managing the processes needed to complete issuance Parameters: @@ -127,17 +128,27 @@ def __init__(self, name, alias, base, bran, registryName=None, schema=None, edge """ self.name = name - self.alias = alias + self.registryName = registryName + self.timestamp = timestamp self.hby = existing.setupHby(name=name, base=base, bran=bran) + self.hab = self.hby.habByName(alias) + if self.hab is None: + raise ValueError(f"invalid alias {alias}") + self.rgy = credentialing.Regery(hby=self.hby, name=name, base=base) self.hbyDoer = habbing.HaberyDoer(habery=self.hby) # setup doer self.counselor = grouping.Counselor(hby=self.hby) self.registrar = credentialing.Registrar(hby=self.hby, rgy=self.rgy, counselor=self.counselor) self.org = connecting.Organizer(hby=self.hby) + self.postman = forwarding.Poster(hby=self.hby) + notifier = notifying.Notifier(self.hby) + mux = grouping.Multiplexor(self.hby, notifier=notifier) + exc = exchanging.Exchanger(hby=self.hby, handlers=[]) + grouping.loadHandlers(exc, mux) self.verifier = verifying.Verifier(hby=self.hby, reger=self.rgy.reger) mbx = indirecting.MailboxDirector(hby=self.hby, topics=["/receipt", "/multisig", "/credential"], - verifier=self.verifier) + verifier=self.verifier, exc=exc) self.credentialer = credentialing.Credentialer(hby=self.hby, rgy=self.rgy, registrar=self.registrar, verifier=self.verifier) @@ -153,6 +164,9 @@ def __init__(self, name, alias, base, bran, registryName=None, schema=None, edge raise ValueError(f"invalid recipient {recipient}") recp = recp[0]['id'] + if self.timestamp is not None: + data["dt"] = self.timestamp + self.creder = self.credentialer.create(regname=registryName, recp=recp, schema=schema, @@ -160,27 +174,21 @@ def __init__(self, name, alias, base, bran, registryName=None, schema=None, edge rules=rules, data=data, private=private) - print(f"Writing credential {self.creder.said} to credential.json") - f = open(out, mode="w") - json.dump(self.creder.crd, f) - f.close() else: - self.creder = proving.Creder(ked=credential) + self.creder = serdering.SerderACDC(sad=credential) # proving.Creder(ked=credential) self.credentialer.validate(creder=self.creder) - self.credentialer.issue(creder=self.creder) - except kering.ConfigurationError as e: print(f"error issuing credential {e}") return - doers = [self.hbyDoer, mbx, self.counselor, self.registrar, self.credentialer] + doers = [self.hbyDoer, mbx, self.counselor, self.registrar, self.credentialer, self.postman] self.toRemove = list(doers) - doers.extend([doing.doify(self.issueDo)]) + doers.extend([doing.doify(self.createDo)]) super(CredentialIssuer, self).__init__(doers=doers) - def issueDo(self, tymth, tock=0.0): + def createDo(self, tymth, tock=0.0): """ Issue Credential doer method @@ -193,9 +201,45 @@ def issueDo(self, tymth, tock=0.0): self.tock = tock _ = (yield self.tock) + registry = self.rgy.registryByName(self.registryName) + hab = registry.hab + + dt = self.creder.attrib["dt"] if "dt" in self.creder.attrib else helping.nowIso8601() + iserder = registry.issue(said=self.creder.said, dt=dt) + + vcid = iserder.ked["i"] + rseq = coring.Seqner(snh=iserder.ked["s"]) + rseal = eventing.SealEvent(vcid, rseq.snh, iserder.said) + rseal = dict(i=rseal.i, s=rseal.s, d=rseal.d) + + if registry.estOnly: + anc = hab.rotate(data=[rseal]) + + else: + anc = hab.interact(data=[rseal]) + + aserder = serdering.SerderKERI(raw=anc) # coring.Serder(raw=anc) + self.credentialer.issue(self.creder, iserder) + self.registrar.issue(self.creder, iserder, aserder) + + acdc = signing.serialize(self.creder, coring.Prefixer(qb64=iserder.pre), coring.Seqner(sn=iserder.sn), + coring.Saider(qb64=iserder.said)) + + if isinstance(self.hab, habbing.GroupHab): + smids = self.hab.db.signingMembers(pre=self.hab.pre) + smids.remove(self.hab.mhab.pre) + + for recp in smids: # this goes to other participants only as a signaling mechanism + exn, atc = grouping.multisigIssueExn(ghab=self.hab, acdc=acdc, iss=iserder.raw, anc=anc) + self.postman.send(src=self.hab.mhab.pre, + dest=recp, + topic="multisig", + serder=exn, + attachment=atc) + while not self.credentialer.complete(said=self.creder.said): self.rgy.processEscrows() yield self.tock - print(f"{self.creder.said} has been issued.") + print(f"{self.creder.said} has been created.") self.remove(self.toRemove) diff --git a/src/keri/app/cli/commands/vc/export.py b/src/keri/app/cli/commands/vc/export.py index 0cfee7cb2..b18c93232 100644 --- a/src/keri/app/cli/commands/vc/export.py +++ b/src/keri/app/cli/commands/vc/export.py @@ -11,7 +11,7 @@ from hio.base import doing from keri.app.cli.common import existing -from keri.core import eventing, coring +from keri.core import serdering from keri.vdr import credentialing logger = help.ogler.getLogger() @@ -28,7 +28,6 @@ dest="bran", default=None) # passcode => bran parser.add_argument("--said", "-s", help="SAID of the credential to export.", required=True) -parser.add_argument("--signatures", help="export signatures as attachments to the credential", action="store_true") parser.add_argument("--tels", help="export the transaction event logs for the credential and any chained credentials", action="store_true") parser.add_argument("--kels", help="export the key event logs for the issuer's of the credentials", action="store_true") @@ -42,21 +41,18 @@ def export_credentials(args): """ Command line list credential registries handler """ - - sigs = args.signatures tels = args.tels kels = args.kels - chains = args.chains + chains = args.chains if args.chains is not None else {} if args.full: - sigs = tels = kels = chains = True + tels = kels = chains = True ed = ExportDoer(name=args.name, alias=args.alias, base=args.base, bran=args.bran, said=args.said, - sigs=sigs, tels=tels, kels=kels, chains=chains, @@ -66,9 +62,8 @@ def export_credentials(args): class ExportDoer(doing.DoDoer): - def __init__(self, name, alias, base, bran, said, sigs, tels, kels, chains, files): + def __init__(self, name, alias, base, bran, said, tels, kels, chains, files): self.said = said - self.sigs = sigs self.tels = tels self.kels = kels self.chains = chains @@ -101,19 +96,19 @@ def exportDo(self, tymth, tock=0.0): self.outputCred(said=self.said) def outputCred(self, said): - creder, sadsigers, sadcigars = self.rgy.reger.cloneCred(said=said) + creder, *_ = self.rgy.reger.cloneCred(said=said) if self.kels: issr = creder.issuer self.outputKEL(issr) if self.tels: - if creder.status is not None: - self.outputTEL(creder.status) + if creder.regi is not None: + self.outputTEL(creder.regi) self.outputTEL(creder.said) if self.chains: - chains = creder.chains + chains = creder.edge if creder.edge is not None else {} saids = [] for key, source in chains.items(): if key == 'd': @@ -130,15 +125,9 @@ def outputCred(self, said): if self.files: f = open(f"{creder.said}-acdc.cesr", 'w') f.write(creder.raw.decode("utf-8")) - if self.sigs: - f.write(eventing.proofize(sadtsgs=sadsigers, sadcigars=sadcigars, pipelined=True).decode("utf-8")) f.close() else: sys.stdout.write(creder.raw.decode("utf-8")) - if self.sigs: - sys.stdout.write(eventing.proofize(sadtsgs=sadsigers, sadcigars=sadcigars, pipelined=True).decode( - "utf-8")) - sys.stdout.flush() def outputTEL(self, regk): @@ -150,7 +139,7 @@ def outputTEL(self, regk): if f is not None: f.write(msg.decode("utf-8")) else: - serder = coring.Serder(raw=msg) + serder = serdering.SerderKERI(raw=msg) atc = msg[serder.size:] sys.stdout.write(serder.raw.decode("utf-8")) sys.stdout.write(atc.decode("utf-8")) @@ -167,7 +156,7 @@ def outputKEL(self, pre): if f is not None: f.write(msg.decode("utf-8")) else: - serder = coring.Serder(raw=msg) + serder = serdering.SerderKERI(raw=msg) atc = msg[serder.size:] sys.stdout.write(serder.raw.decode("utf-8")) sys.stdout.write(atc.decode("utf-8")) diff --git a/src/keri/app/cli/commands/vc/list.py b/src/keri/app/cli/commands/vc/list.py index bfc30de43..45717c015 100644 --- a/src/keri/app/cli/commands/vc/list.py +++ b/src/keri/app/cli/commands/vc/list.py @@ -121,8 +121,9 @@ def listDo(self, tymth, tock=0.0): for said in saids: print(said.qb64) else: - print(f"Current {'issued' if self.issued else 'received'} credentials for {self.hab.name} ({self.hab.pre}):\n") - creds = self.rgy.reger.cloneCreds(saids) + print(f"Current {'issued' if self.issued else 'received'}" + f" credentials for {self.hab.name} ({self.hab.pre}):\n") + creds = self.rgy.reger.cloneCreds(saids, self.hab.db) for idx, cred in enumerate(creds): sad = cred['sad'] status = cred["status"] diff --git a/src/keri/app/cli/commands/vc/present.py b/src/keri/app/cli/commands/vc/present.py deleted file mode 100644 index 9dedc899a..000000000 --- a/src/keri/app/cli/commands/vc/present.py +++ /dev/null @@ -1,130 +0,0 @@ -# -*- encoding: utf-8 -*- -""" -KERI -keri.kli.commands module - -""" -import argparse - -from hio import help -from hio.base import doing - -from keri.app import connecting, forwarding -from keri.app.cli.common import existing -from keri.core import coring -from keri.vc import protocoling -from keri.vdr import credentialing - -logger = help.ogler.getLogger() - -parser = argparse.ArgumentParser(description='Send credential presentation for specified credential to recipient') -parser.set_defaults(handler=lambda args: present_credential(args), - transferable=True) -parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True) -parser.add_argument('--alias', '-a', help='human readable alias for the identifier to whom the credential was issued', - required=True) -parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore', - required=False, default="") -parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)', - dest="bran", default=None) # passcode => bran - -parser.add_argument("--said", "-s", help="SAID of the credential to present.", required=True) -parser.add_argument("--include", "-i", help="send credential and all other cryptographic artifacts with presentation", - action="store_true") -parser.add_argument("--recipient", "-r", help="alias or qb64 AID ") - - -def present_credential(args): - """ Command line credential presentation handler - - """ - - ed = PresentDoer(name=args.name, - alias=args.alias, - base=args.base, - bran=args.bran, - said=args.said, - recipient=args.recipient, - include=args.include) - return [ed] - - -class PresentDoer(doing.DoDoer): - - def __init__(self, name, alias, base, bran, said, recipient, include): - self.said = said - self.recipient = recipient - self.include = include - - self.hby = existing.setupHby(name=name, base=base, bran=bran) - self.hab = self.hby.habByName(alias) - self.org = connecting.Organizer(hby=self.hby) - self.rgy = credentialing.Regery(hby=self.hby, name=name, base=base) - self.postman = forwarding.Postman(hby=self.hby) - - doers = [self.postman, doing.doify(self.presentDo)] - - super(PresentDoer, self).__init__(doers=doers) - - def presentDo(self, tymth, tock=0.0): - """ Present credential from store and any related material - - Parameters: - tymth (function): injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock (float): injected initial tock value - - Returns: doifiable Doist compatible generator method - - """ - # enter context - self.wind(tymth) - self.tock = tock - _ = (yield self.tock) - - creder = self.rgy.reger.creds.get(self.said) - if creder is None: - raise ValueError(f"invalid credential SAID {self.said}") - - if self.recipient in self.hby.kevers: - recp = self.recipient - else: - recp = self.org.find("alias", self.recipient) - if len(recp) != 1: - raise ValueError(f"invalid recipient {self.recipient}") - recp = recp[0]['id'] - - if self.include: - credentialing.sendCredential(self.hby, hab=self.hab, reger=self.rgy.reger, postman=self.postman, - creder=creder, recp=recp) - - if self.hab.group: - senderHab = self.hab.mhab - else: - senderHab = self.hab - - if senderHab.pre != creder.issuer: - for msg in senderHab.db.cloneDelegation(senderHab.kever): - serder = coring.Serder(raw=msg) - atc = msg[serder.size:] - self.postman.send(src=senderHab.pre, dest=recp, topic="credential", serder=serder, attachment=atc) - - for msg in senderHab.db.clonePreIter(pre=senderHab.pre): - serder = coring.Serder(raw=msg) - atc = msg[serder.size:] - self.postman.send(src=senderHab.pre, dest=recp, topic="credential", serder=serder, attachment=atc) - - exn, atc = protocoling.presentationExchangeExn(hab=senderHab, reger=self.rgy.reger, said=self.said) - self.postman.send(src=senderHab.pre, dest=recp, topic="credential", serder=exn, attachment=atc) - - while True: - while self.postman.cues: - cue = self.postman.cues.popleft() - if "said" in cue and cue["said"] == exn.said: - print("Presentation sent") - toRemove = [self.postman] - self.remove(toRemove) - return True - yield self.tock - yield self.tock - diff --git a/src/keri/app/cli/commands/vc/registry/incept.py b/src/keri/app/cli/commands/vc/registry/incept.py index 2f516ff4f..77c774f9a 100644 --- a/src/keri/app/cli/commands/vc/registry/incept.py +++ b/src/keri/app/cli/commands/vc/registry/incept.py @@ -3,8 +3,13 @@ from hio import help from hio.base import doing -from keri.app import indirecting, habbing, grouping +from keri.app import indirecting, habbing, grouping, forwarding from keri.app.cli.common import existing +from keri.app.habbing import GroupHab +from keri.app.notifying import Notifier +from keri.core import coring, serdering +from keri.core.eventing import SealEvent +from keri.peer import exchanging from keri.vdr import credentialing logger = help.ogler.getLogger() @@ -29,6 +34,9 @@ parser.add_argument('--alias', '-a', help='human readable alias for the new identifier prefix', required=True) parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)', dest="bran", default=None) # passcode => bran +parser.add_argument('--usage', '-u', help='For multisig issuers, a message to other participants about how this' + ' registry is to be used', + default=None) def registryIncept(args): @@ -41,13 +49,14 @@ def registryIncept(args): estOnly = args.establishment_only noBackers = args.no_backers backers = args.backers + usage = args.usage if noBackers and backers: print("--no-backers and --backers can not both be provided") return -1 icpDoer = RegistryInceptor(name=name, base=base, alias=alias, bran=bran, registryName=registryName, - nonce=nonce, estOnly=estOnly, noBackers=noBackers, baks=backers) + nonce=nonce, estOnly=estOnly, noBackers=noBackers, baks=backers, usage=usage) doers = [icpDoer] return doers @@ -58,7 +67,7 @@ class RegistryInceptor(doing.DoDoer): """ - def __init__(self, name, base, alias, bran, registryName, **kwa): + def __init__(self, name, base, alias, bran, registryName, usage, **kwa): """ Create RegistryIncepter to pass message and process cues Parameters: @@ -74,14 +83,21 @@ def __init__(self, name, base, alias, bran, registryName, **kwa): self.name = name self.alias = alias self.registryName = registryName + self.usage = usage self.hby = existing.setupHby(name=name, base=base, bran=bran) self.rgy = credentialing.Regery(hby=self.hby, name=name, base=base) self.hbyDoer = habbing.HaberyDoer(habery=self.hby) # setup doer counselor = grouping.Counselor(hby=self.hby) + self.postman = forwarding.Poster(hby=self.hby) + + notifier = Notifier(self.hby) + mux = grouping.Multiplexor(self.hby, notifier=notifier) + exc = exchanging.Exchanger(hby=self.hby, handlers=[]) + grouping.loadHandlers(exc, mux) - mbx = indirecting.MailboxDirector(hby=self.hby, topics=["/receipt", "/multisig", "/replay"]) + mbx = indirecting.MailboxDirector(hby=self.hby, topics=["/receipt", "/multisig", "/replay"], exc=exc) self.registrar = credentialing.Registrar(hby=self.hby, rgy=self.rgy, counselor=counselor) - doers = [self.hbyDoer, counselor, self.registrar, mbx] + doers = [self.hbyDoer, counselor, self.registrar, self.postman, mbx] self.toRemove = list(doers) doers.extend([doing.doify(self.inceptDo, **kwa)]) @@ -103,7 +119,37 @@ def inceptDo(self, tymth, tock=0.0, **kwa): _ = (yield self.tock) hab = self.hby.habByName(self.alias) - registry = self.registrar.incept(name=self.registryName, pre=hab.pre, conf=kwa) + if hab is None: + raise ValueError(f"{self.alias} is not a valid AID alias") + + estOnly = "estOnly" in kwa and kwa["estOnly"] + registry = self.rgy.makeRegistry(name=self.registryName, prefix=hab.pre, **kwa) + + rseal = SealEvent(registry.regk, "0", registry.regd) + rseal = dict(i=rseal.i, s=rseal.s, d=rseal.d) + if estOnly: + anc = hab.rotate(data=[rseal]) + else: + anc = hab.interact(data=[rseal]) + + aserder = serdering.SerderKERI(raw=bytes(anc)) # coring.Serder(raw=bytes(anc)) + self.registrar.incept(iserder=registry.vcp, anc=aserder) + + if isinstance(hab, GroupHab): + usage = self.usage + if usage is None: + usage = input(f"Please enter a description of the credential registry: ") + + smids = hab.db.signingMembers(pre=hab.pre) + smids.remove(hab.mhab.pre) + + for recp in smids: # this goes to other participants only as a signaling mechanism + exn, atc = grouping.multisigRegistryInceptExn(ghab=hab, vcp=registry.vcp.raw, anc=anc, usage=usage) + self.postman.send(src=hab.mhab.pre, + dest=recp, + topic="multisig", + serder=exn, + attachment=atc) while not self.registrar.complete(pre=registry.regk, sn=0): self.rgy.processEscrows() diff --git a/src/keri/app/cli/commands/vc/registry/list.py b/src/keri/app/cli/commands/vc/registry/list.py index ad7fb1d80..3c299895a 100644 --- a/src/keri/app/cli/commands/vc/registry/list.py +++ b/src/keri/app/cli/commands/vc/registry/list.py @@ -15,7 +15,7 @@ logger = help.ogler.getLogger() -parser = argparse.ArgumentParser(description='List credential registry names and identfiers') +parser = argparse.ArgumentParser(description='List credential registry names and identifiers') parser.set_defaults(handler=lambda args: list_registries(args), transferable=True) parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True) @@ -50,6 +50,5 @@ def registries(tymth, tock=0.0, **opts): print(registry.name, ":", registry.regk, ":", registry.hab.pre) except ConfigurationError as e: - print(e) print(f"identifier prefix for {name} does not exist, incept must be run first", ) return -1 diff --git a/src/keri/app/cli/commands/vc/registry/status.py b/src/keri/app/cli/commands/vc/registry/status.py index 846cb1704..f24f57a50 100644 --- a/src/keri/app/cli/commands/vc/registry/status.py +++ b/src/keri/app/cli/commands/vc/registry/status.py @@ -5,7 +5,7 @@ from keri.app import indirecting, habbing, grouping from keri.app.cli.common import existing -from keri.core import coring +from keri.core import coring, serdering from keri.vdr import credentialing logger = help.ogler.getLogger() @@ -98,7 +98,7 @@ def statusDo(self, tymth, tock=0.0): if self.verbose: cloner = reg.reger.clonePreIter(pre=reg.regk, fn=0) # create iterator at 0 for msg in cloner: - srdr = coring.Serder(raw=msg) + srdr = serdering.SerderKERI(raw=msg) print(srdr.pretty()) print() diff --git a/src/keri/app/cli/commands/vc/revoke.py b/src/keri/app/cli/commands/vc/revoke.py index a56bf7104..ed2c41ec9 100644 --- a/src/keri/app/cli/commands/vc/revoke.py +++ b/src/keri/app/cli/commands/vc/revoke.py @@ -8,10 +8,12 @@ from hio.base import doing from keri import kering -from keri.app import indirecting, habbing, grouping, forwarding, connecting +from keri.app import indirecting, habbing, grouping, forwarding, connecting, notifying from keri.app.cli.common import existing from keri.app.habbing import GroupHab -from keri.core import coring +from keri.core import coring, serdering +from keri.core.eventing import SealEvent +from keri.peer import exchanging from keri.vdr import credentialing, verifying parser = argparse.ArgumentParser(description='Revoke a verifiable credential') @@ -55,28 +57,33 @@ def __init__(self, name, alias, said, base, bran, registryName, send, timestamp, self.counselor = grouping.Counselor(hby=self.hby) self.registrar = credentialing.Registrar(hby=self.hby, rgy=self.rgy, counselor=self.counselor) self.verifier = verifying.Verifier(hby=self.hby, reger=self.rgy.reger) - self.postman = forwarding.Postman(hby=self.hby) + self.postman = forwarding.Poster(hby=self.hby) + notifier = notifying.Notifier(self.hby) + mux = grouping.Multiplexor(self.hby, notifier=notifier) + exc = exchanging.Exchanger(hby=self.hby, handlers=[]) + grouping.loadHandlers(exc, mux) mbx = indirecting.MailboxDirector(hby=self.hby, topics=["/receipt", "/multisig", "/credential"], - verifier=self.verifier) + verifier=self.verifier, exc=exc) doers = [self.hbyDoer, mbx, self.counselor, self.registrar, self.postman] self.toRemove = list(doers) doers.extend([doing.doify(self.revokeDo)]) super(RevokeDoer, self).__init__(doers=doers, **kwa) - def revokeDo(self, tymth, tock=0.0, **opts): - """ + def revokeDo(self, tymth, tock=0.0): + """ Revoke Credential doer method - Parameters: - tymth: - tock: - **opts: - Returns: + Parameters: + tymth (function): injected function wrapper closure returned by .tymen() of + Tymist instance. Calling tymth() returns associated Tymist .tyme. + tock (float): injected initial tock value """ - yield self.tock + self.wind(tymth) + self.tock = tock + _ = (yield self.tock) registry = self.rgy.registryByName(self.registryName) if registry is None: @@ -93,42 +100,63 @@ def revokeDo(self, tymth, tock=0.0, **opts): if self.timestamp is not None: kwargs['dt'] = self.timestamp - self.registrar.revoke(regk=registry.regk, said=creder.said, **kwargs) + registry = self.rgy.regs[registry.regk] + hab = registry.hab - while not self.registrar.complete(creder.said, sn=1): - yield self.tock + state = registry.tever.vcState(vci=creder.said) + if state is None or state.et not in (coring.Ilks.iss, coring.Ilks.rev): + raise kering.ValidationError(f"credential {creder.said} not is correct state for revocation") + + rserder = registry.revoke(said=creder.said, **kwargs) + + vcid = rserder.ked["i"] + rseq = coring.Seqner(snh=rserder.ked["s"]) + rseal = SealEvent(vcid, rseq.snh, rserder.said) + rseal = dict(i=rseal.i, s=rseal.s, d=rseal.d) - recps = [creder.subject['i']] if 'i' in creder.subject else [] - if self.send is not None: - recps.extend(self.send) + if registry.estOnly: + anc = hab.rotate(data=[rseal]) + else: + anc = hab.interact(data=[rseal]) - senderHab = self.hab.mhab if isinstance(self.hab, GroupHab) else self.hab + aserder = serdering.SerderKERI(raw=bytes(anc)) + self.registrar.revoke(creder, rserder, aserder) - if len(recps) > 0: + senderHab = self.hab + if isinstance(self.hab, GroupHab): + senderHab = self.hab.mhab + smids = self.hab.db.signingMembers(pre=self.hab.pre) + smids.remove(self.hab.mhab.pre) + + for recp in smids: # this goes to other participants only as a signaling mechanism + exn, atc = grouping.multisigRevokeExn(ghab=self.hab, said=creder.said, rev=rserder.raw, anc=anc) + self.postman.send(src=self.hab.mhab.pre, + dest=recp, + topic="multisig", + serder=exn, + attachment=atc) + + while not self.registrar.complete(creder.said, sn=1): + yield self.tock + + if self.hab.witnesser() and 'i' in creder.attrib: + recp = creder.attrib['i'] msgs = [] for msg in self.hby.db.clonePreIter(pre=creder.issuer): - serder = coring.Serder(raw=msg) + serder = serdering.SerderKERI(raw=msg) atc = msg[serder.size:] msgs.append((serder, atc)) for msg in self.rgy.reger.clonePreIter(pre=creder.said): - serder = coring.Serder(raw=msg) + serder = serdering.SerderKERI(raw=msg) atc = msg[serder.size:] msgs.append((serder, atc)) - sent = 0 - for send in recps: - if send in self.hby.kevers: - recp = send - else: - recp = self.org.find("alias", send) - if len(recp) != 1: - raise ValueError(f"invalid recipient {send}") - recp = recp[0]['id'] - for (serder, atc) in msgs: - self.postman.send(src=senderHab.pre, dest=recp, topic="credential", serder=serder, attachment=atc) - sent += 1 - - while not len(self.postman.cues) == sent: + for (serder, atc) in msgs: + self.postman.send(src=senderHab.pre, dest=recp, topic="credential", serder=serder, + attachment=atc) + + last = msgs[-1][0] + while not self.postman.sent(said=last.said): yield self.tock except kering.ValidationError as ex: diff --git a/src/keri/app/cli/commands/wallet/start.py b/src/keri/app/cli/commands/wallet/start.py deleted file mode 100644 index ab8509b0d..000000000 --- a/src/keri/app/cli/commands/wallet/start.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- encoding: utf-8 -*- -""" -KERI -keri.kli.witness module - -Witness command line interface -""" -import argparse -import logging - -from keri import __version__ -from keri import help -from keri.app import indirecting, storing, habbing -from keri.app.cli.common import existing -from keri.core import scheming -from keri.peer import exchanging -from keri.vc import walleting, protocoling -from keri.vdr import verifying - -d = "Runs KERI Agent controller.\n" -d += "Example:\nagent -t 5621\n" -parser = argparse.ArgumentParser(description=d) -parser.set_defaults(handler=lambda args: launch(args)) -parser.add_argument('-V', '--version', - action='version', - version=__version__, - help="Prints out version of script runner.") -parser.add_argument('-n', '--name', - action='store', - default="wallet", - help="Name of controller. Default is wallet.") - - -def launch(args): - help.ogler.level = logging.INFO - help.ogler.reopen(name=args.name, temp=True, clear=True) - - return runWallet(name=args.name) - - -def runWallet(name="wallet", base="", bran=None): - """ - Setup and run one wallet - """ - - hby = existing.setupHby(name=name, base=base, bran=bran) - hbyDoer = habbing.HaberyDoer(habery=hby) # setup doer - doers = [hbyDoer] - - verifier = verifying.Verifier(hby=hby) - wallet = walleting.Wallet(reger=verifier.reger, name=name) - walletDoer = walleting.WalletDoer(hby=hby, verifier=verifier) - - jsonSchema = scheming.JSONSchema(resolver=scheming.CacheResolver(db=hby.db)) - issueHandler = protocoling.IssueHandler(hby=hby, verifier=verifier) - requestHandler = protocoling.PresentationRequestHandler(hby=hby, wallet=wallet, typ=jsonSchema) - exchanger = exchanging.Exchanger(db=hby.db, handlers=[issueHandler, requestHandler]) - - mbx = storing.Mailboxer(name=name) - rep = storing.Respondant(hby=hby, mbx=mbx) - mdir = indirecting.MailboxDirector(hby=hby, exc=exchanger, rep=rep, topics=["/receipt", "/replay", "/credential"]) - - doers.extend([exchanger, mdir, rep, walletDoer]) - - return doers \ No newline at end of file diff --git a/src/keri/app/cli/commands/watcher/rotate.py b/src/keri/app/cli/commands/watcher/rotate.py deleted file mode 100644 index 19d4c8e45..000000000 --- a/src/keri/app/cli/commands/watcher/rotate.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- encoding: utf-8 -*- -""" -keri.kli.commands.watcher module - -""" -import argparse - -from hio.base import doing - -from keri import help -from keri.app import watching -from keri.app.cli.common import existing - -parser = argparse.ArgumentParser(description='Rotate watcher prefix') -parser.set_defaults(handler=lambda args: rotateWatcher(args)) -parser.add_argument('--name', '-n', help='Human readable reference', required=True) -parser.add_argument("--watcher", "-w", help="QB64 identifier prefix of watcher to rotate", default="", required=True) - -logger = help.ogler.getLogger() - - -def rotateWatcher(args): - name = args.name - wat = args.watcher - - watr = WatcherRotate(name=name, wat=wat) - return [watr] - - -class WatcherRotate(doing.DoDoer): - - def __init__(self, name, wat, **kwa): - self.watcher = wat - self.hab, doers = existing.setupHabitat(name=name) - self.rotr = watching.WatcherClientRotateDoer(hab=self.hab) - doers.extend([self.rotr]) - self.toRemove = list(doers) - - doers.extend([doing.doify(self.rotateDo)]) - - super(WatcherRotate, self).__init__(doers=doers, **kwa) - - - def rotateDo(self, tymth, tock=0.0, **opts): - # enter context - yield self.tock - - self.rotr.msgs.append(self.watcher) - - while not self.rotr.cues: - yield self.tock - - habr = self.hab.db.habs.get(self.hab.name) - print("New Watcher Set:") - for wat in habr.watchers: - print("\t{}".format(wat)) - - self.remove(self.toRemove) diff --git a/src/keri/app/cli/commands/watcher/start.py b/src/keri/app/cli/commands/watcher/start.py index 65d72b74a..5c9801daf 100644 --- a/src/keri/app/cli/commands/watcher/start.py +++ b/src/keri/app/cli/commands/watcher/start.py @@ -11,7 +11,7 @@ from hio.core.tcp import serving from keri import help -from keri.app import directing, indirecting, watching, habbing, storing +from keri.app import directing, indirecting, habbing, storing parser = argparse.ArgumentParser(description='Start watcher') parser.set_defaults(handler=lambda args: startWatcher(args)) @@ -64,8 +64,6 @@ def setupWatcher(name="watcher", controller=None, alias="watcher", base="", bran mbx = storing.Mailboxer(name=name) rep = storing.Respondant(hby=hby, mbx=mbx) - kiwiServer = watching.KiwiServer(hab=hab, app=app, rep=rep, controller=controller) - httpEnd = indirecting.HttpEnd(hab=hab, app=app, rep=rep, mbx=mbx) app.add_route("/", httpEnd) @@ -77,6 +75,6 @@ def setupWatcher(name="watcher", controller=None, alias="watcher", base="", bran directant = directing.Directant(hab=hab, server=server) - doers.extend([directant, serverDoer, httpServerDoer, httpEnd, rep, kiwiServer]) + doers.extend([directant, serverDoer, httpServerDoer, httpEnd, rep]) return doers diff --git a/src/keri/app/cli/commands/witness/demo.py b/src/keri/app/cli/commands/witness/demo.py index 1566dea5d..dd83dc849 100644 --- a/src/keri/app/cli/commands/witness/demo.py +++ b/src/keri/app/cli/commands/witness/demo.py @@ -7,18 +7,20 @@ """ import argparse +import logging from hio.base import doing from keri.app import habbing, indirecting, configing from keri.core.coring import Salter +from keri import help parser = argparse.ArgumentParser(description="Run a demo collection of witnesses") parser.set_defaults(handler=lambda args: demo(args)) -# help.ogler.level = logging.INFO -# logger = help.ogler.getLogger() +help.ogler.level = logging.INFO +logger = help.ogler.getLogger() def demo(_): diff --git a/src/keri/app/cli/commands/witness/start.py b/src/keri/app/cli/commands/witness/start.py index bc3bf0d0d..f8ace796d 100644 --- a/src/keri/app/cli/commands/witness/start.py +++ b/src/keri/app/cli/commands/witness/start.py @@ -10,7 +10,7 @@ from keri import __version__ from keri import help -from keri.app import directing, indirecting, habbing, keeping +from keri.app import directing, indirecting, habbing, keeping, configing from keri.app.cli.common import existing d = "Runs KERI witness controller.\n" @@ -28,7 +28,7 @@ parser.add_argument('-T', '--tcp', action='store', default=5632, - help="Local port number the HTTP server listens on. Default is 5632.") + help="Local port number the TCP server listens on. Default is 5632.") parser.add_argument('-n', '--name', action='store', default="witness", @@ -38,6 +38,15 @@ parser.add_argument('--alias', '-a', help='human readable alias for the new identifier prefix', required=True) parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)', dest="bran", default=None) # passcode => bran +parser.add_argument("--config-dir", "-c", dest="configDir", help="directory override for configuration data") +parser.add_argument('--config-file', + dest="configFile", + action='store', + default=None, + help="configuration filename override") +parser.add_argument("--keypath", action="store", required=False, default=None) +parser.add_argument("--certpath", action="store", required=False, default=None) +parser.add_argument("--cafilepath", action="store", required=False, default=None) def launch(args): @@ -54,13 +63,19 @@ def launch(args): alias=args.alias, bran=args.bran, tcp=int(args.tcp), - http=int(args.http)) + http=int(args.http), + configDir=args.configDir, + configFile=args.configFile, + keypath=args.keypath, + certpath=args.certpath, + cafilepath=args.cafilepath) logger.info("\n******* Ended Witness for %s listening: http/%s, tcp/%s" ".******\n\n", args.name, args.http, args.tcp) -def runWitness(name="witness", base="", alias="witness", bran="", tcp=5631, http=5632, expire=0.0): +def runWitness(name="witness", base="", alias="witness", bran="", tcp=5631, http=5632, expire=0.0, + configDir="", configFile="", keypath=None, certpath=None, cafilepath=None): """ Setup and run one witness """ @@ -72,10 +87,14 @@ def runWitness(name="witness", base="", alias="witness", bran="", tcp=5631, http aeid = ks.gbls.get('aeid') + cf = None + if configFile is not None: + cf = configing.Configer(name=configFile, headDirPath=configDir, temp=False, reopen=True, clear=False) + if aeid is None: - hby = habbing.Habery(name=name, base=base, bran=bran) + hby = habbing.Habery(name=name, base=base, bran=bran, cf=cf) else: - hby = existing.setupHby(name=name, base=base, bran=bran) + hby = existing.setupHby(name=name, base=base, bran=bran, cf=cf) hbyDoer = habbing.HaberyDoer(habery=hby) # setup doer doers = [hbyDoer] @@ -83,6 +102,9 @@ def runWitness(name="witness", base="", alias="witness", bran="", tcp=5631, http doers.extend(indirecting.setupWitness(alias=alias, hby=hby, tcpPort=tcp, - httpPort=http)) + httpPort=http, + keypath=keypath, + certpath=certpath, + cafilepath=cafilepath)) directing.runController(doers=doers, expire=expire) diff --git a/src/keri/app/cli/common/displaying.py b/src/keri/app/cli/common/displaying.py index e3a999390..65745f2cf 100644 --- a/src/keri/app/cli/common/displaying.py +++ b/src/keri/app/cli/common/displaying.py @@ -6,6 +6,7 @@ import sys from keri.app.cli.common import terming +from keri.app.habbing import GroupHab from keri.db import dbing @@ -26,7 +27,7 @@ def printIdentifier(hby, pre, label="Identifier"): dgkey = dbing.dgKey(ser.preb, ser.saidb) wigs = hab.db.getWigs(dgkey) dgkey = dbing.dgKey(ser.preb, kever.lastEst.d) - anchor = hab.db.getAes(dgkey) + seal = hab.db.getAes(dgkey) print(f"Alias: \t{hab.name}") print("{}: {}".format(label, pre)) @@ -34,13 +35,13 @@ def printIdentifier(hby, pre, label="Identifier"): if kever.delegated: print("Delegated Identifier") sys.stdout.write(f" Delegator: {kever.delegator} ") - if anchor: + if seal: print(f"{terming.Colors.OKGREEN}{terming.Symbols.CHECKMARK} Anchored{terming.Colors.ENDC}") else: print(f"{terming.Colors.FAIL}{terming.Symbols.FAILED} Not Anchored{terming.Colors.ENDC}") print() - if hab.group: + if isinstance(hab, GroupHab): print("Group Identifier") sys.stdout.write(f" Local Indentifier: {hab.mhab.pre} ") if hab.accepted: @@ -60,7 +61,7 @@ def printIdentifier(hby, pre, label="Identifier"): print("{}: {}".format(label, hab.pre)) print("Seq No:\t{}".format(0)) - if hab.group: + if isinstance(hab, GroupHab): print("Group Identifier") sys.stdout.write(f" Local Indentifier: {hab.mhab.pre} ") if hab.accepted: diff --git a/src/keri/app/cli/common/existing.py b/src/keri/app/cli/common/existing.py index 05fbff721..287d4e033 100644 --- a/src/keri/app/cli/common/existing.py +++ b/src/keri/app/cli/common/existing.py @@ -85,7 +85,7 @@ def existingHab(name, alias, base="", bran=None): Parameters: name(str): name of habitat to create - alias(str): alias for the identfier required + alias(str): alias for the identifier required base(str): optional base directory prefix bran(str): optional passcode if the Habery was created encrypted """ diff --git a/src/keri/app/cli/common/incepting.py b/src/keri/app/cli/common/incepting.py index 8aaadc4e5..6a0789c91 100644 --- a/src/keri/app/cli/common/incepting.py +++ b/src/keri/app/cli/common/incepting.py @@ -10,12 +10,12 @@ def addInceptingArgs(parser): """ Add command line arguments for each of the properties in InceptOptions """ - parser.add_argument('--transferable', '-tf', type=bool, default=None, + parser.add_argument('--transferable', '-tf', action="store_true", help='Whether the prefix is transferable or non-transferable') parser.add_argument('--wits', '-w', default=[], required=False, action="append", metavar="", help='New set of witnesses, replaces all existing witnesses. Can appear multiple times') parser.add_argument('--toad', '-t', default=None, required=False, type=int, - help='int or str hex of witness threshold (threshold of acceptable duplicity)',) + help='int or str hex of witness threshold (threshold of accountable duplicity)',) parser.add_argument('--icount', '-ic', default=None, required=False, help='incepting key count for number of keys used for inception') parser.add_argument('--isith', '-s', default=None, required=False, diff --git a/src/keri/app/cli/kli.py b/src/keri/app/cli/kli.py index 49ca880dd..a0bde194f 100644 --- a/src/keri/app/cli/kli.py +++ b/src/keri/app/cli/kli.py @@ -25,9 +25,13 @@ def main(): directing.runController(doers=doers, expire=0.0) except Exception as ex: - # print(f"ERR: {ex}") - # return -1 - raise ex + import os + if os.getenv('DEBUG_KLI'): + import traceback + traceback.print_exc() + else: + print(f"ERR: {ex}") + return -1 if __name__ == "__main__": diff --git a/src/keri/app/configing.py b/src/keri/app/configing.py index 4e8da3210..41a807a72 100644 --- a/src/keri/app/configing.py +++ b/src/keri/app/configing.py @@ -23,7 +23,7 @@ def openCF(cls=None, filed=True, **kwa): """ if cls == None: # can't reference class before its defined below cls = Configer - return filing.openFiler(cls=cls, filed=True, **kwa) + return filing.openFiler(cls=cls, filed=filed, **kwa) class Configer(filing.Filer): @@ -83,7 +83,7 @@ def __init__(self, name="conf", base="main", filed=True, mode="r+b", """ super(Configer, self).__init__(name=name, base=base, - filed=True, + filed=filed, mode=mode, fext=fext, **kwa) diff --git a/src/keri/app/connecting.py b/src/keri/app/connecting.py index 3c9892177..7ab81dd13 100644 --- a/src/keri/app/connecting.py +++ b/src/keri/app/connecting.py @@ -23,7 +23,7 @@ def __init__(self, hby): self.hby = hby def update(self, pre, data): - """ Add or update contact information in data for the identfier prefix + """ Add or update contact information in data for the identifier prefix Parameters: pre (str): qb64 identifier prefix of contact information to update @@ -126,7 +126,7 @@ def get(self, pre, field=None): return data def list(self): - """ Return list of all contact information for all remote identfiers + """ Return list of all contact information for all remote identifiers Returns: list: All contact information diff --git a/src/keri/app/delegating.py b/src/keri/app/delegating.py index 959855815..433332128 100644 --- a/src/keri/app/delegating.py +++ b/src/keri/app/delegating.py @@ -8,17 +8,18 @@ from hio import help from hio.base import doing -from hio.help import decking from . import agenting, forwarding -from ..core import coring +from .habbing import GroupHab +from .. import kering +from ..core import coring, eventing, serdering from ..db import dbing from ..peer import exchanging logger = help.ogler.getLogger() -class Boatswain(doing.DoDoer): +class Sealer(doing.DoDoer): """ Sends messages to Delegator of an identifier and wait for the anchoring event to be processed to ensure the inception or rotation event has been approved by the delegator. @@ -27,7 +28,7 @@ class Boatswain(doing.DoDoer): """ - def __init__(self, hby, msgs=None, cues=None, **kwa): + def __init__(self, hby, proxy=None, **kwa): """ For the current event, gather the current set of witnesses, send the event, gather all receipts and send them to all other witnesses @@ -40,130 +41,156 @@ def __init__(self, hby, msgs=None, cues=None, **kwa): """ self.hby = hby - self.msgs = msgs if msgs is not None else decking.Deck() - self.cues = cues if cues is not None else decking.Deck() - self.postman = forwarding.Postman(hby=hby) + self.postman = forwarding.Poster(hby=hby) self.witq = agenting.WitnessInquisitor(hby=hby) + self.witDoer = agenting.Receiptor(hby=self.hby) + self.proxy = proxy - super(Boatswain, self).__init__(doers=[self.witq, self.postman, doing.doify(self.anchorDo)], **kwa) + super(Sealer, self).__init__(doers=[self.witq, self.witDoer, self.postman, doing.doify(self.escrowDo)], + **kwa) - def anchorDo(self, tymth=None, tock=0.0): - """ - Returns doifiable Doist compatible generator method (doer dog) + def delegation(self, pre, sn=None, proxy=None): + if pre not in self.hby.habs: + raise kering.ValidationError(f"{pre} is not a valid local AID for delegation") - Usage: - add result of doify on this method to doers list + # load the hab of the delegated identifier to anchor + hab = self.hby.habs[pre] + delpre = hab.kever.delegator # get the delegator identifier + if delpre not in hab.kevers: + raise kering.ValidationError(f"delegator {delpre} not found, unable to process delegation") - Parameters: - tymth is injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock is injected initial tock value + dkever = hab.kevers[delpre] # and the delegator's kever + sn = sn if sn is not None else hab.kever.sner.num - """ - self.wind(tymth) - self.tock = tock - _ = (yield self.tock) + # load the event and signatures + evt = hab.makeOwnEvent(sn=sn) - while True: - while self.msgs: - msg = self.msgs.popleft() - pre = msg["pre"] + smids = [] + if isinstance(hab, GroupHab): + phab = hab.mhab + smids = hab.smids + elif hab.kever.sn > 0: + phab = hab + elif proxy is not None: + phab = proxy + elif self.proxy is not None: + phab = self.proxy + else: + raise kering.ValidationError("no proxy to send messages for delegation") - if pre not in self.hby.habs: - continue + # Send exn message for notification purposes + exn, atc = delegateRequestExn(phab, delpre=delpre, evt=bytes(evt), aids=smids) - # load the hab of the delegated identifier to anchor - hab = self.hby.habs[pre] - alias = hab.name - delpre = hab.kever.delegator # get the delegator identifier - dkever = hab.kevers[delpre] # and the delegator's kever + self.postman.send(hab=phab, dest=hab.kever.delegator, topic="delegate", serder=exn, attachment=atc) - sn = msg["sn"] if "sn" in msg else hab.kever.sner.num + srdr = serdering.SerderKERI(raw=evt) + del evt[:srdr.size] + self.postman.send(hab=phab, dest=delpre, topic="delegate", serder=srdr, attachment=evt) - # load the event and signatures - evt = hab.makeOwnEvent(sn=sn) - srdr = coring.Serder(raw=evt) - del evt[:srdr.size] + seal = dict(i=srdr.pre, s=srdr.snh, d=srdr.said) + self.witq.query(hab=phab, pre=dkever.prefixer.qb64, anchor=seal) - smids = [] - if hab.group: - phab = hab.mhab - smids = hab.smids - elif srdr.ked["t"] == coring.Ilks.dip: # are we incepting a new event? - phab = self.proxy(alias, hab.kever) # create a proxy identifier for comms - if phab.kever.wits: - witDoer = agenting.WitnessReceiptor(hby=self.hby) - self.extend([witDoer]) + self.hby.db.dune.pin(keys=(srdr.pre, srdr.said), val=srdr) - witDoer.msgs.append(dict(pre=phab.pre)) - while not witDoer.cues: - _ = yield self.tock + def complete(self, prefixer, seqner, saider=None): + """ Check for completed delegation protocol for the specific event - self.remove([witDoer]) + Parameters: + prefixer (Prefixer): qb64 identifier prefix of event to check + seqner (Seqner): sequence number of event to check + saider (Saider): optional digest of event to verify - icp = phab.db.cloneEvtMsg(pre=phab.pre, fn=0, dig=phab.kever.serder.saidb) - ser = coring.Serder(raw=icp) - del icp[:ser.size] + Returns: - self.postman.send(src=phab.pre, dest=hab.kever.delegator, topic="delegate", serder=ser, - attachment=icp) - else: - phab = self.hby.habByName(f"{alias}-proxy") + """ + csaider = self.hby.db.cdel.get(keys=(prefixer.qb64, seqner.qb64)) + if not csaider: + return False + else: + if saider and (csaider.qb64 != saider.qb64): + raise kering.ValidationError(f"invalid delegation protocol escrowed event {csaider.qb64}-{saider.qb64}") - # Send exn message for notification purposes - exn, atc = delegateRequestExn(phab, delpre=delpre, ked=srdr.ked, aids=smids) - # exn of /oobis of all multisig participants to rootgar - self.postman.send(src=phab.pre, dest=hab.kever.delegator, topic="delegate", serder=exn, attachment=atc) - self.postman.send(src=phab.pre, dest=delpre, topic="delegate", serder=srdr, attachment=evt) + return True - yield from self.waitForAnchor(phab, hab, dkever, srdr) + def escrowDo(self, tymth, tock=1.0): + """ Process escrows of group multisig identifiers waiting to be compeleted. - self.cues.append(msg) - yield self.tock + Steps involve: + 1. Sending local event with sig to other participants + 2. Waiting for signature threshold to be met. + 3. If elected and delegated identifier, send complete event to delegator + 4. If delegated, wait for delegator's anchored seal + 5. If elected, send event to witnesses and collect receipts. + 6. Otherwise, wait for fully receipted event - yield self.tock + Parameters: + tymth (function): injected function wrapper closure returned by .tymen() of + Tymist instance. Calling tymth() returns associated Tymist .tyme. + tock (float): injected initial tock value. Default to 1.0 to slow down processing - def waitForAnchor(self, phab, hab, dkever, serder): - anchor = dict(i=serder.said, s=serder.sn, d=serder.said) - self.witq.query(src=phab.pre, pre=dkever.prefixer.qb64, anchor=anchor) + """ + # enter context + self.wind(tymth) + self.tock = tock + _ = (yield self.tock) while True: - if serder := self.hby.db.findAnchoringEvent(dkever.prefixer.qb64, anchor=anchor): - seqner = coring.Seqner(sn=serder.sn) - couple = seqner.qb64b + serder.saidb - dgkey = dbing.dgKey(hab.kever.prefixer.qb64b, hab.kever.serder.saidb) - self.hby.db.setAes(dgkey, couple) # authorizer event seal (delegator/issuer) - break - yield - - return True + self.processEscrows() + yield 0.5 - def proxy(self, alias, kever): - """ Create a proxy identifier for forward and query messages + def processEscrows(self): + self.processUnanchoredEscrow() + self.processPartialWitnessEscrow() - Uses witness and witness threshold configuration from delegated identifier to create - a proxy identifier that will be able to send forward exn messages and query messages. + def processUnanchoredEscrow(self): + """ + Process escrow of partially signed multisig group KEL events. Message + processing will send this local controllers signature to all other participants + then this escrow waits for signatures from all other participants - Parameters: - alias (str): human readable name of identifier to create a proxy for - kever (Kever): key event representation of identitifer to create proxy for + """ + for (pre, said), serder in self.hby.db.dune.getItemIter(): # group partial witness escrow + kever = self.hby.kevers[pre] + dkever = self.hby.kevers[kever.delegator] + + seal = dict(i=serder.pre, s=serder.snh, d=serder.said) + if dserder := self.hby.db.findAnchoringSealEvent(dkever.prefixer.qb64, seal=seal): + seqner = coring.Seqner(sn=dserder.sn) + couple = seqner.qb64b + dserder.saidb + dgkey = dbing.dgKey(kever.prefixer.qb64b, kever.serder.saidb) + self.hby.db.setAes(dgkey, couple) # authorizer event seal (delegator/issuer) + self.witDoer.msgs.append(dict(pre=pre, sn=serder.sn)) - Returns: + # Move to escrow waiting for witness receipts + print(f"Waiting for fully signed witness receipts for {serder.sn}") + self.hby.db.dpwe.pin(keys=(pre, said), val=serder) + self.hby.db.dune.rem(keys=(pre, said)) + def processPartialWitnessEscrow(self): """ - palias = f"{alias}-proxy" - kwargs = dict( - transferable=True, - wits=kever.wits, - icount=1, - isith='1', - ncount=0, - nsith='0', - toad=kever.toader.num, - ) + Process escrow of delegated events that do not have a full compliment of receipts + from witnesses yet. When receipting is complete, remove from escrow and cue up a message + that the event is complete. - hab = self.hby.makeHab(palias, **kwargs) - return hab + """ + for (pre, said), serder in self.hby.db.dpwe.getItemIter(): # group partial witness escrow + kever = self.hby.kevers[pre] + dgkey = dbing.dgKey(pre, serder.said) + seqner = coring.Seqner(sn=serder.sn) + + # Load all the witness receipts we have so far + wigs = self.hby.db.getWigs(dgkey) + if len(wigs) == len(kever.wits): # We have all of them, this event is finished + if len(kever.wits) > 0: + witnessed = False + for cue in self.witDoer.cues: + if cue["pre"] == serder.pre and cue["sn"] == seqner.sn: + witnessed = True + if not witnessed: + continue + print(f"Witness receipts complete, {pre} confirmed.") + self.hby.db.dpwe.rem(keys=(pre, said)) + self.hby.db.cdel.put(keys=(pre, seqner.qb64), val=coring.Saider(qb64=serder.said)) def loadHandlers(hby, exc, notifier): @@ -179,99 +206,81 @@ def loadHandlers(hby, exc, notifier): exc.addHandler(delreq) -class DelegateRequestHandler(doing.DoDoer): +class DelegateRequestHandler: """ Handler for multisig group inception notification EXN messages """ resource = "/delegate/request" - def __init__(self, hby, notifier, **kwa): + def __init__(self, hby, notifier): """ Parameters: - mbx (Mailboxer) of format str names accepted for offers - controller (str) qb64 identity prefix of controller - cues (decking.Deck) of outbound cue messages from handler + hby (Habery) database environment for this handler + notifier (str) notifier for converting delegate request exn messages to controller notifications """ self.hby = hby self.notifier = notifier - self.msgs = decking.Deck() - self.cues = decking.Deck() - super(DelegateRequestHandler, self).__init__(**kwa) + def handle(self, serder, attachments=None): + """ Do route specific processsing of delegation request messages + + Parameters: + serder (Serder): Serder of the exn delegation request message + attachments (list): list of tuples of pather, CESR SAD path attachments to the exn event - def do(self, tymth, tock=0.0, **opts): """ - Handle incoming messages by parsing and verifying the credential and storing it in the wallet + src = serder.pre + pay = serder.ked['a'] + embeds = serder.ked['e'] - Parameters: - payload is dict representing the body of a multisig/incept message - pre is qb64 identifier prefix of sender - sigers is list of Sigers representing the sigs on the /credential/issue message - verfers is list of Verfers of the keys used to sign the message + delpre = pay["delpre"] + if delpre not in self.hby.habs: + logger.error(f"invalid delegate request message, no local delpre for evt=: {pay}") + return - """ - self.wind(tymth) - self.tock = tock - yield self.tock + data = dict( + src=src, + r='/delegate/request', + delpre=delpre, + ked=embeds["evt"] + ) + if "aids" in pay: + data["aids"] = pay["aids"] - while True: - while self.msgs: - msg = self.msgs.popleft() - if "pre" not in msg: - logger.error(f"invalid delegate request message, missing pre. evt=: {msg}") - continue - - prefixer = msg["pre"] - if "payload" not in msg: - logger.error(f"invalid delegate request message, missing payload. evt=: {msg}") - continue - - pay = msg["payload"] - if "ked" not in pay or "delpre" not in pay: - logger.error(f"invalid delegate request payload, ked and delpre are required. payload=: {pay}") - continue - - src = prefixer.qb64 - delpre = pay["delpre"] - if delpre not in self.hby.habs: - logger.error(f"invalid delegate request message, no local delpre for evt=: {pay}") - continue - - data = dict( - src=src, - r='/delegate/request', - delpre=delpre, - ked=pay["ked"] - ) - if "aids" in pay: - data["aids"] = pay["aids"] - - self.notifier.add(attrs=data) - # if I am multisig, send oobi information of participants in (delegateeeeeeee) mutlisig group to his - # multisig group - - yield - yield - - -def delegateRequestExn(hab, delpre, ked, aids=None): + self.notifier.add(attrs=data) + + +def delegateRequestExn(hab, delpre, evt, aids=None): + """ + + Parameters: + hab (Hab): database environment of sender + delpre (str): qb64 AID of delegator + evt (bytes): serialized and signed event requiring delegation approval + aids (list): list of multisig AIDs participating + + Returns: + + """ data = dict( delpre=delpre, - ked=ked + ) + + embeds = dict( + evt=evt ) if aids is not None: data["aids"] = aids # Create `exn` peer to peer message to notify other participants UI - exn = exchanging.exchange(route=DelegateRequestHandler.resource, modifiers=dict(), - payload=data) - ims = hab.endorse(serder=exn, last=True, pipelined=False) + exn, _ = exchanging.exchange(route=DelegateRequestHandler.resource, modifiers=dict(), + payload=data, sender=hab.pre, embeds=embeds) + ims = hab.endorse(serder=exn, last=False, pipelined=False) del ims[:exn.size] return exn, ims - diff --git a/src/keri/app/directing.py b/src/keri/app/directing.py index 65a411f75..70bd96313 100644 --- a/src/keri/app/directing.py +++ b/src/keri/app/directing.py @@ -187,10 +187,6 @@ def __init__(self, hab, client, verifier=None, exchanger=None, direct=True, doer else: self.tvy = None - if self.exc is not None: - doers.extend([doing.doify(self.exchangerDo)]) - - self.parser = parsing.Parser(ims=self.client.rxbs, framed=True, kvy=self.kevery, @@ -292,34 +288,6 @@ def escrowDo(self, tymth=None, tock=0.0, **opts): yield return False # should never get here except forced close - - def exchangerDo(self, tymth=None, tock=0.0, **opts): - """ - Returns doifiable Doist compatibile generator method (doer dog) to process - .tevery.cues deque - - Doist Injected Attributes: - g.tock = tock # default tock attributes - g.done = None # default done state - g.opts - - Parameters: - tymth is injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock is injected initial tock value - opts is dict of injected optional additional parameters - - Usage: - add result of doify on this method to doers list - """ - yield # enter context - while True: - for rep in self.exc.processResponseIter(): - self.sendMessage(rep["msg"], label="response") - yield # throttle just do one cue at a time - yield - return False # should never get here except forced close - def sendMessage(self, msg, label=""): """ Sends message msg and loggers label if any @@ -571,9 +539,6 @@ def __init__(self, hab, remoter, verifier=None, exchanger=None, doers=None, **kw else: self.tevery = None - if self.exchanger is not None: - doers.extend([doing.doify(self.exchangerDo)]) - self.kevery.registerReplyRoutes(router=rvy.rtr) self.parser = parsing.Parser(ims=self.remoter.rxbs, @@ -682,34 +647,6 @@ def escrowDo(self, tymth=None, tock=0.0, **opts): yield return False # should never get here except forced close - - def exchangerDo(self, tymth=None, tock=0.0, **opts): - """ - Returns Doist compatibile generator method (doer dog) to process - .tevery.cues deque - - Doist Injected Attributes: - g.tock = tock # default tock attributes - g.done = None # default done state - g.opts - - Parameters: - tymth is injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock is injected initial tock value - opts is dict of injected optional additional parameters - - Usage: - add to doers list - """ - yield # enter context - while True: - for rep in self.exchanger.processResponseIter(): - self.sendMessage(rep["msg"], label="response") - yield # throttle just do one cue at a time - yield - return False # should never get here except forced close - def sendMessage(self, msg, label=""): """ Sends message msg and loggers label if any diff --git a/src/keri/app/forwarding.py b/src/keri/app/forwarding.py index 634b12d3a..748a09a6a 100644 --- a/src/keri/app/forwarding.py +++ b/src/keri/app/forwarding.py @@ -5,19 +5,24 @@ module for enveloping and forwarding KERI message """ - +import random +from ordered_set import OrderedSet as oset from hio.base import doing -from hio.help import decking +from hio.help import decking, ogler from keri import kering from keri.app import agenting -from keri.core import coring, eventing +from keri.app.habbing import GroupHab +from keri.core import coring, eventing, serdering from keri.db import dbing +from keri.kering import Roles from keri.peer import exchanging +logger = ogler.getLogger() + -class Postman(doing.DoDoer): +class Poster(doing.DoDoer): """ DoDoer that wraps any KERI event (KEL, TEL, Peer to Peer) in a /fwd `exn` envelope and delivers them to one of the target recipient's witnesses for store and forward @@ -25,14 +30,14 @@ class Postman(doing.DoDoer): """ - def __init__(self, hby, evts=None, cues=None, klas=None, **kwa): + def __init__(self, hby, mbx=None, evts=None, cues=None, **kwa): self.hby = hby + self.mbx = mbx self.evts = evts if evts is not None else decking.Deck() self.cues = cues if cues is not None else decking.Deck() - self.klas = klas if klas is not None else agenting.HttpWitnesser doers = [doing.doify(self.deliverDo)] - super(Postman, self).__init__(doers=doers, **kwa) + super(Poster, self).__init__(doers=doers, **kwa) def deliverDo(self, tymth=None, tock=0.0): """ @@ -57,77 +62,87 @@ def deliverDo(self, tymth=None, tock=0.0): recp = evt["dest"] tpc = evt["topic"] srdr = evt["serder"] + atc = evt["attachment"] if "attachment" in evt else None # Get the hab of the sender - hab = self.hby.habs[src] - - # Get the kever of the recipient and choose a witness - wit = agenting.mailbox(hab, recp) - if not wit: - print(f"exiting because can't find wit for {recp}") + if "hab" in evt: + hab = evt["hab"] + else: + hab = self.hby.habs[src] + + ends = hab.endsFor(recp) + try: + # If there is a controller, agent or mailbox in ends, send to all + if {Roles.controller, Roles.agent, Roles.mailbox} & set(ends): + for role in (Roles.controller, Roles.agent, Roles.mailbox): + if role in ends: + if role == Roles.mailbox: + yield from self.forward(hab, ends[role], recp=recp, serder=srdr, atc=atc, topic=tpc) + else: + yield from self.sendDirect(hab, ends[role], serder=srdr, atc=atc) + + # otherwise send to one witness + elif Roles.witness in ends: + yield from self.forwardToWitness(hab, ends[Roles.witness], recp=recp, serder=srdr, atc=atc, topic=tpc) + else: + logger.info(f"No end roles for {recp} to send evt={recp}") + continue + except kering.ConfigurationError as e: + logger.error(f"Error sending to {recp} with ends={ends}. Err={e}") continue - - msg = bytearray() - msg.extend(introduce(hab, wit)) - # Transpose the signatures to point to the new location - - # create the forward message with payload embedded at `a` field - fwd = exchanging.exchange(route='/fwd', modifiers=dict(pre=recp, topic=tpc), - payload=srdr.ked) - ims = hab.endorse(serder=fwd, last=True, pipelined=False) - - if "attachment" in evt: - atc = bytearray() - attachment = evt["attachment"] - pather = coring.Pather(path=["a"]) - atc.extend(pather.qb64b) - atc.extend(attachment) - ims.extend(coring.Counter(code=coring.CtrDex.PathedMaterialQuadlets, - count=(len(atc) // 4)).qb64b) - ims.extend(atc) - - witer = agenting.witnesser(hab=hab, wit=wit) - - msg.extend(ims) - witer.msgs.append(bytearray(msg)) # make a copy - self.extend([witer]) - - while not witer.idle: - _ = (yield self.tock) + # Get the kever of the recipient and choose a witness self.cues.append(dict(dest=recp, topic=tpc, said=srdr.said)) + yield self.tock yield self.tock - def send(self, src, dest, topic, serder, attachment=None): + def send(self, dest, topic, serder, src=None, hab=None, attachment=None): """ - Utility function to queue a msg on the Postman's buffer for + Utility function to queue a msg on the Poster's buffer for enveloping and forwarding to a witness Parameters: src (str): qb64 identifier prefix of sender + hab (Hab): Sender identifier habitat dest (str) is identifier prefix qb64 of the intended recipient topic (str): topic of message serder (Serder) KERI event message to envelope and forward: attachment (bytes): attachment bytes """ + src = src if src is not None else hab.pre evt = dict(src=src, dest=dest, topic=topic, serder=serder) if attachment is not None: evt["attachment"] = attachment + if hab is not None: + evt["hab"] = hab self.evts.append(evt) + def sent(self, said): + """ Check if message with given SAID was sent + + Parameters: + said (str): qb64 SAID of message to check for + """ + + for cue in self.cues: + if cue["said"] == said: + return True + + return False + def sendEvent(self, hab, fn=0): """ Returns generator for sending event and waiting until send is complete """ # Send KEL event for processing icp = self.hby.db.cloneEvtMsg(pre=hab.pre, fn=fn, dig=hab.kever.serder.saidb) - ser = coring.Serder(raw=icp) + ser = serdering.SerderKERI(raw=icp) del icp[:ser.size] - sender = hab.mhab.pre if hab.group is not None else hab.pre + sender = hab.mhab.pre if isinstance(hab, GroupHab) else hab.pre self.send(src=sender, dest=hab.kever.delegator, topic="delegate", serder=ser, attachment=icp) while True: if self.cues: @@ -138,8 +153,239 @@ def sendEvent(self, hab, fn=0): self.cues.append(cue) yield self.tock + def sendDirect(self, hab, ends, serder, atc): + for ctrl, locs in ends.items(): + witer = agenting.messengerFrom(hab=hab, pre=ctrl, urls=locs) + + msg = bytearray(serder.raw) + if atc is not None: + msg.extend(atc) + + witer.msgs.append(bytearray(msg)) # make a copy + self.extend([witer]) + + while not witer.idle: + _ = (yield self.tock) + + self.remove([witer]) + + def forward(self, hab, ends, recp, serder, atc, topic): + # If we are one of the mailboxes, just store locally in mailbox + owits = oset(ends.keys()) + if self.mbx and owits.intersection(hab.prefixes): + msg = bytearray(serder.raw) + if atc is not None: + msg.extend(atc) + self.mbx.storeMsg(topic=f"{recp}/{topic}".encode("utf-8"), msg=msg) + return + + # Its not us, randomly select a mailbox and forward it on + mbx, mailbox = random.choice(list(ends.items())) + msg = bytearray() + msg.extend(introduce(hab, mbx)) + # create the forward message with payload embedded at `a` field + + evt = bytearray(serder.raw) + evt.extend(atc) + fwd, atc = exchanging.exchange(route='/fwd', modifiers=dict(pre=recp, topic=topic), + payload={}, embeds=dict(evt=evt), sender=hab.pre) + ims = hab.endorse(serder=fwd, last=False, pipelined=False) + + # Transpose the signatures to point to the new location + witer = agenting.messengerFrom(hab=hab, pre=mbx, urls=mailbox) + msg.extend(ims) + msg.extend(atc) + + witer.msgs.append(bytearray(msg)) # make a copy + self.extend([witer]) + + while not witer.idle: + _ = (yield self.tock) + + def forwardToWitness(self, hab, ends, recp, serder, atc, topic): + # If we are one of the mailboxes, just store locally in mailbox + owits = oset(ends.keys()) + if self.mbx and owits.intersection(hab.prefixes): + msg = bytearray(serder.raw) + if atc is not None: + msg.extend(atc) + self.mbx.storeMsg(topic=f"{recp}/{topic}".encode("utf-8"), msg=msg) + return + + # Its not us, randomly select a mailbox and forward it on + mbx, mailbox = random.choice(list(ends.items())) + msg = bytearray() + msg.extend(introduce(hab, mbx)) + # create the forward message with payload embedded at `a` field + + evt = bytearray(serder.raw) + evt.extend(atc) + fwd, atc = exchanging.exchange(route='/fwd', modifiers=dict(pre=recp, topic=topic), + payload={}, embeds=dict(evt=evt), sender=hab.pre) + ims = hab.endorse(serder=fwd, last=False, pipelined=False) + + # Transpose the signatures to point to the new location + witer = agenting.messengerFrom(hab=hab, pre=mbx, urls=mailbox) + msg.extend(ims) + msg.extend(atc) + + witer.msgs.append(bytearray(msg)) # make a copy + self.extend([witer]) + + while not witer.idle: + _ = (yield self.tock) + + +class StreamPoster: + """ + DoDoer that wraps any KERI event (KEL, TEL, Peer to Peer) in a /fwd `exn` envelope and + delivers them to one of the target recipient's witnesses for store and forward + to the intended recipient -class ForwardHandler(doing.Doer): + """ + + def __init__(self, hby, recp, src=None, hab=None, mbx=None, topic=None, headers=None, **kwa): + if hab is not None: + self.hab = hab + else: + self.hab = hby.habs[src] + + self.hby = hby + self.hab = hab + self.recp = recp + self.src = src + self.messagers = [] + self.mbx = mbx + self.topic = topic + self.headers = headers + self.evts = decking.Deck() + + def deliver(self): + """ + Returns: doifiable Doist compatible generator method that processes + a queue of messages and envelopes them in a `fwd` message + and sends them to one of the witnesses of the recipient for + store and forward. + + Usage: + add result of doify on this method to doers list + """ + msg = bytearray() + + while self.evts: + evt = self.evts.popleft() + + serder = evt["serder"] + atc = evt["attachment"] if "attachment" in evt else b'' + + msg.extend(serder.raw) + msg.extend(atc) + + if len(msg) == 0: + return [] + + ends = self.hab.endsFor(self.recp) + try: + # If there is a controller or agent in ends, send to all + if {Roles.controller, Roles.agent, Roles.mailbox} & set(ends): + for role in (Roles.controller, Roles.agent, Roles.mailbox): + if role in ends: + if role == Roles.mailbox: + return self.forward(self.hab, ends[role], msg=msg, topic=self.topic) + else: + return self.sendDirect(self.hab, ends[role], msg=msg) + # otherwise send to one witness + elif Roles.witness in ends: + return self.forward(self.hab, ends[Roles.witness], msg=msg, topic=self.topic) + + else: + logger.info(f"No end roles for {self.recp} to send evt={self.recp}") + return [] + + except kering.ConfigurationError as e: + logger.error(f"Error sending to {self.recp} with ends={ends}. Err={e}") + return [] + + def send(self, serder, attachment=None): + """ + Utility function to queue a msg on the Poster's buffer for + enveloping and forwarding to a witness + + Parameters: + serder (Serder) KERI event message to envelope and forward: + attachment (bytes): attachment bytes + + """ + ends = self.hab.endsFor(self.recp) + try: + # If there is a controller, agent or mailbox in ends, send to all + if {Roles.controller, Roles.agent, Roles.mailbox} & set(ends): + for role in (Roles.controller, Roles.agent, Roles.mailbox): + if role in ends: + if role == Roles.mailbox: + serder, attachment = self.createForward(self.hab, serder=serder, ends=ends, + atc=attachment, topic=self.topic) + + # otherwise send to one witness + elif Roles.witness in ends: + serder, attachment = self.createForward(self.hab, ends=ends, serder=serder, + atc=attachment, topic=self.topic) + else: + logger.info(f"No end roles for {self.recp} to send evt={self.recp}") + raise kering.ValidationError(f"No end roles for {self.recp} to send evt={self.recp}") + except kering.ConfigurationError as e: + logger.error(f"Error sending to {self.recp} with ends={ends}. Err={e}") + raise kering.ValidationError(f"Error sending to {self.recp} with ends={ends}. Err={e}") + + evt = dict(serder=serder) + if attachment is not None: + evt["attachment"] = attachment + + self.evts.append(evt) + + def sendDirect(self, hab, ends, msg): + for ctrl, locs in ends.items(): + self.messagers.append(agenting.streamMessengerFrom(hab=hab, pre=ctrl, urls=locs, msg=msg, + headers=self.headers)) + + return self.messagers + + def createForward(self, hab, ends, serder, atc, topic): + # If we are one of the mailboxes, just store locally in mailbox + owits = oset(ends.keys()) + if self.mbx and owits.intersection(hab.prefixes): + msg = bytearray(serder.raw) + if atc is not None: + msg.extend(atc) + self.mbx.storeMsg(topic=f"{self.recp}/{topic}".encode("utf-8"), msg=msg) + return None, None + + # Its not us, randomly select a mailbox and forward it on + evt = bytearray(serder.raw) + evt.extend(atc) + fwd, atc = exchanging.exchange(route='/fwd', modifiers=dict(pre=self.recp, topic=topic), + payload={}, embeds=dict(evt=evt), sender=hab.pre) + ims = hab.endorse(serder=fwd, last=False, pipelined=False) + return fwd, ims + atc + + def forward(self, hab, ends, msg, topic): + # If we are one of the mailboxes, just store locally in mailbox + owits = oset(ends.keys()) + if self.mbx and owits.intersection(hab.prefixes): + self.mbx.storeMsg(topic=f"{self.recp}/{topic}".encode("utf-8"), msg=msg) + return [] + + # Its not us, randomly select a mailbox and forward it on + mbx, mailbox = random.choice(list(ends.items())) + ims = bytearray() + ims.extend(introduce(hab, mbx)) + ims.extend(msg) + + self.messagers.append(agenting.streamMessengerFrom(hab=hab, pre=mbx, urls=mailbox, msg=bytes(ims))) + return self.messagers + + +class ForwardHandler: """ Handler for forward `exn` messages used to envelope other KERI messages intended for another recipient. This handler acts as a mailbox for other identifiers and stores the messages in a local database. @@ -175,68 +421,45 @@ class ForwardHandler(doing.Doer): resource = "/fwd" - def __init__(self, hby, mbx, cues=None, **kwa): + def __init__(self, hby, mbx): """ Parameters: + hby (Habery): database environment mbx (Mailboxer): message storage for store and forward - formats (list) of format str names accepted for offers - cues (Optional(decking.Deck)): outbound cue messages """ self.hby = hby - self.msgs = decking.Deck() - self.cues = cues if cues is not None else decking.Deck() self.mbx = mbx - super(ForwardHandler, self).__init__(**kwa) - - def do(self, tymth, tock=0.0, **opts): - """ Handle incoming messages by parsing and verifiying the credential and storing it in the wallet + def handle(self, serder, attachments=None): + """ Do route specific processsing of IPEX protocol exn messages Parameters: - tymth (function): injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock (float): injected initial tock value - - Messages: - payload is dict representing the body of a /credential/issue message - pre is qb64 identifier prefix of sender - sigers is list of Sigers representing the sigs on the /credential/issue message - verfers is list of Verfers of the keys used to sign the message + serder (Serder): Serder of the IPEX protocol exn message + attachments (list): list of tuples of root pathers and CESR SAD path attachments to the exn event """ - # start enter context - self.wind(tymth) - self.tock = tock - yield self.tock - while True: - while self.msgs: - msg = self.msgs.popleft() - payload = msg["payload"] - modifiers = msg["modifiers"] - attachments = msg["attachments"] - - recipient = modifiers["pre"] - topic = modifiers["topic"] - resource = f"{recipient}/{topic}" - - pevt = bytearray() - for pather, atc in attachments: - ked = pather.resolve(payload) - sadder = coring.Sadder(ked=ked, kind=eventing.Serials.json) - pevt.extend(sadder.raw) - pevt.extend(atc) - - if not pevt: - print("error with message, nothing to forward", msg) - continue + embeds = serder.ked['e'] + modifiers = serder.ked['q'] if 'q' in serder.ked else {} - self.mbx.storeMsg(topic=resource, msg=pevt) - yield self.tock + recipient = modifiers["pre"] + topic = modifiers["topic"] + resource = f"{recipient}/{topic}" - yield self.tock + pevt = bytearray() + for pather, atc in attachments: + ked = pather.resolve(embeds) + sadder = coring.Sadder(ked=ked, kind=eventing.Serials.json) + pevt.extend(sadder.raw) + pevt.extend(atc) + + if not pevt: + print("error with message, nothing to forward", serder.ked) + return + + self.mbx.storeMsg(topic=resource, msg=pevt) def introduce(hab, wit): @@ -275,6 +498,7 @@ def introduce(hab, wit): # no vrcs or rct of own icp from remote so send own inception for msg in hab.db.clonePreIter(pre=hab.pre): msgs.extend(msg) - - msgs.extend(hab.replyEndRole(cid=hab.pre, role=kering.Roles.witness)) + for msg in hab.db.cloneDelegation(hab.kever): + msgs.extend(msg) + msgs.extend(hab.replyEndRole(cid=hab.pre)) return msgs diff --git a/src/keri/app/grouping.py b/src/keri/app/grouping.py index 2a932d7f0..1607ee29f 100644 --- a/src/keri/app/grouping.py +++ b/src/keri/app/grouping.py @@ -5,181 +5,55 @@ module for enveloping and forwarding KERI message """ -from ordered_set import OrderedSet as oset -from hio import help from hio.base import doing from hio.help import decking -from keri import kering -from keri.app import forwarding, delegating, agenting -from keri.core import coring -from keri.core.coring import Number -from keri.db import dbing, basing -from keri.db.dbing import snKey -from keri.help import helping -from keri.peer import exchanging -from keri.vc import proving +from .. import kering +from .. import help +from ..app import delegating, agenting +from ..core import coring, routing, eventing, parsing, serdering +from ..db import dbing +from ..db.dbing import snKey +from ..peer import exchanging logger = help.ogler.getLogger() class Counselor(doing.DoDoer): - def __init__(self, hby, **kwa): + def __init__(self, hby, swain=None, proxy=None, **kwa): self.hby = hby - self.postman = forwarding.Postman(hby=hby) - self.swain = delegating.Boatswain(hby=self.hby) - self.witDoer = agenting.WitnessReceiptor(hby=self.hby) + self.swain = swain if swain is not None else delegating.Sealer(hby=self.hby) + self.proxy = proxy + self.witDoer = agenting.Receiptor(hby=self.hby) self.witq = agenting.WitnessInquisitor(hby=hby) - doers = [self.postman, self.swain, self.witq, self.witDoer, doing.doify(self.escrowDo)] + doers = [self.swain, self.witq, self.witDoer, doing.doify(self.escrowDo)] super(Counselor, self).__init__(doers=doers, **kwa) - def start(self, prefixer, seqner, saider, mid, smids, rmids=None): + def start(self, ghab, prefixer, seqner, saider): """ Begin processing of escrowed group multisig identifier Escrow identifier for multisigs, witness receipts and delegation anchor Parameters: + ghab (Hab): group Habitat prefixer (Prefixer): prefixer of group identifier - seqner (Seqner): seqner of inception event of group identifier - saider (Saider): saider of inception event of group identifier - mid (str): group member (local) identifier prefix qb64 - smids (list): group signing member ids qb64 (multisig group) - need to contribute current signing key - rmids (list | None): group rotating member ids qb64 (multisig group) - need to contribute digest of next rotating key + seqner (Seqner): seqner of event of group identifier + saider (Saider): saider of event of group identifier """ - evt = getEscrowedEvent(db=self.hby.db, pre=prefixer.qb64, sn=seqner.sn) - serder = coring.Serder(raw=evt) + evt = ghab.makeOwnEvent(sn=seqner.sn, allowPartiallySigned=True) + serder = serdering.SerderKERI(raw=evt) del evt[:serder.size] - others = list(oset(smids + (rmids or []))) - - others.remove(mid) # don't send to self - - print(f"Sending multisig event to {len(others)} other participants") - for recpt in others: - self.postman.send(src=mid, dest=recpt, topic="multisig", serder=serder, attachment=evt) - - print(f"Waiting for other signatures for {seqner.sn}...") + print(f"Waiting for other signatures for {serder.pre}:{seqner.sn}...") return self.hby.db.gpse.add(keys=(prefixer.qb64,), val=(seqner, saider)) - def rotate(self, ghab, smids, *, rmids=None, isith=None, nsith=None, - toad=None, cuts=None, adds=None, data=None, local=True): - """ Begin processing of escrowed group multisig identifier - - Escrow identifier for multisigs, witness receipts and delegation anchor - - Parameters: - ghab (Hab): group identifier Hab - smids (list): group signing member identifier prefixes qb64 - need to contribute newly current signing keys - rmids (list): group rotating member identifier prefixes qb64 - need to contribute next rotating key digests - isith (Optional[int,str]) currentsigning threshold as int or str hex - or list of str weights - nsith (Optional[int,str])next signing threshold as int or str hex - or list of str weights - toad (int) or str hex of witness threshold after cuts and adds - cuts (list) of qb64 pre of witnesses to be removed from witness list - adds (list) of qb64 pre of witnesses to be added to witness list - data (list) of dicts of committed data such as seals - local (bool) True means rotate local AID automatically - - RotateRecord: - date (str | None): datetime of rotation - smids (list): group signing member identifiers qb64 - smsns (list): of group signing member seq num of last est evt as hex str - rmids (list): group rotating member identifiers qb64 - rmsns (list): of group rotating member seq num of last est evt as hex strs - sn (str | None ): at or after proposed seq num of group est event as hex str - isith (str | list | None): current signing threshold - nsith (str | list | None): next signing threshold - toad (int | None): threshold of accountable duplicity - cuts (list | None): list of backers to remove qb64 - adds (list | None): list of backers to add qb64 - data (list | None): seals in rotation event - - - - ToDo: NRR - Add midxs for each group member identifier or just the local member - for mhab.pre - Then store these with rotationRecord to be used by .processPartialAidEscrow() - - This code assumes that at the time of this formation of the group - rotation record, none of the members in either smids or smids has - yet to rotate to the key state to be used in the group rotation. This - takes a snapshot vector clock as list of the sequence numbers to ensure - all members see the same key state for all other members. Rotation must - therefore use the keystate that that latest est evt is at least +1 - of the sequence number in the vector clock. - number of members that must have contributed is configuration dependent - - - - - """ - mid = ghab.mhab.pre - s, r = ghab.members() - smids = smids if smids is not None else s - rmids = rmids if rmids is not None else r - both = list(oset(smids + (rmids or []))) - - if mid not in both: - raise kering.ConfigurationError(f"local identifier {mid} not elected" - f" as member of rotation: {both}") - - if rmids is None: # default the same for both lists - rmids = list(smids) - - smsns = [] # vector clock of sns of signing member last est evt - for mid in smids: - try: - skever = ghab.kevers[mid] - except KeyError as ex: - logger.error(f"Missing KEL for group singing member={mid}" - f" of rotation for group={ghab.pre}.") - raise kering.MissingAidError(f"Missing KEL for group signing " - f"member={mid} of rotation for" - f" group={ghab.pre}.") from ex - smsns.append(Number(num=skever.lastEst.s).numh) - - rmsns = [] # vector clock of sn of rotating member lst est evt - for mid in rmids: - try: - rkever = ghab.kevers[mid] - except KeyError as ex: - logger.error(f"Missing KEL for group rotating member={mid}" - f" of rotation for group={ghab.pre}.") - raise kering.MissingAidError(f"Missing KEL for group rotating " - f"member={mid} of rotation for" - f" group={ghab.pre}.") from ex - rmsns.append(Number(num=rkever.lastEst.s).numh) - - gkever = ghab.kever - rec = basing.RotateRecord(date=helping.nowIso8601(), - smids=smids, smsns=smsns, - rmids=rmids, rmsns=rmsns, - sn=Number(num=gkever.sn + 1).numh, - isith=isith, nsith=nsith, - toad=toad, cuts=cuts, adds=adds, - data=data) - - # perform local member rotation and then wait for own witnesses to receipt - if local: - ghab.mhab.rotate() # rotate own local member hab - print(f"Rotated local member={ghab.mhab.pre}, waiting for witness receipts") - self.witDoer.msgs.append(dict(pre=ghab.mhab.pre, sn=ghab.mhab.kever.sn)) - - return self.hby.db.glwe.put(keys=(ghab.pre,), val=rec) - def complete(self, prefixer, seqner, saider=None): """ Check for completed multsig protocol for the specific event @@ -207,7 +81,7 @@ def escrowDo(self, tymth, tock=1.0): 1. Sending local event with sig to other participants 2. Waiting for signature threshold to be met. 3. If elected and delegated identifier, send complete event to delegator - 4. If delegated, wait for delegator's anchor + 4. If delegated, wait for delegator's anchored seal 5. If elected, send event to witnesses and collect receipts. 6. Otherwise, wait for fully receipted event @@ -227,377 +101,10 @@ def escrowDo(self, tymth, tock=1.0): yield 0.5 def processEscrows(self): - self.processLocalWitnessEscrow() - self.processPartialAidEscrow() self.processPartialSignedEscrow() self.processDelegateEscrow() self.processPartialWitnessEscrow() - def processLocalWitnessEscrow(self): - """ - Process escrow of group multisig events that do not have a full compliment of receipts - from witnesses yet. When receipting is complete, remove from escrow and cue up a message - that the event is complete. - - """ - for (pre,), rec in self.hby.db.glwe.getItemIter(): # group partial witness escrow - ghab = self.hby.habs[pre] - mid = ghab.mhab.pre - pkever = ghab.mhab.kever - dgkey = dbing.dgKey(mid, pkever.serder.saidb) - - # Load all the witness receipts we have so far - wigs = self.hby.db.getWigs(dgkey) - # should not require all witnesses merely the witness threshold - if len(wigs) == len(pkever.wits): # We have all of them, this event is finished - self.hby.db.glwe.rem(keys=(pre,)) - - rot = self.hby.db.cloneEvtMsg(mid, pkever.sn, pkever.serder.said) # grab latest est evt - - others = list(oset(rec.smids + (rec.rmids or []))) # others = list(rec.smids) - others.remove(mid) - serder = coring.Serder(raw=rot) - del rot[:serder.size] - - print(f"Sending local rotation event to {len(others)} other participants") - for recpt in others: - self.postman.send(src=mid, dest=recpt, topic="multisig", serder=serder, attachment=rot) - - return self.hby.db.gpae.put(keys=(ghab.pre,), val=rec) - - def processPartialAidEscrow(self): - """ - Process escrow of group multisig rotate request for missing rotations by - other participants. Message processing will send this local controller's - rotation event to all other participants then this escrow waits for - rotations from all other participants to return. - - # group partial member aid escrow - self.gpae = koming.Komer(db=self, subkey='gpae.', - schema=RotateRecord) - - RotateRecord - sn: int | None # sequence number of est event - isith: str | list | None # current signing threshold - nsith: str | list | None # next signing threshold - toad: int | None # threshold of accountable duplicity - cuts: list | None # list of backers to remove qb64 - adds: list | None # list of backers to add qb64 - data: list | None # seals - date: str | None # datetime of rotation - smids: list | None # group signing member ids - rmids: list | None = None # group rotating member ids - - # group member last contribution records keyed by aid of member - self.gcrs = koming.Komer(db=self, subkey='gcrs.', - schema=ContributeRecord) - ContributeRecord - date (str | None): datetime of rotation - smids (list): group signing member identifiers qb64 - smsns (list): of group signing member seq nums of last est evt as hex str - key (str | None): qb64 of signing key contributed by given member if any - rmids (list): group rotating member identifiers qb64 - rmsns (list): of group rotating member seq nums of last est evt as hex strs - dig (str | None): qb64 of rotating key digest contributed by given member if any - sn (str): of last est evt contributed to by member as hex str - said (str): # said of last est evt contributed to by member as qb64 - - - ContributeRecord database for each member of each group hab as keyed by: - (group hab aid, member aid) records the reference to members est event that - contributed to last group est evt as well as contributif mkever.lastEsts.s != grec.smsns[i]: - raise kering.GroupFormationError(f"Invalid rotation " - f"state of smid={mid}.")ed key material - of either both siging key and rotating key dig from contributing member - est event. - - The logic for required member rotation (i.e. MUST rotate before group event - can be formed) is as follows: - - A) IF a given smid in rotate grec.smids was exposed in the previous - group est evt as indicated by inclusion in its contribute - mrec.smids if any assuming no keys are recycled. - THEN given smid must be rotated before its signing key material can be - contributed to new rotation event. - - This rule makes the conservative assumption that a rotation is always - considered prophylactic for all smids even if some of them may not have - been suspected of compromise. A corner case is that a given member smid - was not also an rmid of the prior group est event so it could be reused - as a smid in a subsequent group est event. The conservative prophylatic - assumption is that regardless of any mitigating circumstance that might - allow its use without rotation, any smid contributed previously cannot be - re-contributed for any reason without rotation in a subsequent est event for - the group. In other words a rotation forces all smids to be prophylatically - rotated. - - This rule also enforces that any current smid that corresponds to a - prior rmid that has already been exposed as a smid must therefore be - rotated so that the member est event that is contributing to the - new group est event is the first time exposure of the digest of the now smid. - In other words if the rmid was not exposed as a smid in the contrib record - then its ok to use the rmid without rotating. So checking the smid covers - all must rotate cases. - - - B) OTHERWISE the current est event for the smid/rmid MUST NOT be rotated. - - This ensures that every member applies the same logic for evaluating the - key material contribution of every other member. Each MUST evaluate to - the same condition of either MUST rotate relative to member last est event - captured by smsns, rmsns in contrib record or MUST NOT rotate. - - There are Two primary MUST not rotate cases: - - 1) A current member smid does not have a contribute record so it has - yet to contribute in any way to a group est event. Therefore - is has yet to expose any key material that thereby requires a - rotation prior to contribution. - - 2) A current member smid signing key material is newly added as key - material i.e. is not in the prior contrib record as a smid. - - a) This covers the case of a custodial signing key that - that never appears as a next rotating key digest. - Key member is given by smid - - b) This covers the case of an rmid held in reserve that has yet to - be exposed as a smid in the contrib record. So the reserve does - not have to rotate prior to contributing in the current rotate - record but can use its held in reserve key material as is. - - - This logic assumes that a given member is not recycling old key material - for either signing keys or rotating key digests. - i.e. logic assumes that each member only exposes each rotating key - one time only and only uses a given signing key up to the next est event. - I.E. once a signing key has been rotated out it is never rotated back in. - - Use of an HDK or CSPRNG random key generation algorithm ensures that - keys will not be recycled. If a member does not protect itself against - key material recycling then recycled keys may have already been exposed - making them vulnerable to surprise quantum attack or may have been - otherwise compromised. This logic does not protect against deep recycling - only that the last use is either appropriate or MUST be rotated before - being used in the new rotation. Thus it protects against inadvertent reuse - of the last keys (i.e. stale keys) and assumes that new keys are never - recycled. - - Failure Cases: - - For MUST and MUST Not rotate case we must ensure everyone captures - the same est event for the rotating member in the event that a member - performs a surprise recovery rotation during group rotation formation - but after the rotate record is captured and if not that everyone fails. - - In all cases, the same failure logic MUST also apply. The rule is all - must form the same group or no group must be formed. - - We can enforce either zero or one rotations uniformly but not more than - one. - - What if the rotating member has rotated more than once since the rotate - record was captured? - - If so then there is a new race condition. - - We can say that if they have rotated more than once in the short time - frame of the formation of a new rotation group then there is a problem - and therefore the formation should fail, in order to prevent the race - condition. - - Note this is a different race condition than the member rotating only - once to recover control during the formation process. The MUST rotate - rules will work if there is only one rotation for a MUST rotate for any - reason either because of the group rotation or serendipidously for a - recovery rotation at the same time. - - This means then that we need a hard test that the sn of the MUST rotate set - must be exactly 1 greater than the sn captured in the rotate record. - We must check the sn of the latest establishment event not the latest event. - The sn of the last Est Evt must be 1 greater. The rotate record captures - the sequence number of the last Est Evt. By capturing the sn of the last - est event we can allow there to be interaction events in the KELs of - members and therefore we do not have to enforce EO (Establishment Only) - config trait on member KELs. - - Likewise we have the case where the member of a MUST NOT rotate does a - rotation to recover from a compromise after the rotate record if formed. - We need a hard test for a MUST NOT rotate which is the sn of the last - est event MUST equal the sn in the rotate record. - Otherwise the group rotation formation must fail in order to prevent - a race condition. This means that the MUST NOT rotate set will fail if - any of them must perform a recovery rotation during group formation. - - In general on a likelihood frequency basis: - - Members in the MUST NOT rotate set are contributing largely unexposed - key material that is less likely to have become compromised during group - formation. - - (the one exception is a group member that has been rotated out and - rotated back in and its current signing key material may not have been - used in the group but may have been used otherwise for some time. This - is only signing key material not next key material that has not been - exposed). - - Nonetheless, typically its less likely that a MUST NOT member will need - to perform a recovery rotation during rotation group formation. - - Whereas members in the MUST rotate set in all cases are contributing - already exposed key material. So the likelihood of a compromise during - group rotation formation needing a recover rotation during group formation - is higher. So the current rules avoid group formation failure if one - rotation happens during formation for MUST rotate members but not - two or more. So the trade-off is reasonable. - - - - - # group partial signature escrow - self.gpse = subing.CatCesrIoSetSuber(db=self, subkey='gpse.', - klas=(coring.Seqner, coring.Saider)) - - - - ToDo: NRR - - Questions: - - How does a participant know it has already been rotated to support the - rotation event? I thought we were not requiring the sequence numbers - to be the same? - - Are we assuming that before this escrow is created some other facility - rotates the participant so we can assume here that the participant has - always aready been rotated so that its prior next, current, and next - are already set up to contribute to the this group rotation event? - - The current code is making an assumption that if the zeroth next key digest - of the local hab is not found amongst the next key digests of the group hab then - update the group next digests. If is is found then the current group - digests is correct. This seems that not all the participants wil generate - the same rotation event based on what the key state they each see locally - for other members? - - When creating Habery.group hab and merfers provided then - gkever.digers are the provided next digers (migers). - How are the merfers and migers provided? They are provided via - the makeGroupHab which is provided with the smids and rmids - so if rmids is set correctly then the migers is already the correct - set of next digers. So why are we updating it below?? - - Seems like logic is assuming perfect rotation has happended for all - smids and rmids prior to this escrow being checked, otherwise the actual - rotation event may not include all the prescribed verfers from the smids - nor all the prescribed digers from the rmids. The logic should be 100%. - Either all the prescibed smids verfers are included and all prescribed - rmid digers are included or it fails. - - If we can assume that all the smids and rmids have already performed their - inidivual member rotation prior the group rotation being formed then - there should be no need for this escrow. The troubling question is what - problem does this escrow solve? - - - - grec includes the dual indices for current and next for new rotation. - Need to fix this logic to be for new rotation rules - need to use both rec.smids and rec.rmids - both = list(oset(smids + (rmids or []))) because next rotation keys may be - disjoint from current signing keys and all members must contribute - either both current signing key and next rotating key digest - - Logic to determine if current local hab kever is ok to use is based on: - if latest prior est event in database has been exposed as current for the local hab - if so then the local hab must rotate and the sn must be at least one greater - if current key was not exposed then the local hab does not need to be rotated and the - unexposed next key can be reused in the new rotation event. - - - - """ - # ignore saider of group rotation event in this escrow because it is not - # not yet formed yet ??? - - # - for (pre,), grec in self.hby.db.gpae.getItemIter(): # group partial member aid escrow - ghab = self.hby.habs[pre] # get group hab instanace at group hab id pre - gkever = ghab.kever # group hab's Kever instance key state - verfers = gkever.verfers - - # collect merfers of member verfers whose member satisfies - # rotation rules relative to their previous contribution. - # None is placeholder for member who has not yet satisfied rotation - # rules. - # member's newly rotated verfers in order to contribute to group event - - merfers = [None] * len(grec.smids) - for i, mid in enumerate(grec.smids): # assumes kever or else no rec - mkever = self.hby.kevers[mid] # get key state for given member - - # walk member kel to find event if event where member contributed to - # group est event from which verfers is taken - if (result := gkever.fetchLatestContribFrom(verfer=mkever.verfers[0])) is None: - merfers[i] = mkever.verfers[0] - - else: # use result here - sn, csi, merfer = result # unpack result - if mkever.sn > sn: - merfers[i] = mkever.verfers[0] - else: - continue - - if None in merfers: # not all members have contributed - continue - - # contribute diger from each rmid member to group event - migers = [self.hby.kevers[mid].digers[0] for mid in grec.rmids] - - # use new isith when provided otherwise default to prior isith - isith = grec.isith if grec.isith is not None else gkever.tholder.sith - - # use new nsith when provided otherwise default to prior nsith - nsith = grec.nsith if grec.nsith is not None else gkever.ntholder.sith - - # rot is locally signed group multisig rotation event message - # note actual seq num of group rotation event may be later than proposed - # because an automatic aync interaction event may have occurred while - # waiting for the group event to process and Hab.rotate just increments - - rot = ghab.rotate(isith=isith, nsith=nsith, - toad=grec.toad, cuts=grec.cuts, adds=grec.adds, data=grec.data, - merfers=merfers, migers=migers) - serder = coring.Serder(raw=rot) - del rot[:serder.size] # strip signatures from - - others = list(oset(grec.smids + (grec.rmids or []))) # list(rec.smids) - others.remove(ghab.mhab.pre) - print(f"Sending rotation event to {len(others)} other participants") - - exn, ims = multisigRotateExn(ghab, - aids=grec.smids, - smids=grec.smids, - rmids=grec.rmids, - ked=serder.ked) - for recpt in others: - self.postman.send(src=ghab.mhab.pre, dest=recpt, topic="multisig", - serder=serder, attachment=rot) - self.postman.send(src=ghab.mhab.pre, - dest=recpt, - topic="multisig", - serder=exn, - attachment=ims) - - self.hby.db.gpae.rem((pre,)) # remove rot rec from this escrow - - print("Waiting for other signatures...") - # change below to put the said in the keys not the val - # should also fix the delegated escrow as well move to key space - return self.hby.db.gpse.add(keys=(ghab.pre,), - val=(coring.Seqner(sn=serder.sn), - serder.saider)) - def processPartialSignedEscrow(self): """ Process escrow of partially signed multisig group KEL events. Message @@ -609,8 +116,6 @@ def processPartialSignedEscrow(self): snkey = dbing.snKey(pre, seqner.sn) sdig = self.hby.db.getKeLast(key=snkey) if sdig: - sraw = self.hby.db.getEvt(key=dbing.dgKey(pre=pre, dig=bytes(sdig))) - self.hby.db.gpse.rem(keys=(pre,)) ghab = self.hby.habs[pre] kever = ghab.kever @@ -629,10 +134,13 @@ def processPartialSignedEscrow(self): # We are a delegated identifier, must wait for delegator approval for dip and drt if witered: # We are elected to perform delegation and witnessing messaging print(f"We are the witnesser, sending {pre} to delegator") - self.swain.msgs.append(dict(pre=pre, sn=seqner.sn)) + self.swain.delegation(pre=pre, sn=seqner.sn) else: anchor = dict(i=pre, s=seqner.snh, d=saider.qb64) - self.witq.query(src=ghab.mhab.pre, pre=kever.delegator, anchor=anchor) + if self.proxy: + self.witq.query(hab=self.proxy, pre=kever.delegator, anchor=anchor) + else: + self.witq.query(src=ghab.mhab.pre, pre=kever.delegator, anchor=anchor) print("Waiting for delegation approval...") self.hby.db.gdee.add(keys=(pre,), val=(seqner, saider)) @@ -659,21 +167,26 @@ def processDelegateEscrow(self): keys = [verfer.qb64 for verfer in kever.verfers] witer = ghab.mhab.kever.verfers[0].qb64 == keys[0] # We are elected to perform delegation and witnessing - if serder := self.hby.db.findAnchoringEvent(kever.delegator, anchor=anchor): - aseq = coring.Seqner(sn=serder.sn) - couple = aseq.qb64b + serder.saidb - dgkey = dbing.dgKey(pre, saider.qb64b) - self.hby.db.setAes(dgkey, couple) # authorizer event seal (delegator/issuer) - self.hby.db.gdee.rem(keys=(pre,)) - print(f"Delegation approval for {pre} received.") + if witer: # We are elected witnesser, We've already done out part in Boatswain, we are done. + if self.swain.complete(prefixer=kever.prefixer, seqner=coring.Seqner(sn=kever.sn)): + self.hby.db.gdee.rem(keys=(pre,)) + print(f"Delegation approval for {pre} received.") - if witer: # We are elected witnesser, send off event to witnesses - print(f"We are the witnesser, sending {pre} to witnesses") - self.witDoer.msgs.append(dict(pre=pre, sn=seqner.sn)) + self.hby.db.cgms.put(keys=(pre, seqner.qb64), val=saider) - # Move to escrow waiting for witness receipts - print(f"Waiting for witness receipts for {pre}") - self.hby.db.gpwe.add(keys=(pre,), val=(seqner, saider)) + else: # Not witnesser, we need to look for the anchor and then wait for receipts + if serder := self.hby.db.findAnchoringSealEvent(kever.delegator, seal=anchor): + aseq = coring.Seqner(sn=serder.sn) + couple = aseq.qb64b + serder.saidb + dgkey = dbing.dgKey(pre, saider.qb64b) + self.hby.db.setAes(dgkey, couple) # authorizer event seal (delegator/issuer) + self.hby.db.gdee.rem(keys=(pre,)) + print(f"Delegation approval for {pre} received.") + + # Move to escrow waiting for witness receipts + print(f"Waiting for witness receipts for {pre}") + self.hby.db.gdee.rem(keys=(pre,)) + self.hby.db.gpwe.add(keys=(pre,), val=(seqner, saider)) def processPartialWitnessEscrow(self): """ @@ -688,10 +201,10 @@ def processPartialWitnessEscrow(self): # Load all the witness receipts we have so far wigs = self.hby.db.getWigs(dgkey) + ghab = self.hby.habs[pre] + keys = [verfer.qb64 for verfer in kever.verfers] + witer = ghab.mhab.kever.verfers[0].qb64 == keys[0] if len(wigs) == len(kever.wits): # We have all of them, this event is finished - ghab = self.hby.habs[pre] - keys = [verfer.qb64 for verfer in kever.verfers] - witer = ghab.mhab.kever.verfers[0].qb64 == keys[0] if witer and len(kever.wits) > 0: witnessed = False for cue in self.witDoer.cues: @@ -702,475 +215,292 @@ def processPartialWitnessEscrow(self): print(f"Witness receipts complete, {pre} confirmed.") self.hby.db.gpwe.rem(keys=(pre,)) self.hby.db.cgms.put(keys=(pre, seqner.qb64), val=saider) + elif not witer: + self.witDoer.gets.append(dict(pre=pre, sn=seqner.sn)) - def pendingEvents(self, pre): - """ Return information about any pending events for a given AID - - Parameters: - pre (str): qb64 identifier of distributed multisig AID - - Returns: - Prefixer, Saider: prefixer of identifier and saider of the event (if available) - - ToDo: NRR - sn in rec.sn is now a hex str. How is the event data use the sn. Does - it merely display or does it do logic on the sn? Need use to change - to understand its a hex str not an int. - - Note: - Actual seq num of group rotation event may be later than proposed in - RotationRecord, rec because an automatic async interaction event may - have occurred while waiting for the group event to process and - Hab.rotate just increments whatever is latest sn - - """ - key = (pre,) - evts = [] - if (rec := self.hby.db.gpae.get(keys=key)) is not None: # RotateRecord - data = dict( - aids=rec.smids, - sn=rec.sn, - isith=rec.isith, - nsith=rec.nsith, - timestamp=rec.date, - toad=rec.toad, - cuts=rec.cuts, - adds=rec.adds, - data=rec.data - ) - evts.append(data) - - if (rec := self.hby.db.glwe.get(keys=key)) is not None: # RotateRecord - data = dict( - aids=rec.smids, - sn=rec.sn, - isith=rec.isith, - nsith=rec.nsith, - timestamp=rec.date, - toad=rec.toad, - cuts=rec.cuts, - adds=rec.adds, - data=rec.data - ) - evts.append(data) - - return evts - - -def loadHandlers(hby, exc, notifier): - """ Load handlers for the peer-to-peer distributed group multisig protocol - - Parameters: - hby (Habery): Database and keystore for environment - exc (Exchanger): Peer-to-peer message router - notifier (Notifier): Database for storing mailbox messages +class MultisigNotificationHandler: """ - incept = MultisigInceptHandler(hby=hby, notifier=notifier) - exc.addHandler(incept) - rotate = MultisigRotateHandler(hby=hby, notifier=notifier) - exc.addHandler(rotate) - interact = MultisigInteractHandler(hby=hby, notifier=notifier) - exc.addHandler(interact) - issue = MultisigIssueHandler(notifier=notifier) - exc.addHandler(issue) - - -class MultisigInceptHandler(doing.DoDoer): - """ - Handler for multisig group inception notification EXN messages + Handler for multisig coordination EXN messages """ - resource = "/multisig/icp" - persist = True - def __init__(self, hby, notifier, **kwa): - """ + def __init__(self, resource, mux): + """ Create an exn handler for multisig messages Parameters: - mbx (Mailboxer) of format str names accepted for offers - cues (decking.Deck) of outbound cue messages from handler - + resource: + mux: """ - self.hby = hby - self.notifier = notifier - self.msgs = decking.Deck() - self.cues = decking.Deck() + self.resource = resource + self.mux = mux - super(MultisigInceptHandler, self).__init__(**kwa) - - def do(self, tymth, tock=0.0, **opts): - """ - - Handle incoming messages by parsing and verifying the credential and storing it in the wallet + def handle(self, serder, attachments=None): + """ Do route specific processsing of multisig exn messages Parameters: - payload is dict representing the body of a multisig/incept message - pre is qb64 identifier prefix of sender - sigers is list of Sigers representing the sigs on the /credential/issue message - verfers is list of Verfers of the keys used to sign the message + serder (Serder): Serder of the exn multisig message + attachments (list): list of tuples of pather, CESR SAD path attachments to the exn event """ - self.wind(tymth) - self.tock = tock - yield self.tock + self.mux.add(serder=serder) - while True: - while self.msgs: - msg = self.msgs.popleft() - if "pre" not in msg: - logger.error(f"invalid incept message, missing pre. evt: {msg}") - continue - - prefixer = msg["pre"] - if "payload" not in msg: - logger.error(f"invalid incept message, missing payload. evt: {msg}") - continue - - pay = msg["payload"] - if "aids" not in pay or "ked" not in pay: - logger.error(f"invalid incept payload, aids and ked are required. payload: {pay}") - continue - - src = prefixer.qb64 - aids = pay["aids"] +def loadHandlers(exc, mux): + """ Load handlers for the peer-to-peer distributed group multisig protocol - hab = None - for aid in aids: - if aid in self.hby.habs: - hab = self.hby.habs[aid] + Parameters: + exc (Exchanger): Peer-to-peer message router + mux (Multiplexor): Multisig communication coordinator - if hab is None: - logger.error(f"invalid incept message, no local event in aids: {pay}") - continue + """ + exc.addHandler(MultisigNotificationHandler(resource="/multisig/icp", mux=mux)) + exc.addHandler(MultisigNotificationHandler(resource="/multisig/rot", mux=mux)) + exc.addHandler(MultisigNotificationHandler(resource="/multisig/ixn", mux=mux)) + exc.addHandler(MultisigNotificationHandler(resource="/multisig/vcp", mux=mux)) + exc.addHandler(MultisigNotificationHandler(resource="/multisig/iss", mux=mux)) + exc.addHandler(MultisigNotificationHandler(resource="/multisig/rev", mux=mux)) + exc.addHandler(MultisigNotificationHandler(resource="/multisig/exn", mux=mux)) + exc.addHandler(MultisigNotificationHandler(resource="/multisig/rpy", mux=mux)) - if src not in pay["aids"] or src not in hab.kevers: - logger.error(f"invalid incept message, source not knows or not part of group. evt: {msg}") - continue - data = dict( - r='/multisig/icp/init', - src=src, - aids=aids, - ked=pay["ked"] - ) - self.notifier.add(attrs=data) +def multisigInceptExn(hab, smids, rmids, icp, delegator=None): + """ - yield - yield + Args: + hab (Hab): habitat of local multisig member AID + smids (list): list of qb64 AIDs of members with signing authority + rmids (list): list of qb64 AIDs of members with rotation authority + icp (bytes): serialized inception event with CESR streamed attachments + delegator (str): qb64 AID of Delegator is group multisig is a delegated AID + Returns: + tuple: (Serder, bytes): Serder of exn message and CESR attachments -def multisigInceptExn(hab, aids, ked, delegator=None): + """ + rmids = rmids if rmids is not None else smids + serder = serdering.SerderKERI(raw=icp) # coring.Serder(raw=icp) data = dict( - aids=aids, - ked=ked + gid=serder.pre, + smids=smids, + rmids=rmids, + ) + + embeds = dict( + icp=icp, ) if delegator is not None: data |= dict(delegator=delegator) # Create `exn` peer to peer message to notify other participants UI - exn = exchanging.exchange(route=MultisigInceptHandler.resource, modifiers=dict(), - payload=data) - ims = hab.endorse(serder=exn, last=True, pipelined=False) + exn, end = exchanging.exchange(route="/multisig/icp", modifiers=dict(), + payload=data, embeds=embeds, sender=hab.pre) + ims = hab.endorse(serder=exn, last=False, pipelined=False) del ims[:exn.size] + ims.extend(end) return exn, ims -class MultisigRotateHandler(doing.DoDoer): +def multisigRotateExn(ghab, smids, rmids, rot): """ - Handler for multisig group inception notification EXN messages - - """ - resource = "/multisig/rot" - - def __init__(self, hby, notifier, **kwa): - """ - - Parameters: - mbx (Mailboxer) of format str names accepted for offers - controller (str) qb64 identity prefix of controller - cues (decking.Deck) of outbound cue messages from handler - - """ - self.hby = hby - self.notifier = notifier - self.msgs = decking.Deck() - self.cues = decking.Deck() - - super(MultisigRotateHandler, self).__init__(**kwa) - - def do(self, tymth, tock=0.0, **opts): - """ Process incoming notifications for a group rotation - - Handle incoming messages by parsing and verifiying the credential and storing it in the wallet - - Parameters: - payload is dict representing the body of a multisig/incept message - pre is qb64 identifier prefix of sender - sigers is list of Sigers representing the sigs on the /credential/issue message - verfers is list of Verfers of the keys used to sign the message - - ToDo: NRR - fix to use both ghab.smids and ghab.rmids - - """ - self.wind(tymth) - self.tock = tock - yield self.tock - while True: - while self.msgs: - msg = self.msgs.popleft() - - if "pre" not in msg: - logger.error(f"invalid rotation message, missing pre. evt: {msg}") - continue - - prefixer = msg["pre"] - if "payload" not in msg: - logger.error(f"invalid rotation message, missing payload. evt: {msg}") - continue + Args: + ghab (GroupHab): habitat of group multisig AID + smids (list): list of qb64 AIDs of members with signing authority + rmids (list): list of qb64 AIDs of members with rotation authority + rot (bytes): serialized rotation event with CESR streamed attachments - pay = msg["payload"] - if "smids" not in pay or "rmids" not in "aids" not in pay or "gid" not in pay or "ked" not in pay: - logger.error(f"invalid rotation payload, aids, gid and ked are required. payload: {pay}") - continue + Returns: + tuple: (Serder, bytes): Serder of exn message and CESR attachments - src = prefixer.qb64 - aids = pay["aids"] - smids = pay["smids"] - rmids = pay["rmids"] - gid = pay["gid"] - ked = pay["ked"] + """ + embeds = dict( + rot=rot, + ) - if src not in smids or src not in self.hby.kevers: - logger.error(f"invalid incept message, source not knows or not part of group. evt: {msg}") - continue + exn, end = exchanging.exchange(route="/multisig/rot", modifiers=dict(), + payload=dict(gid=ghab.pre, + smids=smids, + rmids=rmids), sender=ghab.mhab.pre, + embeds=embeds) + ims = ghab.mhab.endorse(serder=exn, last=False, pipelined=False) + atc = bytearray(ims[exn.size:]) + atc.extend(end) - data = dict( - r='/multisig/rot', - i=gid, - src=src, - aids=aids, - smids=smids, - rmids=rmids, - ked=ked - ) + return exn, atc - self.notifier.add(attrs=data) - yield +def multisigInteractExn(ghab, aids, ixn): + """ Create a peer to peer message to propose a multisig group interaction event - yield + Parameters: + ghab (GroupHab): group Hab to endorse the message + aids (list): qb64 identifier prefixes to include in the interaction event + ixn (bytes): serialized interaction event with CESR streamed attachments + Returns: + tuple: (Serder, bytes): Serder of exn message and CESR attachments + """ -def multisigRotateExn(ghab, aids, ked, smids=None, rmids=None): - smids = smids if smids is not None else aids - rmids = rmids if rmids is not None else smids + embeds = dict( + ixn=ixn, + ) - exn = exchanging.exchange(route=MultisigRotateHandler.resource, modifiers=dict(), - payload=dict(gid=ghab.pre, - aids=aids, - smids=smids, - rmids=rmids, - ked=ked) - ) - ims = ghab.mhab.endorse(serder=exn, last=True, pipelined=False) + exn, end = exchanging.exchange(route="/multisig/ixn", modifiers=dict(), + payload=dict(gid=ghab.pre, + smids=aids), sender=ghab.mhab.pre, + embeds=embeds) + ims = ghab.mhab.endorse(serder=exn, last=False, pipelined=False) atc = bytearray(ims[exn.size:]) + atc.extend(end) return exn, atc -class MultisigInteractHandler(doing.DoDoer): - """ - Handler for multisig group inception notification EXN messages - - """ - resource = "/multisig/ixn" +def multisigRegistryInceptExn(ghab, usage, vcp, anc): + """ Create a peer to peer message to propose a credential registry inception from a multisig group identifier - def __init__(self, hby, notifier, **kwa): - """ + Either rot or ixn are required but not both - Parameters: - mbx (Mailboxer) of format str names accepted for offers - cues (decking.Deck) of outbound cue messages from handler - - """ - self.hby = hby - self.notifier = notifier - self.msgs = decking.Deck() - self.cues = decking.Deck() + Parameters: + ghab (GroupHab): identifier Hab for ensorsing the message to send + usage (str): human readable reason for creating the credential registry + vcp (bytes): serialized Credentials registry inception event + anc (bytes): CESR stream of serialized and signed event anchoring registry inception event - super(MultisigInteractHandler, self).__init__(**kwa) + Returns: + tuple: (Serder, bytes): Serder of exn message and CESR attachments - def do(self, tymth, tock=0.0, **opts): - """ Process incoming notifications for a group interaction events + """ - Handle incoming messages by storing a message in the mailbox of the controller + embeds = dict( + vcp=vcp, + anc=anc + ) - Parameters: - payload is dict representing the body of a multisig/ixn message - pre is qb64 identifier prefix of sender + exn, end = exchanging.exchange(route="/multisig/vcp", payload={'gid': ghab.pre, 'usage': usage}, + sender=ghab.mhab.pre, embeds=embeds) + evt = ghab.mhab.endorse(serder=exn, last=False, pipelined=False) + atc = bytearray(evt[exn.size:]) + atc.extend(end) - ToDo: NRR - fix to use both ghab.smids and ghab.rmids + return exn, atc - """ - self.wind(tymth) - self.tock = tock - yield self.tock - while True: - while self.msgs: - msg = self.msgs.popleft() +def multisigIssueExn(ghab, acdc, iss, anc): + """ Create a peer to peer message to propose a credential creation from a multisig group identifier - if "pre" not in msg: - logger.error(f"invalid rotation message, missing pre. evt: {msg}") - continue + Either rot or ixn are required but not both - prefixer = msg["pre"] - if "payload" not in msg: - logger.error(f"invalid rotation message, missing payload. evt: {msg}") - continue + Parameters: + ghab (GroupHab): identifier Hab for ensorsing the message to send + acdc (bytes): serialized Credential + iss (bytes): CESR stream of serialized and TEL issuance event + anc (bytes): CESR stream of serialized and signed anchoring event anchoring creation - pay = msg["payload"] - if "sn" not in pay or "aids" not in pay or "gid" not in pay: - logger.error(f"invalid rotation payload, aids and gid are required. payload: {pay}") - continue + Returns: + tuple: (Serder, bytes): Serder of exn message and CESR attachments - src = prefixer.qb64 - sn = pay["sn"] - aids = pay["aids"] - gid = pay["gid"] + """ - ghab = self.hby.habs[gid] - if ghab is None: - logger.error(f"invalid rotate message, not a local group: {pay}") - continue + embeds = dict( + acdc=acdc, + iss=iss, + anc=anc + ) - smids, _ = ghab.members() - if src not in smids or src not in ghab.kevers: - logger.error(f"invalid incept message, source not knows or not part of group. evt: {msg}") - continue + exn, end = exchanging.exchange(route="/multisig/iss", payload={'gid': ghab.pre}, + sender=ghab.mhab.pre, embeds=embeds) + evt = ghab.mhab.endorse(serder=exn, last=False, pipelined=False) + atc = bytearray(evt[exn.size:]) + atc.extend(end) - data = dict( - r='/multisig/ixn', - src=src, - gid=gid, - sn=sn, - aids=aids, - ) - data["data"] = pay["data"] if "data" in pay else None + return exn, atc - self.notifier.add(data) - yield - yield +def multisigRevokeExn(ghab, said, rev, anc): + """ Create a peer to peer message to propose a credential revocation from a multisig group identifier -def multisigInteractExn(ghab, sn, aids, data): - """ Create a peer to peer message to propose a multisig group interaction event + Either rot or ixn are required but not both Parameters: - ghab (Hab): group Hab to endorse the message - sn (int): sequence number of proposed interaction event - aids (list): qb64 identifier prefixes to include in the interaction event - data (list): data to anchor in the interaction event + ghab (GroupHab): identifier Hab for ensorsing the message to send + said (str): qb64 SAID of credential being revoked + rev (bytes): CESR stream of serialized and TEL revocation event + anc (bytes): CESR stream of serialized and signed anchoring event anchoring revocation Returns: - Serder: Serder of exn message to send - butearray: attachment signatures - """ - - exn = exchanging.exchange(route=MultisigInteractHandler.resource, modifiers=dict(), - payload=dict(gid=ghab.pre, - sn=sn, - aids=aids, - data=data) - ) - ims = ghab.mhab.endorse(serder=exn, last=True, pipelined=False) - atc = bytearray(ims[exn.size:]) - - return exn, atc - - -class MultisigIssueHandler(doing.DoDoer): - """ - Handler for multisig group issuance notification EXN messages + tuple: (Serder, bytes): Serder of exn message and CESR attachments """ - resource = "/multisig/issue" - def __init__(self, notifier, **kwa): - """ + embeds = dict( + rev=rev, + anc=anc + ) - Parameters: - mbx (Mailboxer) of format str names accepted for offers - controller (str) qb64 identity prefix of controller - cues (decking.Deck) of outbound cue messages from handler + exn, end = exchanging.exchange(route="/multisig/rev", payload={'gid': ghab.pre, 'said': said}, + sender=ghab.mhab.pre, embeds=embeds) + evt = ghab.mhab.endorse(serder=exn, last=False, pipelined=False) + atc = bytearray(evt[exn.size:]) + atc.extend(end) - """ - self.notifier = notifier - self.msgs = decking.Deck() - self.cues = decking.Deck() + return exn, atc - super(MultisigIssueHandler, self).__init__(**kwa) - def do(self, tymth, tock=0.0, **opts): - """ +def multisigRpyExn(ghab, rpy): + """ Create a peer to peer message to propose a credential revocation from a multisig group identifier - Handle incoming messages by parsing and verifiying the credential and storing it in the wallet + Either rot or ixn are required but not both - Parameters: - payload is dict representing the body of a multisig/incept message - pre is qb64 identifier prefix of sender - sigers is list of Sigers representing the sigs on the /credential/issue message - verfers is list of Verfers of the keys used to sign the message + Parameters: + ghab (GroupHab): identifier Hab for ensorsing the message to send + rpy (bytes): CESR stream of serialized and reply event - """ - self.wind(tymth) - self.tock = tock - yield self.tock + Returns: + tuple: (Serder, bytes): Serder of exn message and CESR attachments - while True: - while self.msgs: - msg = self.msgs.popleft() - pl = msg["payload"] + """ - try: - creder = proving.Creder(ked=pl) - data = dict( - r="/multisig/issue", - ked=creder.ked - ) + embeds = dict( + rpy=rpy + ) - self.notifier.add(attrs=data) + exn, end = exchanging.exchange(route="/multisig/rpy", payload={'gid': ghab.pre}, + sender=ghab.mhab.pre, embeds=embeds) + evt = ghab.mhab.endorse(serder=exn, last=False, pipelined=False) + atc = bytearray(evt[exn.size:]) + atc.extend(end) - except ValueError as ex: - logger.error(f"unable to process multisig credential issue proposal {pl}: {ex}") - yield - yield + return exn, atc -def multisigIssueExn(hab, creder): +def multisigExn(ghab, exn): """ Create a peer to peer message to propose a credential issuance from a multisig group identifier + Either rot or ixn are required but not both + Parameters: - hab (Hab): identifier Hab for ensorsing the message to send - creder (Creder): Creder instance of the issued credential + ghab (GroupHab): identifier Hab for ensorsing the message to send + exn (bytes): CESR stream of serialized echange message, with signatures Returns: - Serder: Serder of exn message to send - butearray: attachment signatures + tuple: (Serder, bytes): Serder of exn message and CESR attachments """ - exn = exchanging.exchange(route="/multisig/issue", payload=creder.ked) - evt = hab.mhab.endorse(serder=exn, last=True, pipelined=False) - atc = bytearray(evt[exn.size:]) + embeds = dict( + exn=exn + ) - return exn, atc + wexn, end = exchanging.exchange(route="/multisig/exn", payload={'gid': ghab.pre}, sender=ghab.mhab.pre, + embeds=embeds) + evt = ghab.mhab.endorse(serder=wexn, last=False, pipelined=False) + atc = bytearray(evt[wexn.size:]) + atc.extend(end) + + return wexn, atc def getEscrowedEvent(db, pre, sn): @@ -1182,7 +512,7 @@ def getEscrowedEvent(db, pre, sn): dig = bytes(dig) key = dbing.dgKey(pre, dig) # digest key msg = db.getEvt(key) - serder = coring.Serder(raw=bytes(msg)) + serder = serdering.SerderKERI(raw=bytes(msg)) sigs = [] for sig in db.getSigsIter(key): @@ -1203,3 +533,162 @@ def getEscrowedEvent(db, pre, sn): msg.extend(couple) return msg + + +class Multiplexor: + """ Multiplexor (mux) is responsible for coordinating peer-to-peer messages between group multisig participants + + When new messages arrive the Mux will associate the SAID of the embedded messages with the exn message said + as well as the sender. This will allow the controller of the participant in the group multisig to have knowledge + of who has sent what messages and whether they match. In addition, if the controller of the local participant + has already approved the messages embedded in this exn, the messages will be passed thru a non-local parser. + + Attributes: + hby (habbing.Habery): database environment for local Habs + rtr (routing.Router): routes reply 'rpy' messages + rvy (routing.Revery): factory that processes reply 'rpy' messages + exc (exchanging.Exchanger): processor and router for peer-to-peer msgs + kvy (eventing.Kevery): factory for local processing of local event msgs + psr (parsing.Parser): parses local messages for .kvy .rvy + notifier (notifying.Notifier): stores notices for numan consumption + + Parameters: + hby (habbing.Habery): database environment for local Habs + notifier (notifying.Notifier): stores notices for numan consumption + + + """ + + def __init__(self, hby, notifier): + """ Create Multiplexor for local database and Habs + + Parameters: + hby (habbing.Habery): database environment for local Habs + notifier (notifying.Notifier): stores notices for numan consumption + + """ + self.hby = hby + self.rtr = routing.Router() + self.rvy = routing.Revery(db=self.hby.db, rtr=self.rtr) + self.exc = exchanging.Exchanger(hby=self.hby, handlers=[]) + self.kvy = eventing.Kevery(db=self.hby.db, lax=False, local=False, rvy=self.rvy) + self.kvy.registerReplyRoutes(router=self.rtr) + self.psr = parsing.Parser(framed=True, kvy=self.kvy, rvy=self.rvy, exc=self.exc) + + self.notifier = notifier + + def add(self, serder): + """ Process /multisig message by associating the exn with the SAID of the embedded event section + + Adds the exn message contained in `serder` to the set of messages received for a given set of embedded + events. Ensures this is a /multisig message with the correct properties and then stores the SAID of the + exn message and the prefix of the sender associated with the SAID of the embedded event section. Also + sends the controller of the local participant a notice. + + This method will extract and parse the embedded events if the local participant has already approved the + events so that any addition signatures can be processed. + + Parameters: + serder (coring.Serder): peer-to-peer exn "/multisig" message to coordinate from other participants + + Returns: + + """ + ked = serder.ked + if 'e' not in ked: # No embedded events + return + + embed = ked['e'] + esaid = embed['d'] + sender = ked['i'] + route = ked['r'] + payload = ked['a'] + + # Route specific logic to ensure this is a valid exn for a local participant. + match route.split("/"): + case ["", "multisig", "icp"]: + mids = payload["smids"] + if "rmids" in payload: + mids.extend(payload["rmids"]) + member = any([True for mid in mids if mid in self.hby.kevers]) + if not member: + raise ValueError(f"invalid request to join group, not member in mids={mids}") + + case ["", "multisig", "rot"]: + gid = payload["gid"] + if gid not in self.hby.habs: + mids = payload["smids"] + mids.extend(payload["rmids"]) + member = any([True for mid in mids if mid in self.hby.kevers]) + if not member: + raise ValueError(f"invalid request to join group, not member in mids={mids}") + + case ["", "multisig", *_]: + gid = payload["gid"] + if gid not in self.hby.habs: + raise ValueError(f"invalid request to participate in group, not member of gid={gid}") + + case _: + raise ValueError(f"invalid route {route} for multiplexed exn={ked}") + + if len(self.hby.db.meids.get(keys=(esaid,))) == 0: # No one has submitted this message yet + if sender not in self.hby.habs: # We are not sending this one, notify us + data = dict( + r=route, + d=serder.said + ) + + self.notifier.add(attrs=data) + + self.hby.db.meids.add(keys=(esaid,), val=coring.Saider(qb64=serder.said)) + self.hby.db.maids.add(keys=(esaid,), val=coring.Prefixer(qb64=serder.pre)) + + submitters = self.hby.db.maids.get(keys=(esaid,)) + if sender not in self.hby.habs: # We are not sending this one, need to parse if already approved + + # If we've already submitted an identical payload, parse this one because we've approved it + approved = any([True for sub in submitters if sub.qb64 in self.hby.kevers]) + if approved: + # Clone exn from database, ensuring it is stored with valid signatures + exn, paths = exchanging.cloneMessage(self.hby, said=serder.said) + e = exn.ked['e'] + ims = bytearray() + + # Loop through all the embedded events, extract the attachments for those events... + for key, val in e.items(): + if not isinstance(val, dict): + continue + + sadder = coring.Sadder(ked=val) + ims.extend(sadder.raw) + if key in paths: + atc = paths[key] + ims.extend(atc) + + # ... and parse + self.psr.parse(ims=ims) + + else: + # Should we prod the user with another submission if we haven't already approved it? + route = ked['r'] + data = dict( + r=route, + d=serder.said, + e=embed['d'] + ) + + self.notifier.add(attrs=data) + + def get(self, esaid): + saiders = self.hby.db.meids.get(keys=(esaid,)) + + exns = [] + for saider in saiders: + exn, paths = exchanging.cloneMessage(hby=self.hby, said=saider.qb64) + exns.append(dict( + exn=exn.ked, + paths={k: path.decode("utf-8") for k, path in paths.items()}, + )) + + return exns + diff --git a/src/keri/app/habbing.py b/src/keri/app/habbing.py index e2c22cc87..98677fd99 100644 --- a/src/keri/app/habbing.py +++ b/src/keri/app/habbing.py @@ -5,33 +5,25 @@ """ import json -import os from contextlib import contextmanager -from urllib.parse import urlsplit from math import ceil -from ordered_set import OrderedSet as oset +from urllib.parse import urlsplit from hio.base import doing -from hio.core import wiring -from hio.core.tcp import clienting, serving from hio.help import hicting -from keri.peer import exchanging -from . import keeping, configing, directing +from keri.peer import exchanging +from . import keeping, configing from .. import help from .. import kering -from ..core import coring, eventing, parsing, routing -from ..core.coring import Serder +from ..core import coring, eventing, parsing, routing, serdering from ..db import dbing, basing -from ..kering import MissingSignatureError +from ..kering import MissingSignatureError, Roles logger = help.ogler.getLogger() -SALT = coring.Salter(raw=b'0123456789abcdef').qb64 # '0AAwMTIzNDU2Nzg5YWJjZGVm' - - @contextmanager -def openHby(*, name="test", base="", temp=True, salt=SALT, **kwa): +def openHby(*, name="test", base="", temp=True, salt=None, **kwa): """ Context manager wrapper for Habery instance. Context 'with' statements call .close on exit of 'with' block @@ -76,6 +68,7 @@ def openHby(*, name="test", base="", temp=True, salt=SALT, **kwa): """ habery = None + salt = salt if not None else coring.Salter(raw=b'0123456789abcdef').qb64 try: habery = Habery(name=name, base=base, temp=temp, salt=salt, **kwa) yield habery @@ -111,80 +104,6 @@ def openHab(name="test", base="", salt=b'0123456789abcdef', temp=True, cf=None, yield hby, hab -def setupHabery(name="who", base="main", temp=False, curls=None, remote="eve", iurls=None): - """ - Setup and return doers list to run controller - - Parameters: - name(str): is the name used for a specific habitat - base(str) is the name used for shared resources i.e. Baser and Keeper - The habitat specific config file will be in base/name - temp(bool): True creates Habery in temp directory - curls (list[str]): local controller's service endpoint urls - remote (str): name of remote direct mode target - iurls (list[str]): oobi urls - - Load endpoint database with named target urls including http not just tcp - - - conf file json - { - dt: "isodatetime", - curls: ["tcp://localhost:5620/"], - iurls: ["tcp://localhost:5621/?name=eve"], - } - """ - - if not curls: - curls = ["ftp://localhost:5620/"] - - if not iurls: # blind oobi - iurls = [f"ftp://localhost:5621/?role={kering.Roles.peer}&name={remote}"] - - # setup databases for dependency injection and config file - ks = keeping.Keeper(name=base, temp=temp) # not opened by default, doer opens - ksDoer = keeping.KeeperDoer(keeper=ks) # doer do reopens if not opened and closes - db = basing.Baser(name=base, temp=temp) # not opened by default, doer opens - dbDoer = basing.BaserDoer(baser=db, reload=True) # doer do reopens if not opened and closes - cf = configing.Configer(name=name, base=base, temp=temp) - cfDoer = configing.ConfigerDoer(configer=cf) - conf = cf.get() - if not conf: # setup config file - conf = dict(dt=help.nowIso8601(), curls=curls, iurls=iurls) - cf.put(conf) - - # setup habery - hby = Habery(name=name, base=base, ks=ks, db=db, cf=cf, temp=temp) - hbyDoer = HaberyDoer(habery=hby) # setup doer - - # setup wirelog to create test vectors - path = os.path.dirname(__file__) - path = os.path.join(path, 'logs') - wl = wiring.WireLog(samed=True, filed=True, name=name, prefix='keri', - reopen=True, headDirPath=path) - wireDoer = wiring.WireLogDoer(wl=wl) # setup doer - - localPort = 5620 - remotePort = 5621 - # setup local directmode tcp server - server = serving.Server(host="", port=localPort, wl=wl) - serverDoer = serving.ServerDoer(server=server) # setup doer - directant = directing.Directant(hab=hby, server=server) - # Reactants created on demand by directant - - # setup default remote direct mode client to remote party - client = clienting.Client(host='127.0.0.1', port=remotePort, wl=wl) - clientDoer = clienting.ClientDoer(client=client) # setup doer - director = directing.Director(hab=hby, client=client, tock=0.125) - reactor = directing.Reactor(hab=hby, client=client) - - logger.info("\nController resources at %s/%s\nListening on TCP port %s to " - "port %s.\n\n", hby.base, hby.name, localPort, remotePort) - - return [ksDoer, dbDoer, cfDoer, hbyDoer, wireDoer, clientDoer, director, reactor, - serverDoer, directant] - - class Habery: """Habery class provides shared database environments for all its Habitats Key controller and identifier controller shared configuration file, keystore @@ -299,11 +218,12 @@ def __init__(self, *, name='test', base="", temp=False, self.mgr = None # wait to setup until after ks is known to be opened self.rtr = routing.Router() self.rvy = routing.Revery(db=self.db, rtr=self.rtr) - self.exc = exchanging.Exchanger(db=self.db, handlers=[], local=True) + self.exc = exchanging.Exchanger(hby=self, handlers=[]) self.kvy = eventing.Kevery(db=self.db, lax=False, local=True, rvy=self.rvy) self.kvy.registerReplyRoutes(router=self.rtr) self.psr = parsing.Parser(framed=True, kvy=self.kvy, rvy=self.rvy, exc=self.exc) self.habs = {} # empty .habs + self.namespaces = {} # empty .namespaces self._signator = None self.inited = False @@ -373,7 +293,7 @@ def setup(self, *, seed=None, aeid=None, bran=None, pidx=None, algo=None, aeid = signer.verfer.qb64 # lest it remove encryption if salt is None: # salt for signing keys not aeid seed - salt = SALT + salt = coring.Salter(raw=b'0123456789abcdef').qb64 else: salt = coring.Salter(qb64=salt).qb64 @@ -407,11 +327,20 @@ def loadHabs(self): pre = habord.hid # create Hab instance and inject dependencies - if habord.mid: + if habord.mid and not habord.sid: hab = GroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr, rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr, name=name, pre=pre, temp=self.temp, smids=habord.smids) groups.append(habord) + elif habord.sid and not habord.mid: + hab = SignifyHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr, + rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr, + name=name, pre=habord.sid) + elif habord.sid and habord.mid: + hab = SignifyGroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr, + rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr, + name=name, pre=habord.sid) + groups.append(habord) else: hab = Hab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr, rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr, @@ -428,13 +357,51 @@ def loadHabs(self): hab.inited = True self.habs[hab.pre] = hab + for keys, habord in self.db.nmsp.getItemIter(): + ns = keys[0] + name = ".".join(keys[1:]) # detupleize the database key name + pre = habord.hid + + # create Hab instance and inject dependencies + if habord.mid and not habord.sid: + hab = GroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr, + rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr, + name=name, ns=ns, pre=pre, temp=self.temp, smids=habord.smids) + groups.append(habord) + elif habord.sid and not habord.mid: + hab = SignifyHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr, + rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr, + name=name, ns=ns, pre=habord.sid) + elif habord.sid and habord.mid: + hab = SignifyGroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr, + rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr, + name=name, pre=habord.sid) + groups.append(habord) + else: + hab = Hab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr, + rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr, + name=name, ns=ns, pre=pre, temp=self.temp) + + # Rules for acceptance + # if its delegated its accepted into its own local KEL even if the + # delegator has not sealed it + if not hab.accepted and not habord.mid: + raise kering.ConfigurationError(f"Problem loading Hab pre=" + f"{pre} name={name} from db.") + + # read in config file and process any oobis or endpoints for hab + hab.inited = True + if ns not in self.namespaces: + self.namespaces[ns] = dict() + self.namespaces[ns][hab.pre] = hab + # Populate the participant hab after loading all habs for habord in groups: self.habs[habord.hid].mhab = self.habs[habord.mid] self.reconfigure() # post hab load reconfiguration - def makeHab(self, name, **kwa): + def makeHab(self, name, ns=None, cf=None, **kwa): """Make new Hab with name, pre is generated from **kwa Parameters: (Passthrough to hab.make) @@ -454,16 +421,26 @@ def makeHab(self, name, **kwa): events allowed in KEL for this Hab data (list | None): seal dicts """ - hab = Hab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr, + if ns is not None and "." in ns: + raise kering.ConfigurationError("Hab namespace names are not allowed to contain the '.' character") + + cf = cf if cf is not None else self.cf + hab = Hab(ks=self.ks, db=self.db, cf=cf, mgr=self.mgr, rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr, - name=name, temp=self.temp) + name=name, ns=ns, temp=self.temp) hab.make(**kwa) - self.habs[hab.pre] = hab + + if ns is None: + self.habs[hab.pre] = hab + else: + if ns not in self.namespaces: + self.namespaces[ns] = dict() + self.namespaces[ns][hab.pre] = hab return hab - def makeGroupHab(self, group, mhab, smids, rmids=None, **kwa): + def makeGroupHab(self, group, mhab, smids, rmids=None, ns=None, **kwa): """Make new Group Hab using group has group hab name, with lhab as local participant. @@ -528,13 +505,187 @@ def makeGroupHab(self, group, mhab, smids, rmids=None, **kwa): # create group Hab in this Habery hab = GroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr, rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr, - name=group, mhab=mhab, smids=smids, rmids=rmids, temp=self.temp) + name=group, ns=ns, mhab=mhab, smids=smids, rmids=rmids, temp=self.temp) + + hab.make(**kwa) # finish making group hab with injected pass throughs + if ns is None: + self.habs[hab.pre] = hab + else: + if ns not in self.namespaces: + self.namespaces[ns] = dict() + self.namespaces[ns][hab.pre] = hab + + return hab + + def joinGroupHab(self, pre, group, mhab, smids, rmids=None, ns=None): + """Make new Group Hab using group has group hab name, with lhab as local + participant. + + Parameters: (non-pass-through): + pre (str): qb64 identifier prefix of group + group (str): human readable alias for group identifier + mhab (Hab): group member (local) hab + smids (list): group member signing ids (qb64) from which to extract + inception event current signing keys + rmids (list | None): group member rotation ids (qb64) from which to extract + inception event next key digests + if rmids is None then use assign smids to rmids + if rmids is empty then no next key digests + which means group identifier is no longer transferable. + + + """ + + if mhab.pre not in smids and mhab.pre not in rmids: + raise kering.ConfigurationError(f"Local member identifier " + f"{mhab.pre} must be member of " + f"smids ={smids} and/or " + f"rmids={rmids}.") + + for mid in smids: + if mid not in self.kevers: + raise kering.ConfigurationError(f"KEL missing for signing member " + f"identifier {mid} from group's " + f"current members ={smids}") + + if rmids is not None: + for rmid in rmids: + if rmid not in self.kevers: + raise kering.ConfigurationError(f"KEL missing for next member " + f"identifier {rmid} in group's" + f" next members ={rmids}") + + # create group Hab in this Habery + hab = GroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr, + rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr, + name=group, ns=ns, mhab=mhab, smids=smids, rmids=rmids, temp=self.temp) + + hab.pre = pre + habord = basing.HabitatRecord(hid=hab.pre, + mid=mhab.pre, + smids=smids, + rmids=rmids) + + hab.save(habord) + hab.prefixes.add(pre) + hab.inited = True + + if ns is None: + self.habs[hab.pre] = hab + else: + if ns not in self.namespaces: + self.namespaces[ns] = dict() + self.namespaces[ns][hab.pre] = hab + + return hab + + def makeSignifyHab(self, name, ns=None, **kwa): + # create group Hab in this Habery + hab = SignifyHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr, + rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr, + name=name, ns=ns, temp=self.temp) + + hab.make(**kwa) # finish making group hab with injected pass throughs + if ns is None: + self.habs[hab.pre] = hab + else: + if ns not in self.namespaces: + self.namespaces[ns] = dict() + self.namespaces[ns][hab.pre] = hab + + return hab + + def makeSignifyGroupHab(self, name, mhab, ns=None, **kwa): + # create group Hab in this Habery + hab = SignifyGroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr, + rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr, + name=name, mhab=mhab, ns=ns, temp=self.temp) hab.make(**kwa) # finish making group hab with injected pass throughs - self.habs[hab.pre] = hab + if ns is None: + self.habs[hab.pre] = hab + else: + if ns not in self.namespaces: + self.namespaces[ns] = dict() + self.namespaces[ns][hab.pre] = hab + + return hab + + def joinSignifyGroupHab(self, pre, name, mhab, smids, rmids=None, ns=None): + """Make new Group Hab using group has group hab name, with lhab as local + participant. + + Parameters: (non-pass-through): + pre (str): qb64 identifier prefix of group + name (str): human readable alias for group identifier + mhab (Hab): group member (local) hab + smids (list): group member signing ids (qb64) from which to extract + inception event current signing keys + rmids (list | None): group member rotation ids (qb64) from which to extract + inception event next key digests + if rmids is None then use assign smids to rmids + if rmids is empty then no next key digests + which means group identifier is no longer transferable. + + + """ + + if mhab.pre not in smids and mhab.pre not in rmids: + raise kering.ConfigurationError(f"Local member identifier " + f"{mhab.pre} must be member of " + f"smids ={smids} and/or " + f"rmids={rmids}.") + + for mid in smids: + if mid not in self.kevers: + raise kering.ConfigurationError(f"KEL missing for signing member " + f"identifier {mid} from group's " + f"current members ={smids}") + + if rmids is not None: + for rmid in rmids: + if rmid not in self.kevers: + raise kering.ConfigurationError(f"KEL missing for next member " + f"identifier {rmid} in group's" + f" next members ={rmids}") + + # create group Hab in this Habery + hab = SignifyGroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr, + rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr, + name=name, mhab=mhab, ns=ns, temp=self.temp) + + hab.pre = pre + habord = basing.HabitatRecord(hid=hab.pre, + mid=mhab.pre, + smids=smids, + rmids=rmids) + + hab.save(habord) + hab.prefixes.add(pre) + hab.inited = True + + if ns is None: + self.habs[hab.pre] = hab + else: + if ns not in self.namespaces: + self.namespaces[ns] = dict() + self.namespaces[ns][hab.pre] = hab return hab + def deleteHab(self, name): + hab = self.habByName(name) + if not hab: + return False + + if not self.db.habs.rem(keys=(name,)): + return False + + del self.habs[hab.pre] + self.db.prefixes.remove(hab.pre) + + return True + def extractMerfersMigers(self, smids, rmids=None): """ Extract the public key verfer and next digest diger from the current @@ -562,7 +713,7 @@ def extractMerfersMigers(self, smids, rmids=None): for mid in rmids: kever = self.kevers[mid] - digers = kever.digers + digers = kever.ndigers if digers: # abandoned id may have empty next digers migers.append(digers[0]) if len(digers) > 1: @@ -599,17 +750,44 @@ def prefixes(self): """ return self.db.prefixes - def habByName(self, name): + def habByPre(self, pre): + """ + Returns the Hab from and namespace including the default namespace. + + Args: + pre (str): qb64 aid of hab to find + + Returns: + Hab: Hab instance for the aid pre or None + """ + hab = None + if pre in self.habs: + hab = self.habs[pre] + else: + for nsp in self.namespaces.values(): + if pre in nsp: + hab = nsp[pre] + + return hab + + def habByName(self, name, ns=None): """ Returns: hab (Hab): instance from .habs by name if any otherwise None Parameters: name (str): alias of Hab + ns (str): optional namespace of hab """ - if (habord := self.db.habs.get(name)) is not None: + if ns is not None: + if (habord := self.db.nmsp.get(keys=(ns, name))) is not None: + habs = self.namespaces[ns] + return habs[habord.hid] if habord.hid in habs else None + + elif (habord := self.db.habs.get(name)) is not None: return self.habs[habord.hid] if habord.hid in self.habs else None + return None def reconfigure(self): @@ -687,7 +865,7 @@ def __init__(self, db, name=SIGNER, **kwa): self.db.hbys.pin(name, self.pre) else: self.pre = spre - self._hab = Hab(name=name, db=db, pre=self.pre, **kwa) + self._hab = Hab(name=name, db=db, pre=self.pre, **kwa) def sign(self, ser): """ Sign the data in ser with the Signator's private key using the Manager @@ -815,7 +993,7 @@ class BaseHab: """ def __init__(self, ks, db, cf, mgr, rtr, rvy, kvy, psr, *, - name='test', pre=None, temp=False): + name='test', ns=None, pre=None, temp=False): """ Initialize instance. @@ -848,13 +1026,42 @@ def __init__(self, ks, db, cf, mgr, rtr, rvy, kvy, psr, *, self.psr = psr # injected self.name = name + self.ns = ns self.pre = pre # wait to setup until after db is known to be opened self.temp = True if temp else False self.inited = False self.delpre = None # assigned laster if delegated - def make(self, DnD, code, data, delpre, digers, estOnly, isith, nsith, toad, verfers, wits): + def make(self, DnD, code, data, delpre, estOnly, isith, verfers, nsith, digers, toad, wits): + """ + Creates Serder of inception event for provided parameters. + Assumes injected dependencies were already setup. + + Parameters: + isith (int | str | list | None): incepting signing threshold as + int, str hex, or list weighted if any, otherwise compute + default from verfers + code (str): prefix derivation code default Blake3 + nsith (int, str, list | None ): next signing threshold as int, + str hex or list weighted if any, otherwise compute default from + digers + verfers (list[Verfer]): Verfer instances for initial signing keys + digers (list[Diger] | None) Diger instances for next key digests + toad (int |str| None): int or str hex of witness threshold if + specified else compute default based on number of wits (backers) + wits (list | None): qb64 prefixes of witnesses if any + delpre (str | None): qb64 of delegator identifier prefix if any + estOnly (bool | None): True means add trait eventing.TraitCodex.EstOnly + which means only establishment events allowed in KEL for this Hab + False (default) means allows non-est events and no trait is added. + DnD (bool): True means add trait of eventing.TraitCodex.DnD which + means do not allow delegated identifiers from this identifier + False (default) means do allow and no trait is added. + + data (list | None): seal dicts + + """ icount = len(verfers) ncount = len(digers) if digers is not None else 0 if isith is None: # compute default @@ -878,7 +1085,8 @@ def make(self, DnD, code, data, delpre, digers, estOnly, isith, nsith, toad, ver ndigs=[diger.qb64 for diger in digers], toad=toad, wits=wits, - cnfg=cnfg, ) + cnfg=cnfg, + code=code) else: serder = eventing.incept(keys=keys, isith=cst, @@ -891,6 +1099,14 @@ def make(self, DnD, code, data, delpre, digers, estOnly, isith, nsith, toad, ver data=data) return serder + def save(self, habord): + if self.ns is None: + self.db.habs.put(keys=self.name, + val=habord) + else: + self.db.nmsp.put(keys=(self.ns, self.name), + val=habord) + def reconfigure(self): """Apply configuration from config file managed by .cf. to this Hab. Assumes that .pre and signing keys have been setup in order to create @@ -932,35 +1148,6 @@ def reconfigure(self): stamp=help.toIso8601(dt=dt))) self.psr.parse(ims=msgs) - def recreate(self, serder, opre, verfers): - """ Recreate the Hab with new identifier prefix. - - """ - - self.pre = serder.ked["i"] # new pre - self.mgr.move(old=opre, new=self.pre) - - habr = self.db.habs.get(self.name) - # may want db method that updates .habs. and .prefixes together - self.db.habs.pin(keys=self.name, - val=basing.HabitatRecord(hid=self.pre, - watchers=habr.watchers)) - self.prefixes.add(self.pre) - - # self.kvy = eventing.Kevery(db=self.db, lax=False, local=True) - # create inception event - sigers = self.mgr.sign(ser=serder.raw, verfers=verfers) - self.kvy.processEvent(serder=serder, sigers=sigers) - # self.psr = parsing.Parser(framed=True, kvy=self.kvy) - if self.pre not in self.kevers: - raise kering.ConfigurationError("Improper Habitat inception for " - "pre={}.".format(self.pre)) - - @property - def group(self): - """ True means this is a group multisig Hab """ - return False - @property def iserder(self): """ @@ -972,7 +1159,7 @@ def iserder(self): if (raw := self.db.getEvt(eventing.dgKey(pre=self.pre, dig=bytes(dig)))) is None: raise kering.ConfigurationError("Missing inception event for " "Habitat pre={}.".format(self.pre)) - return coring.Serder(raw=bytes(raw)) + return serdering.SerderKERI(raw=bytes(raw)) @property def kevers(self): @@ -1003,30 +1190,25 @@ def incept(self, **kwa): """Alias for .make """ self.make(**kwa) - def rotate(self, *, isith=None, nsith=None, ncount=None, toad=None, cuts=None, adds=None, - data=None, merfers=None, migers=None): + def rotate(self, *, verfers=None, digers=None, isith=None, nsith=None, toad=None, cuts=None, adds=None, + data=None): """ Perform rotation operation. Register rotation in database. Returns: bytearrayrotation message with attached signatures. Parameters: + verfers (list | None): Verfer instances of public keys qb64 + digers (list | None): Diger instances of public next key digests qb64 isith (int |str | None): current signing threshold as int or str hex or list of str weights default is prior next sith nsith (int |str | None): next, next signing threshold as int or str hex or list of str weights default is based on isith when None - ncount (int | None): next number of signing keys - default is len of prior next digs toad (int | str | None): hex of witness threshold after cuts and adds cuts (list | None): of qb64 pre of witnesses to be removed from witness list adds (list | None): of qb64 pre of witnesses to be added to witness list data (list | None): of dicts of committed data such as seals - merfers (list | None): group member Verfer instances of public keys qb64, - one collected from each multisig group member - migers (list | None): group member Diger instances of public - next key digests qb64 - one collected from each multisig group member """ @@ -1037,19 +1219,6 @@ def rotate(self, *, isith=None, nsith=None, ncount=None, toad=None, cuts=None, a isith = kever.ntholder.sith # use prior next sith as default if nsith is None: nsith = isith # use new current as default - if ncount is None: - ncount = len(kever.digers) # use len of prior next digers as default - - if merfers: # multisig group rotate so not from keystore - verfers = merfers - digers = migers - else: # from keystore - try: - verfers, digers = self.mgr.replay(pre=self.pre) - except IndexError: # old next is new current - verfers, digers = self.mgr.rotate(pre=self.pre, - ncount=ncount, - temp=self.temp) if isith is None: # compute default from newly rotated verfers above isith = f"{max(1, ceil(len(verfers) / 2)):x}" @@ -1061,10 +1230,19 @@ def rotate(self, *, isith=None, nsith=None, ncount=None, toad=None, cuts=None, a keys = [verfer.qb64 for verfer in verfers] + indices = [] + for idx, diger in enumerate(kever.ndigers): + pdigs = [coring.Diger(ser=verfer.qb64b, code=diger.code).qb64 for verfer in verfers] + if diger.qb64 in pdigs: + indices.append(idx) + + if not kever.ntholder.satisfy(indices): + raise kering.ValidationError("invalid rotation, new key set unable to satisfy prior next signing threshold") + if kever.delegator is not None: # delegator only shows up in delcept serder = eventing.deltate(pre=kever.prefixer.qb64, keys=keys, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=kever.sner.num + 1, isith=cst, nsith=nst, @@ -1077,7 +1255,7 @@ def rotate(self, *, isith=None, nsith=None, ncount=None, toad=None, cuts=None, a else: serder = eventing.rotate(pre=kever.prefixer.qb64, keys=keys, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=kever.sner.num + 1, isith=cst, nsith=nst, @@ -1104,14 +1282,14 @@ def rotate(self, *, isith=None, nsith=None, ncount=None, toad=None, cuts=None, a return msg - def interact(self, data=None): + def interact(self, *, data=None): """ Perform interaction operation. Register interaction in database. Returns: bytearray interaction message with attached signatures. """ kever = self.kever serder = eventing.interact(pre=kever.prefixer.qb64, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=kever.sner.num + 1, data=data) @@ -1123,12 +1301,13 @@ def interact(self, data=None): self.kvy.processEvent(serder=serder, sigers=sigers) except MissingSignatureError: pass - except Exception: - raise kering.ValidationError("Improper Habitat rotation for " - "pre={}.".format(self.pre)) + except Exception as ex: + raise kering.ValidationError("Improper Habitat interaction for " + "pre={}.".format(self.pre)) from ex return msg + def sign(self, ser, verfers=None, indexed=True, indices=None, ondices=None, **kwa): """Sign given serialization ser using appropriate keys. Use provided verfers or .kever.verfers to lookup keys to sign. @@ -1159,6 +1338,25 @@ def sign(self, ser, verfers=None, indexed=True, indices=None, ondices=None, **kw indices=indices, ondices=ondices) + def decrypt(self, ser, verfers=None, **kwa): + """Sign given serialization ser using appropriate keys. + Use provided verfers or .kever.verfers to lookup keys to sign. + + Parameters: + ser (bytes): serialization to sign + verfers (list[Verfer] | None): Verfer instances to get pub verifier + keys to lookup private siging keys. + verfers None means use .kever.verfers. Assumes that when group + and verfers is not None then provided verfers must be .kever.verfers + + """ + if verfers is None: + verfers = self.kever.verfers # when group these provide group signing keys + + return self.mgr.decrypt(ser=ser, + verfers=verfers, + ) + def query(self, pre, src, query=None, **kwa): """ Create, sign and return a `qry` message against the attester for the prefix @@ -1177,7 +1375,6 @@ def query(self, pre, src, query=None, **kwa): query['i'] = pre query["src"] = src serder = eventing.query(query=query, **kwa) - return self.endorse(serder, last=True) def endorse(self, serder, last=False, pipelined=True): @@ -1203,7 +1400,7 @@ def endorse(self, serder, last=False, pipelined=True): seal = eventing.SealLast(i=kever.prefixer.qb64) else: seal = eventing.SealEvent(i=kever.prefixer.qb64, - s=hex(kever.lastEst.s), + s="{:x}".format(kever.lastEst.s), d=kever.lastEst.d) sigers = self.sign(ser=serder.raw, @@ -1223,23 +1420,40 @@ def endorse(self, serder, last=False, pipelined=True): return msg - def exchange(self, serder, save=False): + def exchange(self, route, + payload, + recipient, + date=None, + eid=None, + dig=None, + modifiers=None, + embeds=None, + save=False): """ Returns signed exn, message of serder with count code and receipt couples (pre+cig) Builds msg and then processes it into own db to validate """ # sign serder event + + serder, end = exchanging.exchange(route=route, + payload=payload, + sender=self.pre, + recipient=recipient, + date=date, + dig=dig, + modifiers=modifiers, + embeds=embeds) + if self.kever.prefixer.transferable: - seal = eventing.SealLast(i=self.kever.prefixer.qb64) - sigers = self.sign(ser=serder.raw, - indexed=True) - msg = eventing.messagize(serder=serder, sigers=sigers, seal=seal) + msg = self.endorse(serder=serder, pipelined=False) else: cigars = self.sign(ser=serder.raw, indexed=False) msg = eventing.messagize(serder, cigars=cigars) + msg.extend(end) + if save: self.psr.parseOne(ims=bytearray(msg)) # process local copy into db @@ -1272,11 +1486,16 @@ def receipt(self, serder): self.psr.parseOne(ims=bytearray(msg)) # process local copy into db return msg + def witness(self, serder): """ Returns own receipt, rct, message of serder with count code and witness indexed receipt signatures if key state of serder.pre shows that own pre is a current witness of event in serder + + Before calling this must check that serder being witnessed has been + accepted as valid event into controller's KEL + """ if self.kever.prefixer.transferable: # not non-transferable prefix raise ValueError("Attempt to create witness receipt with" @@ -1307,6 +1526,7 @@ def witness(self, serder): self.psr.parseOne(ims=bytearray(msg)) # process local copy into db return msg + def replay(self, pre=None, fn=0): """ Returns replay of FEL first seen event log for pre starting from fn @@ -1518,6 +1738,44 @@ def fetchWitnessUrls(self, cid: str, scheme: str = "", eids=None, enabled=enabled, allowed=allowed)) + def endsFor(self, pre): + """ Load Authroized endpoints for provided AID + + Args: + pre (str): qb64 aid for which to load ends + + Returns: + dict: nest dict of Roles -> eid -> Schemes -> endpoints + + """ + ends = dict() + + for (_, erole, eid), end in self.db.ends.getItemIter(keys=(pre,)): + locs = dict() + urls = self.fetchUrls(eid=eid, scheme="") + for rscheme, url in urls.firsts(): + locs[rscheme] = url + + if erole not in ends: + ends[erole] = dict() + + ends[erole][eid] = locs + + witrolls = dict() + if kever := self.kevers[pre] if pre in self.kevers else None: + for eid in kever.wits: + locs = dict() + urls = self.fetchUrls(eid=eid, scheme="") + for rscheme, url in urls.firsts(): + locs[rscheme] = url + + witrolls[eid] = locs + + if len(witrolls) > 0: + ends[Roles.witness] = witrolls + + return ends + def reply(self, **kwa): """ Returns: @@ -1551,6 +1809,37 @@ def makeEndRole(self, eid, role=kering.Roles.controller, allow=True, stamp=None) route = "/end/role/add" if allow else "/end/role/cut" return self.reply(route=route, data=data, stamp=stamp) + def loadEndRole(self, cid, eid, role=kering.Roles.controller): + msgs = bytearray() + end = self.db.ends.get(keys=(cid, role, eid)) + if end and (end.enabled or end.allowed): + said = self.db.eans.get(keys=(cid, role, eid)) + serder = self.db.rpys.get(keys=(said.qb64,)) + cigars = self.db.scgs.get(keys=(said.qb64,)) + tsgs = eventing.fetchTsgs(db=self.db.ssgs, saider=said) + + if len(cigars) == 1: + (verfer, cigar) = cigars[0] + cigar.verfer = verfer + else: + cigar = None + + if len(tsgs) > 0: + (prefixer, seqner, diger, sigers) = tsgs[0] + seal = eventing.SealEvent(i=prefixer.qb64, + s=seqner.snh, + d=diger.qb64) + else: + sigers = None + seal = None + + msgs.extend(eventing.messagize(serder=serder, + cigars=[cigar] if cigar else [], + sigers=sigers, + seal=seal, + pipelined=True)) + return msgs + def makeLocScheme(self, url, eid=None, scheme="http", stamp=None): """ Returns: @@ -1569,7 +1858,7 @@ def makeLocScheme(self, url, eid=None, scheme="http", stamp=None): data = dict(eid=eid, scheme=scheme, url=url) return self.reply(route="/loc/scheme", data=data, stamp=stamp) - def replyLocScheme(self, eid, scheme=None): + def replyLocScheme(self, eid, scheme=""): """ Returns a reply message stream composed of entries authed by the given eid from the appropriate reply database including associated attachments @@ -1599,18 +1888,31 @@ def replyLocScheme(self, eid, scheme=None): def loadLocScheme(self, eid, scheme=None): msgs = bytearray() - keys = (eid, scheme) + keys = (eid, scheme) if scheme else (eid,) for (pre, _), said in self.db.lans.getItemIter(keys=keys): serder = self.db.rpys.get(keys=(said.qb64,)) cigars = self.db.scgs.get(keys=(said.qb64,)) + tsgs = eventing.fetchTsgs(db=self.db.ssgs, saider=said) if len(cigars) == 1: (verfer, cigar) = cigars[0] cigar.verfer = verfer else: cigar = None + + if len(tsgs) > 0: + (prefixer, seqner, diger, sigers) = tsgs[0] + seal = eventing.SealEvent(i=prefixer.qb64, + s=seqner.snh, + d=diger.qb64) + else: + sigers = None + seal = None + msgs.extend(eventing.messagize(serder=serder, - cigars=[cigar], + cigars=[cigar] if cigar else [], + sigers=sigers, + seal=seal, pipelined=True)) return msgs @@ -1654,26 +1956,29 @@ def replyEndRole(self, cid, role=None, eids=None, scheme=""): if eids is None: eids = [] - if role == kering.Roles.witness: - if kever := self.kevers[cid] if cid in self.kevers else None: - witness = self.pre in kever.wits # see if we are cid's witness + if cid not in self.kevers: + return msgs - # latest key state for cid - for eid in kever.wits: - if not eids or eid in eids: - if eid == self.pre: - msgs.extend(self.replyLocScheme(eid=eid, scheme=scheme)) - else: - msgs.extend(self.loadLocScheme(eid=eid, scheme=scheme)) - if not witness: # we are not witness, send auth records - msgs.extend(self.makeEndRole(eid=eid, role=role)) - if witness: # we are witness, set KEL as authz - msgs.extend(self.replay(cid)) + msgs.extend(self.replay(cid)) + + kever = self.kevers[cid] + witness = self.pre in kever.wits # see if we are cid's witness + + if role == kering.Roles.witness: + # latest key state for cid + for eid in kever.wits: + if not eids or eid in eids: + if eid == self.pre: + msgs.extend(self.replyLocScheme(eid=eid, scheme=scheme)) + else: + msgs.extend(self.loadLocScheme(eid=eid, scheme=scheme)) + if not witness: # we are not witness, send auth records + msgs.extend(self.makeEndRole(eid=eid, role=role)) for (_, erole, eid), end in self.db.ends.getItemIter(keys=(cid,)): if (end.enabled or end.allowed) and (not role or role == erole) and (not eids or eid in eids): - msgs.extend(self.replyLocScheme(eid=eid, scheme=scheme)) - msgs.extend(self.makeEndRole(eid=eid, role=erole)) + msgs.extend(self.loadLocScheme(eid=eid, scheme=scheme)) + msgs.extend(self.loadEndRole(cid=cid, eid=eid, role=erole)) return msgs @@ -1701,23 +2006,28 @@ def replyToOobi(self, aid, role, eids=None): # not permiteed in .habs.oobis return self.replyEndRole(cid=aid, role=role, eids=eids) - def getOwnEvent(self, sn): + def getOwnEvent(self, sn, allowPartiallySigned=False): """ Returns: message Serder and controller signatures of own event at sequence number sn from retrieving event at sn and associated signatures from database. Parameters: - sn is int sequence number of event + sn (int): is int sequence number of event + allowPartiallySigned(bool): True means attempt to load from partial signed escrow """ - dig = self.db.getKeLast(dbing.snKey(self.pre, sn)) + key = dbing.snKey(self.pre, sn) + dig = self.db.getKeLast(key) + if dig is None and allowPartiallySigned: + dig = self.db.getPseLast(key) + if dig is None: raise kering.MissingEntryError("Missing event for pre={} at sn={}." "".format(self.pre, sn)) dig = bytes(dig) key = dbing.dgKey(self.pre, dig) # digest key msg = self.db.getEvt(key) - serder = coring.Serder(raw=bytes(msg)) + serder = serdering.SerderKERI(raw=bytes(msg)) sigs = [] for sig in self.db.getSigsIter(key): @@ -1727,17 +2037,20 @@ def getOwnEvent(self, sn): return serder, sigs, couple - def makeOwnEvent(self, sn): + def makeOwnEvent(self, sn, allowPartiallySigned=False): """ Returns: messagized bytearray message with attached signatures of own event at sequence number sn from retrieving event at sn and associated signatures from database. Parameters: - sn is int sequence number of event + sn(int): is int sequence number of event + allowPartiallySigned(bool): True means attempt to load from partial signed escrow + """ msg = bytearray() - serder, sigs, couple = self.getOwnEvent(sn=sn) + serder, sigs, couple = self.getOwnEvent(sn=sn, + allowPartiallySigned=allowPartiallySigned) msg.extend(serder.raw) msg.extend(coring.Counter(code=coring.CtrDex.ControllerIdxSigs, count=len(sigs)).qb64b) # attach cnt @@ -1751,13 +2064,13 @@ def makeOwnEvent(self, sn): return msg - def makeOwnInception(self): + def makeOwnInception(self, allowPartiallySigned=False): """ Returns: messagized bytearray message with attached signatures of own inception event by retrieving event and signatures from database. """ - return self.makeOwnEvent(sn=0) + return self.makeOwnEvent(sn=0, allowPartiallySigned=allowPartiallySigned) def processCues(self, cues): """ @@ -1781,7 +2094,7 @@ def processCuesIter(self, cues): """ while cues: # iteratively process each cue in cues msgs = bytearray() - cue = cues.popleft() + cue = cues.pull() #cues.popleft() cueKin = cue["kin"] # type or kind of cue if cueKin in ("receipt",): # cue to receipt a received event from other pre @@ -1820,10 +2133,26 @@ def processCuesIter(self, cues): msg = self.reply(data=data, route=route) yield msg + # ToDo XXXX cue for kin = "query" various types of queries + # (query witness, query delegation etc) + # ToDo XXXX cue for kin = "notice" new event + # ToDo XXXX cue for kin = "witness" to create witness receipt own is witness + # ToDo XXXX cue for kin = "noticeBadCloneFN" + # ToDo XXXX cue for kin = "approveDelegation" own is delegator + + # ToDo XXXX cue for kin = "keyStateSaved" + # ToDo XXXX cue for kin = "psUnescrow" + # ToDo XXXX cue for kin = "stream" + # ToDo XXXX cue for kin = "invalid" + + + def witnesser(self): + return True + class Hab(BaseHab): """ - Hab class provides a given idetnifier controller's local resource environment + Hab class provides a given idetnifier controller's local resource environment i.e. hab or habitat. Includes dependency injection of database, keystore, configuration file as well as Kevery and key store Manager.. @@ -1859,18 +2188,20 @@ class Hab(BaseHab): kevers (dict): of eventing.Kever instances from KELs in local db keyed by qb64 prefix. Read through cache of of kevers of states for KELs in db.states - iserder (coring.Serder): own inception event + iserder (serdering.SerderKERI): own inception event prefixes (OrderedSet): local prefixes for .db accepted (bool): True means accepted into local KEL. False otherwise """ + def __init__(self, **kwa): super(Hab, self).__init__(**kwa) - def make(self, *, secrecies=None, iridx=0, code=coring.MtrDex.Blake3_256, - transferable=True, isith=None, icount=1, nsith=None, ncount=None, - toad=None, wits=None, delpre=None, estOnly=False, DnD=False, hidden=False, data=None): + def make(self, *, secrecies=None, iridx=0, code=coring.MtrDex.Blake3_256, dcode=coring.MtrDex.Blake3_256, + icode=coring.MtrDex.Ed25519_Seed, transferable=True, isith=None, icount=1, nsith=None, ncount=None, + toad=None, wits=None, delpre=None, estOnly=False, DnD=False, hidden=False, data=None, algo=None, + salt=None, tier=None): """ Finish setting up or making Hab from parameters includes inception. Assumes injected dependencies were already setup. @@ -1879,6 +2210,8 @@ def make(self, *, secrecies=None, iridx=0, code=coring.MtrDex.Blake3_256, secrecies (list | None): list of secrets to preload key pairs if any iridx (int): initial rotation index after ingestion of secrecies code (str): prefix derivation code default Blake3 + icode (str): signing key code default Ed25519 + dcode (str): next key derivation code default Blake3 transferable (bool): True means pre is transferable (default) False means pre is nontransferable isith (int | str | list | None): incepting signing threshold as @@ -1902,6 +2235,10 @@ def make(self, *, secrecies=None, iridx=0, code=coring.MtrDex.Blake3_256, hidden (bool): A hidden Hab is not included in the list of Habs. data (list | None): seal dicts + algo is str key creation algorithm code + salt(str): qb64 salt for randomization when salty algorithm used + tier(str): is str security criticality tier code when using salty algorithm + """ if not (self.ks.opened and self.db.opened and self.cf.opened): @@ -1916,23 +2253,39 @@ def make(self, *, secrecies=None, iridx=0, code=coring.MtrDex.Blake3_256, nsith = '0' code = coring.MtrDex.Ed25519N + stem = self.name if self.ns is None else f"{self.ns}{self.name}" if secrecies: # replay ipre, _ = self.mgr.ingest(secrecies, iridx=iridx, ncount=ncount, - stem=self.name, + stem=stem, transferable=transferable, temp=self.temp) verfers, digers = self.mgr.replay(pre=ipre, advance=False) else: # use defaults verfers, digers = self.mgr.incept(icount=icount, + icode=icode, ncount=ncount, - stem=self.name, + stem=stem, transferable=transferable, + dcode=dcode, + algo=algo, + salt=salt, + tier=tier, temp=self.temp) - serder = super(Hab, self).make(DnD, code, data, delpre, digers, estOnly, isith, nsith, toad, verfers, wits) + serder = super(Hab, self).make(isith=isith, + verfers=verfers, + nsith=nsith, + digers=digers, + code=code, + toad=toad, + wits=wits, + estOnly=estOnly, + DnD=DnD, + delpre=delpre, + data=data) self.pre = serder.ked["i"] # new pre @@ -1940,13 +2293,10 @@ def make(self, *, secrecies=None, iridx=0, code=coring.MtrDex.Blake3_256, self.mgr.move(old=opre, new=self.pre) # move to incept event pre # may want db method that updates .habs. and .prefixes together - # ToDo: NRR add dual indices to HabitatRecord so know how to sign in future. - habord = basing.HabitatRecord(hid=self.pre, - mid=None,) + habord = basing.HabitatRecord(hid=self.pre) if not hidden: - self.db.habs.put(keys=self.name, - val=habord) + self.save(habord) self.prefixes.add(self.pre) # sign handles group hab with .mhab case @@ -1967,12 +2317,261 @@ def make(self, *, secrecies=None, iridx=0, code=coring.MtrDex.Blake3_256, self.inited = True + @property + def algo(self): + pp = self.ks.prms.get(self.pre) + return pp.algo -class GroupHab(BaseHab): + def rotate(self, *, isith=None, nsith=None, ncount=None, toad=None, cuts=None, adds=None, + data=None, **kwargs): + """ + Perform rotation operation. Register rotation in database. + Returns: bytearrayrotation message with attached signatures. + + Parameters: + isith (int |str | None): current signing threshold as int or str hex + or list of str weights + default is prior next sith + nsith (int |str | None): next, next signing threshold as int + or str hex or list of str weights + default is based on isith when None + ncount (int | None): next number of signing keys + default is len of prior next digs + toad (int | str | None): hex of witness threshold after cuts and adds + cuts (list | None): of qb64 pre of witnesses to be removed from witness list + adds (list | None): of qb64 pre of witnesses to be added to witness list + data (list | None): of dicts of committed data such as seals + + + """ + # recall that kever.pre == self.pre + kever = self.kever # before rotation kever is prior next + + if ncount is None: + ncount = len(kever.ndigers) # use len of prior next digers as default + + try: + verfers, digers = self.mgr.replay(pre=self.pre) + except IndexError: # old next is new current + verfers, digers = self.mgr.rotate(pre=self.pre, + ncount=ncount, + temp=self.temp) + return super(Hab, self).rotate(verfers=verfers, digers=digers, + isith=isith, + nsith=nsith, + toad=toad, + cuts=cuts, + adds=adds, + data=data) + + +class SignifyHab(BaseHab): """ Hab class provides a given idetnifier controller's local resource environment i.e. hab or habitat. Includes dependency injection of database, keystore, configuration file as well as Kevery and key store Manager.. + """ + + def __init__(self, **kwa): + super(SignifyHab, self).__init__(**kwa) + + def make(self, *, serder, sigers, **kwargs): + self.pre = serder.ked["i"] # new pre + self.prefixes.add(self.pre) + + self.processEvent(serder, sigers) + + habord = basing.HabitatRecord(hid=self.pre, sid=self.pre) + self.save(habord) + + self.inited = True + + def sign(self, ser, verfers=None, indexed=True, indices=None, ondices=None, **kwa): + """Sign given serialization ser using appropriate keys. + Use provided verfers or .kever.verfers to lookup keys to sign. + + Parameters: + ser (bytes): serialization to sign + verfers (list[Verfer] | None): Verfer instances to get pub verifier + keys to lookup private siging keys. + verfers None means use .kever.verfers. Assumes that when group + and verfers is not None then provided verfers must be .kever.verfers + indexed (bool): When not mhab then + True means use use indexed signatures and return + list of Siger instances. + False means do not use indexed signatures and return + list of Cigar instances + indices (list[int] | None): indices (offsets) + when indexed == True. See Manager.sign + ondices (list[int | None] | None): other indices (offsets) + when indexed is True. See Manager.sign + + """ + raise kering.KeriError("Signify hab does not support local signing") + + def rotate(self, *, serder=None, sigers=None, **kwargs): + """ + Perform rotation operation. Register rotation in database. + Returns: bytearrayrotation message with attached signatures. + + Parameters: + serder (Serder): pre-created rotation event + sigers (list[Siger]): Siger instances on next rotation event + + """ + msg = eventing.messagize(serder, sigers=sigers) + self.processEvent(serder, sigers) + return msg + + def interact(self, *, serder=None, sigers=None, **kwargs): + """ + Perform interaction operation. Register interaction in database. + Returns: bytearray interaction message with attached signatures. + """ + msg = eventing.messagize(serder, sigers=sigers) + self.processEvent(serder, sigers) + return msg + + def exchange(self, serder, seal=None, sigers=None, save=False): + """ + Returns signed exn, message of serder with count code and receipt + couples (pre+cig) + Builds msg and then processes it into own db to validate + """ + # sign serder event + msg = eventing.messagize(serder=serder, sigers=sigers, seal=seal) + + if save: + self.psr.parseOne(ims=bytearray(msg)) # process local copy into db + + return msg + + def processEvent(self, serder, sigers): + """ Process event with signatures raising any exception that occurs + + Performs event processing using local Kevery allowing raising all exceptions + + Args: + serder (Serder): event serder to process + sigers (list): list of Siger or Cigar instances representing signatures over serder.raw + + """ + + try: + # verify event, update kever state, and escrow if group + self.kvy.processEvent(serder=serder, sigers=sigers) + except Exception: + raise kering.ConfigurationError(f"Improper Habitat event type={serder.ked['t']} for " + f"pre={self.pre}.") + + def replyEndRole(self, cid, role=None, eids=None, scheme=""): + + """ + Returns a reply message stream composed of entries authed by the given + cid from the appropriate reply database including associated attachments + in order to disseminate (percolate) BADA reply data authentication proofs. + + Currently uses promiscuous model for permitting endpoint discovery. + Future is to use identity constraint graph to constrain discovery + of whom by whom. + + cid and not role and not scheme then: + end authz for all eids in all roles and loc url for all schemes at each eid + if eids then only eids in eids else all eids + + cid and not role and scheme then: + end authz for all eid in all roles and loc url for scheme at each eid + if eids then only eids in eids else all eids + + cid and role and not scheme then: + end authz for all eid in role and loc url for all schemes at each eid + if eids then only eids in eids else all eids + + cid and role and scheme then: + end authz for all eid in role and loc url for scheme at each eid + if eids then only eids in eids else all eids + + + Parameters: + cid (str): identifier prefix qb64 of controller authZ endpoint provided + eid is witness + role (str): authorized role for eid + eids (list): when provided restrict returns to only eids in eids + scheme (str): url scheme + """ + msgs = bytearray() + + if eids is None: + eids = [] + + # introduce yourself, please + msgs.extend(self.replay(cid)) + + if role == kering.Roles.witness: + if kever := self.kevers[cid] if cid in self.kevers else None: + witness = self.pre in kever.wits # see if we are cid's witness + + # latest key state for cid + for eid in kever.wits: + if not eids or eid in eids: + msgs.extend(self.loadLocScheme(eid=eid, scheme=scheme)) + if not witness: # we are not witness, send auth records + msgs.extend(self.makeEndRole(eid=eid, role=role)) + if witness: # we are witness, set KEL as authz + msgs.extend(self.replay(cid)) + + for (_, erole, eid), end in self.db.ends.getItemIter(keys=(cid,)): + if (end.enabled or end.allowed) and (not role or role == erole) and (not eids or eid in eids): + msgs.extend(self.replay(eid)) + msgs.extend(self.loadLocScheme(eid=eid, scheme=scheme)) + msgs.extend(self.loadEndRole(cid=cid, eid=eid, role=erole)) + + return msgs + + +class SignifyGroupHab(SignifyHab): + def __init__(self, mhab=None, **kwa): + self.mhab = mhab + super(SignifyGroupHab, self).__init__(**kwa) + + def make(self, *, serder, sigers, **kwargs): + self.pre = serder.ked["i"] # new pre + self.prefixes.add(self.pre) + + self.processEvent(serder, sigers) + + habord = basing.HabitatRecord(hid=self.pre, mid=self.mhab.pre, sid=self.pre) + self.save(habord) + + self.inited = True + + def processEvent(self, serder, sigers): + """ Process event with signatures ignoring missing signature exceptions + + Performs event processing using local Kevery allowing missing signature exceptions to be ignored + so multisig events can be created with a single local signature + + Args: + serder (Serder): event serder to process + sigers (list): list of Siger or Cigar instances representing signatures over serder.raw + + """ + + try: + # verify event, update kever state, and escrow if group + self.kvy.processEvent(serder=serder, sigers=sigers) + except MissingSignatureError: + pass + except Exception: + raise kering.ValidationError(f"Improper Habitat event type={serder.ked['t']} for " + f"pre={self.pre}.") + + +class GroupHab(BaseHab): + """ + Hab class provides a given idetnifier controller's local resource environment + i.e. hab or habitat. Includes dependency injection of database, keystore, + configuration file as well as Kevery and key store Manager. Attributes: (Injected) ks (keeping.Keeper): lmdb key store @@ -2006,12 +2605,13 @@ class GroupHab(BaseHab): kevers (dict): of eventing.Kever instances from KELs in local db keyed by qb64 prefix. Read through cache of of kevers of states for KELs in db.states - iserder (coring.Serder): own inception event + iserder (serdering.SerderKERI): own inception event prefixes (OrderedSet): local prefixes for .db accepted (bool): True means accepted into local KEL. False otherwise """ + def __init__(self, smids, mhab=None, rmids=None, **kwa): """ Initialize instance. @@ -2099,19 +2699,20 @@ def make(self, *, code=coring.MtrDex.Blake3_256, transferable=True, isith=None, verfers = merfers digers = migers - serder = super(GroupHab, self).make(DnD, code, data, delpre, digers, estOnly, isith, nsith, toad, verfers, wits) + serder = super(GroupHab, self).make(isith=isith, + verfers=verfers, + nsith=nsith, + digers=digers, + code=code, + toad=toad, + wits=wits, + estOnly=estOnly, + DnD=DnD, + delpre=delpre, + data=data) self.pre = serder.ked["i"] # new pre - habord = basing.HabitatRecord(hid=self.pre, - mid=None, - smids=self.smids, - rmids=self.rmids) - habord.mid = self.mhab.pre - self.db.habs.put(keys=self.name, - val=habord) - self.prefixes.add(self.pre) - # sign handles group hab with .mhab case sigers = self.sign(ser=serder.raw, verfers=verfers) @@ -2125,12 +2726,36 @@ def make(self, *, code=coring.MtrDex.Blake3_256, transferable=True, isith=None, raise kering.ConfigurationError("Improper Habitat inception for " "pre={} {}".format(self.pre, ex)) + habord = basing.HabitatRecord(hid=self.pre, + mid=self.mhab.pre, + smids=self.smids, + rmids=self.rmids) + + self.save(habord) + self.prefixes.add(self.pre) + self.inited = True - @property - def group(self): - """ True means this is a group multisig Hab """ - return True + def rotate(self, serder=None, **kwargs): + + if serder is None: + return super(GroupHab, self).rotate(**kwargs) + + # sign handles group hab with .mhab case + sigers = self.sign(ser=serder.raw, verfers=serder.verfers, rotated=True) + + # update own key event verifier state + msg = eventing.messagize(serder, sigers=sigers) + + try: + self.kvy.processEvent(serder=serder, sigers=sigers) + except MissingSignatureError: + pass + except Exception as ex: + raise kering.ValidationError("Improper Habitat rotation for " + "pre={self.pre}.") from ex + + return msg def sign(self, ser, verfers=None, indexed=True, rotated=False, indices=None, ondices=None): """ Sign given serialization ser using appropriate keys. @@ -2195,10 +2820,10 @@ def sign(self, ser, verfers=None, indexed=True, rotated=False, indices=None, ond # then mhab participates as group prior next at index pni. # else pni is None which means mhab only participates as new key. # get nexter of .mhab's prior Next est event - migers = self.mhab.kever.fetchPriorDigers(sn=sn-1) + migers = self.mhab.kever.fetchPriorDigers(sn=sn - 1) if migers: # not None or not empty mig = migers[0].qb64 # always use first prior dig of mhab - digs = [diger.qb64 for diger in self.kever.digers] # group habs prior digs + digs = [diger.qb64 for diger in self.kever.ndigers] # group habs prior digs try: pni = digs.index(mig) # find mhab dig index in group hab digs except ValueError: # not found @@ -2245,45 +2870,18 @@ def query(self, pre, src, query=None, **kwa): return self.mhab.endorse(serder, last=True) - def members(self): - """ Seach local Kevers database to find the current members of this group AID - - Returns: - (smids, rmids): A tuple of lists of signing member AIDs and rotation member AIDs + def witnesser(self): + """This method name does not match logic??? """ + kever = self.kever + keys = [verfer.qb64 for verfer in kever.verfers] + sigs = self.db.getSigs(dbing.dgKey(self.pre, kever.serder.saidb)) + if not sigs: # otherwise its a list of sigs + return False - verfers = self.kever.verfers - skeys = oset([verfer.qb64 for verfer in verfers]) - - digers = self.kever.digers - dkeys = oset([diger.qb64 for diger in digers]) - - smids = [None] * len(verfers) - rmids = [None] * len(digers) - for (preb, _, raw) in self.db.getAllItemIter(db=self.db.evts): - pre = bytes(preb).decode("utf-8") - serder = coring.Serder(raw=bytes(raw)) - if not serder.est: - continue - - if len(serder.verfers) != 1 or len(serder.digers) != 1: - continue - - skey = serder.verfers[0].qb64 - if skey in skeys: - idx = skeys.index(skey) - smids[idx] = pre - - dkey = serder.digers[0].qb64 - if dkey in dkeys: - idx = dkeys.index(dkey) - rmids[idx] = pre - - if None not in smids and None not in rmids: - break - - if None in smids or None in rmids: - raise kering.ConfigurationError(f"Unable to find members for {self.pre}") + sigers = [coring.Siger(qb64b=bytes(sig)) for sig in sigs] + windex = min([siger.index for siger in sigers]) - return smids, rmids \ No newline at end of file + # True if Elected to perform delegation and witnessing + return self.mhab.kever.verfers[0].qb64 == keys[windex] diff --git a/src/keri/app/httping.py b/src/keri/app/httping.py index ecd6494cd..6f815d4d8 100644 --- a/src/keri/app/httping.py +++ b/src/keri/app/httping.py @@ -16,7 +16,7 @@ from keri import help from keri import kering -from keri.core import coring, parsing +from keri.core import coring, parsing, serdering from keri.end import ending from keri.help import helping @@ -24,6 +24,7 @@ CESR_CONTENT_TYPE = "application/cesr+json" CESR_ATTACHMENT_HEADER = "CESR-ATTACHMENT" +CESR_DESTINATION_HEADER = "CESR-DESTINATION" class SignatureValidationComponent(object): @@ -112,19 +113,21 @@ def parseCesrHttpRequest(req): return cr -def createCESRRequest(msg, client, path=None): +def createCESRRequest(msg, client, dest, path=None): """ Turns a KERI message into a CESR http request against the provided hio http Client Parameters msg: KERI message parsable as Serder.raw + dest (str): qb64 identifier prefix of destination controller client: hio http Client that will send the message as a CESR request + path (str): path to post to """ path = path if path is not None else "/" try: - serder = coring.Serder(raw=msg) + serder = serdering.SerderKERI(raw=msg) except kering.ShortageError as ex: # need more bytes raise kering.ExtractionError("unable to extract a valid message to send as HTTP") else: # extracted successfully @@ -137,7 +140,8 @@ def createCESRRequest(msg, client, path=None): ("Content-Type", CESR_CONTENT_TYPE), ("Content-Length", len(body)), ("connection", "close"), - (CESR_ATTACHMENT_HEADER, attachments) + (CESR_ATTACHMENT_HEADER, attachments), + (CESR_DESTINATION_HEADER, dest) ]) client.request( @@ -148,13 +152,14 @@ def createCESRRequest(msg, client, path=None): ) -def streamCESRRequests(client, ims, path=None): +def streamCESRRequests(client, ims, dest, path=None): """ Turns a stream of KERI messages into CESR http requests against the provided hio http Client Parameters - ims (bytearray): stream of KERI messages parsable as Serder.raw client (Client): hio http Client that will send the message as a CESR request + ims (bytearray): stream of KERI messages parsable as Serder.raw + dest (str): qb64 identifier prefix of destination controller path (str): path to post to Returns @@ -173,7 +178,7 @@ def streamCESRRequests(client, ims, path=None): cnt = 0 while ims: # extract and deserialize message from ims try: - serder = coring.Serder(raw=ims) + serder = coring.Sadder(raw=ims) except kering.ShortageError as ex: # need more bytes raise kering.ExtractionError("unable to extract a valid message to send as HTTP") else: # extracted successfully @@ -189,7 +194,8 @@ def streamCESRRequests(client, ims, path=None): headers = Hict([ ("Content-Type", CESR_CONTENT_TYPE), ("Content-Length", len(body)), - (CESR_ATTACHMENT_HEADER, attachment) + (CESR_ATTACHMENT_HEADER, attachment), + (CESR_DESTINATION_HEADER, dest) ]) client.request( @@ -212,18 +218,24 @@ def __init__(self): doers = [doing.doify(self.clientDo)] super(Clienter, self).__init__(doers=doers) - def request(self, method, url): + def request(self, method, url, body=None, headers=None): purl = parse.urlparse(url) - client = http.clienting.Client(scheme=purl.scheme, - hostname=purl.hostname, - port=purl.port, - portOptional=True) + try: + client = http.clienting.Client(scheme=purl.scheme, + hostname=purl.hostname, + port=purl.port, + portOptional=True) + except Exception as e: + print(f"error establishing client connection={e}") + return None client.request( method=method, - path=purl.path, - qargs=parse.parse_qs(purl.query), + path=f"{purl.path}?{purl.query}", + qargs=None, + headers=headers, + body=body ) clientDoer = http.clienting.ClientDoer(client=client) diff --git a/src/keri/app/indirecting.py b/src/keri/app/indirecting.py index bab048bae..96f1f4207 100644 --- a/src/keri/app/indirecting.py +++ b/src/keri/app/indirecting.py @@ -14,16 +14,17 @@ from ordered_set import OrderedSet as oset from hio.base import doing -from hio.core import http +from hio.core import http, tcp from hio.core.tcp import serving from hio.help import decking import keri.app.oobiing from . import directing, storing, httping, forwarding, agenting, oobiing +from .habbing import GroupHab from .. import help, kering -from ..core import eventing, parsing, routing +from ..core import eventing, parsing, routing, coring, serdering from ..core.coring import Ilks -from ..db import basing +from ..db import basing, dbing from ..end import ending from ..help import helping from ..peer import exchanging @@ -33,7 +34,8 @@ logger = help.ogler.getLogger() -def setupWitness(hby, alias="witness", mbx=None, tcpPort=5631, httpPort=5632): +def setupWitness(hby, alias="witness", mbx=None, aids=None, tcpPort=5631, httpPort=5632, + keypath=None, certpath=None, cafilepath=None): """ Setup witness controller and doers @@ -51,14 +53,14 @@ def setupWitness(hby, alias="witness", mbx=None, tcpPort=5631, httpPort=5632): mbx = mbx if mbx is not None else storing.Mailboxer(name=alias, temp=hby.temp) forwarder = forwarding.ForwardHandler(hby=hby, mbx=mbx) - exchanger = exchanging.Exchanger(db=hby.db, handlers=[forwarder]) + exchanger = exchanging.Exchanger(hby=hby, handlers=[forwarder]) clienter = httping.Clienter() oobiery = keri.app.oobiing.Oobiery(hby=hby, clienter=clienter) app = falcon.App(cors_enable=True) ending.loadEnds(app=app, hby=hby, default=hab.pre) - oobiRes = oobiing.loadEnds(app=app, hby=hby, prefix="/ext") - rep = storing.Respondant(hby=hby, mbx=mbx) + oobiing.loadEnds(app=app, hby=hby, prefix="/ext") + rep = storing.Respondant(hby=hby, mbx=mbx, aids=aids) rvy = routing.Revery(db=hby.db, cues=cues) kvy = eventing.Kevery(db=hby.db, @@ -82,28 +84,58 @@ def setupWitness(hby, alias="witness", mbx=None, tcpPort=5631, httpPort=5632): httpEnd = HttpEnd(rxbs=parser.ims, mbx=mbx) app.add_route("/", httpEnd) + receiptEnd = ReceiptEnd(hab=hab, inbound=cues, aids=aids) + app.add_route("/receipts", receiptEnd) - server = http.Server(port=httpPort, app=app) + server = createHttpServer(httpPort, app, keypath, certpath, cafilepath) + if not server.reopen(): + raise RuntimeError(f"cannot create http server on port {httpPort}") httpServerDoer = http.ServerDoer(server=server) # setup doers regDoer = basing.BaserDoer(baser=verfer.reger) - server = serving.Server(host="", port=tcpPort) - serverDoer = serving.ServerDoer(server=server) + if tcpPort is not None: + server = serving.Server(host="", port=tcpPort) + if not server.reopen(): + raise RuntimeError(f"cannot create tcp server on port {tcpPort}") + serverDoer = serving.ServerDoer(server=server) - directant = directing.Directant(hab=hab, server=server, verifier=verfer) + directant = directing.Directant(hab=hab, server=server, verifier=verfer) + doers.extend([directant, serverDoer]) - witStart = WitnessStart(hab=hab, parser=parser, cues=cues, + witStart = WitnessStart(hab=hab, parser=parser, cues=receiptEnd.outbound, kvy=kvy, tvy=tvy, rvy=rvy, exc=exchanger, replies=rep.reps, responses=rep.cues, queries=httpEnd.qrycues) - doers.extend(oobiRes) - doers.extend([regDoer, exchanger, directant, serverDoer, httpServerDoer, rep, witStart, *oobiery.doers]) - + doers.extend([regDoer, httpServerDoer, rep, witStart, receiptEnd, *oobiery.doers]) return doers +def createHttpServer(port, app, keypath=None, certpath=None, cafilepath=None): + """ + Create an HTTP or HTTPS server depending on whether TLS key material is present + Parameters: + port (int) : port to listen on for all HTTP(s) server instances + app (falcon.App) : application instance to pass to the http.Server instance + keypath (string) : the file path to the TLS private key + certpath (string) : the file path to the TLS signed certificate (public key) + cafilepath (string): the file path to the TLS CA certificate chain file + Returns: + hio.core.http.Server + """ + if keypath is not None and certpath is not None and cafilepath is not None: + servant = tcp.ServerTls(certify=False, + keypath=keypath, + certpath=certpath, + cafilepath=cafilepath, + port=port) + server = http.Server(port=port, app=app, servant=servant) + else: + server = http.Server(port=port, app=app) + return server + + class WitnessStart(doing.DoDoer): """ Doer to print witness prefix after initialization @@ -121,8 +153,7 @@ def __init__(self, hab, parser, kvy, tvy, rvy, exc, cues=None, replies=None, res self.responses = responses if responses is not None else decking.Deck() self.cues = cues if cues is not None else decking.Deck() - doers = [doing.doify(self.start), doing.doify(self.msgDo), - doing.doify(self.exchangerDo), doing.doify(self.escrowDo), doing.doify(self.cueDo)] + doers = [doing.doify(self.start), doing.doify(self.msgDo), doing.doify(self.escrowDo), doing.doify(self.cueDo)] super().__init__(doers=doers, **opts) def start(self, tymth=None, tock=0.0): @@ -215,7 +246,7 @@ def cueDo(self, tymth=None, tock=0.0): while True: while self.cues: - cue = self.cues.popleft() + cue = self.cues.pull() # self.cues.popleft() cueKin = cue["kin"] if cueKin == "stream": self.queries.append(cue) @@ -224,30 +255,6 @@ def cueDo(self, tymth=None, tock=0.0): yield self.tock yield self.tock - def exchangerDo(self, tymth=None, tock=0.0): - """ - Returns doifiable Doist compatibile generator method (doer dog) to process - .exc responses and pass them on to the HTTPRespondant - - Parameters: - tymth (function): injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock (float): injected initial tock value - - Usage: - add result of doify on this method to doers list - """ - self.wind(tymth) - self.tock = tock - _ = (yield self.tock) - - while True: - for rep in self.exc.processResponseIter(): - self.replies.append(rep) - yield # throttle just do one cue at a time - yield - - class Indirector(doing.DoDoer): """ Base class for Indirect Mode KERI Controller Doer with habitat and @@ -464,9 +471,7 @@ class MailboxDirector(doing.DoDoer): .doers is list of Doers or Doer like generator functions Attributes: - hab (Habitat: local controller's context - client (serving.Client): hio TCP client instance. - Assumes operated by another doer. + hby (Habitat: local controller's context Properties: tyme (float): relative cycle time of associated Tymist, obtained @@ -552,9 +557,6 @@ def __init__(self, hby, topics, ims=None, verifier=None, kvy=None, exc=None, rep else: self.tvy = None - if self.exchanger is not None: - doers.extend([doing.doify(self.exchangerDo)]) - self.parser = parsing.Parser(ims=self.ims, framed=True, kvy=self.kvy, @@ -626,6 +628,11 @@ def addPollers(self, hab): self.prefixes.add(hab.pre) + def addPoller(self, hab, witness): + poller = Poller(hab=hab, topics=self.topics, witness=witness) + self.pollers.append(poller) + self.extend([poller]) + def processPollIter(self): """ Iterate through cues and yields one or more responses for each cue. @@ -694,6 +701,8 @@ def escrowDo(self, tymth=None, tock=0.0): while True: self.kvy.processEscrows() self.rvy.processEscrowReply() + if self.exchanger is not None: + self.exchanger.processEscrow() if self.tvy is not None: self.tvy.processEscrows() if self.verifier is not None: @@ -701,37 +710,6 @@ def escrowDo(self, tymth=None, tock=0.0): yield - def exchangerDo(self, tymth=None, tock=0.0): - """ - Returns doifiable Doist compatibile generator method (doer dog) to process - .tevery.cues deque - - Doist Injected Attributes: - g.tock = tock # default tock attributes - g.done = None # default done state - g.opts - - Parameters: - tymth is injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock is injected initial tock value - - Usage: - add result of doify on this method to doers list - """ - self.wind(tymth) - self.tock = tock - _ = (yield self.tock) - - while True: - self.exchanger.processEscrow() - yield - - for rep in self.exchanger.processResponseIter(): - self.rep.reps.append(rep) - yield # throttle just do one cue at a time - yield - @property def times(self): times = dict() @@ -805,12 +783,12 @@ def eventDo(self, tymth=None, tock=0.0): else: topics[topic] = 0 - if self.hab.group: + if isinstance(self.hab, GroupHab): msg = self.hab.mhab.query(pre=self.pre, src=self.witness, route="mbx", query=q) else: msg = self.hab.query(pre=self.pre, src=self.witness, route="mbx", query=q) - httping.createCESRRequest(msg, client) + httping.createCESRRequest(msg, client, dest=self.witness) while client.requests: yield self.tock @@ -911,27 +889,69 @@ def on_post(self, req, rep): rep.set_header('connection', "close") cr = httping.parseCesrHttpRequest(req=req) - serder = eventing.Serder(ked=cr.payload, kind=eventing.Serials.json) - msg = bytearray(serder.raw) + sadder = coring.Sadder(ked=cr.payload, kind=eventing.Serials.json) + msg = bytearray(sadder.raw) msg.extend(cr.attachments.encode("utf-8")) self.rxbs.extend(msg) - ilk = serder.ked["t"] - if ilk in (Ilks.icp, Ilks.rot, Ilks.ixn, Ilks.dip, Ilks.drt, Ilks.exn, Ilks.rpy): + if sadder.proto in ("ACDC",): rep.set_header('Content-Type', "application/json") rep.status = falcon.HTTP_204 - elif ilk in (Ilks.vcp, Ilks.vrt, Ilks.iss, Ilks.rev, Ilks.bis, Ilks.brv): - rep.set_header('Content-Type', "application/json") - rep.status = falcon.HTTP_204 - elif ilk in (Ilks.qry,): - if serder.ked["r"] in ("mbx",): - rep.set_header('Content-Type', "text/event-stream") - rep.status = falcon.HTTP_200 - rep.stream = QryRpyMailboxIterable(mbx=self.mbx, cues=self.qrycues, said=serder.said) - else: + else: + ilk = sadder.ked["t"] + if ilk in (Ilks.icp, Ilks.rot, Ilks.ixn, Ilks.dip, Ilks.drt, Ilks.exn, Ilks.rpy): rep.set_header('Content-Type', "application/json") rep.status = falcon.HTTP_204 + elif ilk in (Ilks.vcp, Ilks.vrt, Ilks.iss, Ilks.rev, Ilks.bis, Ilks.brv): + rep.set_header('Content-Type', "application/json") + rep.status = falcon.HTTP_204 + elif ilk in (Ilks.qry,): + if sadder.ked["r"] in ("mbx",): + rep.set_header('Content-Type', "text/event-stream") + rep.status = falcon.HTTP_200 + rep.stream = QryRpyMailboxIterable(mbx=self.mbx, cues=self.qrycues, said=sadder.said) + else: + rep.set_header('Content-Type', "application/json") + rep.status = falcon.HTTP_204 + + def on_put(self, req, rep): + """ + Handles PUT for KERI mbx event messages. + + Parameters: + req (Request) Falcon HTTP request + rep (Response) Falcon HTTP response + + --- + summary: Accept KERI events with attachment headers and parse + description: Accept KERI events with attachment headers and parse. + tags: + - Events + requestBody: + required: true + content: + application/json: + schema: + type: object + description: KERI event message + responses: + 200: + description: Mailbox query response for server sent events + 204: + description: KEL or EXN event accepted. + """ + if req.method == "OPTIONS": + rep.status = falcon.HTTP_200 + return + + rep.set_header('Cache-Control', "no-cache") + rep.set_header('connection', "close") + + self.rxbs.extend(req.bounded_stream.read()) + + rep.set_header('Content-Type', "application/json") + rep.status = falcon.HTTP_204 class QryRpyMailboxIterable: @@ -949,7 +969,7 @@ def __iter__(self): def __next__(self): if self.iter is None: if self.cues: - cue = self.cues.popleft() + cue = self.cues.pull() # self.cues.popleft() serder = cue["serder"] if serder.said == self.said: kin = cue["kin"] @@ -999,3 +1019,162 @@ def __next__(self): return data raise StopIteration + + +class ReceiptEnd(doing.DoDoer): + """ Endpoint class for Witnessing receipting functionality + + Most times a witness will be able to return its receipt for an event inband. This API + will provide that functionality. When an event needs to be escrowed, this POST API + will return a 202 and also provides a generic GET API for retrieving a receipt for any + event. + + """ + + def __init__(self, hab, inbound=None, outbound=None, aids=None): + self.hab = hab + self.inbound = inbound if inbound is not None else decking.Deck() + self.outbound = outbound if outbound is not None else decking.Deck() + self.aids = aids + self.receipts = set() + self.psr = parsing.Parser(framed=True, + kvy=self.hab.kvy) + + super(ReceiptEnd, self).__init__(doers=[doing.doify(self.interceptDo)]) + + def on_post(self, req, rep): + """ Receipt POST endpoint handler + + Parameters: + req (Request): Falcon HTTP request object + rep (Response): Falcon HTTP response object + + """ + + if req.method == "OPTIONS": + rep.status = falcon.HTTP_200 + return + + rep.set_header('Cache-Control', "no-cache") + rep.set_header('connection', "close") + + cr = httping.parseCesrHttpRequest(req=req) + serder = serdering.SerderKERI(sad=cr.payload, kind=eventing.Serials.json) + + pre = serder.ked["i"] + if self.aids is not None and pre not in self.aids: + raise falcon.HTTPBadRequest(description=f"invalid AID={pre} for witnessing receipting") + + ilk = serder.ked["t"] + if ilk not in (Ilks.icp, Ilks.rot, Ilks.ixn, Ilks.dip, Ilks.drt): + raise falcon.HTTPBadRequest(description=f"invalid event type ({ilk})for receipting") + + msg = bytearray(serder.raw) + msg.extend(cr.attachments.encode("utf-8")) + + self.psr.parseOne(ims=msg) + + if pre in self.hab.kevers: + kever = self.hab.kevers[pre] + wits = kever.wits + + if self.hab.pre not in wits: + raise falcon.HTTPBadRequest(description=f"{self.hab.pre} is not a valid witness for {pre} event at " + f"{serder.sn}: wits={wits}") + + rct = self.hab.receipt(serder) + + self.psr.parseOne(bytes(rct)) + + rep.set_header('Content-Type', "application/json+cesr") + rep.status = falcon.HTTP_200 + rep.data = rct + else: + rep.status = falcon.HTTP_202 + + def on_get(self, req, rep): + """ Receipt GET endpoint handler + + Parameters: + req (Request): Falcon HTTP request object + rep (Response): Falcon HTTP response object + + """ + pre = req.get_param("pre") + sn = req.get_param_as_int("sn") + said = req.get_param("said") + + if pre is None: + raise falcon.HTTPBadRequest(description="query param 'pre' is required") + + preb = pre.encode("utf-8") + + if sn is None and said is None: + raise falcon.HTTPBadRequest(description="either 'sn' or 'said' query param is required") + + if sn is not None: + said = self.hab.db.getKeLast(key=dbing.snKey(pre=preb, + sn=sn)) + + if said is None: + raise falcon.HTTPNotFound(description=f"event for {pre} at {sn} ({said}) not found") + + said = bytes(said) + dgkey = dbing.dgKey(preb, said) # get message + if not (raw := self.hab.db.getEvt(key=dgkey)): + raise falcon.HTTPNotFound(description="Missing event for dig={}.".format(said)) + + serder = serdering.SerderKERI(raw=bytes(raw)) + if serder.sn > 0: + wits = [wit.qb64 for wit in self.hab.kvy.fetchWitnessState(pre, serder.sn)] + else: + wits = serder.ked["b"] + + if self.hab.pre not in wits: + raise falcon.HTTPBadRequest(description=f"{self.hab.pre} is not a valid witness for {pre} event at " + f"{serder.sn}, {wits}") + rserder = eventing.receipt(pre=pre, + sn=sn, + said=said.decode("utf-8")) + rct = bytearray(rserder.raw) + if wigs := self.hab.db.getWigs(key=dgkey): + rct.extend(coring.Counter(code=coring.CtrDex.WitnessIdxSigs, + count=len(wigs)).qb64b) + for wig in wigs: + rct.extend(wig) + + rep.set_header('Content-Type', "application/json+cesr") + rep.status = falcon.HTTP_200 + rep.data = rct + + def interceptDo(self, tymth=None, tock=0.0): + """ + Returns doifiable Doist compatibile generator method (doer dog) to process + Kevery and Tevery cues deque + + Usage: + add result of doify on this method to doers list + """ + # enter context + self.wind(tymth) + self.tock = tock + _ = (yield self.tock) + + while True: + while self.inbound: # iteratively process each cue in cues + cue = self.inbound.popleft() + cueKin = cue["kin"] # type or kind of cue + + if cueKin in ("receipt",): # cue to receipt a received event from other pre + serder = cue["serder"] # Serder of received event for other pre + if serder.saidb in self.receipts: + self.receipts.remove(serder.saidb) + else: + self.outbound.append(cue) + + else: + self.outbound.append(cue) + + yield self.tock + + yield self.tock diff --git a/src/keri/app/keeping.py b/src/keri/app/keeping.py index 43d629491..085957fa5 100644 --- a/src/keri/app/keeping.py +++ b/src/keri/app/keeping.py @@ -22,25 +22,20 @@ raw = json.dumps(ked, separators=(",", ":"), ensure_ascii=False).encode("utf-8") """ -import os -import stat -import json import math - -from typing import Union -from dataclasses import dataclass, asdict, field from collections import namedtuple, deque +from dataclasses import dataclass, asdict, field +import pysodium from hio.base import doing from .. import kering -from ..help import helping from ..core import coring from ..db import dbing, subing, koming +from ..help import helping - -Algoage = namedtuple("Algoage", 'randy salty') -Algos = Algoage(randy='randy', salty='salty') # randy is rerandomize, salty is use salt +Algoage = namedtuple("Algoage", 'randy salty group extern') +Algos = Algoage(randy='randy', salty='salty', group="group", extern="extern") # randy is rerandomize, salty is use salt @dataclass() @@ -194,7 +189,7 @@ class Keeper(dbing.LMDBer): } sits (koming.Komer): named sub DB whose values are serialized dicts of PreSit instance - Key is identifer prefix (fully qualified qb64) + Key is identifier prefix (fully qualified qb64) Value is serialized parameter dict of public key situation { old: { pubs: ridx:, kidx, dt:}, @@ -217,7 +212,7 @@ class Keeper(dbing.LMDBer): TailDirPath = "keri/ks" AltTailDirPath = ".keri/ks" TempPrefix = "keri_ks_" - MaxNamedDBs = 8 + MaxNamedDBs = 10 def __init__(self, headDirPath=None, perm=None, reopen=False, **kwa): """ @@ -270,9 +265,21 @@ def reopen(self, **kwa): self.gbls = subing.Suber(db=self, subkey='gbls.') self.pris = subing.CryptSignerSuber(db=self, subkey='pris.') + self.prxs = subing.CesrSuber(db=self, + subkey='prxs.', + klas=coring.Cipher) + self.nxts = subing.CesrSuber(db=self, + subkey='nxts.', + klas=coring.Cipher) + self.smids = subing.CatCesrIoSetSuber(db=self, + subkey='smids.', + klas=(coring.Prefixer, coring.Seqner)) + self.rmids = subing.CatCesrIoSetSuber(db=self, + subkey='rmids.', + klas=(coring.Prefixer, coring.Seqner)) self.pres = subing.CesrSuber(db=self, - subkey='pres.', - klas=coring.Prefixer) + subkey='pres.', + klas=coring.Prefixer) self.prms = koming.Komer(db=self, subkey='prms.', schema=PrePrm,) # New Prefix Parameters @@ -964,7 +971,7 @@ def incept(self, icodes=None, icount=1, icode=coring.MtrDex.Ed25519_Seed, When both ncodes is empty and ncount is 0 then the nxt is null and will not be rotatable. This makes the identifier non-transferable in effect - even when the identifer prefix is transferable. + even when the identifier prefix is transferable. """ # get root defaults to initialize key sequence @@ -1004,15 +1011,16 @@ def incept(self, icodes=None, icount=1, icode=coring.MtrDex.Ed25519_Seed, transferable=transferable, temp=temp) digers = [coring.Diger(ser=signer.verfer.qb64b, code=dcode) for signer in nsigners] - # Secret to encrypt here pp = PrePrm(pidx=pidx, algo=algo, - salt=(creator.salt if not self.encrypter - else self.encrypter.encrypt(ser=creator.salt).qb64), stem=creator.stem, tier=creator.tier) + if creator.salt: + pp.salt = (creator.salt if not self.encrypter + else self.encrypter.encrypt(ser=creator.salt).qb64) + dt = helping.nowIso8601() ps = PreSit( new=PubLot(pubs=[verfer.qb64 for verfer in verfers], @@ -1142,7 +1150,7 @@ def rotate(self, pre, ncodes=None, ncount=1, When both ncodes is empty and ncount is 0 then the nxt is null and will not be rotatable. This makes the identifier non-transferable in effect - even when the identifer prefix is transferable. + even when the identifier prefix is transferable. """ # Secret to decrypt here @@ -1385,6 +1393,56 @@ def sign(self, ser, pubs=None, verfers=None, indexed=True, cigars.append(signer.sign(ser)) # assigns .verfer to cigar return cigars + def decrypt(self, ser, pubs=None, verfers=None): + """ + Returns list of signatures of ser if indexed as Sigers else as Cigars with + .verfer assigned. + + Parameters: + ser (bytes): serialization to sign + pubs (list[str] | None): of qb64 public keys to lookup private keys + one of pubs or verfers is required. If both then verfers is ignored. + verfers (list[Verfer] | None): Verfer instances of public keys + one of pubs or verfers is required. If both then verfers is ignored. + If not pubs then gets public key from verfer.qb64 + + Returns: + bytes: decrypted data + + """ + signers = [] + if pubs: + for pub in pubs: + if self.aeid and not self.decrypter: + raise kering.DecryptError("Unauthorized decryption attempt. " + "Aeid but no decrypter.") + if ((signer := self.ks.pris.get(pub, decrypter=self.decrypter)) + is None): + raise ValueError("Missing prikey in db for pubkey={}".format(pub)) + signers.append(signer) + + else: + for verfer in verfers: + if self.aeid and not self.decrypter: + raise kering.DecryptError("Unauthorized decryption attempt. " + "Aeid but no decrypter.") + if ((signer := self.ks.pris.get(verfer.qb64, + decrypter=self.decrypter)) + is None): + raise ValueError("Missing prikey in db for pubkey={}".format(verfer.qb64)) + signers.append(signer) + + plain = ser + for signer in signers: + sigkey = signer.raw + signer.verfer.raw # sigkey is raw seed + raw verkey + prikey = pysodium.crypto_sign_sk_to_box_sk(sigkey) # raw private encrypt key + pubkey = pysodium.crypto_scalarmult_curve25519_base(prikey) + plain = pysodium.crypto_box_seal_open(plain, pubkey, prikey) # qb64b + + if plain == ser: + raise ValueError("unable to decrypt data") + + return plain def ingest(self, secrecies, iridx=0, ncount=1, ncode=coring.MtrDex.Ed25519_Seed, dcode=coring.MtrDex.Blake3_256, diff --git a/src/keri/app/kiwiing.py b/src/keri/app/kiwiing.py index e2974a939..e69de29bb 100644 --- a/src/keri/app/kiwiing.py +++ b/src/keri/app/kiwiing.py @@ -1,3962 +0,0 @@ -# -*- encoding: utf-8 -*- -""" -KERI -keri.app.agenting module - -""" -import json -from ordered_set import OrderedSet as oset - -import falcon -import mnemonic -from falcon import media -from hio.base import doing -from hio.core import http -from hio.help import decking - -import keri.app.oobiing -from . import grouping, challenging, connecting, notifying, signaling, oobiing -from .. import help -from .. import kering -from ..app import specing, forwarding, agenting, storing, indirecting, httping, habbing, delegating, booting -from ..core import coring, eventing -from ..db import dbing -from ..db.dbing import dgKey -from ..peer import exchanging -from ..vc import proving, protocoling, walleting -from ..vdr import verifying, credentialing - -logger = help.ogler.getLogger() - - -class LockEnd(doing.DoDoer): - """ - ReST API for locking - """ - - def __init__(self, servery, bootConfig): - self.servery = servery - self.bootConfig = bootConfig - - super(LockEnd, self).__init__(doers=[]) - - def on_post(self, _, rep): - """ Lock POST endpoint - - Parameters: - _: falcon.Request HTTP request - rep: falcon.Response HTTP response - - --- - summary: Lock - description: Reloads the API to the boot version - tags: - - Lock - responses: - 200: - description: locked - - - """ - booting.setup(servery=self.servery, controller=self.bootConfig["controller"], - configFile=self.bootConfig["configFile"], - configDir=self.bootConfig["configDir"], - insecure=self.bootConfig["insecure"], - path=self.bootConfig["staticPath"], - headDirPath=self.bootConfig["headDirPath"]) - - rep.status = falcon.HTTP_200 - body = dict(msg="locked") - rep.content_type = "application/json" - rep.data = json.dumps(body).encode("utf-8") - - -class IdentifierEnd(doing.DoDoer): - """ - ReST API for admin of Identifiers - """ - - def __init__(self, hby, **kwa): - self.hby = hby - - self.postman = forwarding.Postman(hby=self.hby) - self.witDoer = agenting.WitnessReceiptor(hby=self.hby) - self.swain = delegating.Boatswain(hby=hby) - self.org = connecting.Organizer(hby=hby) - self.cues = decking.Deck() - - doers = [self.witDoer, self.postman, self.swain, doing.doify(self.eventDo)] - - super(IdentifierEnd, self).__init__(doers=doers, **kwa) - - def on_get(self, _, rep): - """ Identifier GET endpoint - - Parameters: - _: falcon.Request HTTP request - rep: falcon.Response HTTP response - - --- - summary: Get list of agent identifiers - description: Get the list of identifiers associated with this agent - tags: - - Identifiers - responses: - 200: - description: An array of Identifier key state information - content: - application/json: - schema: - description: Key state information for current identifiers - type: array - items: - type: object - properties: - name: - description: habitat local alias - type: string - prefix: - description: qualified base64 identifier prefix - type: string - seq_no: - description: current key event sequence number - type: integer - delegated: - description: Flag indicating whether this identifier is delegated - type: boolean - delegator: - description: qualified base64 identifier prefix of delegator - type: string - witnesses: - description: list of qualified base64 identfier prefixes of witnesses - type: string - public_keys: - description: list of current public keys - type: array - items: - type: string - toad: - description: Current witness threshold - type: integer - isith: - description: Current signing threshold - type: string - receipts: - description: Count of witness receipts received for last key event - type: integer - """ - res = [] - - for pre, hab in self.hby.habs.items(): - info = self.info(hab) - res.append(info) - - rep.status = falcon.HTTP_200 - rep.content_type = "application/json" - rep.data = json.dumps(res).encode("utf-8") - - def on_get_alias(self, _, rep, alias=None): - """ Identifier GET endpoint - - Parameters: - _: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias: option route parameter for specific identifier to get - - --- - summary: Get list of agent identifiers - description: Get identifier information associated with alias - tags: - - Identifiers - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to get - responses: - 200: - description: An array of Identifier key state information - content: - application/json: - schema: - description: Key state information for current identifiers - type: array - items: - type: object - properties: - name: - description: habitat local alias - type: string - prefix: - description: qualified base64 identifier prefix - type: string - seq_no: - description: current key event sequence number - type: integer - delegated: - description: Flag indicating whether this identifier is delegated - type: boolean - delegator: - description: qualified base64 identifier prefix of delegator - type: string - witnesses: - description: list of qualified base64 identfier prefixes of witnesses - type: string - public_keys: - description: list of current public keys - type: array - items: - type: string - toad: - description: Current witness threshold - type: integer - isith: - description: Current signing threshold - type: string - receipts: - description: Count of witness receipts received for last key event - type: integer - """ - hab = self.hby.habByName(alias) - if hab is None: - raise falcon.HTTPNotFound(description=f"no identifier for alias {alias}") - - info = self.info(hab) - - rep.status = falcon.HTTP_200 - rep.content_type = "application/json" - rep.data = json.dumps(info).encode("utf-8") - - def info(self, hab): - data = dict( - name=hab.name, - prefix=hab.pre, - ) - - if hab.group: - data["group"] = dict( - pid=hab.mhab.pre, - aids=hab.smids, - accepted=hab.accepted - ) - - if hab.accepted: - kever = hab.kevers[hab.pre] - ser = kever.serder - dgkey = dbing.dgKey(ser.preb, ser.saidb) - wigs = hab.db.getWigs(dgkey) - data |= dict( - seq_no=kever.sn, - isith=kever.tholder.sith, - public_keys=[verfer.qb64 for verfer in kever.verfers], - nsith=kever.ntholder.sith, - next_keys=kever.digs, # this is misnamed these are not keys but digests of keys - toad=kever.toader.num, - witnesses=kever.wits, - estOnly=kever.estOnly, - DnD=kever.doNotDelegate, - receipts=len(wigs) - ) - - if kever.delegated: - data["delegated"] = kever.delegated - data["delegator"] = kever.delegator - dgkey = dbing.dgKey(hab.kever.prefixer.qb64b, hab.kever.lastEst.d) - anchor = self.hby.db.getAes(dgkey) - data["anchored"] = anchor is not None - - md = self.org.get(hab.pre) - if md is not None: - del md["id"] - data["metadata"] = md - else: - data["metadata"] = {} - - return data - - def on_put_metadata(self, req, rep, alias): - """ Identifier PUT endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias: human readable name of identifier to update contact information - - --- - summary: Update metadata associated with the identfier of the alias - description: Update metadata associated with the identfier of the alias - tags: - - Identifiers - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: human readable name of identifier prefix to add metadata - requestBody: - required: true - content: - application/json: - schema: - description: Contact information - type: object - - responses: - 200: - description: Updated contact information for remote identifier - 400: - description: Invalid identfier used to update contact information - 404: - description: Prefix not found in identifier contact information - """ - body = req.get_media() - hab = self.hby.habByName(name=alias) - - if hab is None: - rep.status = falcon.HTTP_404 - rep.text = f"{alias} does not represent a known identifier." - return - - if "id" in body: - del body["id"] - - if "alias" in body: - newAlias = body["alias"] - del body["alias"] - if not newAlias: - rep.status = falcon.HTTP_400 - rep.text = f"invalid new alias for identifier {hab.pre}." - return - - habord = hab.db.habs.get(keys=alias) - hab.db.habs.put(keys=newAlias, - val=habord) - hab.db.habs.rem(keys=alias) - self.hby.loadHabs() - - self.org.update(hab.pre, body) - contact = self.org.get(hab.pre) - - rep.status = falcon.HTTP_200 - rep.data = json.dumps(contact).encode("utf-8") - - def on_post_metadata(self, req, rep, alias): - """ Identifier Metadata POST endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias: human readable name of identifier to replace contact information - - --- - summary: Replace metadata associated with the identfier of the alias - description: Replace metadata associated with the identfier of the alias - tags: - - Identifiers - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: human readable name of identifier prefix to replace metadata - requestBody: - required: true - content: - application/json: - schema: - description: Contact information - type: object - - responses: - 200: - description: Updated contact information for remote identifier - 400: - description: Invalid identfier used to update contact information - 404: - description: Prefix not found in identifier contact information - """ - body = req.get_media() - hab = self.hby.habByName(name=alias) - - if hab is None: - rep.status = falcon.HTTP_404 - rep.text = f"{alias} does not represent a known identifier." - return - - if "id" in body: - del body["id"] - - if "alias" in body: - newAlias = body["alias"] - if not newAlias: - rep.status = falcon.HTTP_400 - rep.text = f"invalid new alias for identifier {hab.pre}." - return - - del body["alias"] - habord = hab.db.habs.get(keys=alias) - hab.db.habs.put(keys=newAlias, - val=habord) - hab.db.habs.rem(keys=alias) - self.hby.loadHabs() - - self.org.replace(hab.pre, body) - contact = self.org.get(hab.pre) - - rep.status = falcon.HTTP_200 - rep.data = json.dumps(contact).encode("utf-8") - - def on_post_alias(self, req, rep, alias): - """ Identifier POST endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias (str): human readable name for Hab - - --- - summary: Create agent identifier - description: Create agent identifier with the supplied parameters - tags: - - Identifiers - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to create - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - wits: - type: array - items: - type: string - description: human readable alias for the new identfier - responses: - 200: - description: identifier information - - """ - hab = self.hby.habByName(name=alias) - if hab is not None: - rep.status = falcon.HTTP_400 - body = dict(code=falcon.HTTP_400, msg="fInvalid incept request, {alias} already used") - rep.content_type = "application/json" - rep.data = json.dumps(body).encode("utf-8") - return - - body = req.get_media() - - isith = None - if "isith" in body: - isith = body["isith"] - if isinstance(isith, str) and "," in isith: - isith = isith.split(",") - - nsith = None - if "nsith" in body: - nsith = body["nsith"] - if isinstance(nsith, str) and "," in nsith: - nsith = nsith.split(",") - - transferable = body.get("transferable") if "transferable" in body else True - wits = body.get("wits") if "wits" in body else [] - toad = int(body.get("toad")) if "toad" in body else None - icount = int(body.get("count")) if "count" in body else 1 - ncount = int(body.get("ncount")) if "ncount" in body else 1 - estOnly = int(body.get("estOnly")) if "estOnly" in body else False - DnD = int(body.get("DnD")) if "DnD" in body else False - data = body.get("data") if "data" in body else None - - kwa = dict( - transferable=transferable, - wits=wits, - toad=toad, - isith=isith, - icount=icount, - nsith=nsith, - ncount=ncount, - estOnly=estOnly, - DnD=DnD, - data=data, - ) - if "delpre" in body: - kwa["delpre"] = body["delpre"] - - hab = self.hby.makeHab(name=alias, **kwa) - self.cues.append(dict(pre=hab.pre)) - - icp = hab.makeOwnInception() - serder = coring.Serder(raw=icp) - - rep.status = falcon.HTTP_200 - rep.content_type = "application/json" - rep.data = serder.raw - - def on_put_rot(self, req, rep, alias): - """ Identifier PUT endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias (str): human readable name for Hab - - --- - summary: Rotate agent identifier - description: Perform a rotation on the agent's current identifier - tags: - - Identifiers - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to rotate - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - wits: - type: array - description: list of witness identifiers - items: - type: string - adds: - type: array - description: list of witness identifiers to add - items: - type: string - cuts: - type: array - description: list of witness identifiers to remove - items: - type: string - toad: - type: integer - description: witness threshold - default: 1 - isith: - type: string - description: signing threshold - count: - type: integer - description: count of next key commitment. - data: - type: array - description: list of data objects to anchor to this rotation event - items: - type: object - responses: - 200: - description: Rotation successful with KEL event returned - 400: - description: Error creating rotation event - - """ - hab = self.hby.habByName(alias) - if hab is None: - rep.status = falcon.HTTP_400 - rep.data = f"no matching Hab for alias {alias}" - return - - body = req.get_media() - isith = None - if "isith" in body: - isith = body["isith"] - if isinstance(isith, str) and "," in isith: - isith = isith.split(",") - - wits = body.get("wits") - toad = int(body.get("toad")) if "toad" in body else None - count = int(body.get("count")) if "count" in body else None - data = body["data"] if "data" in body else None - cuts = set() - adds = set() - - if wits: - ewits = hab.kever.wits - - # wits= [a,b,c] wits=[b, z] - cuts = set(ewits) - set(wits) - adds = set(wits) - set(ewits) - - try: - rot = hab.rotate(isith=isith, ncount=count, toad=toad, cuts=list(cuts), adds=list(adds), data=data) - self.cues.append(dict(pre=hab.pre)) - - serder = coring.Serder(raw=rot) - rep.status = falcon.HTTP_200 - rep.content_type = "application/json" - rep.data = serder.raw - - except (ValueError, TypeError, Exception) as e: - rep.status = falcon.HTTP_400 - rep.text = e.args[0] - - def on_put_ixn(self, req, rep, alias): - """ Identifier PUT endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias (str): human readable name for Hab - - --- - summary: Interaction event for agent identifier - description: Perform an interaction event on the agent's current identifier - tags: - - Identifiers - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to ineract - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - data: - type: array - description: list of data objects to anchor to this rotation event - items: - type: object - responses: - 200: - description: Interaction successful with KEL event returned - 400: - description: Error creating interaction event - - """ - hab = self.hby.habByName(alias) - if hab is None: - rep.status = falcon.HTTP_400 - rep.data = f"no matching Hab for alias {alias}" - return - - body = req.get_media() - data = body["data"] if "data" in body else None - - try: - ixn = hab.interact(data=data) - self.cues.append(dict(pre=hab.pre)) - - serder = coring.Serder(raw=ixn) - rep.status = falcon.HTTP_200 - rep.content_type = "application/json" - rep.data = serder.raw - - except (ValueError, TypeError, Exception) as e: - rep.status = falcon.HTTP_400 - rep.text = e.args[0] - - def eventDo(self, tymth, tock=0.0): - """ Check for accepted Habs that have not been delegated or receipted and do so - - Parameters: - tymth (function): injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock (float): injected initial tock value - - """ - # enter context - self.wind(tymth) - self.tock = tock - _ = (yield self.tock) - - while True: - while not self.cues: - yield self.tock - - cue = self.cues.popleft() - pre = cue["pre"] - hab = self.hby.habs[pre] - - if hab.group: # Skip if group, they are handled elsewhere - yield self.tock - continue - - if hab.kever.delegator and hab.kever.ilk in (coring.Ilks.dip, coring.Ilks.drt): - dgkey = dgKey(pre=hab.kever.prefixer.qb64, dig=hab.kever.serder.saidb) - anchor = self.hby.db.getAes(dgkey) - if not anchor: - self.swain.msgs.append(dict(alias=hab.name, pre=hab.pre, sn=hab.kever.sn)) - print("Waiting for delegation approval...") - while not self.swain.cues: - yield self.tock - - self.swain.cues.popleft() - print("Delegation anchored") - - dgkey = dbing.dgKey(hab.kever.serder.preb, hab.kever.serder.saidb) - wigs = hab.db.getWigs(dgkey) - if len(wigs) != len(hab.kever.wits): - self.witDoer.msgs.append(dict(pre=hab.pre)) - while True: - yield self.tock - wigs = hab.db.getWigs(dgkey) - if len(wigs) == len(hab.kever.wits): - break - - if hab.kever.delegator: - yield from self.postman.sendEvent(hab=hab, fn=hab.kever.sn) - - yield self.tock - - -class KeyStateEnd: - - def __init__(self, hby, counselor): - self.hby = hby - self.counselor = counselor - - def on_get(self, _, rep, prefix): - """ - - Parameters: - _ (Request): falcon.Request HTTP request - rep (Response): falcon.Response HTTP response - prefix (str): qb64 identifier prefix to load key state and key event log - - --- - summary: Display key event log (KEL) for given identifier prefix - description: If provided qb64 identifier prefix is in Kevers, return the current state of the - identifier along with the KEL and all associated signatures and receipts - tags: - - Ket Event Log - parameters: - - in: path - name: prefix - schema: - type: string - required: true - description: qb64 identifier prefix of KEL to load - responses: - 200: - description: Key event log and key state of identifier - 404: - description: Identifier not found in Key event database - - - """ - if prefix not in self.hby.kevers: - rep.status = falcon.HTTP_404 - rep.text = f"no information found for {prefix}" - return - - kever = self.hby.kevers[prefix] - pre = kever.prefixer.qb64 - preb = kever.prefixer.qb64b - - res = dict( - pre=pre, - state=kever.state().ked - ) - - kel = [] - for fn, dig in self.hby.db.getFelItemPreIter(preb, fn=0): - try: - event = eventing.loadEvent(self.hby.db, preb, dig) - except ValueError as e: - rep.status = falcon.HTTP_400 - rep.text = e.args[0] - return - - kel.append(event) - - key = dbing.snKey(pre=pre, sn=0) - # load any partially witnesses events for this prefix - for ekey, edig in self.hby.db.getPweItemsNextIter(key=key): - pre, sn = dbing.splitKeySN(ekey) # get pre and sn from escrow item - try: - kel.append(eventing.loadEvent(self.hby.db, pre, edig)) - except ValueError as e: - rep.status = falcon.HTTP_400 - rep.text = e.args[0] - return - - # load any partially signed events from this prefix - for ekey, edig in self.hby.db.getPseItemsNextIter(key=key): - pre, sn = dbing.splitKeySN(ekey) # get pre and sn from escrow item - try: - kel.append(eventing.loadEvent(self.hby.db, pre, edig)) - except ValueError as e: - rep.status = falcon.HTTP_400 - rep.text = e.args[0] - return - - res["kel"] = kel - - # Check to see if we have any pending distributed multisig events - evts = [] - if pre in self.hby.habs: - hab = self.hby.habs[pre] - if hab.group: - evts = self.counselor.pendingEvents(pre) - res["pending"] = evts - - rep.status = falcon.HTTP_200 - rep.content_type = "application/json" - rep.data = json.dumps(res).encode("utf-8") - - def on_get_pubkey(self, _, rep, pubkey): - """ - - Parameters: - _ (Request): falcon.Request HTTP request - rep (Response): falcon.Response HTTP response - pubkey (str): qb64 public key for which to search - - --- - summary: Display key event log (KEL) for given identifier prefix - description: If provided qb64 identifier prefix is in Kevers, return the current state of the - identifier along with the KEL and all associated signatures and receipts - tags: - - Ket Event Log - parameters: - - in: path - name: pubkey - schema: - type: string - required: true - description: qb64 identifier prefix of KEL to load - responses: - 200: - description: Key event log and key state of identifier - 404: - description: Identifier not found in Key event database - - - """ - found = None - for pre, digb, raw in self.hby.db.getAllItemIter(db=self.hby.db.evts): - serder = coring.Serder(raw=bytes(raw)) - if len(serder.ked['k']) == 1 and pubkey in serder.ked['k']: - found = serder - - if found is None: - rep.status = falcon.HTTP_404 - rep.data = json.dumps(dict(msg="Public key not found")).encode("utf-8") - return - - rep.status = falcon.HTTP_200 - rep.data = json.dumps(found.ked).encode("utf-8") - - -class RegistryEnd(doing.DoDoer): - """ - ReST API for admin of credential issuance and revocation registries - - """ - - def __init__(self, hby, rgy, registrar, **kwa): - self.hby = hby - self.rgy = rgy - self.registrar = registrar - - super(RegistryEnd, self).__init__(doers=[], **kwa) - - def on_get(self, _, rep): - """ Registries GET endpoint - - Parameters: - _: falcon.Request HTTP request - rep: falcon.Response HTTP response - - --- - summary: List credential issuance and revocation registies - description: List credential issuance and revocation registies - tags: - - Registries - responses: - 200: - description: array of current credential issuance and revocation registies - - """ - res = [] - for name, registry in self.rgy.regs.items(): - rd = dict( - name=registry.name, - regk=registry.regk, - pre=registry.hab.pre, - state=registry.tever.state().ked - ) - res.append(rd) - - rep.status = falcon.HTTP_200 - rep.content_type = "application/json" - rep.data = json.dumps(res).encode("utf-8") - - def on_post(self, req, rep): - """ Registries POST endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - - --- - summary: Request to create a credential issuance and revocation registry - description: Request to create a credential issuance and revocation registry - tags: - - Registries - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: - type: string - description: name of the new registry - alias: - type: string - description: name of identifier to associate as the issuer of the new credential registry - toad: - type: integer - description: Backer receipt threshold - nonce: - type: string - description: qb64 encoded ed25519 random seed for registry - noBackers: - type: boolean - required: False - description: True means to not allow seperate backers from identifier's witnesses. - baks: - type: array - items: - type: string - description: List of qb64 AIDs of witnesses to be used for the new group identfier. - estOnly: - type: boolean - required: false - default: false - description: True means to not allow interaction events to anchor credential events. - responses: - 202: - description: registry inception request has been submitted - - """ - body = req.get_media() - - if "name" not in body: - rep.status = falcon.HTTP_400 - rep.text = "name is a required parameter to create a verifiable credential registry" - return - - if "alias" not in body: - rep.status = falcon.HTTP_400 - rep.text = "alias is a required parameter to create a verifiable credential registry" - return - - alias = body["alias"] - hab = self.hby.habByName(alias) - if hab is None: - rep.status = falcon.HTTP_404 - rep.text = "alias is not a valid reference to an identfier" - return - - c = dict() - if "noBackers" in body: - c["noBackers"] = body["noBackers"] - if "baks" in body: - c["baks"] = body["baks"] - if "toad" in body: - c["toad"] = body["toad"] - if "estOnly" in body: - c["estOnly"] = body["estOnly"] - if "nonce" in body: - c["nonce"] = body["nonce"] - - self.registrar.incept(name=body["name"], pre=hab.pre, conf=c) - - rep.status = falcon.HTTP_202 - - -class CredentialEnd(doing.DoDoer): - """ - ReST API for admin of credentials - - """ - - def __init__(self, hby, rgy, registrar, credentialer, verifier, notifier): - """ Create endpoint for issuing and listing credentials - - Endpoints for issuing and listing credentials from non-group identfiers only - - Parameters: - hby (Habery): identifier database environment - rgy (Regery): credential registry database environment - verifier (Verifier): credential verifier - registrar (Registrar): credential registry protocol manager - credentialer: (Credentialer): credential protocol manager - notifier (Notifier): outbound notifications - - """ - self.hby = hby - self.rgy = rgy - self.credentialer = credentialer - self.registrar = registrar - self.verifier = verifier - self.postman = forwarding.Postman(hby=self.hby) - self.notifier = notifier - self.evts = decking.Deck() - - super(CredentialEnd, self).__init__(doers=[self.postman, doing.doify(self.evtDo)]) - - def on_get(self, req, rep, alias): - """ Credentials GET endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias (str): human readable name of identifier to load credentials for - - --- - summary: List credentials in credential store (wallet) - description: List issued or received credentials current verified - tags: - - Credentials - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to create - - in: query - name: type - schema: - type: string - description: type of credential to return, [issued|received] - required: true - - in: query - name: schema - schema: - type: string - description: schema to filter by if provided - required: false - responses: - 200: - description: Credential list. - content: - application/json: - schema: - description: Credentials - type: array - items: - type: object - - """ - typ = req.params.get("type") - schema = req.params.get("schema") - - hab = self.hby.habByName(name=alias) - if hab is None: - rep.status = falcon.HTTP_400 - rep.text = "Invalid alias {} for credentials" \ - "".format(alias) - return - - creds = [] - if typ == "issued": - saids = self.rgy.reger.issus.get(keys=hab.pre) - elif typ == "received": - saids = self.rgy.reger.subjs.get(keys=hab.pre) - else: - rep.status = falcon.HTTP_400 - rep.text = f"Invalid type {typ}" - return - - if schema is not None: - scads = self.rgy.reger.schms.get(keys=schema) - saids = [saider for saider in saids if saider.qb64 in [saider.qb64 for saider in scads]] - - creds = self.rgy.reger.cloneCreds(saids) - - rep.status = falcon.HTTP_200 - rep.content_type = "application/json" - rep.data = json.dumps(creds).encode("utf-8") - - def on_get_export(self, _, rep, alias, said): - """ Credentials GET endpoint - - Parameters: - _: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias (str): human readable name of identifier to load credentials for - said (str): SAID of credential to export - - --- - summary: Export credential and all supporting cryptographic material - description: Export credential and all supporting cryptographic material - tags: - - Credentials - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to create - - in: path - name: said - schema: - type: string - required: true - description: SAID of credential to export - responses: - 200: - description: Credential export. - content: - application/json+cesr: - schema: - description: Credential - type: object - - """ - hab = self.hby.habByName(name=alias) - if hab is None: - rep.status = falcon.HTTP_400 - rep.text = "Invalid alias {} for credentials".format(alias) - return - - data = self.outputCred(hab, said) - - rep.status = falcon.HTTP_200 - rep.content_type = "application/json+cesr" - rep.data = bytes(data) - - def outputCred(self, hab, said): - out = bytearray() - creder, sadsigers, sadcigars = self.rgy.reger.cloneCred(said=said) - chains = creder.chains - saids = [] - for key, source in chains.items(): - if key == 'd': - continue - - if not isinstance(source, dict): - continue - - saids.append(source['n']) - - for said in saids: - out.extend(self.outputCred(hab, said)) - - issr = creder.issuer - for msg in self.hby.db.clonePreIter(pre=issr): - serder = coring.Serder(raw=msg) - atc = msg[serder.size:] - out.extend(serder.raw) - out.extend(atc) - - if creder.status is not None: - for msg in self.rgy.reger.clonePreIter(pre=creder.status): - serder = coring.Serder(raw=msg) - atc = msg[serder.size:] - out.extend(serder.raw) - out.extend(atc) - - for msg in self.rgy.reger.clonePreIter(pre=creder.said): - serder = coring.Serder(raw=msg) - atc = msg[serder.size:] - out.extend(serder.raw) - out.extend(atc) - - out.extend(creder.raw) - out.extend(eventing.proofize(sadtsgs=sadsigers, sadcigars=sadcigars, pipelined=True)) - - return out - - def on_post(self, req, rep, alias): - """ Initiate a credential issuance - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias: qb64 identfier prefix of issuer of credential - - --- - summary: Perform credential issuance - description: Perform credential issuance - tags: - - Credentials - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to create - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - registry: - type: string - description: Alias of credential issuance/revocation registry (aka status) - recipient: - type: string - description: AID of credential issuance/revocation recipient - schema: - type: string - description: SAID of credential schema being issued - source: - type: object - description: ACDC edge or edge group for chained credentials - properties: - d: - type: string - description: SAID of reference chain - s: - type: string - description: SAID of reference chain schema - credentialData: - type: object - description: dynamic map of values specific to the schema - private: - type: boolean - description: flag to inidicate this credential should support privacy preserving presentations - responses: - 200: - description: Credential issued. - content: - application/json: - schema: - description: Credential - type: object - - """ - body = req.get_media() - hab = self.hby.habByName(alias) - if hab is None: - rep.status = falcon.HTTP_400 - rep.text = "Invalid alias {} for credential issuance" \ - "".format(alias) - return None - - regname = body.get("registry") - recp = body.get("recipient") - schema = body.get("schema") - source = body.get("source") - rules = body.get("rules") - data = body.get("credentialData") - private = body.get("private") is not None and body.get("private") is True - - edges = None - if source is not None: - try: - _, edges = coring.Saider.saidify(sad=source) - except KeyError: - edges = source - - try: - creder = self.credentialer.create(regname, recp, schema, edges, rules, data, private=private) - self.credentialer.issue(creder=creder) - - except kering.ConfigurationError as e: - rep.status = falcon.HTTP_400 - rep.text = e.args[0] - return - - # cue up an event to send notification when complete - self.evts.append(dict(topic="/credential", r="/iss/complete", d=creder.said)) - - rep.status = falcon.HTTP_200 - rep.data = creder.raw - - def on_post_iss(self, req, rep, alias=None): - """ Initiate a credential issuance from a group multisig identfier - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias: qb64 identfier prefix of issuer of credential - - --- - summary: Initiate credential issuance from a group multisig identifier - description: Initiate credential issuance from a group multisig identifier - tags: - - Group Credentials - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to create - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - registry: - type: string - description: AID of credential issuance/revocation registry (aka status) - recipient: - type: string - description: AID of credential issuance/revocation recipient - schema: - type: string - description: SAID of credential schema being issued - source: - type: array - description: list of credential chain sources (ACDC) - items: - type: object - properties: - d: - type: string - description: SAID of reference chain - s: - type: string - description: SAID of reference chain schema - rules: - type: array - description: list of credential chain sources (ACDC) - credentialData: - type: object - description: dynamic map of values specific to the schema - private: - type: boolean - description: flag to inidicate this credential should support privacy preserving presentations - responses: - 200: - description: Credential issued. - content: - application/json: - schema: - description: Credential - type: object - - - """ - body = req.get_media() - hab = self.hby.habByName(alias) - if hab is None or hab.mhab is None: - rep.status = falcon.HTTP_400 - rep.text = "Invalid alias {} for group credentials" \ - "".format(alias) - return - - regname = body.get("registry") - recp = body.get("recipient") - schema = body.get("schema") - source = body.get("source") - rules = body.get("rules") - data = body.get("credentialData") - private = body.get("private") is not None and body.get("private") is True - - edges = None - if source is not None: - try: - _, edges = coring.Saider.saidify(sad=source) - except KeyError: - edges = source - - try: - creder = self.credentialer.create(regname, recp, schema, edges, rules, data, private=private) - self.credentialer.issue(creder=creder) - except kering.ConfigurationError as e: - rep.status = falcon.HTTP_400 - rep.text = e.args[0] - return - - exn, atc = grouping.multisigIssueExn(hab=hab, creder=creder) - - smids, rmids = hab.members() - others = list(oset(smids + (rmids or []))) - others.remove(hab.mhab.pre) - - for recpt in others: - self.postman.send(src=hab.mhab.pre, dest=recpt, topic="multisig", serder=exn, attachment=atc) - - # cue up an event to send notification when complete - self.evts.append(dict(topic="/multisig", r="/iss/complete", d=creder.said)) - - rep.status = falcon.HTTP_200 - rep.data = creder.pretty().encode("utf-8") - - - def on_put_iss(self, req, rep, alias=None): - """ Participate in a credential issuance from a group identfier - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias: qb64 identfier prefix of issuer of credential - - --- - summary: Participate in a credential issuance from a group multisig identifier - description: Participate in a credential issuance from a group multisig identifier - tags: - - Group Credentials - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to create - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - credential: - type: object - description: Fully populated ACDC credential to issue - responses: - 200: - description: Credential issued. - content: - application/json: - schema: - description: Credential - type: object - - - """ - body = req.get_media() - hab = self.hby.habByName(alias) - if hab is None or hab.mhab is None: - rep.status = falcon.HTTP_400 - rep.text = "Invalid alias {} for group credentials" \ - "".format(alias) - return None - - if "credential" not in body: - rep.status = falcon.HTTP_400 - rep.text = "credential required in body" - return None - - data = body["credential"] - creder = proving.Creder(ked=data) - - try: - self.credentialer.validate(creder=creder) - self.credentialer.issue(creder=creder) - except kering.ConfigurationError as e: - rep.status = falcon.HTTP_400 - rep.text = e.args[0] - return - - # cue up an event to send notification when complete - self.evts.append(dict(topic="/multisig", r="/iss/complete", d=creder.said)) - - rep.status = falcon.HTTP_200 - rep.data = creder.pretty().encode("utf-8") - - def revoke(self, req, rep, said): - regname = req.get_param("registry") - - registry = self.rgy.registryByName(regname) - if registry is None: - rep.status = falcon.HTTP_400 - rep.text = "Credential registry {} does not exist. It must be created before issuing " \ - "credentials".format(regname) - return False - - try: - creder = self.verifier.reger.creds.get(keys=(said,)) - if creder is None: - rep.status = falcon.HTTP_NOT_FOUND - rep.text = "credential not found" - return False - - self.registrar.revoke(regk=registry.regk, said=creder.said) - except kering.ValidationError as ex: - rep.status = falcon.HTTP_CONFLICT - rep.text = ex.args[0] - return False - - return True - - def on_delete(self, req, rep, alias=None): - """ Credential DELETE endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias: qb64 identfier prefix of issuer of credential - - --- - summary: Revoke credential - description: PArticipate in a credential revocation for a group multisig issuer - tags: - - Credentials - parameters: - - in: query - name: registry - schema: - type: string - description: SAID of credential registry - required: true - - in: path - name: alias - schema: - type: string - description: human readable alias for issuer identifier - required: true - - in: query - name: said - schema: - type: string - description: SAID of credential to revoke - required: true - - responses: - 202: - description: credential successfully revoked. - - """ - hab = self.hby.habByName(alias) - if hab is None: - rep.status = falcon.HTTP_400 - rep.text = "Invalid alias {} for credential revocation" \ - "".format(alias) - return None - - said = req.params.get("said") - - if self.revoke(req=req, rep=rep, said=said): - # cue up an event to send notification when complete - self.evts.append(dict(topic="/credential", r="/rev/complete", d=said)) - - rep.status = falcon.HTTP_202 - - # Else the revoke method handled the status - - def on_post_rev(self, req, rep, alias=None, said=None): - """ Credential DELETE endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias: qb64 identfier prefix of issuer of credential - said: qb64 SAID of the credential to be revoked - - --- - summary: Revoke credential - description: Initiate a credential revocation for a group multisig issuer - tags: - - Group Credentials - parameters: - - in: query - name: registry - schema: - type: string - description: SAID of credential registry - required: true - - in: path - name: alias - schema: - type: string - description: human readable alias for issuer identifier - required: true - - in: path - name: said - schema: - type: string - description: SAID of credential to revoke - required: true - - responses: - 202: - description: credential successfully revoked. - - """ - hab = self.hby.habByName(alias) - if hab is None or hab.mhab is None: - rep.status = falcon.HTTP_400 - rep.text = "Invalid alias {} for group credentials" \ - "".format(alias) - return None - - if self.revoke(req=req, rep=rep, said=said): - # TODO: SEND revocation proposal exn to others! - - # cue up an event to send notification when complete - self.evts.append(dict(topic="/multisig", r="/rev/complete", d=said)) - - rep.status = falcon.HTTP_202 - - # Else the revoke method handled the status - - def on_put_rev(self, req, rep, alias=None, said=None): - """ Credential DELETE endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias: qb64 identfier prefix of issuer of credential - said: qb64 identifier prefix of recipient of credential - - --- - summary: Revoke credential - description: PArticipate in a credential revocation for a group multisig issuer - tags: - - Group Credentials - parameters: - - in: query - name: registry - schema: - type: string - description: SAID of credential registry - required: true - - in: path - name: alias - schema: - type: string - description: human readable alias for issuer identifier - required: true - - in: path - name: said - schema: - type: string - description: SAID of credential to revoke - required: true - - responses: - 202: - description: credential successfully revoked. - - """ - hab = self.hby.habByName(alias) - if hab is None or hab.mhab is None: - rep.status = falcon.HTTP_400 - rep.text = "Invalid alias {} for group credentials" \ - "".format(alias) - return None - - if self.revoke(req=req, rep=rep, said=said): - # cue up an event to send notification when complete - self.evts.append(dict(topic="/multisig", r="/rev/complete", d=said)) - - rep.status = falcon.HTTP_202 - - # Else the revoke method handled the status - - def evtDo(self, tymth, tock=0.5): - """ Monitor results of inception initiation and raise a cue when one completes - - Parameters: - tymth (function): injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock (float): injected initial tock value - - Returns: doifiable Doist compatible generator method for monitoring events - - """ - # enter context - self.wind(tymth) - self.tock = tock - _ = (yield self.tock) - - while True: - if not self.evts: - yield self.tock - continue - - evt = self.evts.popleft() - tpc = evt["topic"] - said = evt["d"] - route = evt["r"] - - if route == "/iss/complete": - if self.credentialer.complete(said=said): - self.notifier.add(dict( - r=f"{tpc}{route}", - a=dict(d=said), - )) - else: - self.evts.append(evt) - - elif route == "/rev/complete": - if self.registrar.complete(pre=said, sn=1): - self.notifier.add(dict( - r=f"{tpc}{route}", - a=dict(d=said), - )) - else: - self.evts.append(evt) - - yield self.tock - - -class PresentationEnd(doing.DoDoer): - """ - ReST API for admin of credential presentation requests - - """ - - def __init__(self, hby, reger): - """ Create endpoint handler for credential presentations and requests - - Parameters: - hby (Habery): database environment for identifiers - reger (Reger): database environment for credentials - - """ - self.hby = hby - self.reger = reger - self.org = connecting.Organizer(hby=hby) - self.postman = forwarding.Postman(hby=self.hby) - - super(PresentationEnd, self).__init__(doers=[self.postman]) - - def on_post_request(self, req, rep, alias): - """ Presentation Request POST endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias (str): human readable name for Hab - - --- - summary: Request credential presentation - description: Send a credential presentation request peer to peer (exn) message to recipient - tags: - - Credentials - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to create - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - recipient: - type: string - required: true - description: qb64 AID to send presentation request to - schema: - type: string - required: true - description: qb64 SAID of schema for credential being requested - issuer: - type: string - required: false - description: qb64 AID of issuer of credential being requested - responses: - 202: - description: credential presentation request message sent - - """ - hab = self.hby.habByName(alias) - if hab is None: - rep.status = falcon.HTTP_400 - rep.text = f"Invalid alias {alias} for credential request" - return None - - body = req.get_media() - recp = body.get("recipient") - if recp is None: - rep.status = falcon.HTTP_400 - rep.text = "recp is required, none provided" - return - - schema = body.get("schema") - if schema is None: - rep.status = falcon.HTTP_400 - rep.text = "schema is required, none provided" - return - - pl = dict( - s=schema - ) - - issuer = body.get("issuer") - if issuer is not None: - pl['i'] = issuer - - exn = exchanging.exchange(route="/presentation/request", payload=pl) - ims = hab.endorse(serder=exn, last=True, pipelined=False) - del ims[:exn.size] - self.postman.send(src=hab.pre, dest=recp, topic="credential", serder=exn, attachment=ims) - - rep.status = falcon.HTTP_202 - - def on_post_present(self, req, rep, alias): - """ Presentation POST endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias (str): human readable name for Hab - - --- - summary: Send credential presentation - description: Send a credential presentation peer to peer (exn) message to recipient - tags: - - Credentials - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the holder of credential - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - said: - type: string - required: true - description: qb64 SAID of credential to send - recipient: - type: string - required: true - description: qb64 AID to send credential presentation to - include: - type: boolean - required: true - default: true - description: flag indicating whether to stream credential alongside presentation exn - responses: - 202: - description: credential presentation message sent - - """ - hab = self.hby.habByName(alias) - if hab is None: - rep.status = falcon.HTTP_400 - rep.text = f"Invalid alias {alias} for credential presentation" - return None - - body = req.get_media() - said = body.get("said") - if said is None: - rep.status = falcon.HTTP_400 - rep.text = "said is required, none provided" - return - - creder = self.reger.creds.get(said) - if creder is None: - rep.status = falcon.HTTP_404 - rep.text = f"credential {said} not found" - return - - recipient = body.get("recipient") - if recipient is None: - rep.status = falcon.HTTP_400 - rep.text = "recipient is required, none provided" - return - - if recipient in self.hby.kevers: - recp = recipient - else: - recp = self.org.find("alias", recipient) - if len(recp) != 1: - raise ValueError(f"invalid recipient {recipient}") - recp = recp[0]['id'] - - include = body.get("include") - if include: - credentialing.sendCredential(self.hby, hab=hab, reger=self.reger, postman=self.postman, creder=creder, - recp=recp) - - exn, atc = protocoling.presentationExchangeExn(hab=hab, reger=self.reger, said=said) - self.postman.send(src=hab.pre, dest=recp, topic="credential", serder=exn, attachment=atc) - - rep.status = falcon.HTTP_202 - - -class MultisigEndBase(doing.DoDoer): - - def __init__(self, hby, counselor, notifier, doers): - self.hby = hby - self.notifier = notifier - self.counselor = counselor - self.postman = forwarding.Postman(hby=hby) - - self.evts = decking.Deck() - doers.extend([self.postman, doing.doify(self.evtDo)]) - - super(MultisigEndBase, self).__init__(doers=doers) - - def evtDo(self, tymth, tock=0.5): - """ Monitor results of inception initiation and raise a cue when one completes - - Parameters: - tymth (function): injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock (float): injected initial tock value - - Returns: doifiable Doist compatible generator method for monitoring events - - """ - # enter context - self.wind(tymth) - self.tock = tock - _ = (yield self.tock) - - while True: - if not self.evts: - yield self.tock - continue - - evt = self.evts.popleft() - pre = evt["i"] - sn = evt["s"] - saider = coring.Saider(qb64=evt["d"]) if "d" in evt else None - - route = evt["r"] - prefixer = coring.Prefixer(qb64=pre) - seqner = coring.Seqner(sn=sn) - hab = self.hby.habs[pre] - - if self.counselor.complete(prefixer, seqner, saider): - if hab.kever.delegator: - yield from self.postman.sendEvent(hab=hab, fn=hab.kever.sn) - - self.notifier.add(attrs=dict( - r=f"/multisig{route}", - a=dict(i=pre, s=sn), - )) - else: - self.evts.append(evt) - - yield self.tock - - -class MultisigInceptEnd(MultisigEndBase): - """ - ReST API for admin of distributed multisig groups - - """ - - def __init__(self, hby, counselor, notifier): - """ Create an endpoint resource for creating or participating in multisig group identifiers - - Parameters: - hby (Habery): identifier database environment - counselor (Counselor): multisig group communication management - - """ - - self.hby = hby - self.counselor = counselor - self.notifier = notifier - self.postman = forwarding.Postman(hby=self.hby) - doers = [self.postman] - - super(MultisigInceptEnd, self).__init__(hby=hby, notifier=notifier, - counselor=counselor, doers=doers) - - def initialize(self, body, rep, alias): - """Incept group multisig - - ToDo: NRR - - - """ - - if "aids" not in body: - rep.status = falcon.HTTP_400 - rep.text = "Invalid multisig group inception request, 'aids' is required'" - return None, None - - smids = body["aids"] # change body aids to smids for group member ids - rmids = body["rmids"] if "rmids" in body else None - both = list(oset(smids + (rmids or []))) - - mhab = None - for mid in both: - if mid in self.hby.habs: - mhab = self.hby.habs[mid] - break - - if mhab is None: - rep.status = falcon.HTTP_400 - rep.text = "Invalid multisig group inception request, aid list must contain a local identifier'" - return None, None - - if self.hby.habByName(alias) is not None: - rep.status = falcon.HTTP_400 - rep.text = f"Identifier alias {alias} is already in use" - return None, None - - inits = dict() - - isith = None - if "isith" in body: - isith = body["isith"] - if isinstance(isith, str) and "," in isith: - isith = isith.split(",") - - inits["isith"] = isith - - nsith = None - if "nsith" in body: - nsith = body["nsith"] - if isinstance(nsith, str) and "," in nsith: - nsith = nsith.split(",") - - inits["nsith"] = nsith - - if "estOnly" in body: - inits["estOnly"] = body["estOnly"] - if "DnD" in body: - inits["DnD"] = body["DnD"] - - inits["toad"] = body["toad"] if "toad" in body else None - inits["wits"] = body["wits"] if "wits" in body else [] - inits["delpre"] = body["delpre"] if "delpre" in body else None - - try: - ghab = self.hby.makeGroupHab(group=alias, - mhab=mhab, - smids=smids, - rmids=rmids, - **inits) - except ValueError as ex: - rep.status = falcon.HTTP_400 - rep.data = json.dumps(dict(msg=ex.args[0])).encode("utf-8") - return None, None - - return mhab, ghab - - - def icp(self, hab, ghab, aids, rmids=None): - """ - - Args: - ghab (Hab): Group Hab to start processing - hab (Hab): Local participant Hab - aids (list): Other group signing member qb64 ids - rmids (list | None) Other group rotating member qb64 ids - - """ - prefixer = coring.Prefixer(qb64=ghab.pre) - seqner = coring.Seqner(sn=0) - saider = coring.Saider(qb64=prefixer.qb64) - self.counselor.start(prefixer=prefixer, seqner=seqner, saider=saider, - mid=hab.pre, smids=aids, rmids=rmids) - - - def on_post(self, req, rep, alias): - """ Multisig POST endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias (str): human readable name for new multisig identifier from path - - --- - summary: Initiate a multisig group inception - description: Initiate a multisig group inception with the participants identified by the provided AIDs - tags: - - Groups - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to create - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - aids: - type: array - items: - type: string - description: List of qb64 AIDs of participants in multisig group - notify: - type: boolean - required: False - description: True means to send mutlsig incept exn message to other participants - toad: - type: integer - description: Witness receipt threshold - wits: - type: array - items: - type: string - description: List of qb64 AIDs of witnesses to be used for the new group identfier - isith: - type: string - description: Signing threshold for the new group identifier - nsith: - type: string - description: Next signing threshold for the new group identifier - estOnly: - type: boolean - required: False - description: True means this identifier will not allow interaction events. - - responses: - 200: - description: Multisig group AID inception initiated. - - """ - body = req.get_media() - - mhab, ghab = self.initialize(body, rep, alias) - if ghab is None: - return - - if not ghab.accepted: - evt = grouping.getEscrowedEvent(db=self.hby.db, pre=ghab.pre, sn=0) - else: - evt = ghab.makeOwnInception() - - serder = coring.Serder(raw=evt) - - # Create a notification EXN message to send to the other agents - exn, ims = grouping.multisigInceptExn(mhab, aids=ghab.smids, ked=serder.ked) - - others = list(oset(ghab.smids + (ghab.rmids or []))) - #others = list(ghab.smids) - others.remove(mhab.pre) - - for recpt in others: # this goes to other participants only as a signalling mechanism - self.postman.send(src=mhab.pre, dest=recpt, topic="multisig", serder=exn, attachment=ims) - - # signal to the group counselor to start the inception - self.icp(hab=mhab, ghab=ghab, aids=ghab.smids, rmids=ghab.rmids) - - # cue up an event to send notification when complete - self.evts.append(dict(r="/icp/complete", i=serder.pre, s=serder.sn, d=serder.said)) - - rep.status = falcon.HTTP_200 - rep.content_type = "application/json" - rep.data = serder.raw - - - - def on_put(self, req, rep, alias): - """ Multisig PUT endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias (str): human readable name for new multisig identifier from path - - --- - summary: Participate in a multisig group inception - description: Participate in a multisig group rotation - tags: - - Groups - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to create - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - aids: - type: array - items: - type: string - description: List of qb64 AIDs of participants in multisig group - notify: - type: boolean - required: False - description: True means to send mutlsig incept exn message to other participants - toad: - type: integer - description: Witness receipt threshold - wits: - type: array - items: - type: string - description: List of qb64 AIDs of witnesses to be used for the new group identfier - isith: - type: string - description: Signing threshold for the new group identifier - nsith: - type: string - description: Next signing threshold for the new group identifier - estOnly: - type: boolean - required: False - description: True means this identifier will not allow interaction events. - - responses: - 200: - description: Multisig group AID inception initiated. - - """ - body = req.get_media() - hab, ghab = self.initialize(body, rep, alias) - if ghab is None: - return - - if not ghab.accepted: - # Create /multig/incept exn message with icp event and witness oobis as payload events - evt = grouping.getEscrowedEvent(db=self.hby.db, pre=ghab.pre, sn=0) - else: - evt = ghab.makeOwnInception() - - serder = coring.Serder(raw=evt) - - aids = body["aids"] - self.icp(hab=hab, ghab=ghab, aids=aids) - - # Monitor the final creation of this identifier and send out notification - self.evts.append(dict(r="/icp/complete", i=serder.pre, s=serder.sn, d=serder.said)) - - rep.status = falcon.HTTP_200 - rep.content_type = "application/json" - rep.data = serder.raw - - -class MultisigEventEnd(MultisigEndBase): - """ - ReST API for admin of distributed multisig group rotations - - """ - - def __init__(self, hby, counselor, notifier): - - self.hby = hby - self.counselor = counselor - self.postman = forwarding.Postman(hby=self.hby) - doers = [self.postman] - - super(MultisigEventEnd, self).__init__(hby=hby, notifier=notifier, counselor=counselor, doers=doers) - - def initialize(self, body, rep, alias): - if "aids" not in body: - rep.status = falcon.HTTP_400 - rep.text = "Invalid multisig group rotation request, 'aids' is required" - return None - - ghab = self.hby.habByName(alias) - if ghab is None: - rep.status = falcon.HTTP_404 - rep.text = "Invalid multisig group rotation request alias {alias} not found" - return None - - aids = body["aids"] - if ghab.mhab.pre not in aids: - rep.status = falcon.HTTP_400 - rep.text = "Invalid multisig group rotation request, aid list must contain a local identifier" - return None - - return ghab - - def on_post_rot(self, req, rep, alias): - """ Multisig POST endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias (str): path parameter human readable name for identifier to rotate - - --- - summary: Initiate multisig group rotatation - description: Initiate a multisig group rotation with the participants identified by the provided AIDs - tags: - - Groups - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to create - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - aids: - type: array - description: list of particiant identifiers for this rotation - items: - type: string - wits: - type: array - description: list of witness identifiers - items: - type: string - adds: - type: array - description: list of witness identifiers to add - items: - type: string - cuts: - type: array - description: list of witness identifiers to remove - items: - type: string - toad: - type: integer - description: withness threshold - default: 1 - isith: - type: string - description: signing threshold - count: - type: integer - description: count of next key commitment. - data: - type: array - description: list of data objects to anchor to this rotation event - items: - type: object - responses: - 200: - description: Rotation successful with KEL event returned - 400: - description: Error creating rotation event - - """ - body = req.get_media() - - ghab = self.initialize(body, rep, alias) - if ghab is None: - return - - isith = None - if "isith" in body: - isith = body["isith"] - if isinstance(isith, str) and "," in isith: - isith = isith.split(",") - - nsith = None - if "nsith" in body: - nsith = body["nsith"] - if isinstance(nsith, str) and "," in nsith: - nsith = nsith.split(",") - - aids = body["aids"] if "aids" in body else ghab.smids - rmids = body["rmids"] if "rmids" in body else ghab.rmids - toad = body["toad"] if "toad" in body else None - wits = body["wits"] if "wits" in body else [] - adds = body["adds"] if "adds" in body else [] - cuts = body["cuts"] if "cuts" in body else [] - data = body["data"] if "data" in body else None - - if wits: - if cuts or adds: - rep.status = falcon.HTTP_400 - rep.text = "you can only specify wits or cuts and add" - return - - ewits = ghab.kever.wits - - # wits= [a,b,c] wits=[b, z] - cuts = set(ewits) - set(wits) - adds = set(wits) - set(ewits) - - sn = ghab.kever.sn - # begin the rotation process - self.counselor.rotate(ghab=ghab, smids=aids, rmids=rmids, - isith=isith, nsith=nsith, - toad=toad, cuts=list(cuts), adds=list(adds), - data=data) - - # cue up an event to send notification when complete - self.evts.append(dict(r="/rot/complete", i=ghab.pre, s=sn)) - - rep.status = falcon.HTTP_202 - - def on_put_rot(self, req, rep, alias): - """ Multisig PUT endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias (str): human readable name for new multisig identifier from path - - --- - summary: Participate in multisig group rotatation - description: Participate in a multisig group rotation with the participants identified by the provided AIDs - tags: - - Groups - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to create - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - aids: - type: array - description: list of particiant identifiers for this rotation - items: - type: string - wits: - type: array - description: list of witness identifiers - items: - type: string - adds: - type: array - description: list of witness identifiers to add - items: - type: string - cuts: - type: array - description: list of witness identifiers to remove - items: - type: string - toad: - type: integer - description: withness threshold - default: 1 - isith: - type: string - description: signing threshold - count: - type: integer - description: count of next key commitment. - data: - type: array - description: list of data objects to anchor to this rotation event - items: - type: object - responses: - 200: - description: Rotation successful with KEL event returned - 400: - description: Error creating rotation event - - """ - body = req.get_media() - - ghab = self.initialize(body, rep, alias) - if ghab is None: - return - - isith = None - if "isith" in body: - isith = body["isith"] - if isinstance(isith, str) and "," in isith: - isith = isith.split(",") - - nsith = None - if "nsith" in body: - nsith = body["nsith"] - if isinstance(nsith, str) and "," in nsith: - nsith = nsith.split(",") - - aids = body["aids"] if "aids" in body else ghab.smids - rmids = body["rmids"] if "rmids" in body else ghab.rmids - toad = body["toad"] if "toad" in body else None - wits = body["wits"] if "wits" in body else [] - adds = body["adds"] if "adds" in body else [] - cuts = body["cuts"] if "cuts" in body else [] - data = body["data"] if "data" in body else None - - if wits: - if adds or cuts: - rep.status = falcon.HTTP_400 - rep.text = "you can only specify wits or cuts and add" - return - - ewits = ghab.kever.wits - - # wits= [a,b,c] wits=[b, z] - cuts = set(ewits) - set(wits) - adds = set(wits) - set(ewits) - - sn = ghab.kever.sn - self.counselor.rotate(ghab=ghab, smids=aids, rmids=rmids, - isith=isith, nsith=nsith, - toad=toad, cuts=list(cuts), adds=list(adds), - data=data) - - # cue up an event to send notification when complete - self.evts.append(dict(r="/rot/complete", i=ghab.pre, s=sn)) - - rep.status = falcon.HTTP_202 - - def on_post_ixn(self, req, rep, alias): - """ Multisig Interaction POST endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias (str): human readable name for new multisig identifier from path - - --- - summary: Initiate multisig group interaction event - description: Initiate a multisig group interaction event - tags: - - Groups - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to create - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - aids: - type: array - description: list of particiant identifiers for this rotation - items: - type: string - data: - type: array - description: list of data objects to anchor to this rotation event - items: - type: object - responses: - 200: - description: Interaction successful with KEL event returned - 400: - description: Error creating rotation event - """ - body = req.get_media() - - ghab = self.initialize(body, rep, alias) - if ghab is None: - return - - aids = body["aids"] if "aids" in body else ghab.smids - rmids = body["rmids"] if "rmids" in body else ghab.rmids - data = body["data"] if "data" in body else None - - serder = self.ixn(ghab=ghab, data=data, aids=aids) - - exn, atc = grouping.multisigInteractExn(ghab, serder.sner.num, aids, data) - - others = list(oset(ghab.smids + (ghab.rmids or []))) - #others = list(ghab.smids) - others.remove(ghab.mhab.pre) - - for recpt in others: # send notification to other participants as a signalling mechanism - self.postman.send(src=ghab.mhab.pre, dest=recpt, topic="multisig", serder=exn, attachment=atc) - - # cue up an event to send notification when complete - self.evts.append(dict(r="/ixn/complete", i=serder.pre, s=serder.sn, d=serder.said)) - - rep.status = falcon.HTTP_202 - - def on_put_ixn(self, req, rep, alias): - """ Multisig Interaction PUT endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias (str): human readable name for new multisig identifier from path - - --- - summary: Participate in multisig group interaction event - description: Participate in a multisig group interaction event - tags: - - Groups - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to create - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - aids: - type: array - description: list of particiant identifiers for this rotation - items: - type: string - data: - type: array - description: list of data objects to anchor to this rotation event - items: - type: object - responses: - 200: - description: Interaction successful with KEL event returned - 400: - description: Error creating rotation event - """ - body = req.get_media() - - ghab = self.initialize(body, rep, alias) - if ghab is None: - return - - aids = body["aids"] if "aids" in body else ghab.smids - rmids = body["rmids"] if "rmids" in body else ghab.rmids - - data = body["data"] if "data" in body else None - - serder = self.ixn(ghab=ghab, data=data, aids=aids) - # cue up an event to send notification when complete - self.evts.append(dict(r="/ixn/complete", i=serder.pre, s=serder.sn, d=serder.said)) - - rep.status = falcon.HTTP_202 - - - def ixn(self, ghab, data, aids, rmids=None): - """Todo Document this method - - Parameters - """ - ixn = ghab.interact(data=data) - - serder = coring.Serder(raw=ixn) - - prefixer = coring.Prefixer(qb64=ghab.pre) - seqner = coring.Seqner(sn=serder.sn) - saider = coring.Saider(qb64b=serder.saidb) - self.counselor.start(prefixer=prefixer, seqner=seqner, saider=saider, - mid=ghab.mhab.pre, smids=aids, rmids=rmids) - return serder - - -class ChallengeEnd(doing.DoDoer): - """ Resource for Challenge/Response Endpoints """ - - def __init__(self, hby): - """ Initialize Challenge/Response Endpoint - - Parameters: - hby (Habery): database and keystore environment - - """ - self.hby = hby - self.postman = forwarding.Postman(hby=self.hby) - - super(ChallengeEnd, self).__init__(doers=[self.postman]) - - @staticmethod - def on_get(req, rep): - """ Challenge GET endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - - --- - summary: Get list of agent identfiers - description: Get the list of identfiers associated with this agent - tags: - - Challenge/Response - parameters: - - in: query - name: strength - schema: - type: int - description: cryptographic strength of word list - required: false - responses: - 200: - description: An array of Identifier key state information - content: - application/json: - schema: - description: Randon word list - type: object - properties: - words: - type: array - description: random challange word list - items: - type: string - - """ - mnem = mnemonic.Mnemonic(language='english') - s = req.params.get("strength") - strength = int(s) if s is not None else 128 - - words = mnem.generate(strength=strength) - rep.status = falcon.HTTP_200 - rep.content_type = "application/json" - msg = dict(words=words.split(" ")) - rep.data = json.dumps(msg).encode("utf-8") - - def on_post_resolve(self, req, rep, alias): - """ Challenge POST endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias: human readable name of identifier to use to sign the challange/response - - --- - summary: Sign challange message and forward to peer identfiier - description: Sign a challenge word list received out of bands and send `exn` peer to peer message - to recipient - tags: - - Challenge/Response - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to create - requestBody: - required: true - content: - application/json: - schema: - description: Challenge response - properties: - recipient: - type: string - description: human readable alias recipient identifier to send signed challenge to - words: - type: array - description: challenge in form of word list - items: - type: string - responses: - 202: - description: Success submission of signed challenge/response - """ - hab = self.hby.habByName(alias) - if hab is None: - rep.status = falcon.HTTP_400 - rep.text = f"no matching Hab for alias {alias}" - return - - body = req.get_media() - if "words" not in body or "recipient" not in body: - rep.status = falcon.HTTP_400 - rep.text = "challenge response requires 'words' and 'recipient'" - return - - words = body["words"] - recpt = body["recipient"] - payload = dict(i=hab.pre, words=words) - exn = exchanging.exchange(route="/challenge/response", payload=payload) - ims = hab.endorse(serder=exn, last=True, pipelined=False) - del ims[:exn.size] - - senderHab = hab.mhab if hab.group else hab - self.postman.send(src=senderHab.pre, dest=recpt, topic="challenge", serder=exn, attachment=ims) - - rep.status = falcon.HTTP_202 - - def on_post_accept(self, req, rep, alias): - """ Challenge POST accept endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias: human readable name of identifier to use to sign the challange/response - - --- - summary: Sign challange message and forward to peer identfiier - description: Sign a challenge word list received out of bands and send `exn` peer to peer message - to recipient - tags: - - Challenge/Response - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for the identifier to create - requestBody: - required: true - content: - application/json: - schema: - description: Challenge response - properties: - aid: - type: string - description: aid of signer of accepted challenge response - said: - type: array - description: challenge in form of word list - items: - type: string - responses: - 202: - description: Success submission of signed challenge/response - """ - hab = self.hby.habByName(alias) - if hab is None: - rep.status = falcon.HTTP_400 - rep.text = f"no matching Hab for alias {alias}" - return - - body = req.get_media() - if "aid" not in body or "said" not in body: - rep.status = falcon.HTTP_400 - rep.text = "challenge response acceptance requires 'aid' and 'said'" - return - - aid = body["aid"] - said = body["said"] - saider = coring.Saider(qb64=said) - self.hby.db.chas.add(keys=(aid,), val=saider) - - rep.status = falcon.HTTP_202 - - -class NotificationEnd: - def __init__(self, notifier): - """ - REST APIs for Notifications - - Args: - notifier (Notifier): notifier database containing notifications for the controller of the agent - - """ - self.notifier = notifier - - def on_get(self, req, rep): - """ Notification GET endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - --- - summary: Get list of notifcations for the controller of the agent - description: Get list of notifcations for the controller of the agent. Notifications will - be sorted by creation date/time - parameters: - - in: query - name: last - schema: - type: string - required: false - description: qb64 SAID of last notification seen - - in: query - name: limit - schema: - type: integer - required: false - description: size of the result list. Defaults to 25 - tags: - - Notifications - - responses: - 200: - description: List of contact information for remote identifiers - """ - last = req.params.get("last") - limit = req.params.get("limit") - - limit = int(limit) if limit is not None else 25 - - if last is not None: - lastNote = self.notifier.get(last) - if lastNote is not None: - start = lastNote.datetime - else: - start = "" - else: - start = "" - - notes = self.notifier.getNotes(start=start, limit=limit) - out = [note.pad for note in notes] - - rep.status = falcon.HTTP_200 - rep.data = json.dumps(out).encode("utf-8") - - def on_put_said(self, _, rep, said): - """ Notification PUT endpoint - - Parameters: - _: falcon.Request HTTP request - rep: falcon.Response HTTP response - said: qb64 SAID of notification to mark as read - - --- - summary: Mark notification as read - description: Mark notification as read - tags: - - Notifications - parameters: - - in: path - name: said - schema: - type: string - required: true - description: qb64 said of note to mark as read - responses: - 202: - description: Notification successfully marked as read for prefix - 404: - description: No notification information found for SAID - """ - mared = self.notifier.mar(said) - if not mared: - rep.status = falcon.HTTP_404 - rep.data = json.dumps(dict(msg=f"no notification to mark as read for {said}")).encode("utf-8") - return - - rep.status = falcon.HTTP_202 - - def on_delete_said(self, _, rep, said): - """ Notification DELETE endpoint - - Parameters: - _: falcon.Request HTTP request - rep: falcon.Response HTTP response - said: qb64 SAID of notification to delete - - --- - summary: Delete notification - description: Delete notification - tags: - - Notifications - parameters: - - in: path - name: said - schema: - type: string - required: true - description: qb64 said of note to delete - responses: - 202: - description: Notification successfully deleted for prefix - 404: - description: No notification information found for prefix - """ - deleted = self.notifier.noter.rem(said) - if not deleted: - rep.status = falcon.HTTP_404 - rep.text = f"no notification to delete for {said}" - return - - rep.status = falcon.HTTP_202 - - -class ContactEnd: - - def __init__(self, hby, org): - """ - - Parameters: - hby (Habery): identifier environment database - org (Organizer): contact database - """ - - self.hby = hby - self.org = org - - def on_get_list(self, req, rep): - """ Contact plural GET endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - --- - summary: Get list of contact information associated with remote identfiers - description: Get list of contact information associated with remote identfiers. All - information is metadata and kept in local storage only - tags: - - Contacts - parameters: - - in: query - name: group - schema: - type: string - required: false - description: field name to group results by - - in: query - name: filter_field - schema: - type: string - description: field name to search - required: false - - in: query - name: filter_value - schema: - type: string - description: value to search for - required: false - responses: - 200: - description: List of contact information for remote identifiers - """ - # TODO: Add support for sorting - - group = req.params.get("group") - field = req.params.get("filter_field") - val = req.params.get("filter_value") - - if group is not None: - data = dict() - values = self.org.values(group, val) - for value in values: - contacts = self.org.find(group, value) - self.authn(contacts) - data[value] = contacts - - rep.status = falcon.HTTP_200 - rep.data = json.dumps(data).encode("utf-8") - - elif field is not None: - val = req.params.get("filter_value") - if val is None: - rep.status = falcon.HTTP_400 - rep.text = "filter_value if required if field_field is specified" - return - - contacts = self.org.find(field=field, val=val) - self.authn(contacts) - rep.status = falcon.HTTP_200 - rep.data = json.dumps(contacts).encode("utf-8") - - else: - data = [] - contacts = self.org.list() - - for contact in contacts: - aid = contact["id"] - if aid in self.hby.kevers and aid not in self.hby.prefixes: - data.append(contact) - - self.authn(data) - rep.status = falcon.HTTP_200 - rep.data = json.dumps(data).encode("utf-8") - - def authn(self, contacts): - for contact in contacts: - aid = contact['id'] - accepted = [saider.qb64 for saider in self.hby.db.chas.get(keys=(aid,))] - received = [saider.qb64 for saider in self.hby.db.reps.get(keys=(aid,))] - - challenges = [] - for said in received: - exn = self.hby.db.exns.get(keys=(said,)) - challenges.append(dict(dt=exn.ked['dt'], words=exn.ked['a']['words'], said=said, - authenticated=said in accepted)) - - contact["challenges"] = challenges - - wellKnowns = [] - wkans = self.hby.db.wkas.get(keys=(aid,)) - for wkan in wkans: - wellKnowns.append(dict(url=wkan.url, dt=wkan.dt)) - - contact["wellKnowns"] = wellKnowns - - def on_post(self, req, rep, prefix): - """ Contact plural GET endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - prefix: human readable name of identifier to replace contact information - - --- - summary: Create new contact information for an identifier - description: Creates new information for an identifier, overwriting all existing - information for that identifier - tags: - - Contacts - parameters: - - in: path - name: prefix - schema: - type: string - required: true - description: qb64 identifier prefix to add contact metadata to - requestBody: - required: true - content: - application/json: - schema: - description: Contact information - type: object - - responses: - 200: - description: Updated contact information for remote identifier - 400: - description: Invalid identfier used to update contact information - 404: - description: Prefix not found in identifier contact information - """ - body = req.get_media() - if prefix not in self.hby.kevers: - rep.status = falcon.HTTP_404 - rep.text = f"{prefix} is not a known identifier. oobi required before contact information" - return - - if prefix in self.hby.prefixes: - rep.status = falcon.HTTP_400 - rep.text = f"{prefix} is a local identifier, contact information only for remote identifiers" - return - - if "id" in body: - del body["id"] - - self.org.replace(prefix, body) - contact = self.org.get(prefix) - - rep.status = falcon.HTTP_200 - rep.data = json.dumps(contact).encode("utf-8") - - def on_post_img(self, req, rep, prefix): - """ - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - prefix: qb64 identifier prefix of contact to associate with image - - --- - summary: Uploads an image to associate with identfier. - description: Uploads an image to associate with identfier. - tags: - - Contacts - parameters: - - in: path - name: prefix - schema: - type: string - description: identifier prefix to associate image to - requestBody: - required: true - content: - image/jpg: - schema: - type: string - format: binary - image/png: - schema: - type: string - format: binary - responses: - 200: - description: Image successfully uploaded - - """ - if prefix not in self.hby.kevers: - rep.status = falcon.HTTP_404 - rep.text = f"{prefix} is not a known identifier." - return - - if req.content_length > 1000000: - rep.status = falcon.HTTP_400 - rep.text = "image too big to save" - return - - self.org.setImg(pre=prefix, typ=req.content_type, stream=req.bounded_stream) - rep.status = falcon.HTTP_202 - - def on_get_img(self, _, rep, prefix): - """ Contact image GET endpoint - - Parameters: - _: falcon.Request HTTP request - rep: falcon.Response HTTP response - prefix: qb64 identifier prefix of contact information to get - - --- - summary: Get contact image for identifer prefix - description: Get contact image for identifer prefix - tags: - - Contacts - parameters: - - in: path - name: prefix - schema: - type: string - required: true - description: qb64 identifier prefix of contact image to get - responses: - 200: - description: Contact information successfully retrieved for prefix - content: - image/jpg: - schema: - description: Image - type: binary - 404: - description: No contact information found for prefix - """ - if prefix not in self.hby.kevers: - rep.status = falcon.HTTP_404 - rep.text = f"{prefix} is not a known identifier." - return - - data = self.org.getImgData(pre=prefix) - if data is None: - rep.status = falcon.HTTP_404 - rep.text = f"no image available for {prefix}." - return - - rep.status = falcon.HTTP_200 - rep.set_header('Content-Type', data["type"]) - rep.set_header('Content-Length', data["length"]) - rep.stream = self.org.getImg(pre=prefix) - - def on_get(self, _, rep, prefix): - """ Contact GET endpoint - - Parameters: - _: falcon.Request HTTP request - rep: falcon.Response HTTP response - prefix: qb64 identifier prefix of contact information to get - - --- - summary: Get contact information associated with single remote identfier - description: Get contact information associated with single remote identfier. All - information is meta-data and kept in local storage only - tags: - - Contacts - parameters: - - in: path - name: prefix - schema: - type: string - required: true - description: qb64 identifier prefix of contact to get - responses: - 200: - description: Contact information successfully retrieved for prefix - 404: - description: No contact information found for prefix - """ - if prefix not in self.hby.kevers: - rep.status = falcon.HTTP_404 - rep.text = f"{prefix} is not a known identifier." - return - - contact = self.org.get(prefix) - if contact is None: - rep.status = falcon.HTTP_404 - rep.text = "NOT FOUND" - return - - rep.status = falcon.HTTP_200 - rep.data = json.dumps(contact).encode("utf-8") - - def on_put(self, req, rep, prefix): - """ Contact PUT endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - prefix: qb64 identifier to update contact information - - --- - summary: Update provided fields in contact information associated with remote identfier prefix - description: Update provided fields in contact information associated with remote identfier prefix. All - information is metadata and kept in local storage only - tags: - - Contacts - parameters: - - in: path - name: prefix - schema: - type: string - required: true - description: qb64 identifier prefix to add contact metadata to - requestBody: - required: true - content: - application/json: - schema: - description: Contact information - type: object - - responses: - 200: - description: Updated contact information for remote identifier - 400: - description: Invalid identfier used to update contact information - 404: - description: Prefix not found in identifier contact information - """ - body = req.get_media() - if prefix not in self.hby.kevers: - rep.status = falcon.HTTP_404 - rep.text = f"{prefix} is not a known identifier. oobi required before contact information" - return - - if prefix in self.hby.prefixes: - rep.status = falcon.HTTP_400 - rep.text = f"{prefix} is a local identifier, contact information only for remote identifiers" - return - - if "id" in body: - del body["id"] - - self.org.update(prefix, body) - contact = self.org.get(prefix) - - rep.status = falcon.HTTP_200 - rep.data = json.dumps(contact).encode("utf-8") - - def on_delete(self, _, rep, prefix): - """ Contact plural GET endpoint - - Parameters: - _: falcon.Request HTTP request - rep: falcon.Response HTTP response - prefix: qb64 identifier prefix to delete contact information - - --- - summary: Delete contact information associated with remote identfier - description: Delete contact information associated with remote identfier - tags: - - Contacts - parameters: - - in: path - name: prefix - schema: - type: string - required: true - description: qb64 identifier prefix of contact to delete - responses: - 202: - description: Contact information successfully deleted for prefix - 404: - description: No contact information found for prefix - """ - deleted = self.org.rem(prefix) - if not deleted: - rep.status = falcon.HTTP_404 - rep.text = f"no contact information to delete for {prefix}" - return - - rep.status = falcon.HTTP_202 - - -class SchemaEnd: - - def __init__(self, db): - self.db = db - - def on_get(self, _, rep, said): - """ Schema GET endpoint - - Parameters: - _: falcon.Request HTTP request - rep: falcon.Response HTTP response - said: qb64 self-addressing identifier of schema to load - - --- - summary: Get schema JSON of specified schema - description: Get schema JSON of specified schema - tags: - - Schema - parameters: - - in: path - name: said - schema: - type: string - required: true - description: qb64 self-addressing identifier of schema to get - responses: - 200: - description: Schema JSON successfully returned - 404: - description: No schema found for SAID - """ - schemer = self.db.schema.get(keys=(said,)) - if schemer is None: - rep.status = falcon.HTTP_404 - rep.text = "Schema not found" - return - - data = schemer.sed - rep.status = falcon.HTTP_200 - rep.data = json.dumps(data).encode("utf-8") - - def on_get_list(self, _, rep): - """ Schema GET plural endpoint - - Parameters: - _: falcon.Request HTTP request - rep: falcon.Response HTTP response - - --- - summary: Get schema JSON of all schema - description: Get schema JSON of all schema - tags: - - Schema - responses: - 200: - description: Array of all schema JSON - """ - data = [] - for said, schemer in self.db.schema.getItemIter(): - data.append(schemer.sed) - - rep.status = falcon.HTTP_200 - rep.data = json.dumps(data).encode("utf-8") - - -class EscrowEnd: - - def __init__(self, db): - """ Create endpoint for retrieving escrow status - - Parameters: - db (Baser): escrow database - - """ - self.db = db - - def on_get(self, req, rep): - """ - - Parameters: - req (Request): falcon.Request HTTP request - rep (Response): falcon.Response HTTP response - - --- - summary: Display escrow status for entire database or search for single identifier in escrows - description: Display escrow status for entire database or search for single identifier in escrows - tags: - - Escrows - parameters: - - in: query - name: pre - schema: - type: string - required: false - description: qb64 identifier prefix to search for in escrows - - in: query - name: escrow - schema: - type: string - required: false - description: name of escrow to load, ignoring others - responses: - 200: - description: Escrow information - 404: - description: Prefix not found in any escrow - - - """ - rpre = req.params.get("pre") - if rpre is not None: - rpre = rpre.encode("utf-8") - escrow = req.params.get("escrow") - - escrows = dict() - - if (not escrow) or escrow == "out-of-order-events": - oots = list() - key = ekey = b'' # both start same. when not same means escrows found - while True: - for ekey, edig in self.db.getOoeItemsNextIter(key=key): - pre, sn = dbing.splitKeySN(ekey) # get pre and sn from escrow item - if rpre and pre != rpre: - continue - - try: - oots.append(eventing.loadEvent(self.db, pre, edig)) - except ValueError as e: - rep.status = falcon.HTTP_400 - rep.text = e.args[0] - return - - if ekey == key: # still same so no escrows found on last while iteration - break - key = ekey # setup next while iteration, with key after ekey - - escrows["out-of-order-events"] = oots - - if (not escrow) or escrow == "partially-witnessed-events": - pwes = list() - key = ekey = b'' # both start same. when not same means escrows found - while True: # break when done - for ekey, edig in self.db.getPweItemsNextIter(key=key): - pre, sn = dbing.splitKeySN(ekey) # get pre and sn from escrow item - if rpre and pre != rpre: - continue - - try: - pwes.append(eventing.loadEvent(self.db, pre, edig)) - except ValueError as e: - rep.status = falcon.HTTP_400 - rep.text = e.args[0] - return - - if ekey == key: # still same so no escrows found on last while iteration - break - key = ekey # setup next while iteration, with key after ekey - - escrows["partially-witnessed-events"] = pwes - - if (not escrow) or escrow == "partially-signed-events": - pses = list() - key = ekey = b'' # both start same. when not same means escrows found - while True: # break when done - for ekey, edig in self.db.getPseItemsNextIter(key=key): - pre, sn = dbing.splitKeySN(ekey) # get pre and sn from escrow item - if rpre and pre != rpre: - continue - - try: - pses.append(eventing.loadEvent(self.db, pre, edig)) - except ValueError as e: - rep.status = falcon.HTTP_400 - rep.text = e.args[0] - return - - if ekey == key: # still same so no escrows found on last while iteration - break - key = ekey # setup next while iteration, with key after ekey - - escrows["partially-signed-events"] = pses - - if (not escrow) or escrow == "likely-duplicitous-events": - ldes = list() - key = ekey = b'' # both start same. when not same means escrows found - while True: # break when done - for ekey, edig in self.db.getLdeItemsNextIter(key=key): - pre, sn = dbing.splitKeySN(ekey) # get pre and sn from escrow item - if rpre and pre != rpre: - continue - - try: - ldes.append(eventing.loadEvent(self.db, pre, edig)) - except ValueError as e: - rep.status = falcon.HTTP_400 - rep.text = e.args[0] - return - - if ekey == key: # still same so no escrows found on last while iteration - break - key = ekey # setup next while iteration, with key after ekey - - escrows["likely-duplicitous-events"] = ldes - - rep.status = falcon.HTTP_200 - rep.content_type = "application/json" - rep.data = json.dumps(escrows, indent=2).encode("utf-8") - - def on_get_partial(self, req, rep, pre, dig): - """ - - Parameters: - req (Request): falcon.Request HTTP request - rep (Response): falcon.Response HTTP response - pre (str): qb64 identifier prefix of event to load - dig (str) qb64 SAID of the event to load - - --- - summary: Display escrow status for entire database or search for single identifier in escrows - description: Display escrow status for entire database or search for single identifier in escrows - tags: - - Escrows - parameters: - - in: path - name: pre - schema: - type: string - required: true - description: qb64 identifier prefix of event to load - - in: path - name: dig - schema: - type: string - required: true - description: qb64 SAID of the event to load - responses: - 200: - description: Event information - 404: - description: Event match pre and dig not found - - - """ - try: - event = eventing.loadEvent(self.db, pre, dig) - except ValueError: - rep.status = falcon.HTTP_404 - rep.text = "Event not found" - return - - rep.status = falcon.HTTP_200 - rep.content_type = "application/json" - rep.data = json.dumps(event, indent=2).encode("utf-8") - - -class AeidEnd: - - def __init__(self, hby): - """ Initialize endpoint for updating the passcode for this Habery - - Parameters: - hby (Habery): identifier environment database - """ - - self.hby = hby - - @staticmethod - def on_get(req, rep): - """ GET endpoint for passcode resource - - Args: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - - --- - summary: Generate random 22 digit passcode for use in securing and encrypting keystore - description: Generate random 22 digit passcode for use in securing and encrypting keystore - tags: - - Passcode - responses: - 200: - description: Randomly generated 22 character passcode formatted as xxxx-xxxxx-xxxx-xxxxx-xxxx - - """ - return booting.PasscodeEnd.on_get(req, rep) - - def on_post(self, req, rep): - """ AEID POST endpoint - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - - --- - summary: Create new contact information for an identifier - description: Creates new information for an identifier, overwriting all existing - information for that identifier - tags: - - Passcode - parameters: - - in: path - name: prefix - schema: - type: string - required: true - description: qb64 identifier prefix to add contact metadata to - requestBody: - required: true - content: - application/json: - schema: - description: Contact information - type: object - - responses: - 202: - description: AEID successfully updated - 400: - description: Invalid new passcode - 401: - description: Original passcode incorrect - """ - body = req.get_media() - if "current" in body: - cbran = body["current"] - cbran = cbran.replace("-", "") - else: - rep.status = falcon.HTTP_400 - rep.data = json.dumps(dict(msg="Current passcode missing from body")).encode("utf-8") - return - - cbran = coring.MtrDex.Salt_128 + 'A' + cbran[:21] # qb64 salt for seed - csigner = coring.Salter(qb64=cbran).signer(transferable=False, - temp=self.hby.temp, tier=None) - if not self.hby.mgr.encrypter.verifySeed(csigner.qb64): - rep.status = falcon.HTTP_401 - rep.data = json.dumps(dict(msg="Incorrect current passcode")).encode("utf-8") - return - - if "passcode" in body: - bran = body["passcode"] - bran = bran.replace("-", "") - else: - rep.status = falcon.HTTP_400 - rep.data = json.dumps(dict(msg="Passcode missing from body")).encode("utf-8") - return - - if len(bran) < 21: - rep.status = falcon.HTTP_400 - rep.data = json.dumps(dict(msg="Invalid passcode, too short")).encode("utf-8") - return - - bran = coring.MtrDex.Salt_128 + 'A' + bran[:21] # qb64 salt for seed - signer = coring.Salter(qb64=bran).signer(transferable=False, - temp=self.hby.temp) - seed = signer.qb64 - aeid = signer.verfer.qb64 - - self.hby.mgr.updateAeid(aeid, seed) - - rep.status = falcon.HTTP_202 - - -def loadEnds(app, *, - path, - hby, - rgy, - verifier, - counselor, - signaler, - notifier, - registrar, - credentialer, - servery, - bootConfig): - """ - Load endpoints for KIWI admin interface into the provided Falcon app - - Parameters: - app (falcon.App): falcon.App to register handlers with: - path (str): directory location of UI web app files to be served with this API server - hby (Habery): database environment for all endpoints - rgy (Regery): database environment for credentials - rep (Respondant): that routes responses to the appropriate mailboxes - verifier (Verifier): that process credentials - registrar (Registrar): credential registry protocol manager - counselor (Counselor): group multisig identifier communication manager - signaler (Signaler): generator of transient signals to controller of agent - notifier (Notifier): generator of messages for review by controller of agent - credentialer (Credentialer): credential issuance protocol manager - servery (Servery): - bootConfig: (dict): original launch configuration of Servery - - Returns: - list: doers from registering endpoints - - """ - sink = http.serving.StaticSink(staticDirPath=path) - app.add_sink(sink, prefix=sink.DefaultStaticSinkBasePath) - - swagsink = http.serving.StaticSink(staticDirPath="./static") - app.add_sink(swagsink, prefix="/swaggerui") - - lockEnd = LockEnd(servery=servery, bootConfig=bootConfig) - app.add_route("/lock", lockEnd) - - identifierEnd = IdentifierEnd(hby=hby) - app.add_route("/ids", identifierEnd) - app.add_route("/ids/{alias}", identifierEnd, suffix="alias") - app.add_route("/ids/{alias}/metadata", identifierEnd, suffix="metadata") - app.add_route("/ids/{alias}/rot", identifierEnd, suffix="rot") - app.add_route("/ids/{alias}/ixn", identifierEnd, suffix="ixn") - - keyEnd = KeyStateEnd(hby=hby, counselor=counselor) - app.add_route("/keystate/{prefix}", keyEnd) - app.add_route("/keystate/pubkey/{pubkey}", keyEnd, suffix="pubkey") - - registryEnd = RegistryEnd(hby=hby, rgy=rgy, registrar=registrar) - app.add_route("/registries", registryEnd) - - multiIcpEnd = MultisigInceptEnd(hby=hby, counselor=counselor, notifier=notifier) - app.add_route("/groups/{alias}/icp", multiIcpEnd) - multiEvtEnd = MultisigEventEnd(hby=hby, counselor=counselor, notifier=notifier) - app.add_route("/groups/{alias}/rot", multiEvtEnd, suffix="rot") - app.add_route("/groups/{alias}/ixn", multiEvtEnd, suffix="ixn") - - credsEnd = CredentialEnd(hby=hby, rgy=rgy, verifier=verifier, registrar=registrar, credentialer=credentialer, - notifier=notifier) - app.add_route("/credentials/{alias}", credsEnd) - app.add_route("/credentials/{alias}/{said}", credsEnd, suffix="export") - app.add_route("/groups/{alias}/credentials", credsEnd, suffix="iss") - app.add_route("/groups/{alias}/credentials/{said}/rev", credsEnd, suffix="rev") - - presentationEnd = PresentationEnd(hby=hby, reger=rgy.reger) - app.add_route("/credentials/{alias}/presentations", presentationEnd, suffix="present") - app.add_route("/credentials/{alias}/requests", presentationEnd, suffix="request") - - oobiEnd = oobiing.OobiResource(hby=hby) - app.add_route("/oobi/{alias}", oobiEnd, suffix="alias") - app.add_route("/oobi", oobiEnd) - app.add_route("/oobi/groups/{alias}/share", oobiEnd, suffix="share") - - chacha = ChallengeEnd(hby=hby) - app.add_route("/challenge", chacha) - app.add_route("/challenge/{alias}", chacha, suffix="resolve") - app.add_route("/challenge/accept/{alias}", chacha, suffix="accept") - - org = connecting.Organizer(hby=hby) - contact = ContactEnd(hby=hby, org=org) - - app.add_route("/contacts/{prefix}/img", contact, suffix="img") - app.add_route("/contacts/{prefix}", contact) - app.add_route("/contacts", contact, suffix="list") - - notes = NotificationEnd(notifier=notifier) - app.add_route("/notifications", notes) - app.add_route("/notifications/{said}", notes, suffix="said") - - schemaEnd = SchemaEnd(db=hby.db) - app.add_route("/schema", schemaEnd, suffix="list") - app.add_route("/schema/{said}", schemaEnd) - - escrowEnd = EscrowEnd(db=hby.db) - app.add_route("/escrows", escrowEnd) - app.add_route("/escrows/{pre}/{dig}", escrowEnd, suffix="partial") - - aeidEnd = AeidEnd(hby=hby) - app.add_route("/codes", aeidEnd) - - signalEnd = signaling.loadEnds(app, signals=signaler.signals) - resources = [identifierEnd, MultisigInceptEnd, registryEnd, oobiEnd, credsEnd, keyEnd, signalEnd, - presentationEnd, multiIcpEnd, multiEvtEnd, chacha, contact, escrowEnd, lockEnd, aeidEnd] - - app.add_route("/spec.yaml", specing.SpecResource(app=app, title='KERI Interactive Web Interface API', - resources=resources)) - return [identifierEnd, registryEnd, oobiEnd, multiIcpEnd, multiEvtEnd, credsEnd, presentationEnd, lockEnd, chacha] - - -def setup(hby, rgy, servery, bootConfig, *, controller="", insecure=False, staticPath="", **kwargs): - """ Setup and run a KIWI agent - - Parameters: - hby (Habery): database environment for identifiers - rgy (Regery): database environment for credentials - servery (Servery): HTTP server manager for stopping and restarting HTTP servers - bootConfig (dict): original configuration at launch, used to reset during lock - controller (str): qb64 identifier prefix of the controller of this agent - insecure (bool): allow unsigned HTTP requests to the admin interface (non-production ONLY) - staticPath (str): path to static content for this agent - - Returns: - list: Endpoint Doers to execute in Doist for agent. - - """ - - # setup doers - doers = [habbing.HaberyDoer(habery=hby), credentialing.RegeryDoer(rgy=rgy)] - - signaler = signaling.Signaler() - notifier = notifying.Notifier(hby=hby, signaler=signaler) - verifier = verifying.Verifier(hby=hby, reger=rgy.reger) - wallet = walleting.Wallet(reger=verifier.reger, name=hby.name) - - handlers = [] - - mbx = storing.Mailboxer(name=hby.name) - counselor = grouping.Counselor(hby=hby) - registrar = credentialing.Registrar(hby=hby, rgy=rgy, counselor=counselor) - credentialer = credentialing.Credentialer(hby=hby, rgy=rgy, registrar=registrar, verifier=verifier) - - issueHandler = protocoling.IssueHandler(hby=hby, rgy=rgy, notifier=notifier) - requestHandler = protocoling.PresentationRequestHandler(hby=hby, notifier=notifier) - applyHandler = protocoling.ApplyHandler(hby=hby, rgy=rgy, verifier=verifier, name=hby.name) - proofHandler = protocoling.PresentationProofHandler(notifier=notifier) - - handlers.extend([issueHandler, requestHandler, proofHandler, applyHandler]) - - exchanger = exchanging.Exchanger(db=hby.db, handlers=handlers) - challenging.loadHandlers(db=hby.db, signaler=signaler, exc=exchanger) - grouping.loadHandlers(hby=hby, exc=exchanger, notifier=notifier) - oobiery = keri.app.oobiing.Oobiery(hby=hby) - authn = oobiing.Authenticator(hby=hby) - - delegating.loadHandlers(hby=hby, exc=exchanger, notifier=notifier) - oobiing.loadHandlers(hby=hby, exc=exchanger, notifier=notifier) - - rep = storing.Respondant(hby=hby, mbx=mbx) - cues = decking.Deck() - mbd = indirecting.MailboxDirector(hby=hby, - exc=exchanger, - verifier=verifier, - rep=rep, - topics=["/receipt", "/replay", "/multisig", "/credential", "/delegate", - "/challenge", "/oobi"], - cues=cues) - # configure a kevery - doers.extend([exchanger, mbd, rep]) - - # Load admin interface - app = falcon.App(middleware=falcon.CORSMiddleware( - allow_origins='*', allow_credentials='*', expose_headers=['cesr-attachment', 'cesr-date', 'content-type'])) - if not insecure: - app.add_middleware(httping.SignatureValidationComponent(hby=hby, pre=controller)) - app.req_options.media_handlers.update(media.Handlers()) - app.resp_options.media_handlers.update(media.Handlers()) - - endDoers = loadEnds(app, path=staticPath, hby=hby, rgy=rgy, verifier=verifier, - counselor=counselor, registrar=registrar, credentialer=credentialer, - servery=servery, bootConfig=bootConfig, notifier=notifier, signaler=signaler) - - obi = dict(oobiery=oobiery) - doers.extend([rep, counselor, registrar, credentialer, *oobiery.doers, *authn.doers, doing.doify(oobiCueDo, **obi)]) - doers.extend(endDoers) - servery.msgs.append(dict(app=app, doers=doers)) - - -def oobiCueDo(tymth, tock=0.0, **opts): - """ Process Client responses by parsing the messages and removing the client/doer - - Parameters: - tymth (function): injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock (float): injected initial tock value - - """ - obi = opts["oobiery"] - _ = (yield tock) - - while True: - while obi.cues: - cue = obi.cues.popleft() - kin = cue["kin"] - oobi = cue["oobi"] - if kin in ("resolved",): - print(oobi, "succeeded") - elif kin in ("failed",): - print(oobi, "failed") - - yield 0.25 - yield tock diff --git a/src/keri/app/notifying.py b/src/keri/app/notifying.py index 33a683a8e..37128049d 100644 --- a/src/keri/app/notifying.py +++ b/src/keri/app/notifying.py @@ -26,7 +26,7 @@ def notice(attrs, dt=None, read=False): Notice: Notice instance """ - dt = dt if dt is not None else datetime.datetime.now().isoformat() + dt = dt if dt is not None else helping.nowIso8601() if hasattr(dt, "isoformat"): dt = dt.isoformat() @@ -188,6 +188,15 @@ def getItemIter(self, keys: Union[str, Iterable] = b""): for key, val in self.db.getAllItemIter(db=self.sdb, key=self._tokey(keys), split=False): yield self._tokeys(key), self.klas(raw=bytes(val)) + def cntAll(self): + """ + Return count over the all the items in subdb + + Returns: + count of all items + """ + return self.db.cnt(db=self.sdb) + class Noter(dbing.LMDBer): """ @@ -305,44 +314,42 @@ def rem(self, rid): self.ncigs.rem(keys=(rid,)) return self.notes.rem(keys=(dt, rid)) - def getNoteIter(self, start="", limit=25): + def getNoteCnt(self): """ - Returns iterator of tuples (note, cigar) of notices for controller of agent with attached signatures. + Return count over the all Notes - Parameters: - start (Optiona(str,datetime)): date/time to start iteration - limit (int): number of items to return + Returns: + int: count of all items """ - if hasattr(start, "isoformat"): - start = start.isoformat() + return self.notes.cntAll() - res = 0 - for ((_, _), note) in self.notes.getItemIter(keys=(start,)): - cig = self.ncigs.get(keys=(note.rid,)) - yield note, cig - res += 1 - if res == limit: - break - - def getNotes(self, start="", limit=25): + def getNotes(self, start=0, end=25): """ Returns list of tuples (note, cigar) of notes for controller of agent Parameters: - start (Optiona(str,datetime)): date/time to start iteration - limit (int): number of items to return + start (int): number of item to start + end (int): number of last item to return """ if hasattr(start, "isoformat"): start = start.isoformat() notes = [] + it = self.notes.getItemIter(keys=()) + + # Run off the items before start + for _ in range(start): + try: + next(it) + except StopIteration: + break - for ((_, _), note) in self.notes.getItemIter(keys=(start,)): + for ((_, _), note) in it: cig = self.ncigs.get(keys=(note.rid,)) notes.append((note, cig)) - if len(notes) == limit: + if (not end == -1) and len(notes) == (end - start) + 1: break return notes @@ -462,39 +469,28 @@ def mar(self, rid): return False - def getNoteIter(self, start=None, limit=25): + def getNoteCnt(self): """ - Returns iterator of notices that have verified signatures over the data stored + Return count over the all Notes - Parameters: - start (Optiona(str,datetime)): date/time to start iteration - limit (int): number of items to return + Returns: + int: count of all items """ + return self.noter.getNoteCnt() - for note, cig in self.noter.getNoteIter(start=start, limit=limit): - if not self.hby.signator.verify(ser=note.raw, cigar=cig): - raise kering.ValidationError("note stored without valid signature") - - yield note - - def getNotes(self, start="", limit=25): + def getNotes(self, start=0, end=24): """ - - Returns list of notices that have verified signatures over the data stored + Returns list of tuples (note, cigar) of notes for controller of agent Parameters: - start (Optiona(str,datetime)): date/time to start iteration - limit (int): number of items to return - + start (int): number of item to start + end (int): number of last item to return """ - if hasattr(start, "isoformat"): - start = start.isoformat() - + notesigs = self.noter.getNotes(start, end) notes = [] - - for note, cig in self.noter.getNoteIter(start=start, limit=limit): + for note, cig in notesigs: if not self.hby.signator.verify(ser=note.raw, cigar=cig): raise kering.ValidationError("note stored without valid signature") diff --git a/src/keri/app/oobiing.py b/src/keri/app/oobiing.py index b7788e850..41a5e6c92 100644 --- a/src/keri/app/oobiing.py +++ b/src/keri/app/oobiing.py @@ -16,10 +16,11 @@ from keri.core import coring from . import httping +from .habbing import GroupHab from .. import help from .. import kering from ..app import forwarding, connecting -from ..core import routing, eventing, parsing, scheming +from ..core import routing, eventing, parsing, scheming, serdering from ..db import basing from ..end import ending from ..end.ending import OOBI_RE, DOOBI_RE @@ -35,9 +36,7 @@ def loadEnds(app, *, hby, prefix=""): oobiEnd = OobiResource(hby=hby) app.add_route(prefix + "/oobi", oobiEnd) - app.add_route(prefix + "/oobi/groups/{alias}/share", oobiEnd, suffix="share") - - return [oobiEnd] + return [] def loadHandlers(hby, exc, notifier): @@ -53,7 +52,7 @@ def loadHandlers(hby, exc, notifier): exc.addHandler(oobireq) -class OobiResource(doing.DoDoer): +class OobiResource: """ Resource for managing OOBIs @@ -68,11 +67,6 @@ def __init__(self, hby): """ self.hby = hby - self.postman = forwarding.Postman(hby=self.hby) - doers = [self.postman] - - super(OobiResource, self).__init__(doers=doers) - def on_get_alias(self, req, rep, alias=None): """ OOBI GET endpoint @@ -121,24 +115,27 @@ def on_get_alias(self, req, rep, alias=None): if role in (kering.Roles.witness,): # Fetch URL OOBIs for all witnesses oobis = [] for wit in hab.kever.wits: - urls = hab.fetchUrls(eid=wit, scheme=kering.Schemes.http) + urls = hab.fetchUrls(eid=wit, scheme=kering.Schemes.http) or hab.fetchUrls(eid=wit, scheme=kering.Schemes.https) if not urls: rep.status = falcon.HTTP_404 rep.text = f"unable to query witness {wit}, no http endpoint" return - up = urlparse(urls[kering.Schemes.http]) - oobis.append(f"http://{up.hostname}:{up.port}/oobi/{hab.pre}/witness/{wit}") + url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https] + up = urlparse(url) + oobis.append(f"{up.scheme}://{up.hostname}:{up.port}/oobi/{hab.pre}/witness/{wit}") res["oobis"] = oobis elif role in (kering.Roles.controller,): # Fetch any controller URL OOBIs oobis = [] - urls = hab.fetchUrls(eid=hab.pre, scheme=kering.Schemes.http) + urls = hab.fetchUrls(eid=hab.pre, scheme=kering.Schemes.http) or hab.fetchUrls(eid=hab.pre, + scheme=kering.Schemes.https) if not urls: rep.status = falcon.HTTP_404 rep.text = f"unable to query controller {hab.pre}, no http endpoint" return - up = urlparse(urls[kering.Schemes.http]) - oobis.append(f"http://{up.hostname}:{up.port}/oobi/{hab.pre}/controller") + url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https] + up = urlparse(url) + oobis.append(f"{up.scheme}://{up.hostname}:{up.port}/oobi/{hab.pre}/controller") res["oobis"] = oobis else: rep.status = falcon.HTTP_404 @@ -157,8 +154,8 @@ def on_post(self, req, rep): --- summary: Resolve OOBI and assign an alias for the remote identifier - description: Resolve OOBI URL or `rpy` message by process results of request and assign 'alias' in contact - data for resolved identifier + description: Resolve OOBI URL or `rpy` message by process results of request and assign 'alias' in contact data + for resolved identifier tags: - OOBIs requestBody: @@ -206,133 +203,55 @@ def on_post(self, req, rep): rep.status = falcon.HTTP_202 - def on_post_share(self, req, rep, alias): - """ Share OOBI endpoint. - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias: human readable name of the local identifier context for resolving this OOBI - - --- - summary: Share OOBI and alias for remote identifier with other aids - description: Send all other participants in a group AID a copy of the OOBI with suggested alias - tags: - - OOBIs - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for AID to use to sign exn message - requestBody: - required: true - content: - application/json: - schema: - description: OOBI - properties: - oobis: - type: array - items: - type: string - description: URL OOBI - responses: - 202: - description: OOBI resolution to key state successful - - """ - body = req.get_media() - hab = self.hby.habByName(alias) - if hab is None: - rep.status = falcon.HTTP_404 - rep.text = f"Unknown identifier {alias}" - return - - if not hab.group: - rep.status = falcon.HTTP_400 - rep.text = f"Identifer for {alias} is not a group hab, not supported" - return - oobis = body["oobis"] - smids, rmids = hab.members() - both = list(set(smids + (rmids or []))) - for mid in both: # hab.smids - if mid == hab.mhab.pre: - continue - - for oobi in oobis: - exn, atc = oobiRequestExn(hab.mhab, mid, oobi) - self.postman.send(src=hab.mhab.pre, dest=mid, topic="oobi", serder=exn, attachment=atc) - - rep.status = falcon.HTTP_200 - return - - -class OobiRequestHandler(doing.Doer): +class OobiRequestHandler: """ Handler for oobi notification EXN messages """ resource = "/oobis" - def __init__(self, hby, notifier, **kwa): + def __init__(self, hby, notifier): """ Parameters: - mbx (Mailboxer) of format str names accepted for offers - oobiery (Oobiery) OOBI loader + hby (Habery) database environment of the controller + notifier (Notifier) notifier to convert OOBI request exn messages to controller notifications """ self.hby = hby self.notifier = notifier - self.msgs = decking.Deck() - self.cues = decking.Deck() - super(OobiRequestHandler, self).__init__(**kwa) - - def do(self, tymth, tock=0.0, **opts): - """ - - Handle incoming messages processing new contacts via OOBIs + def handle(self, serder, attachments=None): + """ Do route specific processsing of OOBI request messages Parameters: + serder (Serder): Serder of the exn OOBI request message + attachments (list): list of tuples of pather, CESR SAD path attachments to the exn event """ - self.wind(tymth) - self.tock = tock - yield self.tock - - while True: - while self.msgs: - msg = self.msgs.popleft() - prefixer = msg["pre"] - pay = msg["payload"] - if "oobi" not in pay: - print(f"invalid oobi message, missing oobi. evt=: {msg}") - continue - oobi = pay["oobi"] - - src = prefixer.qb64 - obr = basing.OobiRecord(date=helping.nowIso8601()) - self.hby.db.oobis.pin(keys=(oobi,), val=obr) + src = serder.pre + pay = serder.ked['a'] + if "oobi" not in pay: + print(f"invalid oobi message, missing oobi. evt={serder.ked}") + return + oobi = pay["oobi"] - data = dict( - r="/oobi", - src=src, - oobi=oobi - ) + obr = basing.OobiRecord(date=helping.nowIso8601()) + self.hby.db.oobis.pin(keys=(oobi,), val=obr) - purl = parse.urlparse(oobi) - params = parse.parse_qs(purl.query) - if "name" in params: - data["oobialias"] = params["name"][0] + data = dict( + r="/oobi", + src=src, + oobi=oobi + ) - self.notifier.add(attrs=data) + purl = parse.urlparse(oobi) + params = parse.parse_qs(purl.query) + if "name" in params: + data["oobialias"] = params["name"][0] - yield - yield + self.notifier.add(attrs=data) def oobiRequestExn(hab, dest, oobi): @@ -342,9 +261,9 @@ def oobiRequestExn(hab, dest, oobi): ) # Create `exn` peer to peer message to notify other participants UI - exn = exchanging.exchange(route=OobiRequestHandler.resource, modifiers=dict(), - payload=data) - ims = hab.endorse(serder=exn, last=True, pipelined=False) + exn, _ = exchanging.exchange(route=OobiRequestHandler.resource, modifiers=dict(), + payload=data, sender=hab.pre) + ims = hab.endorse(serder=exn, last=False, pipelined=False) del ims[:exn.size] return exn, ims @@ -461,7 +380,7 @@ def processOobis(self): self.request(url, obr) except ValueError as ex: - print("error requesting invalid OOBI URL {}", url) + print(f"error requesting invalid OOBI URL {ex}", url) def processClients(self): """ Process Client responses by parsing the messages and removing the client/doer @@ -536,7 +455,13 @@ def processClients(self): except (kering.ValidationError, ValueError): pass - serder = eventing.Serder(raw=bytearray(response["body"])) + try: + serder = serdering.SerderKERI(raw=bytearray(response["body"])) + except ValueError: + obr.state = Result.failed + self.hby.db.coobi.rem(keys=(url,)) + self.hby.db.roobi.put(keys=(url,), val=obr) + continue if not serder.ked['t'] == coring.Ilks.rpy: obr.state = Result.failed self.hby.db.coobi.rem(keys=(url,)) @@ -593,6 +518,11 @@ def processRetries(self): def request(self, url, obr): client = self.clienter.request("GET", url=url) + if client is None: + self.hby.db.oobis.rem(keys=(url,)) + print(f"error getting client for {url}, aborting OOBI") + return + self.clients[url] = client self.hby.db.oobis.rem(keys=(url,)) self.hby.db.coobi.pin(keys=(url,), val=obr) diff --git a/src/keri/app/querying.py b/src/keri/app/querying.py new file mode 100644 index 000000000..2dc4ce17d --- /dev/null +++ b/src/keri/app/querying.py @@ -0,0 +1,144 @@ +# -*- encoding: utf-8 -*- +""" +keri.app.storing module + +""" +from dataclasses import asdict + +from hio.base import doing +from keri.app import agenting + + +class QueryDoer(doing.DoDoer): + + def __init__(self, hby, hab, kvy, pre, **kwa): + self.hby = hby + self.hab = hab + self.kvy = kvy + self.pre = pre + + doers = [KeyStateNoticer(hby=hby, hab=self.hab, pre=pre, cues=kvy.cues)] + super(QueryDoer, self).__init__(doers=doers, **kwa) + + +class KeyStateNoticer(doing.DoDoer): + + def __init__(self, hby, hab, pre, cues, **opts): + self.hby = hby + self.hab = hab + self.pre = pre + self.cues = cues + self.witq = agenting.WitnessInquisitor(hby=self.hby) + self.witq.query(src=self.hab.pre, pre=self.pre, r="ksn") + + super(KeyStateNoticer, self).__init__(doers=[self.witq], **opts) + + def recur(self, tyme, deeds=None): + if self.pre in self.hby.kevers: + kever = self.hby.kevers[self.pre] + else: + return super(KeyStateNoticer, self).recur(tyme, deeds) + + if self.cues: + cue = self.cues.pull() + match cue['kin']: + case "keyStateSaved": + kcue = cue + ksn = kcue['ksn'] # key state notice dict + match ksn["i"]: + case self.pre: + if kever.sn < int(ksn["s"], 16): + # Add new doer here instead of cueing to a while loop + self.extend([LogQuerier(hby=self.hby, hab=self.hab, ksn=ksn)]) + self.remove([self.witq]) + + else: + return True + + case _: + self.cues.append(cue) + + case _: + self.cues.append(cue) + + return super(KeyStateNoticer, self).recur(tyme, deeds) + + +class LogQuerier(doing.DoDoer): + + def __init__(self, hby, hab, ksn, **opts): + self.hby = hby + self.hab = hab + self.ksn = ksn + self.witq = agenting.WitnessInquisitor(hby=self.hby) + self.witq.query(src=self.hab.pre, pre=self.ksn["i"]) + super(LogQuerier, self).__init__(doers=[self.witq], **opts) + + def recur(self, tyme, deeds=None): + """ + Returns: doifiable Doist compatible generator method + Usage: + add result of doify on this method to doers list + """ + kever = self.hab.kevers[self.ksn["i"]] + if kever.sn >= int(self.ksn['s'], 16): + self.remove([self.witq]) + return True + + return super(LogQuerier, self).recur(tyme, deeds) + + +class SeqNoQuerier(doing.DoDoer): + + def __init__(self, hby, hab, pre, sn, **opts): + self.hby = hby + self.hab = hab + self.pre = pre + self.sn = sn + self.witq = agenting.WitnessInquisitor(hby=self.hby) + self.witq.query(src=self.hab.pre, pre=self.pre, sn="{:x}".format(self.sn)) + super(SeqNoQuerier, self).__init__(doers=[self.witq], **opts) + + def recur(self, tyme, deeds=None): + """ + Returns: doifiable Doist compatible generator method + Usage: + add result of doify on this method to doers list + """ + if self.pre not in self.hab.kevers: + return False + + kever = self.hab.kevers[self.pre] + if kever.sn >= self.sn: + self.remove([self.witq]) + return True + + return super(SeqNoQuerier, self).recur(tyme, deeds) + + +class AnchorQuerier(doing.DoDoer): + + def __init__(self, hby, hab, pre, anchor, **opts): + self.hby = hby + self.hab = hab + self.pre = pre + self.anchor = anchor + self.witq = agenting.WitnessInquisitor(hby=self.hby) + self.witq.query(src=self.hab.pre, pre=self.pre, anchor=anchor) + super(AnchorQuerier, self).__init__(doers=[self.witq], **opts) + + def recur(self, tyme, deeds=None): + """ + Returns: doifiable Doist compatible generator method + Usage: + add result of doify on this method to doers list + """ + if self.pre not in self.hab.kevers: + return False + + kever = self.hab.kevers[self.pre] + if self.hby.db.findAnchoringSealEvent(self.pre, seal=self.anchor): + self.remove([self.witq]) + return True + + return super(AnchorQuerier, self).recur(tyme, deeds) diff --git a/src/keri/app/signaling.py b/src/keri/app/signaling.py index cc8095386..f32e9dbe2 100644 --- a/src/keri/app/signaling.py +++ b/src/keri/app/signaling.py @@ -225,7 +225,6 @@ def on_post(self, req, rep): rep.set_header('Content-Type', "text/event-stream") rep.status = falcon.HTTP_200 - print(f"get for {self.signals}") rep.stream = SignalIterable(signals=self.signals) def on_get(self, req, rep): diff --git a/src/keri/app/signing.py b/src/keri/app/signing.py index 4f1763e25..f8aed4456 100644 --- a/src/keri/app/signing.py +++ b/src/keri/app/signing.py @@ -4,7 +4,18 @@ keri.app.signing module """ -from keri.core import coring, eventing +from ..app.habbing import GroupHab +from ..core import coring, eventing + + +def serialize(creder, prefixer, seqner, saider): + craw = bytearray(creder.raw) + craw.extend(coring.Counter(coring.CtrDex.SealSourceTriples, count=1).qb64b) + craw.extend(prefixer.qb64b) + craw.extend(seqner.qb64b) + craw.extend(saider.qb64b) + + return bytes(craw) def ratify(hab, serder, paths=None, pipelined=False): @@ -109,7 +120,7 @@ def transSeal(hab): """ # create SealEvent or SealLast for endorser's est evt whose keys are # used to sign - if not hab.group: # not a group use own kever + if not isinstance(hab, GroupHab): # not a group use own kever indices = None # use default order else: # group so use gid kever smids, _ = hab.members() @@ -127,7 +138,7 @@ class SadPathSigGroup: """ Transposable group of signatures Supports transposing groups of signatures from transferable or non-transferable - identfiers + identifiers """ diff --git a/src/keri/app/storing.py b/src/keri/app/storing.py index 4e951e5cf..99b38cf37 100644 --- a/src/keri/app/storing.py +++ b/src/keri/app/storing.py @@ -3,19 +3,16 @@ keri.app.storing module """ -import itertools -import random from hio.base import doing from hio.help import decking from ordered_set import OrderedSet as oset -from . import httping, agenting, forwarding +from . import forwarding from .. import help -from ..core import coring +from ..core import coring, serdering from ..core.coring import MtrDex from ..db import dbing, subing -from ..peer import exchanging logger = help.ogler.getLogger() @@ -143,7 +140,7 @@ class Respondant(doing.DoDoer): """ - def __init__(self, hby, reps=None, cues=None, mbx=None, **kwa): + def __init__(self, hby, reps=None, cues=None, mbx=None, aids=None, **kwa): """ Creates Respondant that uses local environment to find the destination KEL and stores peer to peer messages in mbx, the mailboxer @@ -157,8 +154,9 @@ def __init__(self, hby, reps=None, cues=None, mbx=None, **kwa): self.cues = cues if cues is not None else decking.Deck() self.hby = hby + self.aids = aids self.mbx = mbx if mbx is not None else Mailboxer(name=self.hby.name) - self.postman = forwarding.Postman(hby=self.hby) + self.postman = forwarding.Poster(hby=self.hby, mbx=self.mbx) doers = [self.postman, doing.doify(self.responseDo), doing.doify(self.cueDo)] super(Respondant, self).__init__(doers=doers, **kwa) @@ -191,7 +189,6 @@ def responseDo(self, tymth=None, tock=0.0): if recipient not in self.hby.kevers: logger.error("unable to reply, dest {} not found".format(recipient)) continue - recpkev = self.hby.kevers[recipient] senderHab = self.hby.habs[sender] if senderHab.mhab: @@ -199,44 +196,14 @@ def responseDo(self, tymth=None, tock=0.0): else: forwardHab = senderHab - if len(recpkev.wits) == 0: - msg = senderHab.endorse(exn, last=True) - self.mbx.storeMsg(topic=recipient, msg=msg) - else: - wit = random.choice(recpkev.wits) - client, clientDoer = agenting.httpClient(senderHab, wit) - - self.extend([clientDoer]) - - # sign the exn to get the signature - eattach = senderHab.endorse(exn, last=True, pipelined=False) - # TODO: switch to the following and test that outbound events are persisted: - # eattach = senderHab.exchange(exn, save=True) - del eattach[:exn.size] - - # create and sign the forward exn that will contain the exn - fwd = exchanging.exchange(route='/fwd', - modifiers=dict(pre=recipient, topic=topic), payload=exn.ked) - ims = forwardHab.endorse(serder=fwd, last=True, pipelined=False) - - # Attach pathed exn signature to end of message - atc = bytearray() - pather = coring.Pather(path=["a"]) - atc.extend(pather.qb64b) - atc.extend(eattach) - ims.extend(coring.Counter(code=coring.CtrDex.PathedMaterialQuadlets, - count=(len(atc) // 4)).qb64b) - ims.extend(atc) - - httping.createCESRRequest(ims, client) + # sign the exn to get the signature + eattach = senderHab.endorse(exn, last=False, pipelined=False) + del eattach[:exn.size] + self.postman.send(recipient, topic=topic, serder=exn, hab=forwardHab, attachment=eattach) - while not client.responses: - yield self.tock + yield self.tock # throttle just do one cue at a time - self.remove([clientDoer]) - - yield # throttle just do one cue at a time - yield + yield self.tock def cueDo(self, tymth=None, tock=0.0): """ @@ -253,87 +220,64 @@ def cueDo(self, tymth=None, tock=0.0): while True: while self.cues: # iteratively process each cue in cues - msg = bytearray() - cue = self.cues.popleft() + cue = self.cues.pull() # self.cues.popleft() cueKin = cue["kin"] # type or kind of cue - if cueKin in ("receipt",): # cue to receipt a received event from other pre serder = cue["serder"] # Serder of received event for other pre cuedKed = serder.ked cuedPrefixer = coring.Prefixer(qb64=cuedKed["i"]) + + # If respondant configured with list of acceptable AIDs to witness for, check them here + if self.aids is not None and cuedPrefixer.qb64 not in self.aids: + continue + if cuedPrefixer.qb64 in self.hby.kevers: kever = self.hby.kevers[cuedPrefixer.qb64] owits = oset(kever.wits) if match := owits.intersection(self.hby.prefixes): pre = match.pop() - hab = self.hby.habs[pre] - msg.extend(hab.receipt(serder)) - self.mbx.storeMsg(topic=serder.preb + b'/receipt', msg=msg) + hab = self.hby.habByPre(pre) + if hab is None: + continue + + raw = hab.receipt(serder) + rserder = serdering.SerderKERI(raw=raw) + del raw[:rserder.size] + self.postman.send(serder.pre, topic="receipt", serder=rserder, hab=hab, attachment=raw) elif cueKin in ("replay",): src = cue["src"] dest = cue["dest"] msgs = cue["msgs"] - hab = self.hby.habs[src] - if dest not in self.hby.kevers: + hab = self.hby.habByPre(src) + if hab is None: continue - kever = self.hby.kevers[dest] - owits = oset(kever.wits) - - if owits.intersection(self.hby.prefixes): - bmsgs = bytearray(itertools.chain(*msgs)) - self.mbx.storeMsg(topic=kever.prefixer.qb64b + b'/receipt', msg=bmsgs) - - else: - events = list() - atc = bytearray() - for i, msg in enumerate(msgs): - evt = coring.Serder(raw=msg) - events.append(evt.ked) - pather = coring.Pather(path=["a", i]) - btc = pather.qb64b + msg[evt.size:] - atc.extend(coring.Counter(code=coring.CtrDex.PathedMaterialQuadlets, - count=(len(btc) // 4)).qb64b) - atc.extend(btc) - - fwd = exchanging.exchange(route='/fwd', - modifiers=dict(pre=dest, topic="replay"), payload=events) - msg = hab.endorse(fwd, last=True, pipelined=False) - msg.extend(atc) - wit = random.choice(kever.wits) - client, clientDoer = agenting.httpClient(hab, wit) - self.extend([clientDoer]) - - httping.createCESRRequest(msg, client) - - while not client.responses: - yield self.tock + if dest not in self.hby.kevers: + continue - self.remove([clientDoer]) + for msg in msgs: + raw = bytearray(msg) + serder = serdering.SerderKERI(raw=raw) + del raw[:serder.size] + self.postman.send(dest, topic="replay", serder=serder, hab=hab, attachment=raw) elif cueKin in ("reply",): src = cue["src"] serder = cue["serder"] - route = cue["route"] + dest = cue["dest"] if dest not in self.hby.kevers: continue - kever = self.hby.kevers[dest] - owits = oset(kever.wits) - if match := owits.intersection(self.hby.prefixes): - pre = match.pop() - hab = self.hby.habs[pre] - msg.extend(hab.endorse(serder)) - self.mbx.storeMsg(topic=kever.prefixer.qb64b + b'/receipt', msg=msg) - - else: - hab = self.hby.habs[src] - atc = hab.endorse(serder) - del atc[:serder.size] - self.postman.send(src=src, dest=dest, topic="reply", serder=serder, attachment=atc) + hab = self.hby.habByPre(src) + if hab is None: + continue + + atc = hab.endorse(serder) + del atc[:serder.size] + self.postman.send(hab=hab, dest=dest, topic="reply", serder=serder, attachment=atc) yield self.tock diff --git a/src/keri/app/watching.py b/src/keri/app/watching.py deleted file mode 100644 index 5f29d0936..000000000 --- a/src/keri/app/watching.py +++ /dev/null @@ -1,216 +0,0 @@ -# -*- encoding: utf-8 -*- -""" -KERI -keri.app.agenting module - -""" -import json -from math import ceil -import falcon -from hio.base import doing -from hio.help import decking - -from keri.app import keeping, agenting -from keri.core import coring, eventing, parsing -from .. import help, kering -from ..end import ending - -logger = help.ogler.getLogger() - - -class KiwiServer(doing.DoDoer): - """ - Routes for handling UI requests for Watcher Control - - """ - - def __init__(self, hab, controller, cues=None, app=None, **kwa): - self.hab = hab - self.controller = controller - self.app = app if app is not None else falcon.App(cors_enable=True) - self.cues = cues if cues is not None else decking.Deck() - - self.app.add_route("/rotate", self, suffix="rotate") - - doers = [] - - super(KiwiServer, self).__init__(doers=doers, **kwa) - - def on_post_rotate(self, req, rep): - pre = self.controller - prms = self.hab.ks.prms.get(self.hab.pre) - - aeid = self.hab.mgr.aeid - cur = self.hab.kever - - algo = prms.algo - salt = prms.salt - tier = prms.tier - pidx = prms.pidx - - icount = 1 # inception signing key count - ncount = 0 # next key digest count - code = coring.MtrDex.Ed25519N - - mgr = keeping.Manager(ks=self.hab.ks, aeid=aeid, pidx=pidx, - algo=algo, salt=salt, tier=tier) - - verfers, digers = mgr.incept(icount=icount, - ncount=ncount, - algo=keeping.Algos.randy, - transferable=False, - temp=False) - - isith = f"{max(1, ceil(icount / 2)):x}" - nsith = f"{max(0, ceil(ncount / 2)):x}" - - cst = coring.Tholder(sith=isith).sith # current signing threshold - nst = coring.Tholder(sith=nsith).sith # next signing threshold - - - opre = verfers[0].qb64 # old pre default move below to new pre from incept - - serder = eventing.incept(keys=[verfer.qb64 for verfer in verfers], - isith=cst, - nsith=nst, - ndigs=[diger.qb64 for diger in digers], - toad=cur.toader.num, - wits=cur.wits, - code=code) - - icpMsg = bytearray(serder.raw) - sigers = mgr.sign(ser=serder.raw, verfers=verfers) - icpMsg.extend(coring.Counter(code=coring.CtrDex.ControllerIdxSigs, - count=len(sigers)).qb64b) # attach cnt - for sig in sigers: - icpMsg.extend(sig.qb64b) # attach sig - - sigers = self.hab.sign(ser=bytes(icpMsg), - verfers=self.hab.kever.verfers, - indexed=False) - - signage = ending.Signage(markers=sigers, indexed=False, signer=None, ordinal=None, digest=None, - kind=None) - sheaders = ending.signature([signage]) - - self.hab.recreate(serder, opre, verfers) - - for key, val in sheaders.items(): - rep.append_header(key, val) - - rep.content_type = "application/json+CESR" - rep.content_length = len(icpMsg) - rep.data = icpMsg - rep.status = falcon.HTTP_200 - - -class WatcherClientRotateDoer(doing.DoDoer): - """ - Sends KERI Auth'ed HTTP request to specified watcher to rotate identifer. Current - watcher set is then updated by removing the old watcher idenfitier and adding the - new watcher identifier. - - """ - - def __init__(self, hab, msgs=None, cues=None, **kwa): - """ - Create doer to rotate remote watcher identifier prefix - - Parameters: - name: is str name of Habitat - watcher: - kwa: - """ - self.hab = hab - self.msgs = msgs if msgs is not None else decking.Deck() - self.cues = cues if cues is not None else decking.Deck() - - doers = [doing.doify(self.rotateDo)] - super(WatcherClientRotateDoer, self).__init__(doers=doers, **kwa) - - def rotateDo(self, tymth, tock=0.0, **opts): - """ - Returns: doifiable Doist compatible generator method - Usage: - add result of doify on this method to doers list - """ - # enter context - self.wind(tymth) - self.tock = tock - _ = (yield self.tock) - - while True: - while self.msgs: - watcher = self.msgs.popleft() - habr = self.hab.db.habs.get(self.hab.name) - if watcher not in habr.watchers: - raise kering.ValidationError("identifier {} is not a current watcher {}" - "".format(watcher, habr.watchers)) - - payload = dict(pre=self.hab.pre) - raw = json.dumps(payload) - sigers = self.hab.sign(ser=raw.encode("utf-8"), - verfers=self.hab.kever.verfers, - indexed=True) - - signage = ending.Signage(markers=sigers, indexed=True, signer=None, ordinal=None, digest=None, - kind=None) - headers = ending.signature([signage]) - - client, clientDoer = agenting.httpClient(self.hab, watcher) - self.extend([clientDoer]) - - client.request(method="POST", path="/rotate", headers=headers, body=raw) - while not client.responses: - yield self.tock - - resp = client.respond() - if resp.status != 200: - print("Invalid status from watcher:", type(resp.status)) - return - - if not self.authenticate(watcher=watcher, resp=resp): - print("Invalid response from watcher") - return - - wat = self.processWatcherResponse(watcher=watcher, icp=bytes(resp.body)) - - self.remove([clientDoer]) - - self.cues.append(dict(old=watcher, new=wat)) - - yield self.tock - - yield self.tock - - def processWatcherResponse(self, watcher, icp): - ctrlKvy = eventing.Kevery(db=self.hab.db) - parsing.Parser().parse(ims=bytearray(icp), kvy=ctrlKvy) - - srdr = coring.Serder(raw=bytearray(icp)) - wat = srdr.pre - - habr = self.hab.db.habs.get(self.hab.name) - ewats = set(habr.watchers) - - ewats.remove(watcher) - ewats.add(wat) - - habr.watchers = list(ewats) - - self.hab.db.habs.pin(self.hab.name, habr) - return wat - - @staticmethod - def authenticate(watcher, resp): - if "Signature" not in resp.headers: - return False - - signages = ending.designature(resp.headers["Signature"]) - - cigar = signages[0].markers[watcher] - verfer = coring.Verfer(qb64=watcher) - if not verfer.verify(cigar.raw, bytes(resp.body)): - return False - - return True diff --git a/src/keri/core/coring.py b/src/keri/core/coring.py index cdd704b14..4d58794c3 100644 --- a/src/keri/core/coring.py +++ b/src/keri/core/coring.py @@ -20,128 +20,70 @@ import blake3 import hashlib +from cryptography import exceptions +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat +from cryptography.hazmat.primitives.asymmetric import ec, utils + from ..kering import (EmptyMaterialError, RawMaterialError, InvalidCodeError, InvalidCodeSizeError, InvalidVarIndexError, InvalidVarSizeError, InvalidVarRawSizeError, ConversionError, InvalidValueError, InvalidTypeError, ValidationError, VersionError, DerivationError, EmptyListError, - ShortageError, UnexpectedCodeError, DeserializationError, + ShortageError, UnexpectedCodeError, DeserializeError, UnexpectedCountCodeError, UnexpectedOpCodeError) -from ..kering import Versionage, Version +from ..kering import (Versionage, Version, VERRAWSIZE, VERFMT, VERFULLSIZE, + versify, deversify, Rever) +from ..kering import Serials, Serialage, Protos, Protocolage, Ilkage, Ilks +from ..kering import (ICP_LABELS, DIP_LABELS, ROT_LABELS, DRT_LABELS, IXN_LABELS, + RPY_LABELS) +from ..kering import (VCP_LABELS, VRT_LABELS, ISS_LABELS, BIS_LABELS, REV_LABELS, + BRV_LABELS, TSN_LABELS, CRED_TSN_LABELS) + from ..help import helping from ..help.helping import sceil, nonStringIterable -""" -ilk is short for message type -icp = incept, inception -rot = rotate, rotation -ixn = interact, interaction -dip = delcept, delegated inception -drt = deltate, delegated rotation -rct = receipt -ksn = state, key state notice -qry = query -rpy = reply -exn = exchange -exp = expose, sealed data exposition -vcp = vdr incept, verifiable data registry inception -vrt = vdr rotate, verifiable data registry rotation -iss = vc issue, verifiable credential issuance -rev = vc revoke, verifiable credential revocation -bis = backed vc issue, registry-backed transaction event log credential issuance -brv = backed vc revoke, registry-backed transaction event log credential revocation -""" - -Ilkage = namedtuple("Ilkage", ('icp rot ixn dip drt rct ksn qry rpy exn ' - 'pro bar vcp vrt iss rev bis brv ')) - -Ilks = Ilkage(icp='icp', rot='rot', ixn='ixn', dip='dip', drt='drt', rct='rct', - ksn='ksn', qry='qry', rpy='rpy', exn='exn', pro='pro', bar='bar', - vcp='vcp', vrt='vrt', iss='iss', rev='rev', bis='bis', brv='brv') - -Serialage = namedtuple("Serialage", 'json mgpk cbor') -Serials = Serialage(json='JSON', mgpk='MGPK', cbor='CBOR') +Labels = Ilkage(icp=ICP_LABELS, rot=ROT_LABELS, ixn=IXN_LABELS, dip=DIP_LABELS, + drt=DRT_LABELS, rct=[], qry=[], rpy=RPY_LABELS, + exn=[], pro=[], bar=[], + vcp=VCP_LABELS, vrt=VRT_LABELS, iss=ISS_LABELS, rev=REV_LABELS, + bis=BIS_LABELS, brv=BRV_LABELS) -Identage = namedtuple("Identage", "keri acdc") -Idents = Identage(keri="KERI", acdc="ACDC") - -VERRAWSIZE = 6 # hex characters in raw serialization size in version string -# "{:0{}x}".format(300, 6) # make num char in hex a variable -# '00012c' -VERFMT = "{}{:x}{:x}{}{:0{}x}_" # version format string -VERFULLSIZE = 17 # number of characters in full versions string - - -def versify(ident=Idents.keri, version=None, kind=Serials.json, size=0): - """ - Return version string - """ - if ident not in Idents: - raise ValueError("Invalid message identifier = {}".format(ident)) - if kind not in Serials: - raise ValueError("Invalid serialization kind = {}".format(kind)) - version = version if version else Version - return VERFMT.format(ident, version[0], version[1], kind, size, VERRAWSIZE) +DSS_SIG_MODE = "fips-186-3" +ECDSA_256r1_SEEDBYTES = 32 +ECDSA_256k1_SEEDBYTES = 32 Vstrings = Serialage(json=versify(kind=Serials.json, size=0), mgpk=versify(kind=Serials.mgpk, size=0), cbor=versify(kind=Serials.cbor, size=0)) -VEREX = b'(?P[A-Z]{4})(?P[0-9a-f])(?P[0-9a-f])(?P[A-Z]{4})(?P[0-9a-f]{6})_' -Rever = re.compile(VEREX) # compile is faster -MINSNIFFSIZE = 12 + VERFULLSIZE # min bytes in buffer to sniff else need more - +# SAID field labels +Saidage = namedtuple("Saidage", "dollar at id_ i d") -def deversify(vs): - """ - Returns tuple(ident, kind, version, size) - Where: - ident is event type identifier one of Idents - acdc='ACDC', keri='KERI' - kind is serialization kind, one of Serials - json='JSON', mgpk='MGPK', cbor='CBOR' - version is version tuple of type Version - size is int of raw size - - Parameters: - vs is version string str +Saids = Saidage(dollar="$id", at="@id", id_="id", i="i", d="d") - Uses regex match to extract: - event type identifier - serialization kind - keri version - serialization size +def sizeify(ked, kind=None, version=Version): """ - match = Rever.match(vs.encode("utf-8")) # match takes bytes - if match: - ident, major, minor, kind, size = match.group("ident", "major", "minor", "kind", "size") - version = Versionage(major=int(major, 16), minor=int(minor, 16)) - ident = ident.decode("utf-8") - kind = kind.decode("utf-8") + Compute serialized size of ked and update version field + Returns tuple of associated values extracted and or changed by sizeify - if ident not in Idents: - raise ValueError("Invalid message identifier = {}".format(ident)) - if kind not in Serials: - raise ValueError("Invalid serialization kind = {}".format(kind)) - size = int(size, 16) - return ident, kind, version, size - - raise ValueError("Invalid version string = {}".format(vs)) + Returns tuple of (raw, proto, kind, ked, version) where: + raw (str): serialized event as bytes of kind + proto (str): protocol type as value of Protocolage + kind (str): serialzation kind as value of Serialage + ked (dict): key event dict + version (Versionage): instance + Parameters: + ked (dict): key event dict + kind (str): value of Serials is serialization type + if not provided use that given in ked["v"] + version (Versionage): instance supported protocol version for message -def sizeify(ked, kind=None): - """ - ked is key event dict - kind is serialization if given else use one given in ked - Returns tuple of (raw, kind, ked, version) where: - raw is serialized event as bytes of kind - kind is serialzation kind - ked is key event dict - version is Versionage instance Assumes only supports Version """ @@ -149,10 +91,10 @@ def sizeify(ked, kind=None): raise ValueError("Missing or empty version string in key event " "dict = {}".format(ked)) - ident, knd, version, size = deversify(ked["v"]) # extract kind and version - if version != Version: - raise ValueError("Unsupported version = {}.{}".format(version.major, - version.minor)) + proto, vrsn, knd, size = deversify(ked["v"]) # extract kind and version + if vrsn != version: + raise ValueError("Unsupported version = {}.{}".format(vrsn.major, + vrsn.minor)) if not kind: kind = knd @@ -169,14 +111,14 @@ def sizeify(ked, kind=None): fore, back = match.span() # full version string # update vs with latest kind version size - vs = versify(ident=ident, version=version, kind=kind, size=size) + vs = versify(proto=proto, version=vrsn, kind=kind, size=size) # replace old version string in raw with new one raw = b'%b%b%b' % (raw[:fore], vs.encode("utf-8"), raw[back:]) if size != len(raw): # substitution messed up raise ValueError("Malformed version string size = {}".format(vs)) ked["v"] = vs # update ked - return raw, ident, kind, ked, version + return raw, proto, kind, ked, vrsn # Base64 utilities @@ -294,6 +236,7 @@ def nabSextets(b, l): i <<= p # pad with empty bits return (i.to_bytes(n, 'big')) +MINSNIFFSIZE = 12 + VERFULLSIZE # min bytes in buffer to sniff else need more def sniff(raw): """ @@ -311,15 +254,15 @@ def sniff(raw): if not match or match.start() > 12: raise VersionError("Invalid version string in raw = {}".format(raw)) - ident, major, minor, kind, size = match.group("ident", "major", "minor", "kind", "size") + proto, major, minor, kind, size = match.group("proto", "major", "minor", "kind", "size") version = Versionage(major=int(major, 16), minor=int(minor, 16)) kind = kind.decode("utf-8") - ident = ident.decode("utf-8") + proto = proto.decode("utf-8") if kind not in Serials: - raise DeserializationError("Invalid serialization kind = {}".format(kind)) + raise DeserializeError("Invalid serialization kind = {}".format(kind)) size = int(size, 16) - return ident, kind, version, size + return proto, kind, version, size def dumps(ked, kind=Serials.json): @@ -364,25 +307,25 @@ def loads(raw, size=None, kind=Serials.json): try: ked = json.loads(raw[:size].decode("utf-8")) except Exception as ex: - raise DeserializationError("Error deserializing JSON: {}" + raise DeserializeError("Error deserializing JSON: {}" "".format(raw[:size].decode("utf-8"))) elif kind == Serials.mgpk: try: ked = msgpack.loads(raw[:size]) except Exception as ex: - raise DeserializationError("Error deserializing MGPK: {}" + raise DeserializeError("Error deserializing MGPK: {}" "".format(raw[:size])) elif kind == Serials.cbor: try: ked = cbor.loads(raw[:size]) except Exception as ex: - raise DeserializationError("Error deserializing CBOR: {}" + raise DeserializeError("Error deserializing CBOR: {}" "".format(raw[:size])) else: - raise DeserializationError("Invalid deserialization kind: {}" + raise DeserializeError("Invalid deserialization kind: {}" "".format(kind)) return ked @@ -410,9 +353,9 @@ def generateSigners(salt=None, count=8, transferable=True): seed = pysodium.crypto_pwhash(outlen=32, passwd=path, salt=salt, - opslimit=pysodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, - memlimit=pysodium.crypto_pwhash_MEMLIMIT_INTERACTIVE, - alg=pysodium.crypto_pwhash_ALG_DEFAULT) + opslimit=2, # pysodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, + memlimit=67108864, # pysodium.crypto_pwhash_MEMLIMIT_INTERACTIVE, + alg=pysodium.crypto_pwhash_ALG_ARGON2ID13) signers.append(Signer(raw=seed, transferable=transferable)) @@ -478,7 +421,16 @@ class MatterCodex: Short: str = 'M' # Short 2 byte b2 number Big: str = 'N' # Big 8 byte b2 number X25519_Private: str = 'O' # X25519 private decryption key converted from Ed25519 - X25519_Cipher_Seed: str = 'P' # X25519 124 char b64 Cipher of 44 char qb64 Seed + X25519_Cipher_Seed: str = 'P' # X25519 sealed box 124 char qb64 Cipher of 44 char qb64 Seed + ECDSA_256r1_Seed: str = "Q" # ECDSA secp256r1 256 bit random Seed for private key + Tall: str = 'R' # Tall 5 byte b2 number + Large: str = 'S' # Large 11 byte b2 number + Great: str = 'T' # Great 14 byte b2 number + Vast: str = 'U' # Vast 17 byte b2 number + Label1: str = 'V' # Label1 as one char (bytes) field map label lead size 1 + Label2: str = 'W' # Label2 as two char (bytes) field map label lead size 0 + Tag3: str = 'X' # Tag3 3 B64 encoded chars for field tag or packet type, semver, trait like 'DND' + Tag7: str = 'Y' # Tag7 7 B64 encoded chars for field tag or packet kind and version KERIVVV Salt_128: str = '0A' # 128 bit random salt or 128 bit number (see Huge) Ed25519_Sig: str = '0B' # Ed25519 signature. ECDSA_256k1_Sig: str = '0C' # ECDSA secp256k1 signature. @@ -487,28 +439,56 @@ class MatterCodex: SHA3_512: str = '0F' # SHA3 512 bit digest self-addressing derivation. SHA2_512: str = '0G' # SHA2 512 bit digest self-addressing derivation. Long: str = '0H' # Long 4 byte b2 number + ECDSA_256r1_Sig: str = '0I' # ECDSA secp256r1 signature. + Tag1: str = '0J' # Tag1 1 B64 encoded char with pre pad for field tag + Tag2: str = '0K' # Tag2 2 B64 encoded chars for field tag or version VV or trait like 'EO' + Tag5: str = '0L' # Tag5 5 B64 encoded chars with pre pad for field tag + Tag6: str = '0M' # Tag6 6 B64 encoded chars for field tag or protocol kind version like KERIVV (KERI 1.1) or KKKVVV ECDSA_256k1N: str = '1AAA' # ECDSA secp256k1 verification key non-transferable, basic derivation. - ECDSA_256k1: str = '1AAB' # Ed25519 public verification or encryption key, basic derivation + ECDSA_256k1: str = '1AAB' # ECDSA public verification or encryption key, basic derivation Ed448N: str = '1AAC' # Ed448 non-transferable prefix public signing verification key. Basic derivation. Ed448: str = '1AAD' # Ed448 public signing verification key. Basic derivation. Ed448_Sig: str = '1AAE' # Ed448 signature. Self-signing derivation. - Tern: str = '1AAF' # 3 byte b2 number or 4 char B64 str. + Tag4: str = '1AAF' # Tag4 4 B64 encoded chars for field tag or message kind DateTime: str = '1AAG' # Base64 custom encoded 32 char ISO-8601 DateTime - X25519_Cipher_Salt: str = '1AAH' # X25519 100 char b64 Cipher of 24 char qb64 Salt + X25519_Cipher_Salt: str = '1AAH' # X25519 sealed box 100 char qb64 Cipher of 24 char qb64 Salt + ECDSA_256r1N: str = '1AAI' # ECDSA secp256r1 verification key non-transferable, basic derivation. + ECDSA_256r1: str = '1AAJ' # ECDSA secp256r1 verification or encryption key, basic derivation + Null: str = '1AAK' # Null None or empty value + Yes: str = '1AAL' # Yes Truthy Boolean value + No: str = '1AAM' # No Falsey Boolean value TBD1: str = '2AAA' # Testing purposes only fixed with lead size 1 TBD2: str = '3AAA' # Testing purposes only of fixed with lead size 2 - StrB64_L0: str = '4A' # String Base64 Only Lead Size 0 - StrB64_L1: str = '5A' # String Base64 Only Lead Size 1 - StrB64_L2: str = '6A' # String Base64 Only Lead Size 2 - StrB64_Big_L0: str = '7AAA' # String Base64 Only Big Lead Size 0 - StrB64_Big_L1: str = '8AAA' # String Base64 Only Big Lead Size 1 - StrB64_Big_L2: str = '9AAA' # String Base64 Only Big Lead Size 2 - Bytes_L0: str = '4B' # Byte String Leader Size 0 - Bytes_L1: str = '5B' # Byte String Leader Size 1 - Bytes_L2: str = '6B' # ByteString Leader Size 2 - Bytes_Big_L0: str = '7AAB' # Byte String Big Leader Size 0 - Bytes_Big_L1: str = '8AAB' # Byte String Big Leader Size 1 - Bytes_Big_L2: str = '9AAB' # Byte String Big Leader Size 2 + StrB64_L0: str = '4A' # String Base64 only lead size 0 + StrB64_L1: str = '5A' # String Base64 only lead size 1 + StrB64_L2: str = '6A' # String Base64 only lead size 2 + StrB64_Big_L0: str = '7AAA' # String Base64 only big lead size 0 + StrB64_Big_L1: str = '8AAA' # String Base64 only big lead size 1 + StrB64_Big_L2: str = '9AAA' # String Base64 only big lead size 2 + Bytes_L0: str = '4B' # Byte String lead size 0 + Bytes_L1: str = '5B' # Byte String lead size 1 + Bytes_L2: str = '6B' # Byte String lead size 2 + Bytes_Big_L0: str = '7AAB' # Byte String big lead size 0 + Bytes_Big_L1: str = '8AAB' # Byte String big lead size 1 + Bytes_Big_L2: str = '9AAB' # Byte String big lead size 2 + X25519_Cipher_L0: str = '4C' # X25519 sealed box cipher bytes of sniffable plaintext lead size 0 + X25519_Cipher_L1: str = '5C' # X25519 sealed box cipher bytes of sniffable plaintext lead size 1 + X25519_Cipher_L2: str = '6C' # X25519 sealed box cipher bytes of sniffable plaintext lead size 2 + X25519_Cipher_Big_L0: str = '7AAC' # X25519 sealed box cipher bytes of sniffable plaintext big lead size 0 + X25519_Cipher_Big_L1: str = '8AAC' # X25519 sealed box cipher bytes of sniffable plaintext big lead size 1 + X25519_Cipher_Big_L2: str = '9AAC' # X25519 sealed box cipher bytes of sniffable plaintext big lead size 2 + X25519_Cipher_QB64_L0: str = '4D' # X25519 sealed box cipher bytes of QB64 plaintext lead size 0 + X25519_Cipher_QB64_L1: str = '5D' # X25519 sealed box cipher bytes of QB64 plaintext lead size 1 + X25519_Cipher_QB64_L2: str = '6D' # X25519 sealed box cipher bytes of QB64 plaintext lead size 2 + X25519_Cipher_QB64_Big_L0: str = '7AAD' # X25519 sealed box cipher bytes of QB64 plaintext big lead size 0 + X25519_Cipher_QB64_Big_L1: str = '8AAD' # X25519 sealed box cipher bytes of QB64 plaintext big lead size 1 + X25519_Cipher_QB64_Big_L2: str = '9AAD' # X25519 sealed box cipher bytes of QB64 plaintext big lead size 2 + X25519_Cipher_QB2_L0: str = '4D' # X25519 sealed box cipher bytes of QB2 plaintext lead size 0 + X25519_Cipher_QB2_L1: str = '5D' # X25519 sealed box cipher bytes of QB2 plaintext lead size 1 + X25519_Cipher_QB2_L2: str = '6D' # X25519 sealed box cipher bytes of QB2 plaintext lead size 2 + X25519_Cipher_QB2_Big_L0: str = '7AAD' # X25519 sealed box cipher bytes of QB2 plaintext big lead size 0 + X25519_Cipher_QB2_Big_L1: str = '8AAD' # X25519 sealed box cipher bytes of QB2 plaintext big lead size 1 + X25519_Cipher_QB2_Big_L2: str = '9AAD' # X25519 sealed box cipher bytes of QB2 plaintext big lead size 2 def __iter__(self): @@ -570,6 +550,7 @@ class NonTransCodex: Ed25519N: str = 'B' # Ed25519 verification key non-transferable, basic derivation. ECDSA_256k1N: str = '1AAA' # ECDSA secp256k1 verification key non-transferable, basic derivation. Ed448N: str = '1AAC' # Ed448 non-transferable prefix public signing verification key. Basic derivation. + ECDSA_256r1N: str = "1AAI" # ECDSA secp256r1 verification key non-transferable, basic derivation. def __iter__(self): return iter(astuple(self)) @@ -577,7 +558,7 @@ def __iter__(self): NonTransDex = NonTransCodex() # Make instance - +# When add new to DigCodes update Saider.Digests and Serder.Digests class attr @dataclass(frozen=True) class DigCodex: """ @@ -625,16 +606,18 @@ def __iter__(self): NumDex = NumCodex() # Make instance + + @dataclass(frozen=True) class BextCodex: """ - BextCodex is codex all variable sized Base64 Text (Bext) derivation codes. + BextCodex is codex of all variable sized Base64 Text (Bext) derivation codes. Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. """ - StrB64_L0: str = '4A' # String Base64 Only Leader Size 0 - StrB64_L1: str = '5A' # String Base64 Only Leader Size 1 - StrB64_L2: str = '6A' # String Base64 Only Leader Size 2 + StrB64_L0: str = '4A' # String Base64 Only Leader Size 0 + StrB64_L1: str = '5A' # String Base64 Only Leader Size 1 + StrB64_L2: str = '6A' # String Base64 Only Leader Size 2 StrB64_Big_L0: str = '7AAA' # String Base64 Only Big Leader Size 0 StrB64_Big_L1: str = '8AAA' # String Base64 Only Big Leader Size 1 StrB64_Big_L2: str = '9AAA' # String Base64 Only Big Leader Size 2 @@ -646,6 +629,137 @@ def __iter__(self): BexDex = BextCodex() # Make instance + +@dataclass(frozen=True) +class TextCodex: + """ + TextCodex is codex of all variable sized byte string (Text) derivation codes. + Only provide defined codes. + Undefined are left out so that inclusion(exclusion) via 'in' operator works. + """ + Bytes_L0: str = '4B' # Byte String lead size 0 + Bytes_L1: str = '5B' # Byte String lead size 1 + Bytes_L2: str = '6B' # Byte String lead size 2 + Bytes_Big_L0: str = '7AAB' # Byte String big lead size 0 + Bytes_Big_L1: str = '8AAB' # Byte String big lead size 1 + Bytes_Big_L2: str = '9AAB' # Byte String big lead size 2 + + def __iter__(self): + return iter(astuple(self)) + + +TexDex = TextCodex() # Make instance + +@dataclass(frozen=True) +class CipherX25519VarCodex: + """ + CipherX25519VarCodex is codex all variable sized cipher bytes derivation codes + for sealed box encryped ciphertext. Plaintext is B2. + Only provide defined codes. + Undefined are left out so that inclusion(exclusion) via 'in' operator works. + """ + X25519_Cipher_L0: str = '4D' # X25519 sealed box cipher bytes of sniffable plaintext lead size 0 + X25519_Cipher_L1: str = '5D' # X25519 sealed box cipher bytes of sniffable plaintext lead size 1 + X25519_Cipher_L2: str = '6D' # X25519 sealed box cipher bytes of sniffable plaintext lead size 2 + X25519_Cipher_Big_L0: str = '7AAD' # X25519 sealed box cipher bytes of sniffable plaintext big lead size 0 + X25519_Cipher_Big_L1: str = '8AAD' # X25519 sealed box cipher bytes of sniffable plaintext big lead size 1 + X25519_Cipher_Big_L2: str = '9AAD' # X25519 sealed box cipher bytes of sniffable plaintext big lead size 2 + + def __iter__(self): + return iter(astuple(self)) + + +CiXVarDex = CipherX25519VarCodex() # Make instance + + +@dataclass(frozen=True) +class CipherX25519FixQB64Codex: + """ + CipherX25519FixQB64Codex is codex all fixed sized cipher bytes derivation codes + for sealed box encryped ciphertext. Plaintext is B64. + Only provide defined codes. + Undefined are left out so that inclusion(exclusion) via 'in' operator works. + """ + X25519_Cipher_Seed: str = 'P' # X25519 sealed box 124 char qb64 Cipher of 44 char qb64 Seed + X25519_Cipher_Salt: str = '1AAH' # X25519 sealed box 100 char qb64 Cipher of 24 char qb64 Salt + + def __iter__(self): + return iter(astuple(self)) + + +CiXFixQB64Dex = CipherX25519FixQB64Codex() # Make instance + + +@dataclass(frozen=True) +class CipherX25519VarQB64Codex: + """ + CipherX25519VarQB64Codex is codex all variable sized cipher bytes derivation codes + for sealed box encryped ciphertext. Plaintext is QB64. + Only provide defined codes. + Undefined are left out so that inclusion(exclusion) via 'in' operator works. + """ + X25519_Cipher_QB64_L0: str = '4D' # X25519 sealed box cipher bytes of QB64 plaintext lead size 0 + X25519_Cipher_QB64_L1: str = '5E' # X25519 sealed box cipher bytes of QB64 plaintext lead size 1 + X25519_Cipher_QB64_L2: str = '6E' # X25519 sealed box cipher bytes of QB64 plaintext lead size 2 + X25519_Cipher_QB64_Big_L0: str = '7AAD' # X25519 sealed box cipher bytes of QB64 plaintext big lead size 0 + X25519_Cipher_QB64_Big_L1: str = '8AAD' # X25519 sealed box cipher bytes of QB64 plaintext big lead size 1 + X25519_Cipher_QB64_Big_L2: str = '9AAD' # X25519 sealed box cipher bytes of QB64 plaintext big lead size 2 + + def __iter__(self): + return iter(astuple(self)) + + +CiXVarQB64Dex = CipherX25519VarQB64Codex() # Make instance + + +@dataclass(frozen=True) +class CipherX25519AllQB64Codex: + """ + CipherX25519AllQB64Codex is codex all both fixed and variable sized cipher bytes + derivation codes for sealed box encryped ciphertext. Plaintext is B64. + Only provide defined codes. + Undefined are left out so that inclusion(exclusion) via 'in' operator works. + """ + X25519_Cipher_Seed: str = 'P' # X25519 sealed box 124 char qb64 Cipher of 44 char qb64 Seed + X25519_Cipher_Salt: str = '1AAH' # X25519 sealed box 100 char qb64 Cipher of 24 char qb64 Salt + X25519_Cipher_QB64_L0: str = '4D' # X25519 sealed box cipher bytes of QB64 plaintext lead size 0 + X25519_Cipher_QB64_L1: str = '5E' # X25519 sealed box cipher bytes of QB64 plaintext lead size 1 + X25519_Cipher_QB64_L2: str = '6E' # X25519 sealed box cipher bytes of QB64 plaintext lead size 2 + X25519_Cipher_QB64_Big_L0: str = '7AAD' # X25519 sealed box cipher bytes of QB64 plaintext big lead size 0 + X25519_Cipher_QB64_Big_L1: str = '8AAD' # X25519 sealed box cipher bytes of QB64 plaintext big lead size 1 + X25519_Cipher_QB64_Big_L2: str = '9AAD' # X25519 sealed box cipher bytes of QB64 plaintext big lead size 2 + + def __iter__(self): + return iter(astuple(self)) + + +CiXAllQB64Dex = CipherX25519AllQB64Codex() # Make instance + + +@dataclass(frozen=True) +class CipherX25519QB2VarCodex: + """ + CipherX25519QB2VarCodex is codex all variable sized cipher bytes derivation codes + for sealed box encryped ciphertext. Plaintext is B2. + Only provide defined codes. + Undefined are left out so that inclusion(exclusion) via 'in' operator works. + """ + X25519_Cipher_L0: str = '4E' # X25519 sealed box cipher bytes of QB2 plaintext lead size 0 + X25519_Cipher_L1: str = '5E' # X25519 sealed box cipher bytes of QB2 plaintext lead size 1 + X25519_Cipher_L2: str = '6E' # X25519 sealed box cipher bytes of QB2 plaintext lead size 2 + X25519_Cipher_Big_L0: str = '7AAE' # X25519 sealed box cipher bytes of QB2 plaintext big lead size 0 + X25519_Cipher_Big_L1: str = '8AAE' # X25519 sealed box cipher bytes of QB2 plaintext big lead size 1 + X25519_Cipher_Big_L2: str = '9AAE' # X25519 sealed box cipher bytes of QB2 plaintext big lead size 2 + + def __iter__(self): + return iter(astuple(self)) + + +CiXVarQB2Dex = CipherX25519QB2VarCodex() # Make instance + + + + # namedtuple for size entries in Matter and Counter derivation code tables # hs is the hard size int number of chars in hard (stable) part of code # ss is the soft size int number of chars in soft (unstable) part of code @@ -680,6 +794,7 @@ class Matter: qb2 (bytes): binary with derivation code + crypto material transferable (bool): True means transferable derivation code False otherwise digestive (bool): True means digest derivation code False otherwise + prefixive (bool): True means identifier prefix derivation code False otherwise Hidden: _code (str): value for .code property @@ -726,6 +841,15 @@ class Matter: 'N': Sizage(hs=1, ss=0, fs=12, ls=0), 'O': Sizage(hs=1, ss=0, fs=44, ls=0), 'P': Sizage(hs=1, ss=0, fs=124, ls=0), + 'Q': Sizage(hs=1, ss=0, fs=44, ls=0), + 'R': Sizage(hs=1, ss=0, fs=8, ls=0), + 'S': Sizage(hs=1, ss=0, fs=16, ls=0), + 'T': Sizage(hs=1, ss=0, fs=20, ls=0), + 'U': Sizage(hs=1, ss=0, fs=24, ls=0), + 'V': Sizage(hs=1, ss=0, fs=4, ls=1), + 'W': Sizage(hs=1, ss=0, fs=4, ls=0), + 'X': Sizage(hs=1, ss=0, fs=4, ls=0), + 'Y': Sizage(hs=1, ss=0, fs=8, ls=0), '0A': Sizage(hs=2, ss=0, fs=24, ls=0), '0B': Sizage(hs=2, ss=0, fs=88, ls=0), '0C': Sizage(hs=2, ss=0, fs=88, ls=0), @@ -734,6 +858,11 @@ class Matter: '0F': Sizage(hs=2, ss=0, fs=88, ls=0), '0G': Sizage(hs=2, ss=0, fs=88, ls=0), '0H': Sizage(hs=2, ss=0, fs=8, ls=0), + '0I': Sizage(hs=2, ss=0, fs=88, ls=0), + '0J': Sizage(hs=2, ss=0, fs=4, ls=0), + '0K': Sizage(hs=2, ss=0, fs=4, ls=0), + '0L': Sizage(hs=2, ss=0, fs=8, ls=0), + '0M': Sizage(hs=2, ss=0, fs=8, ls=0), '1AAA': Sizage(hs=4, ss=0, fs=48, ls=0), '1AAB': Sizage(hs=4, ss=0, fs=48, ls=0), '1AAC': Sizage(hs=4, ss=0, fs=80, ls=0), @@ -742,6 +871,11 @@ class Matter: '1AAF': Sizage(hs=4, ss=0, fs=8, ls=0), '1AAG': Sizage(hs=4, ss=0, fs=36, ls=0), '1AAH': Sizage(hs=4, ss=0, fs=100, ls=0), + '1AAI': Sizage(hs=4, ss=0, fs=48, ls=0), + '1AAJ': Sizage(hs=4, ss=0, fs=48, ls=0), + '1AAK': Sizage(hs=4, ss=0, fs=4, ls=0), + '1AAL': Sizage(hs=4, ss=0, fs=4, ls=0), + '1AAM': Sizage(hs=4, ss=0, fs=4, ls=0), '2AAA': Sizage(hs=4, ss=0, fs=8, ls=1), '3AAA': Sizage(hs=4, ss=0, fs=8, ls=2), '4A': Sizage(hs=2, ss=2, fs=None, ls=0), @@ -756,8 +890,27 @@ class Matter: '7AAB': Sizage(hs=4, ss=4, fs=None, ls=0), '8AAB': Sizage(hs=4, ss=4, fs=None, ls=1), '9AAB': Sizage(hs=4, ss=4, fs=None, ls=2), + '4C': Sizage(hs=2, ss=2, fs=None, ls=0), + '5C': Sizage(hs=2, ss=2, fs=None, ls=1), + '6C': Sizage(hs=2, ss=2, fs=None, ls=2), + '7AAC': Sizage(hs=4, ss=4, fs=None, ls=0), + '8AAC': Sizage(hs=4, ss=4, fs=None, ls=1), + '9AAC': Sizage(hs=4, ss=4, fs=None, ls=2), + '4D': Sizage(hs=2, ss=2, fs=None, ls=0), + '5D': Sizage(hs=2, ss=2, fs=None, ls=1), + '6D': Sizage(hs=2, ss=2, fs=None, ls=2), + '7AAD': Sizage(hs=4, ss=4, fs=None, ls=0), + '8AAD': Sizage(hs=4, ss=4, fs=None, ls=1), + '9AAD': Sizage(hs=4, ss=4, fs=None, ls=2), + '4E': Sizage(hs=2, ss=2, fs=None, ls=0), + '5E': Sizage(hs=2, ss=2, fs=None, ls=1), + '6E': Sizage(hs=2, ss=2, fs=None, ls=2), + '7AAE': Sizage(hs=4, ss=4, fs=None, ls=0), + '8AAE': Sizage(hs=4, ss=4, fs=None, ls=1), + '9AAE': Sizage(hs=4, ss=4, fs=None, ls=2), } + # Bards table maps first code char. converted to binary sextext of hard size, # hs. Used for ._bexfil. Bards = ({codeB64ToB2(c): hs for c, hs in Hards.items()}) @@ -985,6 +1138,17 @@ def digestive(self): """ return (self.code in DigDex) + + @property + def prefixive(self): + """ + Property prefixive: + Returns True if identifier has prefix derivation code, + False otherwise + """ + return (self.code in PreDex) + + def _infil(self): """ Returns bytes of fully qualified base64 characters @@ -1121,8 +1285,8 @@ def _exfil(self, qb64b): # assumes that unit tests on Matter and MatterCodex ensure that # .Codes and .Sizes are well formed. - # hs consistent and ss == 0 and not fs % 4 and hs > 0 and fs > hs unless - # fs is None + # hs consistent and ss == 0 and not fs % 4 and hs > 0 and fs >= hs + ss + # unless fs is None if len(qb64b) < fs: # need more bytes raise ShortageError(f"Need {fs - len(qb64b)} more chars.") @@ -1212,7 +1376,8 @@ def _bexfil(self, qb2): # assumes that unit tests on Matter and MatterCodex ensure that # .Codes and .Sizes are well formed. - # hs consistent and ss == 0 and not fs % 4 and hs > 0 and fs > hs + # hs consistent and ss == 0 and not fs % 4 and hs > 0 and + # (fs >= hs + ss if fs is not None else True) bfs = sceil(fs * 3 / 4) # bfs is min bytes to hold fs sextets if len(qb2) < bfs: # need more bytes @@ -1478,15 +1643,44 @@ def numh(self): """ return f"{self.num:x}" + + @property + def sn(self): + """Sequence number, sn property getter to mimic Seqner interface + Returns: + sn (int): alias for num + """ + return self.num + + + @property + def snh(self): + """Sequence number hex str, snh property getter to mimic Seqner interface + Returns: + snh (hex str): alias for numh + """ + return self.numh + + + @property def positive(self): """ - Returns True if .num is positive False otherwise. - Because valid number .num must be non-negative, positive False means + Returns True if .num is strictly positive non-zero False otherwise. + Because valid number .num must be non-negative, positive False also means that .num is zero. """ return True if self.num > 0 else False + @property + def inceptive(self): + """ + Returns True if .num == 0 False otherwise. + Because valid number .num must be non-negative, positive False means + that .num is zero. + """ + return True if self.num == 0 else False + class Dater(Matter): """ @@ -1837,7 +2031,6 @@ def root(self, root): """ return Pather(path=root.path + self.path) - def strip(self, root): """ Returns a new Pather with root stipped off the front if it exists @@ -1898,7 +2091,7 @@ def tail(self, serder): Returns: bytes: Value at the end of the path """ - val = self.resolve(sad=serder.ked) + val = self.resolve(sad=serder.sad) if isinstance(val, str): saider = Saider(qb64=val) return saider.qb64b @@ -1962,7 +2155,7 @@ def _resolve(self, val, ptr): keys = list(val) if i >= len(keys): - raise Exception(f"invalid dict pointer index {i} for keys {keys}") + raise KeyError(f"invalid dict pointer index {i} for keys {keys}") cur = val[list(val)[i]] elif idx == "": @@ -1973,12 +2166,12 @@ def _resolve(self, val, ptr): elif isinstance(val, list): i = int(idx) if i >= len(val): - raise Exception(f"invalid array pointer index {i} for array {val}") + raise KeyError(f"invalid array pointer index {i} for array {val}") cur = val[i] else: - raise ValueError("invalid traversal type") + raise KeyError("invalid traversal type") return self._resolve(cur, ptr) @@ -2009,6 +2202,10 @@ def __init__(self, **kwa): if self.code in [MtrDex.Ed25519N, MtrDex.Ed25519]: self._verify = self._ed25519 + elif self.code in [MtrDex.ECDSA_256r1N, MtrDex.ECDSA_256r1]: + self._verify = self._secp256r1 + elif self.code in [MtrDex.ECDSA_256k1N, MtrDex.ECDSA_256k1]: + self._verify = self._secp256k1 else: raise ValueError("Unsupported code = {} for verifier.".format(self.code)) @@ -2028,7 +2225,7 @@ def verify(self, sig, ser): def _ed25519(sig, ser, key): """ Returns True if verified False otherwise - Verifiy ed25519 sig on ser using key + Verify Ed25519 sig on ser using key Parameters: sig is bytes signature @@ -2042,6 +2239,48 @@ def _ed25519(sig, ser, key): return True + @staticmethod + def _secp256r1(sig, ser, key): + """ + Returns True if verified False otherwise + Verify secp256r1 sig on ser using key + + Parameters: + sig is bytes signature + ser is bytes serialization + key is bytes public key + """ + verkey = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), key) + r = int.from_bytes(sig[:32], "big") + s = int.from_bytes(sig[32:], "big") + der = utils.encode_dss_signature(r, s) + try: + verkey.verify(der, ser, ec.ECDSA(hashes.SHA256())) + return True + except exceptions.InvalidSignature: + return False + + @staticmethod + def _secp256k1(sig, ser, key): + """ + Returns True if verified False otherwise + Verify secp256k1 sig on ser using key + + Parameters: + sig is bytes signature + ser is bytes serialization + key is bytes public key + """ + verkey = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), key) + r = int.from_bytes(sig[:32], "big") + s = int.from_bytes(sig[32:], "big") + der = utils.encode_dss_signature(r, s) + try: + verkey.verify(der, ser, ec.ECDSA(hashes.SHA256())) + return True + except exceptions.InvalidSignature: + return False + class Cigar(Matter): """ @@ -2172,6 +2411,13 @@ def __init__(self, raw=None, code=MtrDex.Ed25519_Seed, transferable=True, **kwa) if code == MtrDex.Ed25519_Seed: raw = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) super(Signer, self).__init__(raw=raw, code=code, **kwa) + elif code == MtrDex.ECDSA_256r1_Seed: + raw = pysodium.randombytes(ECDSA_256r1_SEEDBYTES) + super(Signer, self).__init__(raw=bytes(raw), code=code, **kwa) + elif code == MtrDex.ECDSA_256k1_Seed: + raw = pysodium.randombytes(ECDSA_256k1_SEEDBYTES) + super(Signer, self).__init__(raw=bytes(raw), code=code, **kwa) + else: raise ValueError("Unsupported signer code = {}.".format(code)) @@ -2181,6 +2427,22 @@ def __init__(self, raw=None, code=MtrDex.Ed25519_Seed, transferable=True, **kwa) verfer = Verfer(raw=verkey, code=MtrDex.Ed25519 if transferable else MtrDex.Ed25519N) + elif self.code == MtrDex.ECDSA_256r1_Seed: + self._sign = self._secp256r1 + d = int.from_bytes(self.raw, byteorder="big") + sigkey = ec.derive_private_key(d, ec.SECP256R1()) + verkey = sigkey.public_key().public_bytes(encoding=Encoding.X962, format=PublicFormat.CompressedPoint) + verfer = Verfer(raw=verkey, + code=MtrDex.ECDSA_256r1 if transferable + else MtrDex.ECDSA_256r1N) + elif self.code == MtrDex.ECDSA_256k1_Seed: + self._sign = self._secp256k1 + d = int.from_bytes(self.raw, byteorder="big") + sigkey = ec.derive_private_key(d, ec.SECP256K1()) + verkey = sigkey.public_key().public_bytes(encoding=Encoding.X962, format=PublicFormat.CompressedPoint) + verfer = Verfer(raw=verkey, + code=MtrDex.ECDSA_256k1 if transferable + else MtrDex.ECDSA_256k1N) else: raise ValueError("Unsupported signer code = {}.".format(self.code)) @@ -2275,6 +2537,169 @@ def _ed25519(ser, seed, verfer, index, only=False, ondex=None, **kwa): ondex=ondex, verfer=verfer,) + @staticmethod + def _secp256r1(ser, seed, verfer, index, only=False, ondex=None, **kwa): + """ + Returns signature as either Cigar or Siger instance as appropriate for + Ed25519 digital signatures given index and ondex values + + The seed's code determins the crypto key-pair algorithm and signing suite + The signature type, Cigar or Siger, and when indexed the Siger code + may be completely determined by the seed and index values (index, ondex) + by assuming that the index values are intentional. + Without the seed code its more difficult for Siger to + determine when for the Indexer code value should be changed from the + than the provided value with respect to provided but incompatible index + values versus error conditions. + + Parameters: + ser (bytes): serialization to be signed + seed (bytes): raw binary seed (private key) + verfer (Verfer): instance. verfer.raw is public key + index (int |None): main index offset into list such as current signing + None means return non-indexed Cigar + Not None means return indexed Siger with Indexer code derived + from index, conly, and ondex values + only (bool): True means main index only list, ondex ignored + False means both index lists (default), ondex used + ondex (int | None): other index offset into list such as prior next + """ + # compute raw signature sig using seed on serialization ser + d = int.from_bytes(seed, byteorder="big") + sigkey = ec.derive_private_key(d, ec.SECP256R1()) + der = sigkey.sign(ser, ec.ECDSA(hashes.SHA256())) + (r, s) = utils.decode_dss_signature(der) + sig = bytearray(r.to_bytes(32, "big")) + sig.extend(s.to_bytes(32, "big")) + + if index is None: # Must be Cigar i.e. non-indexed signature + return Cigar(raw=sig, code=MtrDex.ECDSA_256r1_Sig, verfer=verfer) + else: # Must be Siger i.e. indexed signature + # should add Indexer class method to get ms main index size for given code + if only: # only main index ondex not used + ondex = None + if index <= 63: # (64 ** ms - 1) where ms is main index size + code = IdrDex.ECDSA_256r1_Crt_Sig # use small current only + else: + code = IdrDex.ECDSA_256r1_Big_Crt_Sig # use big current only + else: # both + if ondex == None: + ondex = index # enable default to be same + if ondex == index and index <= 63: # both same and small + code = IdrDex.ECDSA_256r1_Sig # use small both same + else: # otherwise big or both not same so use big both + code = IdrDex.ECDSA_256r1_Big_Sig # use use big both + + return Siger(raw=sig, + code=code, + index=index, + ondex=ondex, + verfer=verfer,) + + @staticmethod + def _secp256k1(ser, seed, verfer, index, only=False, ondex=None, **kwa): + """ + Returns signature as either Cigar or Siger instance as appropriate for + secp256k1 digital signatures given index and ondex values + + The seed's code determins the crypto key-pair algorithm and signing suite + The signature type, Cigar or Siger, and when indexed the Siger code + may be completely determined by the seed and index values (index, ondex) + by assuming that the index values are intentional. + Without the seed code its more difficult for Siger to + determine when for the Indexer code value should be changed from the + than the provided value with respect to provided but incompatible index + values versus error conditions. + + Parameters: + ser (bytes): serialization to be signed + seed (bytes): raw binary seed (private key) + verfer (Verfer): instance. verfer.raw is public key + index (int |None): main index offset into list such as current signing + None means return non-indexed Cigar + Not None means return indexed Siger with Indexer code derived + from index, conly, and ondex values + only (bool): True means main index only list, ondex ignored + False means both index lists (default), ondex used + ondex (int | None): other index offset into list such as prior next + """ + # compute raw signature sig using seed on serialization ser + d = int.from_bytes(seed, byteorder="big") + sigkey = ec.derive_private_key(d, ec.SECP256K1()) + der = sigkey.sign(ser, ec.ECDSA(hashes.SHA256())) + (r, s) = utils.decode_dss_signature(der) + sig = bytearray(r.to_bytes(32, "big")) + sig.extend(s.to_bytes(32, "big")) + + if index is None: # Must be Cigar i.e. non-indexed signature + return Cigar(raw=sig, code=MtrDex.ECDSA_256k1_Sig, verfer=verfer) + else: # Must be Siger i.e. indexed signature + # should add Indexer class method to get ms main index size for given code + if only: # only main index ondex not used + ondex = None + if index <= 63: # (64 ** ms - 1) where ms is main index size + code = IdrDex.ECDSA_256k1_Crt_Sig # use small current only + else: + code = IdrDex.ECDSA_256k1_Big_Crt_Sig # use big current only + else: # both + if ondex == None: + ondex = index # enable default to be same + if ondex == index and index <= 63: # both same and small + code = IdrDex.ECDSA_256k1_Sig # use small both same + else: # otherwise big or both not same so use big both + code = IdrDex.ECDSA_256k1_Big_Sig # use use big both + + return Siger(raw=sig, + code=code, + index=index, + ondex=ondex, + verfer=verfer,) + + # def derive_index_code(code, index, only=False, ondex=None, **kwa): + # # should add Indexer class method to get ms main index size for given code + # if only: # only main index ondex not used + # ondex = None + # if index <= 63: # (64 ** ms - 1) where ms is main index size, use small current only + # if code == MtrDex.Ed25519_Seed: + # indxSigCode = IdrDex.Ed25519_Crt_Sig + # elif code == MtrDex.ECDSA_256r1_Seed: + # indxSigCode = IdrDex.ECDSA_256r1_Crt_Sig + # elif code == MtrDex.ECDSA_256k1_Seed: + # indxSigCode = IdrDex.ECDSA_256k1_Crt_Sig + # else: + # raise ValueError("Unsupported signer code = {}.".format(code)) + # else: # use big current only + # if code == MtrDex.Ed25519_Seed: + # indxSigCode = IdrDex.Ed25519_Big_Crt_Sig + # elif code == MtrDex.ECDSA_256r1_Seed: + # indxSigCode = IdrDex.ECDSA_256r1_Big_Crt_Sig + # elif code == MtrDex.ECDSA_256k1_Seed: + # indxSigCode = IdrDex.ECDSA_256k1_Big_Crt_Sig + # else: + # raise ValueError("Unsupported signer code = {}.".format(code)) + # else: # both + # if ondex == None: + # ondex = index # enable default to be same + # if ondex == index and index <= 63: # both same and small so use small both same + # if code == MtrDex.Ed25519_Seed: + # indxSigCode = IdrDex.Ed25519_Sig + # elif code == MtrDex.ECDSA_256r1_Seed: + # indxSigCode = IdrDex.ECDSA_256r1_Sig + # elif code == MtrDex.ECDSA_256k1_Seed: + # indxSigCode = IdrDex.ECDSA_256k1_Sig + # else: + # raise ValueError("Unsupported signer code = {}.".format(code)) + # else: # otherwise big or both not same so use big both + # if code == MtrDex.Ed25519_Seed: + # indxSigCode = IdrDex.Ed25519_Big_Sig + # elif code == MtrDex.ECDSA_256r1_Seed: + # indxSigCode = IdrDex.ECDSA_256r1_Big_Sig + # elif code == MtrDex.ECDSA_256k1_Seed: + # indxSigCode = IdrDex.ECDSA_256k1_Big_Sig + # else: + # raise ValueError("Unsupported signer code = {}.".format(code)) + + # return (indxSigCode, ondex) class Salter(Matter): """ @@ -2353,18 +2778,18 @@ def stretch(self, *, size=32, path="", tier=None, temp=False): tier = tier if tier is not None else self.tier if temp: - opslimit = pysodium.crypto_pwhash_OPSLIMIT_MIN - memlimit = pysodium.crypto_pwhash_MEMLIMIT_MIN + opslimit = 1 # pysodium.crypto_pwhash_OPSLIMIT_MIN + memlimit = 8192 # pysodium.crypto_pwhash_MEMLIMIT_MIN else: if tier == Tiers.low: - opslimit = pysodium.crypto_pwhash_OPSLIMIT_INTERACTIVE - memlimit = pysodium.crypto_pwhash_MEMLIMIT_INTERACTIVE + opslimit = 2 # pysodium.crypto_pwhash_OPSLIMIT_INTERACTIVE + memlimit = 67108864 # pysodium.crypto_pwhash_MEMLIMIT_INTERACTIVE elif tier == Tiers.med: - opslimit = pysodium.crypto_pwhash_OPSLIMIT_MODERATE - memlimit = pysodium.crypto_pwhash_MEMLIMIT_MODERATE + opslimit = 3 # pysodium.crypto_pwhash_OPSLIMIT_MODERATE + memlimit = 268435456 # pysodium.crypto_pwhash_MEMLIMIT_MODERATE elif tier == Tiers.high: - opslimit = pysodium.crypto_pwhash_OPSLIMIT_SENSITIVE - memlimit = pysodium.crypto_pwhash_MEMLIMIT_SENSITIVE + opslimit = 4 # pysodium.crypto_pwhash_OPSLIMIT_SENSITIVE + memlimit = 1073741824 # pysodium.crypto_pwhash_MEMLIMIT_SENSITIVE else: raise ValueError("Unsupported security tier = {}.".format(tier)) @@ -2374,7 +2799,7 @@ def stretch(self, *, size=32, path="", tier=None, temp=False): salt=self.raw, opslimit=opslimit, memlimit=memlimit, - alg=pysodium.crypto_pwhash_ALG_DEFAULT) + alg=pysodium.crypto_pwhash_ALG_ARGON2ID13) return (seed) def signer(self, *, code=MtrDex.Ed25519_Seed, transferable=True, path="", @@ -2874,6 +3299,36 @@ def _sha2_256(ser, raw): +@dataclass(frozen=True) +class PreCodex: + """ + PreCodex is codex all identifier prefix derivation codes. + This is needed to verify valid inception events. + Only provide defined codes. + Undefined are left out so that inclusion(exclusion) via 'in' operator works. + """ + Ed25519N: str = 'B' # Ed25519 verification key non-transferable, basic derivation. + Ed25519: str = 'D' # Ed25519 verification key basic derivation + Blake3_256: str = 'E' # Blake3 256 bit digest self-addressing derivation. + Blake2b_256: str = 'F' # Blake2b 256 bit digest self-addressing derivation. + Blake2s_256: str = 'G' # Blake2s 256 bit digest self-addressing derivation. + SHA3_256: str = 'H' # SHA3 256 bit digest self-addressing derivation. + SHA2_256: str = 'I' # SHA2 256 bit digest self-addressing derivation. + Blake3_512: str = '0D' # Blake3 512 bit digest self-addressing derivation. + Blake2b_512: str = '0E' # Blake2b 512 bit digest self-addressing derivation. + SHA3_512: str = '0F' # SHA3 512 bit digest self-addressing derivation. + SHA2_512: str = '0G' # SHA2 512 bit digest self-addressing derivation. + ECDSA_256k1N: str = '1AAA' # ECDSA secp256k1 verification key non-transferable, basic derivation. + ECDSA_256k1: str = '1AAB' # ECDSA public verification or encryption key, basic derivation + ECDSA_256r1N: str = "1AAI" # ECDSA secp256r1 verification key non-transferable, basic derivation. + ECDSA_256r1: str = "1AAJ" # ECDSA secp256r1 verification or encryption key, basic derivation + + def __iter__(self): + return iter(astuple(self)) + + +PreDex = PreCodex() # Make instance + class Prefixer(Matter): """ @@ -2942,23 +3397,23 @@ def __init__(self, raw=None, code=None, ked=None, allows=None, **kwa): if allows is not None and code not in allows: raise ValueError("Unallowed code={} for prefixer.".format(code)) - if code == MtrDex.Ed25519N: - self._derive = self._derive_ed25519N - elif code == MtrDex.Ed25519: - self._derive = self._derive_ed25519 + if code in [MtrDex.Ed25519N, MtrDex.ECDSA_256r1N, MtrDex.ECDSA_256k1N]: + self._derive = self._derive_non_transferable + elif code in [MtrDex.Ed25519, MtrDex.ECDSA_256r1, MtrDex.ECDSA_256k1]: + self._derive = self._derive_transferable elif code == MtrDex.Blake3_256: self._derive = self._derive_blake3_256 else: raise ValueError("Unsupported code = {} for prefixer.".format(code)) # use ked and ._derive from code to derive aid prefix and code - raw, code = self._derive(ked=ked) + raw, code = self.derive(ked=ked) super(Prefixer, self).__init__(raw=raw, code=code, **kwa) - if self.code == MtrDex.Ed25519N: - self._verify = self._verify_ed25519N - elif self.code == MtrDex.Ed25519: - self._verify = self._verify_ed25519 + if self.code in [MtrDex.Ed25519N, MtrDex.ECDSA_256r1N, MtrDex.ECDSA_256k1N]: + self._verify = self._verify_non_transferable + elif self.code in [MtrDex.Ed25519, MtrDex.ECDSA_256r1, MtrDex.ECDSA_256k1]: + self._verify = self._verify_transferable elif self.code == MtrDex.Blake3_256: self._verify = self._verify_blake3_256 else: @@ -2974,8 +3429,16 @@ def derive(self, ked): seed is only used for sig derivation it is the secret key/secret """ - if ked["t"] not in (Ilks.icp, Ilks.dip, Ilks.vcp): - raise ValueError("Nonincepting ilk={} for prefix derivation.".format(ked["t"])) + ilk = ked["t"] + if ilk not in (Ilks.icp, Ilks.dip, Ilks.vcp, Ilks.iss): + raise ValueError("Nonincepting ilk={} for prefix derivation.".format(ilk)) + + labels = getattr(Labels, ilk) + for k in labels: + if k not in ked: + raise ValidationError("Missing element = {} from {} event for " + "evt = {}.".format(k, ilk, ked)) + return (self._derive(ked=ked)) def verify(self, ked, prefixed=False): @@ -2987,11 +3450,19 @@ def verify(self, ked, prefixed=False): Parameters: ked is inception key event dict """ - if ked["t"] not in (Ilks.icp, Ilks.dip, Ilks.vcp): - raise ValueError("Nonincepting ilk={} for prefix derivation.".format(ked["t"])) + ilk = ked["t"] + if ilk not in (Ilks.icp, Ilks.dip, Ilks.vcp, Ilks.iss): + raise ValueError("Nonincepting ilk={} for prefix derivation.".format(ilk)) + + labels = getattr(Labels, ilk) + for k in labels: + if k not in ked: + raise ValidationError("Missing element = {} from {} event for " + "evt = {}.".format(k, ilk, ked)) + return (self._verify(ked=ked, pre=self.qb64, prefixed=prefixed)) - def _derive_ed25519N(self, ked): + def _derive_non_transferable(self, ked): """ Returns tuple (raw, code) of basic nontransferable Ed25519 prefix (qb64) as derived from inception key event dict ked keys[0] @@ -3007,22 +3478,22 @@ def _derive_ed25519N(self, ked): raise DerivationError("Error extracting public key =" " = {}".format(ex)) - if verfer.code not in [MtrDex.Ed25519N]: + if verfer.code not in [MtrDex.Ed25519N, MtrDex.ECDSA_256r1N, MtrDex.ECDSA_256k1N]: raise DerivationError("Mismatch derivation code = {}." "".format(verfer.code)) try: - if verfer.code == MtrDex.Ed25519N and ked["n"]: + if verfer.code in [MtrDex.Ed25519N, MtrDex.ECDSA_256r1N, MtrDex.ECDSA_256k1N] and ked["n"]: raise DerivationError("Non-empty nxt = {} for non-transferable" " code = {}".format(ked["n"], verfer.code)) - if verfer.code == MtrDex.Ed25519N and "b" in ked and ked["b"]: + if verfer.code in [MtrDex.Ed25519N, MtrDex.ECDSA_256r1N, MtrDex.ECDSA_256k1N] and "b" in ked and ked["b"]: raise DerivationError("Non-empty b = {} for non-transferable" " code = {}".format(ked["b"], verfer.code)) - if verfer.code == MtrDex.Ed25519N and "a" in ked and ked["a"]: + if verfer.code in [MtrDex.Ed25519N, MtrDex.ECDSA_256r1N, MtrDex.ECDSA_256k1N] and "a" in ked and ked["a"]: raise DerivationError("Non-empty a = {} for non-transferable" " code = {}".format(ked["a"], verfer.code)) @@ -3032,7 +3503,7 @@ def _derive_ed25519N(self, ked): return (verfer.raw, verfer.code) - def _verify_ed25519N(self, ked, pre, prefixed=False): + def _verify_non_transferable(self, ked, pre, prefixed=False): """ Returns True if verified False otherwise Verify derivation of fully qualified Base64 pre from inception iked dict @@ -3060,7 +3531,7 @@ def _verify_ed25519N(self, ked, pre, prefixed=False): return True - def _derive_ed25519(self, ked): + def _derive_transferable(self, ked): """ Returns tuple (raw, code) of basic Ed25519 prefix (qb64) as derived from inception key event dict ked keys[0] @@ -3076,13 +3547,13 @@ def _derive_ed25519(self, ked): raise DerivationError("Error extracting public key =" " = {}".format(ex)) - if verfer.code not in [MtrDex.Ed25519]: + if verfer.code not in [MtrDex.Ed25519, MtrDex.ECDSA_256r1, MtrDex.ECDSA_256k1]: raise DerivationError("Mismatch derivation code = {}" "".format(verfer.code)) return (verfer.raw, verfer.code) - def _verify_ed25519(self, ked, pre, prefixed=False): + def _verify_transferable(self, ked, pre, prefixed=False): """ Returns True if verified False otherwise Verify derivation of fully qualified Base64 prefix from @@ -3108,6 +3579,7 @@ def _verify_ed25519(self, ked, pre, prefixed=False): return True + def _derive_blake3_256(self, ked): """ Returns tuple (raw, code) of pre (qb64) as blake3 digest @@ -3115,15 +3587,17 @@ def _derive_blake3_256(self, ked): """ ked = dict(ked) # make copy so don't clobber original ked ilk = ked["t"] - if ilk not in (Ilks.icp, Ilks.dip, Ilks.vcp): + if ilk not in (Ilks.icp, Ilks.dip, Ilks.vcp, Ilks.iss): raise DerivationError("Invalid ilk = {} to derive pre.".format(ilk)) # put in dummy pre to get size correct ked["i"] = self.Dummy * Matter.Sizes[MtrDex.Blake3_256].fs - ked["d"] = ked["i"] - raw, ident, kind, ked, version = sizeify(ked=ked) - dig = blake3.blake3(raw).digest() # digest with dummy 'i' - return (dig, MtrDex.Blake3_256) # dig is derived correct new 'i' + ked["d"] = ked["i"] # must be same dummy + #raw, proto, kind, ked, version = sizeify(ked=ked) + raw, _, _, _, _ = sizeify(ked=ked) + dig = blake3.blake3(raw).digest() # digest with dummy 'i' and 'd' + return (dig, MtrDex.Blake3_256) # dig is derived correct new 'i' and 'd' + def _verify_blake3_256(self, ked, pre, prefixed=False): """ @@ -3144,17 +3618,17 @@ def _verify_blake3_256(self, ked, pre, prefixed=False): if prefixed and ked["i"] != pre: # incoming 'i' must match pre return False + if ked["i"] != ked["d"]: # when digestive then SAID must match pre + return False + except Exception as ex: return False return True -Idage = namedtuple("Idage", "dollar at id_ i d") - -Ids = Idage(dollar="$id", at="@id", id_="id", i="i", d="d") -# digest klas, digest size (not default), digest length +# digest algorithm klas, digest size (not default), digest length # size and length are needed for some digest types as function parameters Digestage = namedtuple("Digestage", "klas size length") @@ -3203,7 +3677,7 @@ class Saider(Matter): } def __init__(self, raw=None, *, code=None, sad=None, - kind=None, label=Ids.d, ignore=None, **kwa): + kind=None, label=Saids.d, ignore=None, **kwa): """ See Matter.__init__ for inherited parameters @@ -3212,7 +3686,7 @@ def __init__(self, raw=None, *, code=None, sad=None, kind (str): serialization algorithm of sad, one of Serials used to override that given by 'v' field if any in sad otherwise default is Serials.json - label (str): id field label, one of Ids + label (str): Saidage value as said field label ignore (list): fields to ignore when generating SAID """ @@ -3234,8 +3708,13 @@ def __init__(self, raw=None, *, code=None, sad=None, if code not in DigDex: # need valid code raise ValueError("Unsupported digest code = {}.".format(code)) - # re-derive said raw bytes from sad and code, so code overrides label - raw, sad = self.derive(sad=dict(sad), code=code, kind=kind, label=label, ignore=ignore) + # make copy of sad to derive said raw bytes and new sad + # need new sad because sets sad[label] and sad['v'] fields + raw, sad = self.derive(sad=dict(sad), + code=code, + kind=kind, + label=label, + ignore=ignore) super(Saider, self).__init__(raw=raw, code=code, **kwa) if not self.digestive: @@ -3261,7 +3740,7 @@ def _serialize(clas, sad: dict, kind: str = None): """ knd = Serials.json if 'v' in sad: # versioned sad - _, knd, _, _ = deversify(sad['v']) + _, _, knd, _ = deversify(sad['v']) if not kind: # match logic of Serder for kind kind = knd @@ -3274,7 +3753,7 @@ def saidify(clas, *, code: str = MtrDex.Blake3_256, kind: str = None, - label: str = Ids.d, + label: str = Saids.d, ignore: list = None, **kwa): """ Derives said from sad and injects it into copy of sad and said and @@ -3292,7 +3771,7 @@ def saidify(clas, kind (str): serialization algorithm of sad, one of Serials used to override that given by 'v' field if any in sad otherwise default is Serials.json - label (str): id field label from Ids in which to inject said + label (str): Saidage value as said field label in which to inject said ignore (list): fields to ignore when generating SAID """ @@ -3303,11 +3782,12 @@ def saidify(clas, sad[label] = saider.qb64 return saider, sad + @classmethod def _derive(clas, sad: dict, *, code: str = MtrDex.Blake3_256, kind: str = None, - label: str = Ids.d, + label: str = Saids.d, ignore: list = None): """ Derives raw said from sad with .Dummy filled sad[label] @@ -3321,7 +3801,7 @@ def _derive(clas, sad: dict, *, kind (str): serialization algorithm of sad, one of Serials used to override that given by 'v' field if any in sad otherwise default is Serials.json - label (str): id field label from Ids in which to inject dummy + label (str): Saidage value as said field label in which to inject dummy ignore (list): fields to ignore when generating SAID """ @@ -3332,7 +3812,7 @@ def _derive(clas, sad: dict, *, # fill id field denoted by label with dummy chars to get size correct sad[label] = clas.Dummy * Matter.Sizes[code].fs if 'v' in sad: # if versioned then need to set size in version string - raw, ident, kind, sad, version = sizeify(ked=sad, kind=kind) + raw, proto, kind, sad, version = sizeify(ked=sad, kind=kind) ser = dict(sad) if ignore: @@ -3352,6 +3832,7 @@ def _derive(clas, sad: dict, *, dkwa.update(length=length) return klas(*cpa, **ckwa).digest(**dkwa), sad # raw digest and sad + def derive(self, sad, code=None, **kwa): """ Returns: @@ -3364,13 +3845,14 @@ def derive(self, sad, code=None, **kwa): kind (str): serialization algorithm of sad, one of Serials used to override that given by 'v' field if any in sad otherwise default is Serials.json - label (str): id field label from Ids in which to inject dummy + label (str): Saidage value of said field labelin which to inject dummy """ code = code if code is not None else self.code return self._derive(sad=sad, code=code, **kwa) + def verify(self, sad, *, prefixed=False, versioned=True, code=None, - kind=None, label=Ids.d, ignore=None, **kwa): + kind=None, label=Saids.d, ignore=None, **kwa): """ Returns: result (bool): True means derivation from sad with dummy label @@ -3392,7 +3874,7 @@ def verify(self, sad, *, prefixed=False, versioned=True, code=None, kind (str): serialization algorithm of sad, one of Serials used to override that given by 'v' field if any in sad otherwise default is Serials.json - label (str): id field label from Ids in which to inject dummy + label (str): Saidage value of said field label in which to inject dummy ignore (list): fields to ignore when generating SAID """ try: @@ -3442,12 +3924,16 @@ class IndexerCodex: Ed25519_Crt_Sig: str = 'B' # Ed25519 sig appears in current list only. ECDSA_256k1_Sig: str = 'C' # ECDSA secp256k1 sig appears same in both lists if any. ECDSA_256k1_Crt_Sig: str = 'D' # ECDSA secp256k1 sig appears in current list. + ECDSA_256r1_Sig: str = "E" # ECDSA secp256r1 sig appears same in both lists if any. + ECDSA_256r1_Crt_Sig: str = "F" # ECDSA secp256r1 sig appears in current list. Ed448_Sig: str = '0A' # Ed448 signature appears in both lists. Ed448_Crt_Sig: str = '0B' # Ed448 signature appears in current list only. Ed25519_Big_Sig: str = '2A' # Ed25519 sig appears in both lists. Ed25519_Big_Crt_Sig: str = '2B' # Ed25519 sig appears in current list only. ECDSA_256k1_Big_Sig: str = '2C' # ECDSA secp256k1 sig appears in both lists. ECDSA_256k1_Big_Crt_Sig: str = '2D' # ECDSA secp256k1 sig appears in current list only. + ECDSA_256r1_Big_Sig: str = "2E" # ECDSA secp256r1 sig appears in both lists. + ECDSA_256r1_Big_Crt_Sig: str = "2F" # ECDSA secp256r1 sig appears in current list only. Ed448_Big_Sig: str = '3A' # Ed448 signature appears in both lists. Ed448_Big_Crt_Sig: str = '3B' # Ed448 signature appears in current list only. TBD0: str = '0z' # Test of Var len label L=N*4 <= 4095 char quadlets includes code @@ -3471,12 +3957,16 @@ class IndexedSigCodex: Ed25519_Crt_Sig: str = 'B' # Ed25519 sig appears in current list only. ECDSA_256k1_Sig: str = 'C' # ECDSA secp256k1 sig appears same in both lists if any. ECDSA_256k1_Crt_Sig: str = 'D' # ECDSA secp256k1 sig appears in current list. + ECDSA_256r1_Sig: str = "E" # ECDSA secp256r1 sig appears same in both lists if any. + ECDSA_256r1_Crt_Sig: str = "F" # ECDSA secp256r1 sig appears in current list. Ed448_Sig: str = '0A' # Ed448 signature appears in both lists. Ed448_Crt_Sig: str = '0B' # Ed448 signature appears in current list only. Ed25519_Big_Sig: str = '2A' # Ed25519 sig appears in both lists. Ed25519_Big_Crt_Sig: str = '2B' # Ed25519 sig appears in current list only. ECDSA_256k1_Big_Sig: str = '2C' # ECDSA secp256k1 sig appears in both lists. ECDSA_256k1_Big_Crt_Sig: str = '2D' # ECDSA secp256k1 sig appears in current list only. + ECDSA_256r1_Big_Sig: str = "2E" # ECDSA secp256r1 sig appears in both lists. + ECDSA_256r1_Big_Crt_Sig: str = "2F" # ECDSA secp256r1 sig appears in current list only. Ed448_Big_Sig: str = '3A' # Ed448 signature appears in both lists. Ed448_Big_Crt_Sig: str = '3B' # Ed448 signature appears in current list only. @@ -3495,9 +3985,11 @@ class IndexedCurrentSigCodex: """ Ed25519_Crt_Sig: str = 'B' # Ed25519 sig appears in current list only. ECDSA_256k1_Crt_Sig: str = 'D' # ECDSA secp256k1 sig appears in current list only. + ECDSA_256r1_Crt_Sig: str = "F" # ECDSA secp256r1 sig appears in current list. Ed448_Crt_Sig: str = '0B' # Ed448 signature appears in current list only. Ed25519_Big_Crt_Sig: str = '2B' # Ed25519 sig appears in current list only. ECDSA_256k1_Big_Crt_Sig: str = '2D' # ECDSA secp256k1 sig appears in current list only. + ECDSA_256r1_Big_Crt_Sig: str = "2F" # ECDSA secp256r1 sig appears in current list only. Ed448_Big_Crt_Sig: str = '3B' # Ed448 signature appears in current list only. def __iter__(self): @@ -3516,9 +4008,11 @@ class IndexedBothSigCodex: """ Ed25519_Sig: str = 'A' # Ed25519 sig appears same in both lists if any. ECDSA_256k1_Sig: str = 'C' # ECDSA secp256k1 sig appears same in both lists if any. + ECDSA_256r1_Sig: str = "E" # ECDSA secp256r1 sig appears same in both lists if any. Ed448_Sig: str = '0A' # Ed448 signature appears in both lists. Ed25519_Big_Sig: str = '2A' # Ed25519 sig appears in both listsy. ECDSA_256k1_Big_Sig: str = '2C' # ECDSA secp256k1 sig appears in both lists. + ECDSA_256r1_Big_Sig: str = "2E" # ECDSA secp256r1 sig appears in both lists. Ed448_Big_Sig: str = '3A' # Ed448 signature appears in both lists. def __iter__(self): @@ -3536,10 +4030,10 @@ def __iter__(self): Xizage = namedtuple("Xizage", "hs ss os fs ls") class Indexer: - """ - Indexer is fully qualified cryptographic material primitive base class for - indexed primitives. Indexed codes are a mix of indexed and variable length - because code table has two char codes for compact variable length. + """ Indexer is fully qualified cryptographic material primitive base class for + indexed primitives. In special cases some codes in the Index code table + may be of variable length (i.e. not indexed) when the full size table entry + is None. In that case the index is used instread as the length. Sub classes are derivation code and key event element context specific. @@ -3583,12 +4077,16 @@ class Indexer: 'B': Xizage(hs=1, ss=1, os=0, fs=88, ls=0), 'C': Xizage(hs=1, ss=1, os=0, fs=88, ls=0), 'D': Xizage(hs=1, ss=1, os=0, fs=88, ls=0), + 'E': Xizage(hs=1, ss=1, os=0, fs=88, ls=0), + 'F': Xizage(hs=1, ss=1, os=0, fs=88, ls=0), '0A': Xizage(hs=2, ss=2, os=1, fs=156, ls=0), '0B': Xizage(hs=2, ss=2, os=1, fs=156, ls=0), '2A': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), '2B': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), '2C': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), '2D': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), + '2E': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), + '2F': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), '3A': Xizage(hs=2, ss=6, os=3, fs=160, ls=0), '3B': Xizage(hs=2, ss=6, os=3, fs=160, ls=0), '0z': Xizage(hs=2, ss=2, os=0, fs=None, ls=0), @@ -4186,16 +4684,19 @@ def __iter__(self): @dataclass(frozen=True) class ProtocolGenusCodex: - """ProtocolGenusCodex is codex of protocol genera. + """ProtocolGenusCodex is codex of protocol genera for code table. Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. """ - KERI: str = '--AAA' # KERI ACDC Protocol Stack + KERI: str = '--AAA' # KERI and ACDC Protocol Stacks share the same tables + ACDC: str = '--AAA' # KERI and ACDC Protocol Stacks share the same tables def __iter__(self): - return iter(astuple(self)) + return iter(astuple(self)) # enables inclusion test with "in" + # duplicate values above just result in multiple entries in tuple so + # in inclusion still works ProDex = ProtocolGenusCodex() # Make instance @@ -4604,6 +5105,10 @@ class Sadder: """ Sadder is self addressed data (SAD) serializer-deserializer class + Instance creation of a Sadder does not verifiy it .said property it merely + extracts it. In order to ensure Sadder instance has a verified .said then + must call .saider.verify(sad=self.ked) + Has the following public properties: Properties: @@ -4612,7 +5117,8 @@ class Sadder: kind (str): serialization kind coring.Serials such as JSON, CBOR, MGPK, CESR size (int): number of bytes in serialization version (Versionage): protocol version (Major, Minor) - ident (Identage): protocol identifier such as KERI, ACDC + proto (str): Protocolage value as protocol identifier such as KERI, ACDC + label (str): Saidage value as said field label saider (Saider): of SAID of this SAD .ked['d'] if present said (str): SAID of .saider qb64 saidb (bytes): SAID of .saider qb64b @@ -4625,7 +5131,7 @@ class Sadder: supported kinds are 'json', 'cbor', 'msgpack', 'binary' ._size is int of number of bytes in serialed event only ._version is Versionage instance of event version - ._ident (Identage): protocol type identifier + ._proto (str): Protocolage value as protocol type identifier ._saider (Saider): instance for this Sadder's SAID Note: @@ -4633,33 +5139,38 @@ class Sadder: """ - def __init__(self, raw=b'', ked=None, kind=None, sad=None, code=MtrDex.Blake3_256): + def __init__(self, raw=b'', ked=None, sad=None, kind=None, saidify=False, + code=MtrDex.Blake3_256): """ - Deserialize if raw provided - Serialize if ked provided but not raw + Deserialize if raw provided does not verify assumes embedded said is valid + Serialize if ked provided but not raw verifies if verify is True? When serializing if kind provided then use kind instead of field in ked Parameters: - raw is bytes of serialized event plus any attached signatures + raw (bytes): serialized event ked is key event dict or None if None its deserialized from raw kind is serialization kind string value or None (see namedtuple coring.Serials) supported kinds are 'json', 'cbor', 'msgpack', 'binary' if kind is None then its extracted from ked or raw - code is .diger default digest code + saidify (bool): True means compute said for ked + code is .diger default digest code for computing said .saider """ self._code = code # need default code for .saider if raw: # deserialize raw using property setter self.raw = raw # raw property setter does the deserialization elif ked: # serialize ked using property setter + #ToDo when pass in ked and saidify True then compute said self._kind = kind self.ked = ked # ked property setter does the serialization elif sad: - self._clone(sad=sad) # create saider of sad + # ToDo do we need this or should we be using ked above with saidify flag + self._clone(sad=sad) # copy fields from sad else: raise ValueError("Improper initialization need sad, raw or ked.") + def _clone(self, sad): """ copy hidden attributes from sad """ self._raw = sad.raw @@ -4667,7 +5178,7 @@ def _clone(self, sad): self._kind = sad.kind self._size = sad.size self._version = sad.version - self._ident = sad.ident + self._proto = sad.proto self._saider = sad.saider @@ -4685,7 +5196,7 @@ def _inhale(self, raw): loads and jumps of json use str whereas cbor and msgpack use bytes """ - ident, kind, version, size = sniff(raw) + proto, kind, version, size = sniff(raw) if version != Version: raise VersionError("Unsupported version = {}.{}, expected {}." "".format(version.major, version.minor, Version)) @@ -4694,26 +5205,35 @@ def _inhale(self, raw): ked = loads(raw=raw, size=size, kind=kind) - return ked, ident, kind, version, size + return ked, proto, kind, version, size def _exhale(self, ked, kind=None): """ - ked is key event dict - kind is serialization if given else use one given in ked - Returns tuple of (raw, kind, ked, version) where: - raw is serialized event as bytes of kind - kind is serialzation kind - ked is key event dict - version is Versionage instance + Returns sizeify(ked, kind) + + From sizeify + Returns tuple of (raw, proto, kind, ked, version) where: + raw (str): serialized event as bytes of kind + proto (str): protocol type as value of Protocolage + kind (str): serialzation kind as value of Serialage + ked (dict): key event dict or sad dict + version (Versionage): instance + + Parameters: + ked (dict): key event dict or sad dict + kind (str): value of Serials serialization kind. + When not provided use Assumes only supports Version """ return sizeify(ked=ked, kind=kind) + def compare(self, said=None): """ Returns True if said and either .saider.qb64 or .saider.qb64b match + via string equality == Convenience method to allow comparison of own .saider digest self.raw with some other purported said of self.raw @@ -4741,10 +5261,10 @@ def raw(self): @raw.setter def raw(self, raw): """ raw property setter """ - ked, ident, kind, version, size = self._inhale(raw=raw) + ked, proto, kind, version, size = self._inhale(raw=raw) self._raw = bytes(raw[:size]) # crypto ops require bytes not bytearray self._ked = ked - self._ident = ident + self._proto = proto self._kind = kind self._version = version self._size = size @@ -4758,11 +5278,11 @@ def ked(self): @ked.setter def ked(self, ked): """ ked property setter assumes ._kind """ - raw, ident, kind, ked, version = self._exhale(ked=ked, kind=self._kind) + raw, proto, kind, ked, version = self._exhale(ked=ked, kind=self._kind) size = len(raw) self._raw = raw[:size] self._ked = ked - self._ident = ident + self._proto = proto self._kind = kind self._size = size self._version = version @@ -4776,10 +5296,10 @@ def kind(self): @kind.setter def kind(self, kind): """ kind property setter Assumes ._ked. Serialization kind. """ - raw, ident, kind, ked, version = self._exhale(ked=self._ked, kind=kind) + raw, proto, kind, ked, version = self._exhale(ked=self._ked, kind=kind) size = len(raw) self._raw = raw[:size] - self._ident = ident + self._proto = proto self._ked = ked self._kind = kind self._size = size @@ -4805,14 +5325,14 @@ def version(self): @property - def ident(self): - """ ident property getter - protocol identifer type instance of Identage such as KERI ACDC + def proto(self): + """ proto property getter + protocol identifier type value of Protocolage such as 'KERI' or 'ACDC' Returns: - (Identage): + (str): Protocolage value as protocol type """ - return self._ident + return self._proto @property @@ -4849,214 +5369,6 @@ def pretty(self, *, size=1024): return json.dumps(self.ked, indent=1)[:size if size is not None else None] -class Serder(Sadder): - """ - Serder is versioned protocol key event message serializer-deserializer class - - Only supports current version VERSION - - Has the following public properties: - - Inherited Properties: - raw (bytes): of serialized event only - ked (dict): self addressed data dict - kind (str): serialization kind coring.Serials such as JSON, CBOR, MGPK, CESR - size (int): number of bytes in serialization - version (Versionage): protocol version (Major, Minor) - ident (Identage): protocol identifier such as KERI, ACDC - saider (Saider): of SAID of this SAD .ked['d'] if present - said (str): SAID of .saider qb64 - saidb (bytes): SAID of .saider qb64b - pretty (str): Pretty JSON of this SAD - - Properties: - .diger is Diger instance of digest of .raw - .dig is qb64 digest from .diger - .digb is qb64b digest from .diger - .verfers is list of Verfers converted from .ked["k"] - .werfers is list of Verfers converted from .ked["b"] - .tholder is Tholder instance from .ked["kt'] else None - .ntholder is Tholder instance from .ked["nt'] else None - sner (Number): instance converted from sequence number .ked["s"] hex str - sn (int): sequence number converted from .ked["s"] - fner (Number): instance converted from first seen ordinal number - .ked["f"] hex str if any otherwise None - fn (int): first seen ordinal number converted from .ked["f"] if any - otherwise None - .pre is qb64 str of identifier prefix from .ked["i"] - .preb is qb64b bytes of identifier prefix from .ked["i"] - - Hidden Attributes: - ._raw is bytes of serialized event only - ._ked is key event dict - ._kind is serialization kind string value (see namedtuple coring.Serials) - supported kinds are 'json', 'cbor', 'msgpack', 'binary' - ._version is Versionage instance of event version - ._size is int of number of bytes in serialed event only - ._code is default code for .diger - ._diger is Diger instance of digest of .raw - - Note: - loads and jumps of json use str whereas cbor and msgpack use bytes - - """ - - def __init__(self, raw=b'', ked=None, kind=None, sad=None, code=MtrDex.Blake3_256): - """ - Deserialize if raw provided - Serialize if ked provided but not raw - When serializing if kind provided then use kind instead of field in ked - - Parameters: - raw is bytes of serialized event plus any attached signatures - ked is key event dict or None - if None its deserialized from raw - sad (Sadder) is clonable base class - kind is serialization kind string value or None (see namedtuple coring.Serials) - supported kinds are 'json', 'cbor', 'msgpack', 'binary' - if kind is None then its extracted from ked or raw - code is .diger default digest code - - """ - super(Serder, self).__init__(raw=raw, ked=ked, kind=kind, sad=sad, code=code) - - if self._ident != Idents.keri: - raise ValueError("Invalid ident {}, must be KERI".format(self._ident)) - - - @property - def verfers(self): - """ - Returns list of Verfer instances as converted from .ked['k']. - One for each key. - verfers property getter - """ - if "k" in self.ked: # establishment event - keys = self.ked["k"] - else: # non-establishment event - keys = [] - - return [Verfer(qb64=key) for key in keys] - - @property - def digers(self): - """ - Returns list of Diger instances as converted from .ked['n']. - One for each next key digests. - digers property getter - """ - if "n" in self.ked: # establishment event - digs = self.ked["n"] - else: # non-establishment event - digs = [] - - return [Diger(qb64=dig) for dig in digs] - - @property - def werfers(self): - """ - Returns list of Verfer instances as converted from .ked['b']. - One for each backer (witness). - werfers property getter - """ - if "b" in self.ked: # inception establishment event - wits = self.ked["b"] - else: # non-establishment event - wits = [] - - return [Verfer(qb64=wit) for wit in wits] - - @property - def tholder(self): - """ - Returns Tholder instance as converted from .ked['kt'] or None if missing. - - """ - return Tholder(sith=self.ked["kt"]) if "kt" in self.ked else None - - @property - def ntholder(self): - """ - Returns Tholder instance as converted from .ked['nt'] or None if missing. - - """ - return Tholder(sith=self.ked["nt"]) if "nt" in self.ked else None - - @property - def sner(self): - """ - sner (Number of sequence number) property getter - Returns: - (Number): of .ked["s"] hex number str converted - """ - return Number(num=self.ked["s"]) # auto converts hex num str to int - - - @property - def sn(self): - """ - sn (sequence number) property getter - Returns: - sn (int): of .sner.num from .ked["s"] - """ - return (self.sner.num) - - - @property - def fner(self): - """ - fner (Number of first seen ordinal) property getter - Returns: - (Number): of .ked["f"] hex number str converted - """ - # auto converts hex num str to int - return Number(num=self.ked["f"]) if "f" in self.ked else None - - - @property - def fn(self): - """ - fn (first seen ordinal number) property getter - Returns: - fn (int): of .fner.num from .ked["f"] - """ - return (self.fner.num) - - - @property - def pre(self): - """ - Returns str qb64 of .ked["i"] (identifier prefix) - pre (identifier prefix) property getter - """ - return self.ked["i"] - - - @property - def preb(self): - """ - Returns bytes qb64b of .ked["i"] (identifier prefix) - preb (identifier prefix) property getter - """ - return self.pre.encode("utf-8") - - - @property - def est(self): # establishative - """ Returns True if Serder represents an establishment event """ - return self.ked["t"] in (Ilks.icp, Ilks.rot, Ilks.dip, Ilks.drt) - - - def pretty(self, *, size=1024): - """ - Returns str JSON of .ked with pretty formatting - - ToDo: add default size limit on pretty when used for syslog UDP MCU - like 1024 for ogler.logger - """ - return json.dumps(self.ked, indent=1)[:size if size is not None else None] - - class Tholder: """ @@ -5167,6 +5479,9 @@ def __init__(self, *, thold=None , limen=None, sith=None, **kwa): self._processLimen(limen=limen, **kwa) # kwa for strip elif sith is not None: + if isinstance(sith, str) and not sith: # empty str + raise EmptyMaterialError("Empty threshold expression.") + self._processSith(sith=sith) else: @@ -5473,151 +5788,6 @@ def _satisfy_weighted(self, indices): return False - #@staticmethod - #def exposeds(digers, sigers): - #"""Returns list of ondices (indices) into digers (key digests) as - #exposed by sigers. Uses dual index feature of siger. Assumes that each - #siger.verfer is from the correct key given by siger.index. - #Assumes that digers comes from the list of prior next digests. - - #A key given by siger.verfer (at siger.index in the current key list) - #may expose a prior next key hidden by the diger at siger.ondex in digers. - - #Each returned ondex must be properly exposed by a siger in sigers - #such that the siger's indexed key given by siger.verfer matches the - #siger's ondexed digest from digers. - - #The ondexed digest's code is used to compute the digest of the corresponding - #indexed key verfer to verify that they match. This supports crypto agility - #for different digest codes, i.e. all digests in digers do not have to - #use the same algorithm. - - #Only ondices from properly matching key and digest are returned. - - #Used to extract the indices from the list of prior next digests (digers) - #exposed by the signatures (sigers) on a rotation event of the newly - #current keys given by each .verfer at .index from sigers. Only checks - #keys and digests that correspond to provided signatures not all keys and - #digests defined by the rotation event. - - #Parameters: - #digers (list): of Diger instance prior next digests - #sigers (list): of Siger instances of indexed signature with .verfer - #""" - #odxs = [] - #for siger in sigers: - #try: - #diger = digers[siger.ondex] - #except TypeError as ex: # ondex may be None - #continue - #except IndexError as ex: - #raise ValidationError(f'Invalid ondex={siger.ondex} ' - #f'to expose digest.') from ex - - #kdig = Diger(ser=siger.verfer.qb64b, code=diger.code).qb64 - #if kdig == diger.qb64: - #odxs.append(siger.ondex) - - #return odxs - - - - #def satisfies(self, tholder, indices, digers=None, digs=None): - #"""Given prior next digest list in .digers the provided tholder, - #and indices with either provided digers or digs together constitute a - #satisfycing subset of the prior next threshold. Each index indicates - #which index offset into .digers is the corresponding diger or dig. - - #Returns: - #(bool): True if satisfycing, False otherwise - - #Parameters: - #tholder (Tholder): instance of prior next threshold - #indices (list): of int offsets into .digers - #digers (list | None): of instances of Diger of prior next key digests - #digs (list | None): of digests qb64 of prior next keys - - #""" - #if digers is None: - #if digs is None: - #raise EmptyListError(f"Need digers, digs, verfers, or keys.") - #digers = [Diger(qb64=dig) for dig in digs] - - #return False - - - - #@staticmethod - #def _digest(keys): - #""" - #Returns digs of keys using default digest Blake3 - - #Parameters: - #keys (list): public keys qb64 or qb64b - #""" - #digs = [Diger(ser=key.encode("utf-8") - #if hasattr(key, 'encode') else key).qb64 for key in keys] - - #return digs - - #@staticmethod - #def includes(keys, digs): - #""" - #Returns True if list of Blake3 digests of keys are included as an ordered - #(potentially non-contiguous) subset of digs. - #Each dugest of an element in the provided list keys must appear - #in digs in the same order but not all elements in digs must appear as - #a digest of of an element of keys, i.e returns True if the list of - #digests of keys is an ordered subset of digs - - #Parameters: - #keys (list): public keys qb64 - #digs (list): digests qb64 (prior next digs) - #""" - #kdigs = [Diger(ser=key.encode("utf-8") - #if hasattr(key, 'encode') else key).qb64 for key in keys] - - #if len(kdigs) == len(digs): - #return kdigs == digs - - #elif len(kdigs) < len(digs): - #pdigs = list(digs) # make copy - #finds = [] - #for kdig in kdigs: - #while pdigs: - #pdig = pdigs.pop(0) - #if kdig == pdig: - #finds.append(kdig) - #break - - #if not pdigs: - #break - - #return kdigs == finds - - #else: - #return False - - #@staticmethod - #def matches(sigers, digs): - #"""Returns list of indices from list of sigers for each matching - #Blake3 digest of each siger.verfer qb64 public key to an element of digs - - #Parameters: - #sigers (list): of indexed signatures - #digs (list): digests qb64 (prior next digs) - #""" - #idxs = [] - #for siger in sigers: - #idig = Diger(ser=siger.verfer.qb64b).qb64 # default Blake3 - #try: - #idxs.append(digs.index(idig)) - #except ValueError as ex: - #raise ValidationError(f'indices into verfer unable to locate "' - #f'"{idig} in {digs}') from ex - - #return idxs - class Dicter: """ Dicter class is base class for objects that can be stored in a Suber @@ -5628,7 +5798,7 @@ class Dicter: """ - def __init__(self, raw=b'', pad=None, sad=None, label=Ids.i): + def __init__(self, raw=b'', pad=None, sad=None, label=Saids.i): """ Create Dicter from either pad dict or raw bytes Parameters: diff --git a/src/keri/core/eventing.py b/src/keri/core/eventing.py index be526c287..94a4e7177 100644 --- a/src/keri/core/eventing.py +++ b/src/keri/core/eventing.py @@ -7,48 +7,41 @@ import json import logging from collections import namedtuple -from dataclasses import dataclass, astuple +from dataclasses import dataclass, astuple, asdict, field from urllib.parse import urlsplit from math import ceil -from ordered_set import OrderedSet as oset +from ordered_set import OrderedSet as oset from hio.help import decking -from . import coring -from .coring import (versify, Serials, Ilks, MtrDex, NonTransDex, CtrDex, Counter, +from . import coring, serdering +from .coring import (versify, Serials, Ilks, MtrDex, PreDex, DigDex, + NonTransDex, CtrDex, Counter, Number, Seqner, Siger, Cigar, Dater, Indexer, IdrDex, - Verfer, Diger, Prefixer, Serder, Tholder, Saider) + Verfer, Diger, Prefixer, Tholder, Saider) +from . import serdering from .. import help from .. import kering from ..db import basing, dbing +from ..db.basing import KeyStateRecord, StateEERecord from ..db.dbing import dgKey, snKey, fnKey, splitKeySN, splitKey -from ..help import helping + from ..kering import (MissingEntryError, - ValidationError, MissingSignatureError, + ValidationError, DerivationError, MissingSignatureError, MissingWitnessSignatureError, UnverifiedReplyError, MissingDelegationError, OutOfOrderError, LikelyDuplicitousError, UnverifiedWitnessReceiptError, UnverifiedReceiptError, UnverifiedTransferableReceiptError, QueryNotFoundError) -from ..kering import Version +from ..kering import Version, Versionage +from ..kering import (ICP_LABELS, DIP_LABELS, ROT_LABELS, DRT_LABELS, IXN_LABELS, + RPY_LABELS) + +from ..help import helping logger = help.ogler.getLogger() EscrowTimeoutPS = 3600 # seconds for partial signed escrow timeout -ICP_LABELS = ["v", "i", "s", "t", "kt", "k", "n", - "bt", "b", "c", "a"] -DIP_LABELS = ["v", "i", "s", "t", "kt", "k", "n", - "bt", "b", "c", "a", "di"] -ROT_LABELS = ["v", "i", "s", "t", "p", "kt", "k", "n", - "bt", "br", "ba", "a"] -DRT_LABELS = ["v", "i", "s", "t", "p", "kt", "k", "n", - "bt", "br", "ba", "a"] -IXN_LABELS = ["v", "i", "s", "t", "p", "a"] - -KSN_LABELS = ["v", "i", "s", "p", "d", "f", "dt", "et", "kt", "k", "n", - "bt", "b", "c", "ee", "di"] - -RPY_LABELS = ["v", "t", "d", "dt", "r", "a"] - +MaxIntThold = 2 ** 32 - 1 @dataclass(frozen=True) class TraitCodex: @@ -60,7 +53,8 @@ class TraitCodex: """ EstOnly: str = 'EO' # Only allow establishment events DoNotDelegate: str = 'DND' # Dot not allow delegated identifiers - NoBackers: str = 'NB' # Do not allow any backers for registry + NoBackers: str = 'NB' # Do not allow any registrar backers + Backers: str = 'RB' # Registrar backer provided in Registrar seal def __iter__(self): return iter(astuple(self)) @@ -73,6 +67,12 @@ def __iter__(self): # for the following Seal namedtuples use the ._asdict() method to convert to dict # when using in events +# to convert seal namedtuple to dict use namedtuple._asdict() +# seal == SealEvent(i="abc",s="1",d="efg") +# sealdict =seal._asdict() +# to convet dict to namedtuple use ** unpacking as in seal = SealDigest(**sealdict) +# to check if dict of seal matches fields of associted namedtuple +# if tuple(sealdict.keys()) == SealEvent._fields: # Digest Seal: uniple (d,) # d = digest qb64 of data (usually SAID) @@ -98,6 +98,14 @@ def __iter__(self): # used to indicate to get the latest keys available from KEL for 'i' SealLast = namedtuple("SealLast", 'i') +# Establishment Event for Source of Message: duple (s, d) +# s = sn of event as lowercase hex string no leading zeros, +# d = SAID digest qb64 of event +# the pre is provided in the 'i' field of the message itself which is the qb64 +# of identifier prefix of KEL from which to get est, event given by 's d' +# use SealSourceCouples count code for attachment +SealEst = namedtuple("SealEst", 's d') + # State (latest current) Event: triple (s, t, d) # s = sn of latest event as lowercase hex string no leading zeros, # t = message type of latest event (ilk) @@ -503,7 +511,7 @@ def validateSigs(serder, sigers, verfers, tholder): False otherwise. Parameters: - serder (coring.Serder): instance of message + serder (SerderKERI): instance of message sigers (Iterable): Siger instances of indexed signatures. Index is offset into verfers list each providing verification key verfers (Iterable): Verfer instances of keys @@ -544,7 +552,7 @@ def fetchTsgs(db, saider, snh=None): prefixer (Prefixer): instance trans signer aid, seqner (Seqner): of sn of trans signer key state est event diger (Diger): of digest of trans signer key state est event - signers (list): of Siger instances of indexed signatures + sigers (list): of Siger instances of indexed signatures Parameters: db: (Cesr @@ -572,7 +580,163 @@ def fetchTsgs(db, saider, snh=None): return tsgs -MaxIntThold = 2 ** 32 - 1 + +def state(pre, + sn, + pig, + dig, + fn, + eilk, + keys, + eevt, + stamp=None, # default current datetime + sith=None, # default based on keys + ndigs=None, + nsith=None, + toad=None, # default based on wits + wits=None, # default to [] + cnfg=None, # default to [] + dpre=None, + version=Version, + kind=Serials.json, + intive = False, + ): + """ + Returns instance of KeyStateRecord in support of key state notification messages. + Utility function to automate creation embedded key static notices + + Parameters: + pre (str): identifier prefix qb64 + sn (int): sequence number of latest event + pig (str): SAID qb64 of prior event + dig (str): SAID qb64 of latest (current) event + fn (int): first seen ordinal number of latest event + eilk (str): event (message) type (ilk) of latest (current) event + keys (list): qb64 signing keys + eevt (StateEstEvent): namedtuple (s,d,wr,wa) for latest est event + s = sn of est event + d = SAID of est event + wr = witness remove list (cuts) + wa = witness add list (adds) + stamp (str | None): date-time-stamp RFC-3339 profile of ISO-8601 datetime of + creation of message or data + sith sith (int | str | list | None): current signing threshold input to Tholder + ndigs (list | None): current signing key digests qb64 + nsith int | str | list | None): next signing threshold input to Tholder + toad (int | str | None): witness threshold number if str then hex str + wits (list | None): prior witness identifier prefixes qb64 + cnfg (list | None): strings from TraitDex configuration trait strings + dpre (str | None): identifier prefix qb64 delegator if any + If None then dpre in state is empty "" + version (Version): KERI protocol version string + kind (str): serialization kind from Serials + intive (bool): True means sith, nsith, and toad are serialized as ints + instead of hex str when numeric threshold + + """ + sner = Number(num=sn) # raises InvalidValueError if sn < 0 + fner = Number(num=fn) # raises InvalidValueError if fn < 0 + + if eilk not in (Ilks.icp, Ilks.rot, Ilks.ixn, Ilks.dip, Ilks.drt): + raise ValueError(f"Invalid event type et={eilk} in key state.") + + if stamp is None: + stamp = helping.nowIso8601() + + if sith is None: + sith = "{:x}".format(max(1, ceil(len(keys) / 2))) + + tholder = Tholder(sith=sith) + if tholder.num is not None and tholder.num < 1: + raise ValueError(f"Invalid sith = {tholder.num} less than 1.") + if tholder.size > len(keys): + raise ValueError(f"Invalid sith = {tholder.num} for keys = {keys}") + + if ndigs is None: + ndigs = [] + + if nsith is None: + nsith = max(0, ceil(len(ndigs) / 2)) + + ntholder = Tholder(sith=nsith) + if ntholder.num is not None and ntholder.num < 0: + raise ValueError(f"Invalid nsith = {ntholder.num} less than 0.") + if ntholder.size > len(ndigs): + raise ValueError(f"Invalid nsith = {ntholder.num} for keys = {ndigs}") + + wits = wits if wits is not None else [] + witset = oset(wits) + if len(witset) != len(wits): + raise ValueError(f"Invalid wits = {wits}, has duplicates.") + + if toad is None: + if not witset: + toad = 0 + else: + toad = max(1, ceil(len(witset) / 2)) + + if toad is None: + if not witset: + toad = 0 + else: # compute default f and m for len(wits) + toad = ample(len(witset)) + toader = Number(num=toad) + + if witset: + if toader.num < 1 or toader.num > len(witset): # out of bounds toad + raise ValueError(f"Invalid toad = {toader.num} for wits = {witset}") + else: + if toader.num != 0: # invalid toad + raise ValueError(f"Invalid toad = {toader.num} for wits = {witset}") + + if not eevt or not isinstance(eevt, StateEstEvent): + raise ValueError(f"Missing or invalid latest est event = {eevt} for key " + f"state.") + eesner = Number(numh=eevt.s) # if not whole number raises InvalidValueError + + # cuts is relative to prior wits not current wits provided here + cuts = eevt.br if eevt.br is not None else [] + cutset = oset(cuts) + if len(cutset) != len(cuts): # duplicates in cuts + raise ValueError(f"Invalid cuts = {cuts}, has " + f"duplicates, in latest est event, .") + + # adds is relative to prior wits not current wits provided here + adds = eevt.ba if eevt.ba is not None else [] + addset = oset(adds) + + if len(addset) != len(adds): # duplicates in adds + raise ValueError(f"Invalid adds = {adds}, has duplicates," + f" in latest est event,.") + + if cutset & addset: # non empty intersection + raise ValueError(f"Intersecting cuts = {cuts} and adds = {adds} in " + f"latest est event.") + + ksr = basing.KeyStateRecord( + vn=list(version), # version number as list [major, minor] + i=pre, # qb64 prefix + s=sner.numh, # lowercase hex string no leading zeros + p=pig, + d=dig, + f=fner.numh, # lowercase hex string no leading zeros + dt=stamp, + et=eilk, + kt=(tholder.num if intive and tholder.num is not None and + tholder.num <= MaxIntThold else tholder.sith), + k=keys, # list of qb64 + nt=(ntholder.num if intive and ntholder.num is not None and + ntholder.num <= MaxIntThold else ntholder.sith), + n=ndigs, + bt=toader.num if intive and toader.num <= MaxIntThold else toader.numh, + b=wits, # list of qb64 may be empty + c=cnfg if cnfg is not None else [], + ee=StateEERecord._fromdict(eevt._asdict()), # latest est event dict + di=dpre if dpre is not None else "", + ) + return ksr # return KeyStateRecord use asdict(ksr) to get dict version + + def incept(keys, *, @@ -586,7 +750,7 @@ def incept(keys, version=Version, kind=Serials.json, code=None, - intive = False, + intive=False, delpre=None, ): """ @@ -606,7 +770,8 @@ def incept(keys, kind (str): serialization kind from Serials code (str | None): derivation code for computed prefix intive (bool): True means sith, nsith, and toad are serialized as ints - not hex str when numeric threshold + not hex str when numeric threshold. Most compact JSON representation + when Numbers are small because no quotes. Number accepts both. delpre (str | None): delegator identifier prefix qb64. When not None makes this a msg type "dip", delegated inception event. """ @@ -658,8 +823,6 @@ def incept(keys, data = data if data is not None else [] - # see compact labels in KID0003.md - ked = dict(v=vs, # version string t=ilk, d="", # qb64 SAID @@ -670,40 +833,54 @@ def incept(keys, k=keys, # list of qb64 nt=(ntholder.num if intive and ntholder.num is not None and ntholder.num <= MaxIntThold else ntholder.sith), - n=ndigs, # hash qual Base64 + n=ndigs, # list of hashes qb64 bt=toader.num if intive and toader.num <= MaxIntThold else toader.numh, b=wits, # list of qb64 may be empty c=cnfg, # list of config ordered mappings may be empty a=data, # list of seal dicts ) + pre = "" + saids = None if delpre is not None: # delegated inception with ilk = dip - ked['di'] = delpre - if code is None: - code = MtrDex.Blake3_256 # Defaults to self addressing digest - - - if delpre is None and code is None and len(keys) == 1: - prefixer = Prefixer(qb64=keys[0]) # defaults to not self-addressing code - if prefixer.digestive: - raise ValueError("Invalid code, digestive={}, must be derived from" - " ked.".format(prefixer.code)) - else: - # raises derivation error if non-empty nxt but ephemeral code - prefixer = Prefixer(ked=ked, code=code) # Derive AID from ked and code - - if delpre is not None: - if not prefixer.digestive: - raise ValueError(f"Invalid derivation code = {prefixer.code} " - f"for delegation. Must be digestive") - - ked["i"] = prefixer.qb64 # update pre element in ked with pre qb64 - if prefixer.digestive: - ked["d"] = prefixer.qb64 - else: - _, ked = coring.Saider.saidify(sad=ked) - - return Serder(ked=ked) # return serialized ked + ked['di'] = delpre # SerderKERI .verify will ensure valid prefix + else: # non delegated + if (code is None or code not in DigDex) and len(keys) == 1: # use key[0] as default + ked["i"] = keys[0] # SerderKERI .verify will ensure valid prefix + + if code is not None and code in PreDex: # use code to override all else + saids = {'i': code} + + serder = serdering.SerderKERI(sad=ked, makify=True, saids=saids) + serder._verify() # raises error if fails verifications + return serder + + #if delpre is not None: # delegated inception with ilk = dip + #ked['di'] = delpre + #if code is None: + #code = MtrDex.Blake3_256 # force digestive + + #if delpre is None and code is None and len(keys) == 1: + #prefixer = Prefixer(qb64=keys[0]) # defaults to not digestive code + #if prefixer.digestive: + #raise ValueError("Invalid code, digestive={}, must be derived from" + #" ked.".format(prefixer.code)) + #else: # digestive + ## raises derivation error if non-empty nxt but ephemeral code + #prefixer = Prefixer(ked=ked, code=code) # Derive AID from ked and code + + #if delpre is not None: + #if not prefixer.digestive: + #raise ValueError(f"Invalid derivation code = {prefixer.code} " + #f"for delegation. Must be digestive") + + #ked["i"] = prefixer.qb64 # update pre element in ked with pre qb64 + #if prefixer.digestive: + #ked["d"] = prefixer.qb64 + #else: + #_, ked = coring.Saider.saidify(sad=ked) + + #return Serder(ked=ked) # return serialized ked def delcept(keys, delpre, **kwa): """ @@ -864,9 +1041,14 @@ def rotate(pre, ba=adds, # list of qb64 may be empty a= data if data is not None else [], # list of seals ) - _, ked = coring.Saider.saidify(sad=ked) - return Serder(ked=ked) # return serialized ked + serder = serdering.SerderKERI(sad=ked, makify=True) + serder._verify() # raises error if fails verifications + return serder + + #_, ked = coring.Saider.saidify(sad=ked) + + #return Serder(ked=ked) # return serialized ked def deltate(pre, keys, @@ -932,242 +1114,61 @@ def interact(pre, data = data if data is not None else [] - ked = dict(v=vs, # version string + sad = dict(v=vs, # version string t=ilk, d="", i=pre, # qb64 prefix s=sner.numh, # hex string no leading zeros lowercase - p=dig, # qb64 digest of prior event - a=data, # list of seals - ) - _, ked = coring.Saider.saidify(sad=ked) - - return Serder(ked=ked) # return serialized ked - - -def receipt(pre, - sn, - said, - *, - version=Version, - kind=Serials.json - ): - """ - Returns serder of event receipt message. Used for both non-trans and trans - signers as determined by signature attachment type (cigar or siger) - - Utility function to automate creation of receipts. - - Parameters: - pre is qb64 str of prefix of event being receipted - sn is int sequence number of event being receipted - said is qb64 of said of event being receipted - version is Version instance of receipt - kind is serialization kind of receipt - """ - vs = versify(version=version, kind=kind, size=0) - ilk = Ilks.rct - - sner = Number(num=sn) - if sner.num < 0: # sn for receipt must be >= 1 - raise ValueError(f"Invalid sn = 0x{sner.numh} for rect.") - - ked = dict(v=vs, # version string - t=ilk, # Ilks.rct - d=said, # qb64 digest of receipted event - i=pre, # qb64 prefix - s=sner.numh, # hex string no leading zeros lowercase - ) - - return Serder(ked=ked) # return serialized ked - - -def state(pre, - sn, - pig, - dig, - fn, - eilk, - keys, - eevt, - stamp=None, # default current datetime - sith=None, # default based on keys - ndigs=None, - nsith=None, - toad=None, # default based on wits - wits=None, # default to [] - cnfg=None, # default to [] - dpre=None, - version=Version, - kind=Serials.json, - intive = False, - ): - """ - Returns serder of key state notification message. - Utility function to automate creation of rotation events. - - Parameters: - pre (str): identifier prefix qb64 - sn (int): sequence number of latest event - pig (str): SAID qb64 of prior event - dig (str): SAID qb64 of latest (current) event - fn (int): first seen ordinal number of latest event - eilk (str): event (message) type (ilk) of latest (current) event - keys (list): qb64 signing keys - eevt (StateEstEvent): namedtuple (s,d,wr,wa) for latest est event - s = sn of est event - d = SAID of est event - wr = witness remove list (cuts) - wa = witness add list (adds) - stamp (str | None): date-time-stamp RFC-3339 profile of ISO-8601 datetime of - creation of message or data - sith sith (int | str | list | None): current signing threshold input to Tholder - ndigs (list | None): current signing key digests qb64 - nsith int | str | list | None): next signing threshold input to Tholder - toad (int | str | None): witness threshold number if str then hex str - wits (list | None): prior witness identifier prefixes qb64 - cnfg (list | None): strings from TraitDex configuration trait strings - dpre (str | None): identifier prefix qb64 delegator if any - If None then dpre in state is empty "" - version (Version): KERI protocol version string - kind (str): serialization kind from Serials - intive (bool): True means sith, nsith, and toad are serialized as ints - instead of hex str when numeric threshold - - KeyStateDict: - { - "v": "KERI10JSON00011c_", - "i": "EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM", - "s": "2":, - "p": "EYAfSVPzhzZ-i0d8JZS6b5CMAoTNZH3ULvaU6JR2nmwy", - "d": "EAoTNZH3ULvaU6JR2nmwyYAfSVPzhzZ-i0d8JZS6b5CM", - "f": "3", - "dt": "2020-08-22T20:35:06.687702+00:00", - "et": "rot", - "kt": "1", - "k": ["DaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM"], - "n": "EZ-i0d8JZAoTNZH3ULvaU6JR2nmwyYAfSVPzhzS6b5CM", - "bt": "1", - "b": ["DnmwyYAfSVPzhzS6b5CMZ-i0d8JZAoTNZH3ULvaU6JR2"], - "c": ["eo"], - "ee": - { - "s": "1", - "d": "EAoTNZH3ULvaU6JR2nmwyYAfSVPzhzZ-i0d8JZS6b5CM", - "br": ["Dd8JZAoTNZH3ULvaU6JR2nmwyYAfSVPzhzS6b5CMZ-i0"], - "ba": ["DnmwyYAfSVPzhzS6b5CMZ-i0d8JZAoTNZH3ULvaU6JR2"] - }, - "di": "EYAfSVPzhzS6b5CMaU6JR2nmwyZ-i0d8JZAoTNZH3ULv", - } - - "di": "" when not delegated - "r": "" when no route - """ - vs = versify(version=version, kind=kind, size=0) - - sner = Number(num=sn) # raises InvalidValueError if sn < 0 - - fner = Number(num=fn) # raises InvalidValueError if fn < 0 - - if eilk not in (Ilks.icp, Ilks.rot, Ilks.ixn, Ilks.dip, Ilks.drt): - raise ValueError(f"Invalid event type et={eilk} in key state.") - - if stamp is None: - stamp = helping.nowIso8601() - - if sith is None: - sith = "{:x}".format(max(1, ceil(len(keys) / 2))) - - tholder = Tholder(sith=sith) - if tholder.num is not None and tholder.num < 1: - raise ValueError(f"Invalid sith = {tholder.num} less than 1.") - if tholder.size > len(keys): - raise ValueError(f"Invalid sith = {tholder.num} for keys = {keys}") - - if ndigs is None: - ndigs = [] - - if nsith is None: - nsith = max(0, ceil(len(ndigs) / 2)) - - ntholder = Tholder(sith=nsith) - if ntholder.num is not None and ntholder.num < 0: - raise ValueError(f"Invalid nsith = {ntholder.num} less than 0.") - if ntholder.size > len(ndigs): - raise ValueError(f"Invalid nsith = {ntholder.num} for keys = {ndigs}") - - wits = wits if wits is not None else [] - witset = oset(wits) - if len(witset) != len(wits): - raise ValueError(f"Invalid wits = {wits}, has duplicates.") - - if toad is None: - if not witset: - toad = 0 - else: - toad = max(1, ceil(len(witset) / 2)) - - if toad is None: - if not witset: - toad = 0 - else: # compute default f and m for len(wits) - toad = ample(len(witset)) - toader = Number(num=toad) - - if witset: - if toader.num < 1 or toader.num > len(witset): # out of bounds toad - raise ValueError(f"Invalid toad = {toader.num} for wits = {witset}") - else: - if toader.num != 0: # invalid toad - raise ValueError(f"Invalid toad = {toader.num} for wits = {witset}") + p=dig, # qb64 digest of prior event + a=data, # list of seals + ) - if not eevt or not isinstance(eevt, StateEstEvent): - raise ValueError(f"Missing or invalid latest est event = {eevt} for key " - f"state.") - eesner = Number(numh=eevt.s) # if not whole number raises InvalidValueError + serder = serdering.SerderKERI(sad=sad, makify=True) + serder._verify() # raises error if fails verifications + return serder - # cuts is relative to prior wits not current wits provided here - cuts = eevt.br if eevt.br is not None else [] - cutset = oset(cuts) - if len(cutset) != len(cuts): # duplicates in cuts - raise ValueError(f"Invalid cuts = {cuts}, has " - f"duplicates, in latest est event, .") + #_, ked = coring.Saider.saidify(sad=ked) - # adds is relative to prior wits not current wits provided here - adds = eevt.ba if eevt.ba is not None else [] - addset = oset(adds) + #return Serder(ked=ked) # return serialized ked - if len(addset) != len(adds): # duplicates in adds - raise ValueError(f"Invalid adds = {adds}, has duplicates," - f" in latest est event,.") - if cutset & addset: # non empty intersection - raise ValueError(f"Intersecting cuts = {cuts} and adds = {adds} in " - f"latest est event.") +def receipt(pre, + sn, + said, + *, + version=Version, + kind=Serials.json + ): + """ + Returns serder of event receipt message. Used for both non-trans and trans + signers as determined by signature attachment type (cigar or siger) + Utility function to automate creation of receipts. + + Parameters: + pre is qb64 str of prefix of event being receipted + sn is int sequence number of event being receipted + said is qb64 of said of event being receipted + version is Version instance of receipt + kind is serialization kind of receipt + """ + vs = versify(version=version, kind=kind, size=0) + ilk = Ilks.rct + + sner = Number(num=sn) + if sner.num < 0: # sn for receipt must be >= 1 + raise ValueError(f"Invalid sn = 0x{sner.numh} for rect.") - ksd = dict(v=vs, # version string + sad = dict(v=vs, # version string + t=ilk, # Ilks.rct + d=said, # qb64 digest of receipted event i=pre, # qb64 prefix - s=sner.numh, # lowercase hex string no leading zeros - p=pig, - d=dig, - f=fner.numh, # lowercase hex string no leading zeros - dt=stamp, - et=eilk, - kt=(tholder.num if intive and tholder.num is not None and - tholder.num <= MaxIntThold else tholder.sith), - k=keys, # list of qb64 - nt=(ntholder.num if intive and ntholder.num is not None and - ntholder.num <= MaxIntThold else ntholder.sith), - n=ndigs, - bt=toader.num if intive and toader.num <= MaxIntThold else toader.numh, - b=wits, # list of qb64 may be empty - c=cnfg if cnfg is not None else [], - ee=eevt._asdict(), # latest est event dict - di=dpre if dpre is not None else "", + s=sner.numh, # hex string no leading zeros lowercase ) - return Serder(ked=ksd) # return serialized ksd + serder = serdering.SerderKERI(sad=sad, makify=True) + serder._verify() # raises error if fails verifications + return serder def query(route="", @@ -1196,6 +1197,7 @@ def query(route="", { "v" : "KERI10JSON00011c_", "t" : "qry", + "d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM", "dt": "2020-08-22T17:50:12.988921+00:00", "r" : "logs", "rr": "log/processor", @@ -1210,7 +1212,7 @@ def query(route="", vs = versify(version=version, kind=kind, size=0) ilk = Ilks.qry - ked = dict(v=vs, # version string + sad = dict(v=vs, # version string t=ilk, d="", dt=stamp if stamp is not None else helping.nowIso8601(), @@ -1218,9 +1220,14 @@ def query(route="", rr=replyRoute, q=query, ) - _, ked = coring.Saider.saidify(sad=ked) - return Serder(ked=ked) # return serialized ked + serder = serdering.SerderKERI(sad=sad, makify=True) + serder._verify() # raises error if fails verifications + return serder + + #_, ked = coring.Saider.saidify(sad=ked) + + #return Serder(ked=ked) # return serialized ked def reply(route="", @@ -1251,14 +1258,14 @@ def reply(route="", "r" : "logs/processor", "a" : { - "d": "EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM", + "d": "EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM", "i": "EAoTNZH3ULvYAfSVPzhzS6baU6JR2nmwyZ-i0d8JZ5CM", "name": "John Jones", "role": "Founder", } } """ - label = coring.Ids.d + label = coring.Saids.d vs = versify(version=version, kind=kind, size=0) if data is None: data = {} @@ -1271,34 +1278,68 @@ def reply(route="", a=data if data else {}, # attributes ) - _, sad = coring.Saider.saidify(sad=sad, kind=kind, label=label) + serder = serdering.SerderKERI(sad=sad, makify=True) + serder._verify() # raises error if fails verifications + return serder - saider = coring.Saider(qb64=sad[label]) - if not saider.verify(sad=sad, kind=kind, label=label, prefixed=True): - raise ValidationError("Invalid said = {} for reply msg={}." - "".format(saider.qb64, sad)) + #_, sad = coring.Saider.saidify(sad=sad, kind=kind, label=label) - return Serder(ked=sad) # return serialized Self-Addressed Data (SAD) + #saider = coring.Saider(qb64=sad[label]) + #if not saider.verify(sad=sad, kind=kind, label=label, prefixed=True): + #raise ValidationError("Invalid said = {} for reply msg={}." + #"".format(saider.qb64, sad)) + + #return Serder(ked=sad) # return serialized Self-Addressed Data (SAD) def prod(route="", - replyRoute="", - pre=None, - digs=None, - version=Version, - kind=Serials.json ): + replyRoute="", + query=None, + stamp=None, + version=Version, + kind=Serials.json): """ Returns serder of prod, 'pro', msg to request disclosure via bare, 'bar' msg of data anchored via seal(s) on KEL for identifier prefix, pre, when given by all SAIDs given in digs list. - TBD + { + "v" : "KERI10JSON00011c_", + "t" : "pro", + "d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM", + "dt": "2020-08-22T17:50:12.988921+00:00", + "r" : "data", + "rr": "data/processor", + "q": + { + "d":"EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM" + } + } """ - pass + vs = versify(version=version, kind=kind, size=0) + ilk = Ilks.pro + + sad = dict(v=vs, # version string + t=ilk, + d="", + dt=stamp if stamp is not None else helping.nowIso8601(), + r=route, # resource type for single item request + rr=replyRoute, + q=query, + ) + + serder = serdering.SerderKERI(sad=sad, makify=True) + serder._verify() # raises error if fails verifications + return serder + + #_, ked = coring.Saider.saidify(sad=ked) + + #return Serder(ked=ked) # return serialized ked def bare(route="", data=None, + stamp=None, version=Version, kind=Serials.json): """ @@ -1306,22 +1347,25 @@ def bare(route="", Utility function to automate creation of unhiding (bareing) messages for disclosure of sealed data associated with anchored seals in a KEL. Reference to anchoring seal is provided as an attachment to bare message. - Bare 'bar' message is a SAD item with an associated derived SAID in its - 'd' field. + Bare 'bar' message is a SAD item with an associated derived SAID in a 'd' + field in side its 'a' block. Parameters: route is route path string that indicates data flow handler (behavior) to processs the exposure data is dict of dicts of comitted SADS for SAIDs in seals keyed by SAID + stamp (str): date-time-stamp RFC-3339 profile of ISO-8601 datetime of + creation of message or data version is Version instance kind is serialization kind + { "v" : "KERI10JSON00011c_", "t" : "bar", "d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM", + "dt": "2020-08-22T17:50:12.988921+00:00", "r" : "sealed/processor", - "i": "EAoTNZH3ULvYAfSVPzhzS6baU6JR2nmwyZ-i0d8JZ5CM", "a" : { "EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM": @@ -1340,15 +1384,18 @@ def bare(route="", sad = dict(v=vs, # version string t=Ilks.bar, d="", + dt=stamp if stamp is not None else helping.nowIso8601(), r=route if route is not None else "", # route a=data if data else {}, # dict of SADs ) - _, sad = coring.Saider.saidify(sad=sad) - - return Serder(ked=sad) # return serialized Self-Addressed Data (SAD) + serder = serdering.SerderKERI(sad=sad, makify=True) + serder._verify() # raises error if fails verifications + return serder + #_, sad = coring.Saider.saidify(sad=sad) + #return Serder(ked=sad) # return serialized Self-Addressed Data (SAD) def messagize(serder, *, sigers=None, seal=None, wigers=None, cigars=None, @@ -1357,7 +1404,7 @@ def messagize(serder, *, sigers=None, seal=None, wigers=None, cigars=None, Attaches indexed signatures from sigers and/or cigars and/or wigers to KERI message data from serder Parameters: - serder (Serder): instance containing the event + serder (SerderKERI): instance containing the event sigers (list): of Siger instances (optional) to create indexed signatures seal (Union[SealEvent, SealLast]): optional if sigers and If SealEvent use attachment group code TransIdxSigGroups plus attach @@ -1535,14 +1582,12 @@ class Kever: sner (Number): instance of sequence number fner (Number): instance of first seen ordinal number dater (Dater): instance of first seen datetime - serder (Serder): instance of current event with .serder.diger for digest + serder (SerderKERI): instance of current event with .serder.diger for digest ilk (str): from Ilks for current event type tholder (Tholder): instance for event signing threshold verfers (list): of Verfer instances for current event state set of signing keys - digers (list): of Diger instances for current event state set of + ndigers (list): of Diger instances for current event state set of next (rotation) key digests - nexter (Nexter): instance that provides nexter.digers of next key digests - from .serder.nexter as well as inclusion/matching methods ntholder (Tholder): instance for next (rotation) threshold from serder.ntholder toader (Number): instance of TOAD (threshold of accountable duplicity) @@ -1563,58 +1608,61 @@ class Kever: Properties: sn (int): sequence number property that returns .sner.num fn (int): first seen ordinal number property the returns .fner.num - digs (list): of digests qb64 of .digers + ndigs (list): of digests qb64 of .digers kevers (dict): reference to self.db.kevers - transferable (bool): True if nexter is not none and pre is transferable + transferable (bool): True if .digers is not empty and pre is transferable + + ToDo: - Add Class variable, instance variable and parse support for Registrar Backer config trait. + Add Registrar Backer support: + Class variable, instance variable and parse support config trait. raise error for now - Replace Nexter with Kever.digers and ntholder with new Tholder methods - to replace Nexter methods. + """ EstOnly = False DoNotDelegate = False def __init__(self, *, state=None, serder=None, sigers=None, wigers=None, - db=None, estOnly=None, seqner=None, saider=None, firner=None, dater=None, - cues=None, prefixes=None, local=False, - check=False): + db=None, estOnly=None, delseqner=None, delsaider=None, firner=None, + dater=None, cues=None, prefixes=None, local=False, check=False): """ Create incepting kever and state from inception serder Verify incepting serder against sigers raises ValidationError if not Parameters: - state (Serder): instance of key state - serder is Serder instance of inception event - sigers is list of Siger instances of indexed controller signatures - of event. Index is offset into keys list of latest est event - wigers is list of Siger instances of indexed witness signatures of - event. Index is offset into wits list of latest est event - db is Baser instance of lmdb database - estOnly is boolean trait to indicate establish only event - seqner is Seqner instance of delegating event sequence number. + state (KeyStateRecord | None): instance for key state notice + serder (SerderKERI | None): instance of inception event + sigers (list | None): of Siger instances of indexed controller signatures + of event. Index is offset into keys list from latest est event + wigers (list | None): of Siger instances of indexed witness signatures of + event. Index is offset into wits list from latest est event + db (Baser | None): instance of lmdb database + estOnly (bool | None): True means establishment only events allowed 'EO'. + False all events allowed. + delseqner (Seqner | None): instance of delegating event sequence number. If this event is not delegated then seqner is ignored - Saider is Saider instance of of delegating event said. + delsaider (Saider | None): instance of of delegating event SAID. If this event is not delegated then saider is ignored - firner is optional Seqner instance of cloned first seen ordinal + firner (Seqner | None): instance optional of cloned first seen ordinal If cloned mode then firner maybe provided (not None) When firner provided then compare fn of dater and database and first seen if not match then log and add cue notify problem - dater is optional Dater instance of cloned replay datetime + dater (Dater | None): optional instance of cloned replay datetime If cloned mode then dater maybe provided (not None) When dater provided then use dater for first seen datetime - kevers is reference Kevery.kevers dict when provided needed for - validation of delegation seal .doNotDelegate of delegator - cues is reference to Kevery.cues deque when provided i.e. notices of - events or requests to respond to - prefixes is list of own prefixes for own local habitats. May not be the - prefix of this Kever's event. Some restrictions if present - If empty then promiscuous mode + cues (Deck | None): reference to Kevery.cues Deck when provided + i.e. notices of events or requests to respond to + prefixes (list | None): own prefixes for own local habitats. + May not include the prefix of this Kever's event when inception + has not yet been accepted into KEL + Some restrictions if present + If empty then effectively in promiscuous mode local (bool): True means only process msgs for own controller's - events if .prefixes is not empty. False means only process msgs - for not own events if .prefixes is not empty + events if .prefixes is not empty. + False means only process msgs for not own events + if .prefixes is not empty check (bool): True means do not update the database in any non-idempotent way. Useful for reinitializing the Kevers from a persisted KEL without updating non-idempotent first seen .fels @@ -1638,7 +1686,7 @@ def __init__(self, *, state=None, serder=None, sigers=None, wigers=None, # may update state as we go because if invalid we fail to finish init self.version = serder.version # version dispatch ? - ilk = serder.ked["t"] + ilk = serder.ilk # serder.ked["t"] if ilk not in (Ilks.icp, Ilks.dip): raise ValidationError("Expected ilk = {} or {} got {} for evt = {}." "".format(Ilks.icp, Ilks.dip, @@ -1664,26 +1712,25 @@ def __init__(self, *, state=None, serder=None, sigers=None, wigers=None, wigers=wigers, toader=self.toader, wits=self.wits, - seqner=seqner, - saider=saider) + delseqner=delseqner, + delsaider=delsaider) self.delegator = delegator - if self.delegator is None: - self.delegated = False - else: - self.delegated = True + self.delegated = True if self.delegator else False - wits = serder.ked["b"] + wits = serder.backs # serder.ked["b"] # .validateSigsDelWigs above ensures thresholds met otherwise raises exception # all validated above so may add to KEL and FEL logs as first seen # returns fn == None if already logged fn log is non idempotent fn, dts = self.logEvent(serder=serder, sigers=sigers, wigers=wigers, wits=wits, - first=True if not check else False, seqner=seqner, saider=saider, + first=True if not check else False, + seqner=delseqner, saider=delsaider, firner=firner, dater=dater) if fn is not None: # first is non-idempotent for fn check mode fn is None self.fner = Number(num=fn) self.dater = Dater(dts=dts) - self.db.states.pin(keys=self.prefixer.qb64, val=self.state()) + self.db.states.pin(keys=self.prefixer.qb64, + val=self.state()) @property @@ -1705,12 +1752,12 @@ def fn(self): @property - def digs(self): + def ndigs(self): """ Returns: (list): digs of digers """ - return [diger.qb64 for diger in self.digers] + return [diger.qb64 for diger in self.ndigers] @property @@ -1729,50 +1776,72 @@ def transferable(self): and .nextor is not None False otherwise """ - return True if self.digers and self.prefixer.transferable else False + return True if self.ndigers and self.prefixer.transferable else False + + + def locallyOwned(self, pre=''): + """Returns True if pre is in .prefixes False otherwise. Indicates that + provided identifier prefix is controlled by a local controller from + .prefixes + i.e pre is a locally owned (controlled) AID (identifier prefix) + + Parameters: + pre (str): qb64 identifier prefix + + """ + return pre in self.prefixes + + + def locallyWitnessed(self, serder=None): + """Returns True if a local controller is a witness of this Kever's KEL + of wits in serder of if None then current wits for this Kever. + i.e. self is witnessd by locally owned (controlled) AID (identifier prefix) + + Parameters: + serder ( SerderKERI | None): SerderKERI instace if any + + """ + if serder and serder.pre != self.prefixer.qb64: # same KEL as self + return False + wits = serder.backs if serder is not None else self.wits + return (oset(self.prefixes) & oset(wits)) def reload(self, state): """ - Reload Kever attributes (aka its state) from state serder + Reload Kever attributes (aka its state) from state (KeyStateRecord) Parameters: - state (Serder): instance of key state notice 'ksn' message body + state (KeyStateRecord | None): instance for key state notice """ - for k in KSN_LABELS: - if k not in state.ked: - raise ValidationError("Missing element = {} from {} event." - " evt = {}.".format(k, Ilks.ksn, - state.pretty())) - - self.version = state.version - self.prefixer = Prefixer(qb64=state.pre) - self.sner = state.sner # sequence number Number instance hex str - self.fner = state.fner # first seen ordinal Number hex str - self.dater = Dater(dts=state.ked["dt"]) - self.ilk = state.ked["et"] - self.tholder = Tholder(sith=state.ked["kt"]) - self.ntholder = Tholder(sith=state.ked["nt"]) - self.verfers = [Verfer(qb64=key) for key in state.ked["k"]] - self.digers = [Diger(qb64=dig) for dig in state.ked["n"]] - self.toader = Number(num=state.ked["bt"]) # auto converts from hex num - self.wits = state.ked["b"] - self.cuts = state.ked["ee"]["br"] - self.adds = state.ked["ee"]["ba"] + self.version = Versionage._make(state.vn) + self.prefixer = Prefixer(qb64=state.i) + self.sner = Number(numh=state.s) # sequence number Number instance hex str + self.fner = Number(numh=state.f) # first seen ordinal Number hex str + self.dater = Dater(dts=state.dt) + self.ilk = state.et + self.tholder = Tholder(sith=state.kt) + self.ntholder = Tholder(sith=state.nt) + self.verfers = [Verfer(qb64=key) for key in state.k] + self.ndigers = [Diger(qb64=dig) for dig in state.n] + self.toader = Number(numh=state.bt) # auto converts from hex num + self.wits = state.b + self.cuts = state.ee.br + self.adds = state.ee.ba self.estOnly = False - self.doNotDelegate = True if "DND" in state.ked["c"] else False - self.estOnly = True if "EO" in state.ked["c"] else False - self.lastEst = LastEstLoc(s=int(state.ked['ee']['s'], 16), - d=state.ked['ee']['d']) - self.delegator = state.ked['di'] if state.ked['di'] else None + self.doNotDelegate = True if TraitCodex.DoNotDelegate in state.c else False + self.estOnly = True if TraitCodex.EstOnly in state.c else False + self.lastEst = LastEstLoc(s=int(state.ee.s, 16), + d=state.ee.d) + self.delegator = state.di if state.di else None self.delegated = True if self.delegator else False if (raw := self.db.getEvt(key=dgKey(pre=self.prefixer.qb64, - dig=state.ked['d']))) is None: - raise MissingEntryError("Corresponding event for state={} not found." - "".format(state.pretty())) - self.serder = Serder(raw=bytes(raw)) + dig=state.d))) is None: + raise MissingEntryError(f"Corresponding event not found for state=" + f"{state}.") + self.serder = serdering.SerderKERI(raw=bytes(raw)) # May want to do additional checks here @@ -1782,7 +1851,7 @@ def incept(self, serder, estOnly=None): Parameters: - serder is Serder instance of inception event + serder is SerderKERI instance of inception event estOnly is boolean to indicate establish only events allowed """ ked = serder.ked @@ -1799,21 +1868,21 @@ def incept(self, serder, estOnly=None): [verfer.qb64 for verfer in self.verfers], ked)) + + self.prefixer = Prefixer(qb64=serder.pre) if not self.prefixer.verify(ked=ked, prefixed=True): # invalid prefix raise ValidationError("Invalid prefix = {} for inception evt = {}." "".format(self.prefixer.qb64, ked)) - self.serder = serder # need whole serder for digest agility comparisons - nxt = ked["n"] - if not self.prefixer.transferable and nxt: # nxt must be empty for nontrans prefix - raise ValidationError("Invalid inception nxt not empty for " + ndigs = serder.ndigs # ked["n"] + if not self.prefixer.transferable and ndigs: # nxt must be empty for nontrans prefix + raise ValidationError("Invalid inception next digest list not empty for " "non-transferable prefix = {} for evt = {}." "".format(self.prefixer.qb64, ked)) - self.digers = serder.digers - #self.nexter = serder.nexter + self.ndigers = serder.ndigers self.ntholder = serder.ntholder self.cuts = [] # always empty at inception since no prev event @@ -1848,7 +1917,7 @@ def incept(self, serder, estOnly=None): # need this to recognize recovery events and transferable receipts # last establishment event location - self.lastEst = LastEstLoc(s=self.sner.num, d=self.serder.saider.qb64) + self.lastEst = LastEstLoc(s=self.sner.num, d=self.serder.said) def config(self, serder, estOnly=None, doNotDelegate=None): @@ -1863,30 +1932,30 @@ def config(self, serder, estOnly=None, doNotDelegate=None): else self.DoNotDelegate) else False) # ensure default doNotDelegate is boolean - cnfg = serder.ked["c"] # process cnfg for traits + cnfg = serder.traits # serder.ked["c"] # process cnfg for traits if TraitDex.EstOnly in cnfg: self.estOnly = True if TraitDex.DoNotDelegate in cnfg: self.doNotDelegate = True - def update(self, serder, sigers, wigers=None, seqner=None, saider=None, + def update(self, serder, sigers, wigers=None, delseqner=None, delsaider=None, firner=None, dater=None, check=False): """ Not an inception event. Verify event serder and indexed signatures in sigers and update state Parameters: - serder (Serder): instance of event + serder (SerderKERI): instance of event sigers (list): of SigMat instances of indexed signatures of controller signatures of event. Index is offset into keys list from latest est event and when provided ondex is offset into key digest list from prior next est event to latest est event. wigers (list | None): of Siger instances of indexed witness signatures of event. Index is offset into wits list from latest est event - seqner (Seqner | None): instance of delegating event sequence number. + delseqner (Seqner | None): instance of delegating event sequence number. If this event is not delegated then seqner is ignored - saider (Saider | None): instance of of delegating event said. + delsaider (Saider | None): instance of of delegating event said. If this event is not delegated then diger is ignored firner (Seqner | None): Seqner instance of cloned first seen ordinal If cloned mode then firner maybe provided (not None) @@ -1901,34 +1970,28 @@ def update(self, serder, sigers, wigers=None, seqner=None, saider=None, and timestamps. """ - if not self.transferable: # not transferable so no events after inception allowed - raise ValidationError("Unexpected event = {} is nontransferable " - " state.".format(serder.ked)) ked = serder.ked + if not self.transferable: # not transferable so no further events allowed + raise ValidationError("Unexpected event = {} is nontransferable " + " or abandoned state.".format(ked)) + if serder.pre != self.prefixer.qb64: raise ValidationError("Mismatch event aid prefix = {} expecting" - " = {} for evt = {}.".format(ked["i"], + " = {} for evt = {}.".format(serder.pre, self.prefixer.qb64, ked)) - sner = serder.sner # Number instance ensures whole number for sequence number - ilk = ked["t"] + sner = serder.sner # Number instance ensures whole number for sequence number + ilk = serder.ilk # ked["t"] if ilk in (Ilks.rot, Ilks.drt): # rotation (or delegated rotation) event if self.delegated and ilk != Ilks.drt: raise ValidationError("Attempted non delegated rotation on " "delegated pre = {} with evt = {}." - "".format(ked["i"], ked)) + "".format(serder.pre, ked)) - # labels = DRT_LABELS if ilk == Ilks.dip else ROT_LABELS - labels = DRT_LABELS if ilk == Ilks.drt else ROT_LABELS - for k in labels: - if k not in ked: - raise ValidationError("Missing element = {} from {} event for " - "evt = {}.".format(k, ilk, ked)) - - tholder, toader, wits, cuts, adds = self.rotate(serder, sner) + tholder, toader, wits, cuts, adds = self.rotate(serder) # Validates signers, delegation if any, and witnessing when applicable # returned sigers and wigers are verified signatures @@ -1940,58 +2003,27 @@ def update(self, serder, sigers, wigers=None, seqner=None, saider=None, wigers=wigers, toader=toader, wits=wits, - seqner=seqner, - saider=saider) - - - # move this out of here to where ntholder threshold is verified - # verify newly current keys are subset of prior next digs - #keys = ked["k"] # proposed new current keys - #digs = [diger.qb64 for diger in self.digers] # prior next digs - ## new current keys must be subset of prior next digs - #if not self.ntholder.includes(keys=keys, digs=digs): - #raise ValidationError("Mismatch prior nxt digs = {} with rotation" - #"current keys = {} for evt = {}." - #"".format(digs, keys, ked)) - - #if not self.ntholder.satisfy(indices=self.ntholder.matches(sigers=sigers, - #digs=digs)): - #self.escrowPSEvent(serder=serder, sigers=sigers, wigers=wigers) - #if seqner and saider: - #self.escrowPACouple(serder=serder, seqner=seqner, saider=saider) - #raise MissingSignatureError("Failure satisfying nsith = {} on sigs for {}" - #" for evt = {}.".format(self.ntholder.sith, - #[siger.qb64 for siger in sigers], - #serder.ked)) - - - # current sigers and prior next digers in .digers - ondices = self.exposeds(sigers) - if not self.ntholder.satisfy(indices=ondices): - self.escrowPSEvent(serder=serder, sigers=sigers, wigers=wigers) - if seqner and saider: - self.escrowPACouple(serder=serder, seqner=seqner, saider=saider) - raise MissingSignatureError(f"Failure satisfying nsith=" - f"{self.ntholder.sith} on sigs=" - f"{[siger.qb64 for siger in sigers]}" - f" for evt={serder.ked}.") - - - + delseqner=delseqner, + delsaider=delsaider) + # rotation so check rotation threshold against exposed sigers versus + # prior next digers in .ndigers + #ondices = self.exposeds(sigers) + #if not self.ntholder.satisfy(indices=ondices): + #self.escrowPSEvent(serder=serder, sigers=sigers, wigers=wigers) + #if delseqner and delsaider: # save in case not attached later + #self.escrowPACouple(serder=serder, seqner=delseqner, saider=delsaider) + #raise MissingSignatureError(f"Failure satisfying nsith=" + #f"{self.ntholder.sith} on sigs=" + #f"{[siger.qb64 for siger in sigers]}" + #f" for evt={serder.ked}.") - if delegator != self.delegator: # - raise ValidationError("Erroneous attempted delegated rotation" - " on either undelegated event or with" - " wrong delegator = {} for pre = {}" - " with evt = {}." - "".format(delegator, ked["i"], ked)) # .validateSigsDelWigs above ensures thresholds met otherwise raises exception # all validated above so may add to KEL and FEL logs as first seen fn, dts = self.logEvent(serder=serder, sigers=sigers, wigers=wigers, wits=wits, - first=True if not check else False, seqner=seqner, saider=saider, + first=True if not check else False, seqner=delseqner, saider=delsaider, firner=firner, dater=dater) # nxt and signatures verify so update state @@ -2000,7 +2032,7 @@ def update(self, serder, sigers, wigers=None, seqner=None, saider=None, self.ilk = ilk self.tholder = tholder self.verfers = serder.verfers - self.digers = serder.digers + self.ndigers = serder.ndigers self.ntholder = serder.ntholder self.toader = toader @@ -2009,7 +2041,7 @@ def update(self, serder, sigers, wigers=None, seqner=None, saider=None, self.adds = adds # last establishment event location need this to recognize recovery events - self.lastEst = LastEstLoc(s=self.sner.num, d=self.serder.saider.qb64) + self.lastEst = LastEstLoc(s=self.sner.num, d=self.serder.said) if fn is not None: # first is non-idempotent for fn check mode fn is None self.fner = Number(num=fn) self.dater = Dater(dts=dts) @@ -2033,7 +2065,7 @@ def update(self, serder, sigers, wigers=None, seqner=None, saider=None, if not self.serder.compare(said=ked["p"]): # prior event dig not match raise ValidationError("Mismatch event dig = {} with state dig" " = {} for evt = {}.".format(ked["p"], - self.serder.saider.qb64, + self.serder.said, ked)) # interaction event use keys, sith, toad, and wits from pre-existing Kever state @@ -2066,7 +2098,7 @@ def update(self, serder, sigers, wigers=None, seqner=None, saider=None, raise ValidationError("Unsupported ilk = {} for evt = {}.".format(ilk, ked)) - def rotate(self, serder, sner): + def rotate(self, serder): """ Generic Rotate Operation Validation Processing Validates provisional rotation @@ -2076,32 +2108,35 @@ def rotate(self, serder, sner): of rotation subject to additional validation Parameters: - serder (Serder): instance of rotation ('rot' or 'drt') event. - sner (Number): sequence number instance + serder (SerderKERI): instance of rotation ('rot' or 'drt') event. + """ ked = serder.ked - pre = ked["i"] # controller AID prefix - dig = ked["p"] # prior event said + sner = serder.sner + pre = serder.pre # ked["i"] # controller AID prefix + prior = serder.prior # ked["p"] # prior event said + ilk = serder.ilk if sner.num > self.sner.num + 1: # out of order event - raise ValidationError("Out of order event sn = {} expecting" - " = {} for evt = {}.".format(sner.num, - self.sner.num + 1, - ked)) + raise ValidationError(f"Out of order event sn = {sner.num} expecting" + f" = {self.sner.num + 1} for evt = {ked}.") elif sner.num <= self.sner.num: # stale or recovery # stale events could be duplicitous - # duplicity detection should have happend before .update called - # so raise exception if stale - if sner.num <= self.lastEst.s: # stale event - raise ValidationError("Stale event sn = {} expecting" + # duplicity detection should have happend in Kevery before .update + # and .rotate called so raise exception if stale + # seems redundant but protects bare .update if not called by Kevery + + if ((ilk == Ilks.rot and sner.num <= self.lastEst.s) or + (ilk == Ilks.drt and sner.num < self.lastEst.s)): # stale event + raise ValidationError("Stale event sn = {} expecting" " = {} for evt = {}.".format(sner.num, self.sner.num + 1, ked)) - else: # sn > self.lastEst.sn # recovery event - if self.ilk != Ilks.ixn: # recovery may only override ixn state + else: # recovery event rot sn > self.lastEst.s or drt sn = self.lastEst.s + if ilk == Ilks.rot and self.ilk != Ilks.ixn: # rot recovery may only override ixn state raise ValidationError("Invalid recovery attempt: Recovery" "at ilk = {} not ilk = {} for evt" " = {}.".format(self.ilk, @@ -2119,38 +2154,33 @@ def rotate(self, serder, sner): if praw is None: raise ValidationError("Invalid recovery attempt: " " Bad dig = {}.".format(pdig)) - pserder = Serder(raw=bytes(praw)) # deserialize prior event raw - if not pserder.compare(said=dig): # bad recovery event + pserder = serdering.SerderKERI(raw=bytes(praw)) # deserialize prior event raw + if not pserder.compare(said=prior): # bad recovery event raise ValidationError("Invalid recovery attempt:" "Mismatch recovery event prior dig" "= {} with dig = {} of event sn = {}" - " evt = {}.".format(dig, + " evt = {}.".format(prior, pserder.said, psn, ked)) else: # sn == self.sn + 1 new non-recovery event - if not self.serder.compare(said=dig): # prior event dig not match + if not self.serder.compare(said=prior): # prior event dig not match raise ValidationError("Mismatch event dig = {} with" " state dig = {} for evt = {}." - "".format(dig, self.serder.saider.qb64, ked)) + "".format(prior, self.serder.said, ked)) # check derivation code of pre for non-transferable - if not self.digers: # prior next list is empty so rotations not allowed + if not self.ndigers: # prior next list is empty so rotations not allowed raise ValidationError("Attempted rotation for nontransferable" " prefix = {} for evt = {}." "".format(self.prefixer.qb64, ked)) - #if not self.nexter: # prior next is empty so rotations not allowed - #raise ValidationError("Attempted rotation for nontransferable" - #" prefix = {} for evt = {}." - #"".format(self.prefixer.qb64, ked)) - tholder = serder.tholder # Tholder(sith=ked["kt"]) # parse sith into Tholder instance - keys = ked["k"] # current keys + keys = serder.keys # event's keys ked["k"] if len(keys) < tholder.size: - raise ValidationError("Invalid sith = {} for keys = {} for evt = {}." - "".format(ked["kt"], keys, ked)) + raise ValidationError(f"Invalid sith = {serder.tholder} for keys = " + f"{keys} for evt = {ked}.") @@ -2159,7 +2189,7 @@ def rotate(self, serder, sner): # cuts and add to ensure that indexed signatures on indexed witness # receipts work witset = oset(self.wits) - cuts = ked["br"] + cuts = serder.cuts # ked["br"] cutset = oset(cuts) if len(cutset) != len(cuts): raise ValidationError("Invalid cuts = {}, has duplicates for evt = " @@ -2169,7 +2199,7 @@ def rotate(self, serder, sner): raise ValidationError("Invalid cuts = {}, not all members in wits" " for evt = {}.".format(cuts, ked)) - adds = ked["ba"] + adds = serder.adds # ked["ba"] addset = oset(adds) if len(addset) != len(adds): raise ValidationError("Invalid adds = {}, has duplicates for evt = " @@ -2192,7 +2222,7 @@ def rotate(self, serder, sner): adds, ked)) - toader = Number(num=ked["bt"]) # auto converts hex num to int + toader = serder.bner # Number(num=ked["bt"]) # auto converts hex num to int if wits: if toader.num < 1 or toader.num > len(wits): # out of bounds toad raise ValueError(f"Invalid toad = {toader.num} for backers " @@ -2204,8 +2234,10 @@ def rotate(self, serder, sner): return tholder, toader, wits, cuts, adds + def valSigsDelWigs(self, serder, sigers, verfers, tholder, - wigers, toader, wits, seqner=None, saider=None): + wigers, toader, wits, + delseqner=None, delsaider=None): """ Returns triple (sigers, delegator, wigers) where: sigers is unique validated signature verified members of inputed sigers @@ -2219,20 +2251,20 @@ def valSigsDelWigs(self, serder, sigers, verfers, tholder, Witness validation is a function of wits .prefixes and .local Parameters: - serder is Serder instance of event - sigers is list of Siger instances of indexed controllers signatures. + serder (SerderKERI): instance of event + sigers (list): of Siger instances of indexed controllers signatures. Index is offset into verfers list from which public key may be derived. - verfers is list of Verfer instances of keys from latest est event - tholder is Tholder instance of sith threshold - wigers is list of Siger instances of indexed witness signatures. + verfers (list): of Verfer instances of keys from latest est event + tholder (Tholder): instance of sith threshold + wigers (list): of Siger instances of indexed witness signatures. Index is offset into wits list of associated witness nontrans pre from which public key may be derived. toader (Number): instance of backer witness threshold - wits is list of qb64 non-transferable prefixes of witnesses used to + wits (list): of qb64 non-transferable prefixes of witnesses used to derive werfers for wigers - seqner is Seqner instance of delegating event sequence number. + delseqner (Seqner | None): instance of delegating event sequence number. If this event is not delegated then seqner is ignored - saider is Saider instance of of delegating event said. + delsaider (Saider | None): instance of of delegating event said. If this event is not delegated then saider is ignored """ @@ -2246,32 +2278,45 @@ def valSigsDelWigs(self, serder, sigers, verfers, tholder, sigers, indices = verifySigs(raw=serder.raw, sigers=sigers, verfers=verfers) # sigers now have .verfer assigned - werfers = [Verfer(qb64=wit) for wit in wits] - - # get unique verified wigers and windices lists from wigers list - wigers, windices = verifySigs(raw=serder.raw, sigers=wigers, verfers=werfers) - # each wiger now has werfer of corresponding wit - - # check if fully signed + # check if minimally signed in order to continue processing if not indices: # must have a least one verified sig raise ValidationError("No verified signatures for evt = {}." "".format(serder.ked)) + werfers = [Verfer(qb64=wit) for wit in wits] # get witnes signatures + + # get unique verified wigers and windices lists from wigers list + wigers, windices = verifySigs(raw=serder.raw, sigers=wigers, verfers=werfers) + # each wiger now has added to it a werfer of its wit in its .verfer property + + # escrow if not fully signed vs threshold if not tholder.satisfy(indices): # at least one but not enough self.escrowPSEvent(serder=serder, sigers=sigers, wigers=wigers) - if seqner and saider: - self.escrowPACouple(serder=serder, seqner=seqner, saider=saider) - raise MissingSignatureError("Failure satisfying sith = {} on sigs for {}" - " for evt = {}.".format(tholder.sith, - [siger.qb64 for siger in sigers], - serder.ked)) + if delseqner and delsaider: + self.escrowPACouple(serder=serder, seqner=delseqner, saider=delsaider) + raise MissingSignatureError(f"Failure satisfying sith = {tholder.sith}" + f" on sigs for {[siger.qb64 for siger in sigers]}" + f" for evt = {serder.ked}.") + + if serder.ilk in (Ilks.rot, Ilks.drt): # rotation so check prior next threshold + # prior next threshold in .ntholder and digers in .ndigers + ondices = self.exposeds(sigers) + if not self.ntholder.satisfy(indices=ondices): + self.escrowPSEvent(serder=serder, sigers=sigers, wigers=wigers) + if delseqner and delsaider: # save in case not attached later + self.escrowPACouple(serder=serder, seqner=delseqner, saider=delsaider) + raise MissingSignatureError(f"Failure satisfying prior nsith=" + f"{self.ntholder.sith} with exposed " + f"sigs= {[siger.qb64 for siger in sigers]}" + f" for new est evt={serder.ked}.") + delegator = self.validateDelegation(serder, sigers=sigers, wigers=wigers, - seqner=seqner, saider=saider) + delseqner=delseqner, delsaider=delsaider) - # Kevery .process event logic prevents this from seeing event when + # Kevery .process event logic does not prevent this from seeing event when # not local and event pre is own pre - if serder.pre not in self.prefixes: + if not self.locallyOwned(serder.pre): # not in self.prefixes if ((wits and not self.prefixes) or # in promiscuous mode so assume must verify toad (wits and self.prefixes and not self.local and # not promiscuous nonlocal not (oset(self.prefixes) & oset(wits)))): # own prefix is not a witness @@ -2279,24 +2324,28 @@ def valSigsDelWigs(self, serder, sigers, verfers, tholder, if wits: if toader.num < 1 or toader.num > len(wits): # out of bounds toad - raise ValueError(f"Invalid toad = {toader.num} for wits = {wits}") + raise ValidationError(f"Invalid toad = {toader.num} for wits = {wits}") else: if toader.num != 0: # invalid toad - raise ValueError(f"Invalid toad = {toader.num} for wits = {wits}") + raise ValidationError(f"Invalid toad = {toader.num} for wits = {wits}") if len(windices) < toader.num: # not fully witnessed yet - if self.escrowPWEvent(serder=serder, wigers=wigers, sigers=sigers, seqner=seqner, saider=saider): - self.cues.append(dict(kin="query", q=dict(pre=serder.pre, sn=serder.sn))) + if self.escrowPWEvent(serder=serder, wigers=wigers, sigers=sigers, + seqner=delseqner, saider=delsaider): + # cue to query for witness receipts + self.cues.push(dict(kin="query", q=dict(pre=serder.pre, sn=serder.snh))) raise MissingWitnessSignatureError(f"Failure satisfying toad={toader.num} " f"on witness sigs=" f"{[siger.qb64 for siger in wigers]} " f"for event={serder.ked}.") + + return sigers, delegator, wigers def exposeds(self, sigers): """Returns list of ondices (indices) suitable for Tholder.satisfy - into self.digers (prior next key digests ) as exposed by sigers. + from self.ndigers (prior next key digests ) as exposed by event sigers. Uses dual index feature of siger. Assumes that each siger.verfer is from the correct key given by siger.index and the signature has been verified. @@ -2326,7 +2375,7 @@ def exposeds(self, sigers): odxs = [] for siger in sigers: try: - diger = self.digers[siger.ondex] + diger = self.ndigers[siger.ondex] except TypeError as ex: # ondex may be None continue except IndexError as ex: @@ -2341,109 +2390,265 @@ def exposeds(self, sigers): return odxs - - def validateDelegation(self, serder, sigers, wigers=None, seqner=None, saider=None): + def validateDelegation(self, serder, sigers, wigers=None, delseqner=None, delsaider=None): """ - Returns delegator's qb64 identifier prefix if seal validates with respect to Delegator's KEL + Returns delegator's qb64 identifier prefix if validation successful. + Rules: + If event is not a delegated event then not valid delegation + If delegatee's own event (.mine) then valid delegation + If delegation seal found in delgator's KEL then valid delegation given + valid superseding rules below + Otherwise escrow or reject if error condition + + seal validates with respect to Delegator's KEL Location Seal is from Delegate's establishment event Assumes state setup Parameters: - serder is Serder instance of delegated event serder - sigers is list of Siger instances of indexed controller sigs of + serder (SerderKERI): instance of delegated event serder + sigers (list): of Siger instances of indexed controller sigs of delegated event. Assumes sigers is list of unique verified sigs - wigers is optional list of Siger instance of indexed witness sigs of + wigers (list | None): of optional Siger instance of indexed witness sigs of delegated event. Assumes wigers is list of unique verified sigs - seqner is Seqner instance of delegating event sequence number. - If this event is not delegated then seqner is ignored - saider is Saider instance of of delegating event digest. - If this event is not delegated then diger is ignored + delseqner (Seqner | None): instance of delegating event sequence number. + If this event is not delegated then ignored + delsaider (Saider | None): instance of of delegating event digest. + If this event is not delegated ignored + Returns: - str: qb64 delegator prefix + (str | None): qb64 delegator prefix or None if not delegated + + Superseding Recovery + + Supersede means that after an event has already been accepted as first seen + into a KEL that a different event with the same sequence number is accepted + that supersedes the pre-existing event at that sn. This enables the recovery of + events signed by compromised keys. The result of superseded recovery is that + the KEL is forked at the sn of the superseding event. All events in the + superseded branch of the fork still exist but, by virtue of being superseded, + are disputed. The set of superseding events in the superseding fork forms the authoritative + branch of the KEL. All the already seen superseded events in the superseded fork + still remain in the KEL and may be viewed in order of their original acceptance + because the database stores all accepted events in order of acceptance and + denotes this order using the first seen ordinal number, fn. + The fn is not the same as the sn (sequence number). + Each event accepted into a KEL has a unique fn but multiple events due to + recovery forks may share the same sn. + + + Superseding Rules for Recovery at given SN (sequence number) + + A0. Any rotation event may supersede an interaction event at the same sn. (existing rule) + A1. A non-delegated rotation may not supersede another rotation at the same sn. (modified rule) + A2. An interaction event may not supersede any event. ( existing rule). + + (B. and C. below provide the new rules) + + B. A delegated rotation may supersede another delegated rotation at the same sn + under either of the following conditions: + B1. The superseding rotation's delegating event is later than + the superseded rotation's delegating event in the delegator's KEL, i.e. the + sn of the superseding event's delegation is higher than the superseded event's + delegation. + B2. The sn of the superseding rotation's delegating event is the same as + the sn of the superseded rotation's delegating event in the delegator's KEL + and the superseding rotation's delegating event is a rotation and the + superseded rotation's delegating event is an interaction, + i.e. the superseding rotation's delegating event is itself a superseding + rotation of the superseded rotations delegating interaction event in the + delgator's KEL + + C. IF Neither A nor B is satisfied, then recursively apply rules A. and B. to + the delegating events of those delegating events and so on until either A. or B. + is satisfied, or the root KEL of the delegation has been reached. + C1. If neither A. nor B. is satisfied by recursive application on the + delegator's KEL (i.e. the root KEL of the delegation has been reached without + satisfaction) then the superseding rotation is discarded. The terminal case of + the recursive application will occur at the root KEL which by defintion is + non-delegated wherefore either A. or B. must be satisfied, or else the + superseding rotation must be discarded. """ - if serder.ked['t'] not in (Ilks.dip, Ilks.drt): # not delegated + if serder.ilk not in (Ilks.dip, Ilks.drt): # not delegated return None # delegator is None # verify delegator and attachment pointing to delegating event - if serder.ked['t'] == Ilks.dip: - delegator = serder.ked["di"] - else: + if serder.ilk == Ilks.dip: + delegator = serder.delpre # delegator from dip event + if not delegator: + raise ValidationError(f"Empty or missing delegator for delegated" + f" inception event = {serder.ked}.") + else: # serder.ilk == Ilks.drt so rotation delegator = self.delegator - # if we are the delegatee, accept the event without requiring the delegator validation - if delegator is not None and serder.pre in self.prefixes: + + # if we are the delegatee, accept the event without requiring the + # delegator validation via an anchored delegation seal or by requiring + # it to be witnessed + # ToDo XXXX add local lax check after figure out dist multisig group + # ToDo XXXX add check for witness to accept so that witness will + # add to its KEL without waiting for delegation seal to be anchored in + # delegator's KEL witness cue in Kevery will then generate reciept + if self.locallyOwned(serder.pre) or self.locallyWitnessed(serder=serder): return delegator # during initial delegation we just escrow the delcept event - if seqner is None and saider is None and delegator is not None: + if delseqner is None and delsaider is None and delegator is not None: self.escrowPSEvent(serder=serder, sigers=sigers, wigers=wigers) raise MissingDelegationError("No delegation seal for delegator {} " "with evt = {}.".format(delegator, serder.ked)) - ssn = validateSN(sn=seqner.snh, inceptive=False) + ssn = validateSN(sn=delseqner.snh, inceptive=False) # delseqner Number should already do this + #ssn = sner.num sner is Number seqner is Seqner need to replace Seqners with Numbers - # get the dig of the delegating event - key = snKey(pre=delegator, sn=ssn) + # get the dig of the delegating event. Using getKeLast ensures delegating + # event has not already been superceded + key = snKey(pre=delegator, sn=ssn) # database key raw = self.db.getKeLast(key) # get dig of delegating event if raw is None: # no delegating event at key pre, sn - # create cue to fetch delegating event this may include MFA business logic - # for the delegator + # ToDo XXXX create cue to send query to fetch delegating event from + # delegator + self.cues.push(dict(kin="query", q=dict(pre=delegator, + sn=delseqner.snh, + dig=delsaider.qb64))) # escrow event here - inceptive = True if serder.ked["t"] in (Ilks.icp, Ilks.dip) else False - sn = validateSN(sn=serder.ked["s"], inceptive=inceptive) + inceptive = True if serder.ilk in (Ilks.icp, Ilks.dip) else False + sn = validateSN(sn=serder.snh, inceptive=inceptive) self.escrowPSEvent(serder=serder, sigers=sigers, wigers=wigers) - self.escrowPACouple(serder=serder, seqner=seqner, saider=saider) + self.escrowPACouple(serder=serder, seqner=delseqner, saider=delsaider) raise MissingDelegationError("No delegating event from {} at {} for " "evt = {}.".format(delegator, - saider.qb64, + delsaider.qb64, serder.ked)) # get the delegating event from dig ddig = bytes(raw) - key = dgKey(pre=delegator, dig=ddig) + key = dgKey(pre=delegator, dig=ddig) # database key raw = self.db.getEvt(key) - if raw is None: + if raw is None: # drop event raise ValidationError("Missing delegation from {} at event dig = {} for evt = {}." "".format(delegator, ddig, serder.ked)) - dserder = Serder(raw=bytes(raw)) # delegating event + dserder = serdering.SerderKERI(raw=bytes(raw)) # delegating event # compare digests to make sure they match here - if not dserder.compare(said=saider.qb64): + if not dserder.compare(said=delsaider.qb64): # drop event raise ValidationError("Invalid delegation from {} at event dig = {} for evt = {}." "".format(delegator, ddig, serder.ked)) - if self.kevers is None or delegator not in self.kevers: + if self.kevers is None or delegator not in self.kevers: # drop event raise ValidationError("Missing Kever for delegator = {} for evt = {}." "".format(delegator, serder.ked)) dkever = self.kevers[delegator] - if dkever.doNotDelegate: + if dkever.doNotDelegate: # drop event raise ValidationError("Delegator = {} for evt = {}," " does not allow delegation.".format(delegator, serder.ked)) - pre = serder.ked["i"] - sn = serder.ked["s"] - found = False # find event seal of delegated event in delegating data - for dseal in dserder.ked["a"]: # find delegating seal anchor - if ("i" in dseal and dseal["i"] == pre and - "s" in dseal and dseal["s"] == sn and - "d" in dseal and serder.compare(said=dseal["d"])): # dseal["d"] == dig - found = True - break - if not found: + found = False # find event seal of delegated event in delegating data + # XXXX ToDo need to change logic here to support native CESR seals not just dicts + # for JSON, CBOR, MGPK + for dseal in dserder.seals: # find delegating seal anchor + if tuple(dseal.keys()) == SealEvent._fields: + seal = SealEvent(**dseal) + if (seal.i == serder.pre and + seal.s == serder.sner.numh and + serder.compare(said=seal.d)): + found = True + break + + if not found: # drop event raise ValidationError("Missing delegation from {} in {} for evt = {}." - "".format(delegator, dserder.ked["a"], serder.ked)) + "".format(delegator, dserder.seals, serder.ked)) + + # Assumes database is reverified each bootup chain-of-custody of dic broken. + # Rule for non-supeding delegated rotation of rotation. + # Returning delegator indicates success and eventually results acceptance + # via Kever.logEvent which also writes the delgating event source couple to + # db.aess so we can find it later + if ((serder.ilk == Ilks.dip) or # delegated inception + (serder.sner.num == self.sner.num + 1) or # inorder event + (serder.sner.num == self.sner.num and + self.ilk == Ilks.ixn and + serder.ilk == Ilks.drt)): # recovery rotation superseding ixn + return delegator # indicates delegation valid with return of delegator + + # Kever.logEvent saves authorizer (delegator) seal source couple in + # db.aess data base so can use it here to recusively look up delegating + # events + + # set up recursive search for superseding delegations + serfn = serder # new potentially superseding delegated event i.e. serf new + bossn = dserder # new delegating event of superseding delegated event i.e. boss new + serfo = self.serder # original delegated event i.e. serf original + bosso = self.fetchDelegatingEvent(delegator, serfo) + + while (True): # superseding delegated rotation of rotation recovery rules + # Only get to here if same sn for drt existing and drt superseding + + if (bossn.sn > bosso.sn or # later supersedes + (bossn.Ilk == Ilks.drt and + bosso.Ilk == Ilks.ixn) ): # drt supersedes ixn + return delegator # valid superseding delegation + + if bossn.said == bosso.said: # same delegating event + nseals = [SealEvent(**seal) for seal in bossn.seals + if tuple(seal.keys()) == SealEvent._fields] + nindex = nseals.index(SealEvent(i=serfn.pre, s=serfn.snh, d=serfn.said)) + oseals = [SealEvent(**seal) for seal in bosso.seals + if tuple(seal.keys()) == SealEvent._fields] + oindex = oseals.index(SealEvent(i=serfo.pre, s=serfo.snh, d=serfo.said)) + + if nindex > oindex: # later seal supersedes + # assumes index can't be None + return delegator # valid superseding delegation + + else: + # ToDo: XXXX may want to cue up business logic for delegator + # if self.mine(delegator): # failed attempt at recovery + raise ValidationError(f"Invalid delegation recovery rotation" + f"of {serfo.ked} by {serfn.ked}") + + # tie condition same sn and drt so need to climb delegation chain + serfn = bossn + bossn = self.fetchDelegatingEvent(delegator, serfn) + serfo = bosso + bosso = self.fetchDelegatingEvent(delegator, serfo) + # repeat + + + def fetchDelegatingEvent(self, delegator, serder): + """Returns delegating event by delegator of delegated event given by + serder otherwise raises ValidationError. + Assumes serder is already delegated event + + Parameters: + delegator (str): qb64 of identifier prefix of delegator + serder (SerderKERI): delegated serder + + """ + dgkey = dgKey(pre=serder.preb, dig=serder.saidb) # database key + if (couple := self.db.getAes(dgkey)): # delegation source couple + seqner, saider = deSourceCouple(couple) + dgkey = dgKey(pre=delegator, dig=saider) # event at its said + # get event by dig not by sn at last event because may have been superceded + if not (raw := self.db.getEvt(dgkey)): + # database broken this should never happen so do not supersede + raise ValidationError(f"Missing delegation event for {serder.ked}") + + dserder = serdering.SerderKERI(raw=bytes(raw)) # original delegating event i.e. boss original - # re-verify signatures or trust the database? - # if database is loaded into memory fresh and reverified each bootup - # when custody of disc is in question then trustable otherwise not + else: #try to find seal the hard way + seal = SealEvent(i=serder.pre, s=serder.snh, d=serder.said)._asdict + if not (dserder := self.db.findAnchoringSealEvent(pre=serder.delpre, seal=seal)): + # database broken this should never happen so do not supersede + raise ValidationError(f"Missing delegation source seal for {serder.ked}") + + return dserder - return delegator # return delegator prefix def logEvent(self, serder, sigers=None, wigers=None, wits=None, first=False, seqner=None, saider=None, firner=None, dater=None): @@ -2452,7 +2657,7 @@ def logEvent(self, serder, sigers=None, wigers=None, wits=None, first=False, Update is idempotent. Logs will not write dup at key if already exists. Parameters: - serder is Serder instance of current event + serder is SerderKERI instance of current event sigers is optional list of Siger instance for current event wigers is optional list of Siger instance of indexed witness sigs wits is optional list of current witnesses provide during any establishment event @@ -2482,14 +2687,19 @@ def logEvent(self, serder, sigers=None, wigers=None, wits=None, first=False, if wits: self.db.wits.put(keys=dgkey, vals=[coring.Prefixer(qb64=w) for w in wits]) self.db.putEvt(dgkey, serder.raw) # idempotent (maybe already excrowed) + val = (coring.Prefixer(qb64b=serder.preb), coring.Seqner(sn=serder.sn)) + for verfer in (serder.verfers if serder.verfers is not None else []): + self.db.pubs.add(keys=(verfer.qb64,), val=val) + for diger in (serder.ndigers if serder.ndigers is not None else []): + self.db.digs.add(keys=(diger.qb64,), val=val) if first: # append event dig to first seen database in order - if seqner and saider: # authorized delegated or issued event + if seqner and saider: # delegation for authorized delegated or issued event couple = seqner.qb64b + saider.qb64b - self.db.setAes(dgkey, couple) # authorizer event seal (delegator/issuer) + self.db.setAes(dgkey, couple) # authorizer (delegator/issuer) event seal fn = self.db.appendFe(serder.preb, serder.saidb) if firner and fn != firner.sn: # cloned replay but replay fn not match - if self.cues is not None: - self.cues.append(dict(kin="noticeBadCloneFN", serder=serder, + if self.cues is not None: # cue to notice BadCloneFN + self.cues.push(dict(kin="noticeBadCloneFN", serder=serder, fn=fn, firner=firner, dater=dater)) logger.info("Kever Mismatch Cloned Replay FN: %s First seen " "ordinal fn %s and clone fn %s \nEvent=\n%s\n", @@ -2511,7 +2721,7 @@ def escrowPSEvent(self, serder, sigers, wigers=None): or fully signed delegated event but not yet verified delegation. Parameters: - serder is Serder instance of event + serder is SerderKERI instance of event sigers is list of Siger instances of indexed controller sigs wigers is optional list of Siger instance of indexed witness sigs """ @@ -2538,7 +2748,7 @@ def escrowPACouple(self, serder, seqner, saider): are escrowed elsewhere. Parameters: - serder is Serder instance of delegated or issued event + serder is SerderKERI instance of delegated or issued event seqner is Seqner instance of sn of seal source event of delegator/issuer saider is Saider instance of said of delegator/issuer """ @@ -2553,7 +2763,7 @@ def escrowPWEvent(self, serder, wigers, sigers=None, seqner=None, saider=None): Update associated logs for escrow of partially witnessed event Parameters: - serder is Serder instance of event + serder is SerderKERI instance of event wigers is list of Siger instance of indexed witness sigs sigers is optional list of Siger instances of indexed controller sigs seqner is Seqner instance of sn of seal source event of delegator/issuer @@ -2575,12 +2785,10 @@ def escrowPWEvent(self, serder, wigers, sigers=None, seqner=None, saider=None): return self.db.addPwe(snKey(serder.preb, serder.sn), serder.saidb) - def state(self, kind=Serials.json): + def state(self): """ - Returns Serder instance of current key state notification message + Returns KeyStateRecord instance of current key state - Parameters: - kind is serialization kind for message json, cbor, mgpk """ eevt = StateEstEvent(s="{:x}".format(self.lastEst.s), d=self.lastEst.d, @@ -2595,7 +2803,7 @@ def state(self, kind=Serials.json): return (state(pre=self.prefixer.qb64, sn=self.sn, # property self.sner.num - pig=(self.serder.ked["p"] if "p" in self.serder.ked else ""), + pig=(self.serder.prior if self.serder.prior is not None else ""), dig=self.serder.said, fn=self.fn, # property self.fner.num stamp=self.dater.dts, # need to add dater object for first seen dts @@ -2604,12 +2812,11 @@ def state(self, kind=Serials.json): eevt=eevt, sith=self.tholder.sith, nsith=self.ntholder.sith if self.ntholder else '0', - ndigs=[diger.qb64 for diger in self.digers], + ndigs=[diger.qb64 for diger in self.ndigers], toad=self.toader.num, wits=self.wits, cnfg=cnfg, dpre=self.delegator, - kind=kind ) ) @@ -2642,9 +2849,9 @@ def fetchPriorDigers(self, sn: int | None = None) -> list | None: for digb in self.db.getKelBackIter(pre, sn): dgkey = dgKey(pre, digb) raw = self.db.getEvt(dgkey) - serder = coring.Serder(raw=bytes(raw)) - if serder.est: # establishment event - return serder.digers + serder = serdering.SerderKERI(raw=bytes(raw)) + if serder.estive: # establishment event + return serder.ndigers return None @@ -2687,8 +2894,8 @@ def fetchLatestContribTo(self, verfers, sn: int | None = None): for digb in self.db.getKelBackIter(pre, sn): dgkey = dgKey(pre, digb) raw = self.db.getEvt(dgkey) - serder = coring.Serder(raw=bytes(raw)) - if serder.est: # establishment event + serder = serdering.SerderKERI(raw=bytes(raw)) + if serder.estive: # establishment event key = serder.verfers[0].qb64 try: i = keys.index(key) # find index of key in keys @@ -2740,8 +2947,8 @@ def fetchLatestContribFrom(self, verfer, sn: int | None = None): for digb in self.db.getKelBackIter(pre, sn): dgkey = dgKey(pre, digb) raw = self.db.getEvt(dgkey) - serder = coring.Serder(raw=bytes(raw)) - if serder.est: # establishment event + serder = serdering.SerderKERI(raw=bytes(raw)) + if serder.estive: # establishment event keys = [verfer.qb64 for verfer in serder.verfers] try: i = keys.index(key) # find index of key in keys @@ -2766,7 +2973,6 @@ class Kevery: Has the following public attributes and properties: Attributes: - evts (Deck): of Events i.e. events to process cues (Deck): of Cues i.e. notices of events needing receipt or requests needing response @@ -2808,13 +3014,12 @@ class Kevery: TimeoutKSN = 3600 # seconds to timeout key state notice message escrows TimeoutQNF = 300 # seconds to timeout query not found escrows - def __init__(self, *, evts=None, cues=None, db=None, rvy=None, + def __init__(self, *, cues=None, db=None, rvy=None, lax=True, local=False, cloned=False, direct=True, check=False): """ Initialize instance: Parameters: - evts (Deck): derived from various messages to be processes cues (Deck) notices to create responses to evts kevers is dict of Kever instances of key state in db db (Baser): instance of database @@ -2833,7 +3038,6 @@ def __init__(self, *, evts=None, cues=None, db=None, rvy=None, a persisted KEL without updating non-idempotent first seen .fels and timestamps. """ - self.evts = evts if evts is not None else decking.Deck() # subclass of deque self.cues = cues if cues is not None else decking.Deck() # subclass of deque if db is None: db = basing.Baser(reopen=True) # default name = "main" @@ -2880,39 +3084,27 @@ def fetchWitnessState(self, pre, sn): for digb in self.db.getKelBackIter(preb, sn): dgkey = dgKey(preb, digb) raw = self.db.getEvt(dgkey) - serder = coring.Serder(raw=bytes(raw)) - if serder.est: + serder = serdering.SerderKERI(raw=bytes(raw)) + if serder.estive: wits = self.db.wits.get(dgkey) return wits return [] - def processEvents(self, evts=None): - """ - Process event dicts in evts or if evts is None in .evts - Parameters: - evts (Deck): each entry is dict that matches call signature of - .processEvent - """ - if evts is None: - evts = self.evts - - while evts: - self.processEvent(**evts.pull()) def processEvent(self, serder, sigers, *, wigers=None, - seqner=None, saider=None, + delseqner=None, delsaider=None, firner=None, dater=None): """ Process one event serder with attached indexd signatures sigers Parameters: - serder is Serder instance of event to process + serder is SerderKERI instance of event to process sigers is list of Siger instances of attached controller indexed sigs wigers is optional list of Siger instances of attached witness indexed sigs - seqner is Seqner instance of delegating event sequence number. + delseqner is Seqner instance of delegating event sequence number. If this event is not delegated then seqner is ignored - sadier is Saider instance of of delegating event SAID. + delsaider is Saider instance of of delegating event SAID. If this event is not delegated then saider is ignored firner is optional Seqner instance of cloned first seen ordinal If cloned mode then firner maybe provided (not None) @@ -2923,27 +3115,20 @@ def processEvent(self, serder, sigers, *, wigers=None, When dater provided then use dater for first seen datetime """ # fetch ked ilk pre, sn, dig to see how to process + pre = serder.pre ked = serder.ked + + # See todo for Prefixer fix redundancy XXX try: # see if code of pre is supported and matches size of pre - Prefixer(qb64b=serder.preb) + Prefixer(qb64=pre) except Exception as ex: # if unsupported code or bad size raises error raise ValidationError("Invalid pre = {} for evt = {}." - "".format(serder.pre, ked)) - pre = serder.pre - ked = serder.ked + "".format(pre, ked)) from ex + sn = serder.sn - ilk = ked["t"] + ilk = serder.ilk # ked["t"] said = serder.said - # if not self.lax: # otherwise in promiscuous mode - # if self.local: - # if pre not in self.prefixes: # nonlocal event when in local mode - # raise ValueError("Nonlocal event pre={} not in prefixes={}." - # "when local mode.".format(pre, self.prefixes)) - # else: - # if pre in self.prefixes: # local event when in nonlocal mode - # raise ValueError("Local event pre={} in prefixes when " - # "nonlocal mode.".format(pre, self.prefixes)) if pre not in self.kevers: # first seen event for pre if ilk in (Ilks.icp, Ilks.dip): # first seen and inception so verify event keys @@ -2955,8 +3140,8 @@ def processEvent(self, serder, sigers, *, wigers=None, sigers=sigers, wigers=wigers, db=self.db, - seqner=seqner, - saider=saider, + delseqner=delseqner, + delsaider=delsaider, firner=firner if self.cloned else None, dater=dater if self.cloned else None, cues=self.cues, @@ -2966,13 +3151,28 @@ def processEvent(self, serder, sigers, *, wigers=None, self.kevers[pre] = kever # not exception so add to kevers if self.direct or self.lax or pre not in self.prefixes: # not own event when owned - # create cue for receipt direct mode for now + # create cue for receipt controller or watcher # receipt of actual type is dependent on own type of identifier self.cues.push(dict(kin="receipt", serder=serder)) + elif not self.direct: # notice of new event + self.cues.push(dict(kin="notice", serder=serder)) + + if kever.locallyWitnessed(): + # ToDo XXXX need to cue task here kin = "witness" + self.cues.push(dict(kin="witness", serder=serder)) + + if kever.locallyOwned(kever.delegator): # delegator may be None + # ToDo XXXX need to cue task here to approve delegation by generating + # and anchoring SealEvent of serder in delegators KEL + # may include MFA business logic for the delegator i.e. is local + self.cues.push(dict(kin="approveDelegation", + delegator=kever.delegator, + serder=serder)) + else: # not inception so can't verify sigs etc, add to out-of-order escrow self.escrowOOEvent(serder=serder, sigers=sigers, - seqner=seqner, saider=saider, wigers=wigers) + seqner=delseqner, saider=delsaider, wigers=wigers) raise OutOfOrderError("Out-of-order event={}.".format(ked)) else: # already accepted inception event for pre so already first seen @@ -2993,9 +3193,11 @@ def processEvent(self, serder, sigers, *, wigers=None, wigers, windices = verifySigs(raw=serder.raw, sigers=wigers, - verfers=eserder.werfers) + verfers=eserder.berfers) if sigers or wigers: # at least one verified sig or wig so log evt + # this allows late arriving witness receipts or controller + # signatures to be added to the databse # not first seen inception so ignore return kever.logEvent(serder, sigers=sigers, wigers=wigers) # idempotent update db logs @@ -3005,36 +3207,49 @@ def processEvent(self, serder, sigers, *, wigers=None, else: # rot, drt, or ixn, so sn matters kever = self.kevers[pre] # get existing kever for pre - kever.cues = self.cues sno = kever.sner.num + 1 # proper sn of new inorder event - if not serder.saider.verify(sad=serder.ked): - raise ValidationError("Invalid SAID {} for event {}".format(said, serder.ked)) - if sn > sno: # sn later than sno so out of order escrow # escrow out-of-order event self.escrowOOEvent(serder=serder, sigers=sigers, - seqner=seqner, saider=saider, wigers=wigers) + seqner=delseqner, saider=delsaider, wigers=wigers) raise OutOfOrderError("Out-of-order event={}.".format(ked)) - elif ((sn == sno) or # new inorder event or recovery - (ilk in (Ilks.rot, Ilks.drt) and kever.lastEst.s < sn <= sno)): + elif ((sn == sno) or # inorder event (ixn, rot, drt) or + (ilk == Ilks.rot and # superseding recovery rot or + kever.lastEst.s < sn <= sno) or + (ilk == Ilks.drt and # delegated superseding recovery drt + kever.lastEst.s <= sn <= sno)): + # verify signatures etc and update state if valid # raise exception if problem. # Otherwise adds to KELs kever.update(serder=serder, sigers=sigers, wigers=wigers, - seqner=seqner, saider=saider, + delseqner=delseqner, delsaider=delsaider, firner=firner if self.cloned else None, dater=dater if self.cloned else None, check=self.check) if self.direct or self.lax or pre not in self.prefixes: # not own event when owned - # create cue for receipt direct mode for now + # create cue for receipt controller or watcher # receipt of actual type is dependent on own type of identifier self.cues.push(dict(kin="receipt", serder=serder)) - elif not self.direct: + elif not self.direct: # notice of new event self.cues.push(dict(kin="notice", serder=serder)) + if kever.locallyWitnessed(): + # ToDo XXXX need to cue task here kin = "witness" + self.cues.push(dict(kin="witness", serder=serder)) + + if kever.locallyOwned(kever.delegator): # delegator may be None + # ToDo XXXX need to cue task here to approve delegation by generating + # and anchoring SealEvent of serder in delegators KEL + # may include MFA business logic for the delegator i.e. is local + self.cues.push(dict(kin="approveDelegation", + delegator=kever.delegator, + serder=serder)) + + else: # maybe duplicitous # check if duplicate of existing valid accepted event ddig = bytes(self.db.getKeLast(key=snKey(pre, sn))).decode("utf-8") @@ -3067,7 +3282,7 @@ def processReceiptWitness(self, serder, wigers): Process one witness receipt serder with attached witness sigers Parameters: - serder is Serder instance of serialized receipt message not receipted event + serder is SerderKERI instance of serialized receipt message not receipted event sigers is list of Siger instances that with witness indexed signatures signature in .raw. Index is offset into witness list of latest establishment event for receipted event. Signature uses key pair @@ -3095,7 +3310,7 @@ def processReceiptWitness(self, serder, wigers): dgkey = dgKey(pre=pre, dig=ldig) raw = bytes(self.db.getEvt(key=dgkey)) # retrieve receipted event at dig # assumes db ensures that raw must not be none - lserder = Serder(raw=raw) # deserialize event raw + lserder = serdering.SerderKERI(raw=raw) # deserialize event raw if not lserder.compare(said=ked["d"]): # stale receipt at sn discard raise ValidationError("Stale receipt at sn = {} for rct = {}." @@ -3137,7 +3352,7 @@ def processReceipt(self, serder, cigars): Process one receipt serder with attached cigars Parameters: - serder is Serder instance of serialized receipt message not receipted message + serder is SerderKERI instance of serialized receipt message not receipted message cigars is list of Cigar instances that contain receipt couple signature in .raw and public key in .verfer @@ -3163,7 +3378,7 @@ def processReceipt(self, serder, cigars): dgkey = dgKey(pre=pre, dig=ldig) raw = bytes(self.db.getEvt(key=dgkey)) # retrieve receipted event at dig # assumes db ensures that raw must not be none - lserder = Serder(raw=raw) # deserialize event raw + lserder = serdering.SerderKERI(raw=raw) # deserialize event raw if not lserder.compare(said=ked["d"]): # stale receipt at sn discard raise ValidationError("Stale receipt at sn = {} for rct = {}." @@ -3206,7 +3421,7 @@ def processReceiptCouples(self, serder, cigars, firner=None): Process attachment with receipt couple Parameters: - serder is Serder instance of receipted serialized event message + serder is SerderKERI instance of receipted serialized event message to which receipts are attached from replay cigars is list of Cigar instances that contain receipt couple signature in .raw and public key in .verfer @@ -3298,7 +3513,7 @@ def processReceiptTrans(self, serder, tsgs): # retrieve event by dig assumes if ldig is not None that event exists at ldig ldig = bytes(ldig).decode("utf-8") lraw = self.db.getEvt(key=dgKey(pre=pre, dig=ldig)) - lserder = Serder(raw=bytes(lraw)) + lserder = serdering.SerderKERI(raw=bytes(lraw)) # verify digs match if not lserder.compare(said=ldig): # mismatch events problem with replay raise ValidationError("Mismatch receipt of event at sn = {} with db." @@ -3329,7 +3544,7 @@ def processReceiptTrans(self, serder, tsgs): # retrieve last event itself of receipter est evt from sdig. sraw = self.db.getEvt(key=dgKey(pre=sprefixer.qb64b, dig=bytes(sdig))) # assumes db ensures that sraw must not be none because sdig was in KE - sserder = Serder(raw=bytes(sraw)) + sserder = serdering.SerderKERI(raw=bytes(sraw)) if not sserder.compare(said=saider.qb64): # endorser's dig not match event raise ValidationError("Bad trans indexed sig group at sn = {}" " for ksn = {}." @@ -3416,7 +3631,7 @@ def processReceiptQuadruples(self, serder, trqs, firner=None): # retrieve last event itself of receipter sraw = self.db.getEvt(key=dgKey(pre=sprefixer.qb64b, dig=bytes(sdig))) # assumes db ensures that sraw must not be none because sdig was in KE - sserder = Serder(raw=bytes(sraw)) + sserder = serdering.SerderKERI(raw=bytes(sraw)) if not sserder.compare(said=saider.qb64): # seal dig not match event raise ValidationError("Bad trans receipt quadruple at sn = {}" " for rct = {}." @@ -3463,6 +3678,7 @@ def removeStaleReplyEndRole(self, saider): """ pass + def removeStaleReplyLocScheme(self, saider): """ Process reply escrow at saider for route "/loc/scheme" @@ -3481,6 +3697,7 @@ def registerReplyRoutes(self, router): router.addRoute("/loc/scheme", self, suffix="LocScheme") router.addRoute("/ksn/{aid}", self, suffix="KeyStateNotice") + def processReplyEndRole(self, *, serder, saider, route, cigars=None, tsgs=None, **kwargs): """ @@ -3490,7 +3707,7 @@ def processReplyEndRole(self, *, serder, saider, route, Assumes already validated saider, dater, and route from serder.ked Parameters: - serder (Serder): instance of reply msg (SAD) + serder (SerderKERI): instance of reply msg (SAD) saider (Saider): instance from said in serder (SAD) route (str): reply route cigars (list): of Cigar instances that contain nontrans signing couple @@ -3545,8 +3762,8 @@ def processReplyEndRole(self, *, serder, saider, route, raise ValidationError(f"Usupported route={route} in {Ilks.rpy} " f"msg={serder.ked}.") route = "/end/role" # escrow based on route base + data = serder.ked['a'] - data = serder.ked["a"] for k in ("cid", "role", "eid"): if k not in data: raise ValidationError(f"Missing element={k} from attributes in" @@ -3568,7 +3785,7 @@ def processReplyEndRole(self, *, serder, saider, route, aid=aid, osaider=osaider, cigars=cigars, tsgs=tsgs) if not accepted: - raise UnverifiedReplyError(f"Unverified reply.") + raise UnverifiedReplyError(f"Unverified end role reply. {serder.ked}") self.updateEnd(keys=keys, saider=saider, allowed=allowed) # update .eans and .ends @@ -3581,7 +3798,7 @@ def processReplyLocScheme(self, *, serder, saider, route, Assumes already validated saider, dater, and route from serder.ked Parameters: - serder (Serder): instance of reply msg (SAD) + serder (SerderKERI): instance of reply msg (SAD) saider (Saider): instance from said in serder (SAD) route (str): reply route cigars (list): of Cigar instances that contain nontrans signing couple @@ -3665,7 +3882,7 @@ def processReplyLocScheme(self, *, serder, saider, route, aid=aid, osaider=osaider, cigars=cigars, tsgs=tsgs) if not accepted: - raise UnverifiedReplyError(f"Unverified reply.") + raise UnverifiedReplyError(f"Unverified loc scheme reply. {serder.ked}") self.updateLoc(keys=keys, saider=saider, url=url) # update .lans and .locs @@ -3679,7 +3896,7 @@ def processReplyKeyStateNotice(self, *, serder, saider, route, Assumes already validated saider, dater, and route from serder.ked Parameters: - serder (Serder): instance of reply msg (SAD) + serder (SerderKERI): instance of reply msg (SAD) saider (Saider): instance from said in serder (SAD) route (str): reply route cigars (list): of Cigar instances that contain nontrans signing couple @@ -3741,51 +3958,48 @@ def processReplyKeyStateNotice(self, *, serder, saider, route, f"msg={serder.ked}.") aid = kwargs["aid"] data = serder.ked["a"] - kserder = coring.Serder(ked=data) + try: + ksr = KeyStateRecord._fromdict(d=data) + except Exception as ex: + raise ValidationError(f"Malformed key state notice = {data}.") from ex - for k in KSN_LABELS: - if k not in kserder.ked: - raise ValidationError("Missing element = {} from {} msg." - " ksn = {}.".format(k, Ilks.ksn, - serder.pretty())) # fetch from serder to process - ked = kserder.ked - pre = kserder.pre - sn = kserder.sn + pre = ksr.i + sn = int(ksr.s, 16) # check source and ensure we should accept it - baks = ked["b"] + baks = ksr.b wats = set() for _, habr in self.db.habs.getItemIter(): wats |= set(habr.watchers) # not in promiscuous mode if not self.lax: - if aid != kserder.pre and \ + if aid != ksr.i and \ aid not in baks and \ aid not in wats: raise kering.UntrustedKeyStateSource("key state notice for {} from untrusted source {} " - .format(kserder.pre, aid)) + .format(ksr.pre, aid)) - if kserder.pre in self.kevers: - kever = self.kevers[kserder.pre] - if kserder.sn < kever.sner.num: + if ksr.i in self.kevers: + kever = self.kevers[ksr.i] + if int(ksr.s, 16) < kever.sner.num: raise ValidationError("Skipped stale key state at sn {} for {}." - "".format(kserder.sn, kserder.pre)) + "".format(int(ksr.s, 16), ksr.i)) keys = (pre, aid,) osaider = self.db.knas.get(keys=keys) # get old said if any - dater = coring.Dater(dts=serder.ked["dt"]) + dater = coring.Dater(dts=ksr.dt) # BADA Logic accepted = self.rvy.acceptReply(serder=serder, saider=saider, route=route, aid=aid, osaider=osaider, cigars=cigars, tsgs=tsgs) if not accepted: - raise UnverifiedReplyError(f"Unverified reply.") + raise UnverifiedReplyError(f"Unverified key state notice reply. {serder.ked}") ldig = self.db.getKeLast(key=snKey(pre=pre, sn=sn)) # retrieve dig of last event at sn. - diger = coring.Diger(qb64=ked["d"]) + diger = coring.Diger(qb64=ksr.d) # Only accept key state if for last seen version of event at sn if ldig is not None: # escrow because event does not yet exist in database @@ -3793,15 +4007,15 @@ def processReplyKeyStateNotice(self, *, serder, saider, route, # retrieve last event itself of signer given sdig sraw = self.db.getEvt(key=dgKey(pre=pre, dig=ldig)) # assumes db ensures that sraw must not be none because sdig was in KE - sserder = Serder(raw=bytes(sraw)) + sserder = serdering.SerderKERI(raw=bytes(sraw)) if not sserder.compare(said=diger.qb64b): # mismatch events problem with replay - raise ValidationError("Mismatch keystate at sn = {} with db." - "".format(ked["s"])) + raise ValidationError(f"Mismatch keystate at sn = {int(ksr.s,16)}" + f" with db.") ksaider = coring.Saider(qb64=diger.qb64) - self.updateKeyState(aid=aid, serder=kserder, saider=ksaider, dater=dater) - self.cues.append(dict(kin="keyStateSaved", serder=kserder)) + self.updateKeyState(aid=aid, ksr=ksr, saider=ksaider, dater=dater) + self.cues.push(dict(kin="keyStateSaved", ksn=ksr._asdict())) def updateEnd(self, keys, saider, allowed=None): """ @@ -3838,38 +4052,7 @@ def updateLoc(self, keys, saider, url): self.db.locs.pin(keys=keys, val=locer) # overwrite - def escrowKeyStateNotice(self, *, pre, aid, serder, saider, dater, cigars=None, tsgs=None): - """ - Escrow reply by route - - Parameters: - pre (str): identifier of key state - aid (str): identifier of authorizer of key state - serder (Serder): instance of reply msg (SAD) - saider (Saider): instance from said in serder (SAD) - dater (Dater): instance from date-time in serder (SAD) - cigars (list): of Cigar instances that contain nontrans signing couple - signature in .raw and public key in .verfer - - tsgs (Iterable): of quadruples of form (prefixer, seqner, diger, siger) where: - prefixer is pre of trans endorser - seqner is sequence number of trans endorser's est evt for keys for sigs - diger is digest of trans endorser's est evt for keys for sigs - siger is indexed sig from trans endorser's key from est evt - """ - keys = (saider.qb64,) - self.db.kdts.put(keys=keys, val=dater) # first one idempotent - self.db.ksns.put(keys=keys, val=serder) # first one idempotent - - for prefixer, seqner, diger, sigers in tsgs: # iterate over each tsg - quadkeys = (saider.qb64, prefixer.qb64, f"{seqner.sn:032x}", diger.qb64) - self.db.ksgs.put(keys=quadkeys, vals=sigers) - for cigar in cigars: # process each couple to verify sig and write to db - self.db.kcgs.put(keys=keys, vals=[(cigar.verfer, cigar)]) - - return self.db.knes.put(keys=(pre, aid), vals=[saider]) # overwrite - - def updateKeyState(self, aid, serder, saider, dater): + def updateKeyState(self, aid, ksr, saider, dater): """ Update Reply SAD in database given by by serder and associated databases for attached cig couple or sig quadruple. @@ -3877,7 +4060,7 @@ def updateKeyState(self, aid, serder, saider, dater): Parameters: aid (str): identifier of key state - serder (Serder): instance of reply msg (SAD) + ksr (KeyStateRecord): converted from key state notice dict in reply msg saider (Saider): instance from said in serder (SAD) dater (Dater): instance from date-time in serder (SAD) """ @@ -3885,95 +4068,24 @@ def updateKeyState(self, aid, serder, saider, dater): # Add source of ksn to the key for DATEs too... (source AID, ksn AID) self.db.kdts.put(keys=keys, val=dater) # first one idempotent - self.db.ksns.pin(keys=keys, val=serder) # first one idempotent - # Add source of ksn to the key... (source AID, ksn AID) - self.db.knas.pin(keys=(serder.pre, aid), val=saider) # overwrite + self.db.ksns.pin(keys=keys, val=ksr) # first one idempotent + # Add source of ksr to the key... (ksr AID, source aid) + self.db.knas.pin(keys=(ksr.i, aid), val=saider) # overwrite def removeKeyState(self, saider): if saider: keys = (saider.qb64,) - self.db.ksgs.trim(keys=(saider.qb64, "")) # remove whole branch - self.db.kcgs.rem(keys=keys) self.db.ksns.rem(keys=keys) self.db.kdts.rem(keys=keys) - def processEscrowKeyState(self): - """ - Process escrows for reply messages. Escrows are keyed by reply pre - and val is reply said - - triple (prefixer, seqner, diger) - quadruple (prefixer, seqner, diger, siger) - - """ - for (pre, aid, ion), saider in self.db.knes.getIoItemIter(): - try: - tsgs = fetchTsgs(db=self.db.ksgs, saider=saider) - - keys = (saider.qb64,) - dater = self.db.kdts.get(keys=keys) - serder = self.db.ksns.get(keys=keys) - vcigars = self.db.kcgs.get(keys=keys) - - try: - if not (dater and serder and (tsgs or vcigars)): - raise ValueError(f"Missing escrow artifacts at said={saider.qb64}" - f"for pre={pre}.") - - cigars = [] - if vcigars: - for (verfer, cigar) in vcigars: - cigar.verfer = verfer - cigars.append(cigar) - - # do date math for stale escrow - if ((helping.nowUTC() - dater.datetime) > - datetime.timedelta(seconds=self.TimeoutKSN)): - # escrow stale so raise ValidationError which unescrows below - logger.info("Kevery unescrow error: Stale key state escrow " - " at pre = %s\n", pre) - - raise ValidationError(f"Stale key state escrow at pre = {pre}.") - - self.processReplyKeyStateNotice(serder=serder, saider=saider, route=serder.ked["r"], cigars=cigars, - tsgs=tsgs, aid=aid) - - except kering.OutOfOrderKeyStateError as ex: - # still waiting on missing prior event to validate - if logger.isEnabledFor(logging.DEBUG): - logger.exception("Kevery unescrow attempt failed: %s\n", ex.args[0]) - else: - logger.error("Kevery unescrow attempt failed: %s\n", ex.args[0]) - - except Exception as ex: # other error so remove from reply escrow - self.db.knes.remIokey(iokeys=(pre, aid, ion)) # remove escrow - self.removeKeyState(saider) - if logger.isEnabledFor(logging.DEBUG): - logger.exception("Kevery unescrowed due to error: %s\n", ex.args[0]) - else: - logger.error("Kevery unescrowed due to error: %s\n", ex.args[0]) - - else: # unescrow succeded - self.db.knes.remIokey(iokeys=(pre, aid, ion)) # remove escrow only - logger.info("Kevery unescrow succeeded for key state=\n%s\n", - serder.pretty()) - - except Exception as ex: # log diagnostics errors etc - self.db.knes.remIokey(iokeys=(pre, aid, ion)) # remove escrow - self.removeKeyState(saider) - if logger.isEnabledFor(logging.DEBUG): - logger.exception("Kevery unescrowed due to error: %s\n", ex.args[0]) - else: - logger.error("Kevery unescrowed due to error: %s\n", ex.args[0]) - def processQuery(self, serder, source=None, sigers=None, cigars=None): """ Process query mode replay message for collective or single element query. Assume promiscuous mode for now. Parameters: - serder (Serder) is query message serder + serder (SerderKERI) is query message serder source (Prefixer) identifier prefix of querier sigers (list) of Siger instances of attached controller indexed sigs cigars (list) of Cigar instance of attached non-trans sigs @@ -3992,7 +4104,7 @@ def processQuery(self, serder, source=None, sigers=None, cigars=None): pre = qry["i"] src = qry["src"] anchor = qry["a"] if "a" in qry else None - sn = qry["s"] if "s" in qry else None + sn = int(qry["s"], 16) if "s" in qry else None if pre not in self.kevers: self.escrowQueryNotFoundEvent(serder=serder, prefixer=source, sigers=sigers, cigars=cigars) @@ -4000,7 +4112,7 @@ def processQuery(self, serder, source=None, sigers=None, cigars=None): kever = self.kevers[pre] if anchor: - if not self.db.findAnchoringEvent(pre=pre, anchor=anchor): + if not self.db.findAnchoringSealEvent(pre=pre, seal=anchor): self.escrowQueryNotFoundEvent(serder=serder, prefixer=source, sigers=sigers, cigars=cigars) raise QueryNotFoundError("Query not found error={}.".format(ked)) @@ -4039,8 +4151,9 @@ def processQuery(self, serder, source=None, sigers=None, cigars=None): self.escrowQueryNotFoundEvent(serder=serder, prefixer=source, sigers=sigers, cigars=cigars) raise QueryNotFoundError("Query not found error={}.".format(ked)) - ksn = reply(route=f"/ksn/{src}", data=kever.state().ked) - self.cues.push(dict(kin="reply", src=src, route="/ksn", serder=ksn, dest=source.qb64)) + rserder = reply(route=f"/ksn/{src}", data=kever.state()._asdict()) + self.cues.push(dict(kin="reply", src=src, route="/ksn", serder=rserder, + dest=source.qb64)) elif route == "mbx": pre = qry["i"] @@ -4062,7 +4175,7 @@ def processQuery(self, serder, source=None, sigers=None, cigars=None): def fetchEstEvent(self, pre, sn): """ - Returns Serder instance of establishment event that is authoritative for + Returns SerderKERI instance of establishment event that is authoritative for event in KEL for pre at sn. Returns None if no event at sn accepted in KEL for pre @@ -4082,7 +4195,7 @@ def fetchEstEvent(self, pre, sn): if not raw: return None - serder = Serder(raw=raw) # deserialize event raw + serder = serdering.SerderKERI(raw=raw) # deserialize event raw if serder.ked["t"] in (Ilks.icp, Ilks.dip, Ilks.rot, Ilks.drt): return serder # establishment event so return @@ -4095,7 +4208,7 @@ def escrowOOEvent(self, serder, sigers, seqner=None, saider=None, wigers=None): Update associated logs for escrow of Out-of-Order event Parameters: - serder (Serder): instance of event + serder (SerderKERI): instance of event sigers (list): of Siger instance for event seqner (Seqner): instance of sn of event delegatint/issuing event if any saider (Saider): instance of dig of event delegatint/issuing event if any @@ -4121,7 +4234,7 @@ def escrowQueryNotFoundEvent(self, prefixer, serder, sigers, cigars=None): Parameters: prefixer (Prefixer): source of query message - serder (Serder): instance of event + serder (SerderKERI): instance of event sigers (list): of Siger instance for event cigars (list): of non-transferable receipts """ @@ -4144,7 +4257,7 @@ def escrowLDEvent(self, serder, sigers): Update associated logs for escrow of Likely Duplicitous event Parameters: - serder is Serder instance of event + serder is SerderKERI instance of event sigers is list of Siger instance for event """ dgkey = dgKey(serder.preb, serder.saidb) @@ -4168,7 +4281,7 @@ def escrowUWReceipt(self, serder, wigers, said): for receipted event. Parameters: - serder (Serder): instance of receipt msg not receipted event + serder (SerderKERI): instance of receipt msg not receipted event wigers (list): of Siger instances for witness indexed signature of receipted event said (str) qb64 said of receipted event not serder.dig because @@ -4201,7 +4314,7 @@ def escrowUReceipt(self, serder, cigars, said): cig is non-indexed signature on event with key pair derived from rpre Parameters: - serder (Serder): instance of receipt msg not receipted event + serder (SerderKERI): instance of receipt msg not receipted event cigars (list): of Cigar instances for event receipt said (str): qb64 said in receipt of receipted event not serder.dig because serder is of receipt not receipted event @@ -4358,7 +4471,6 @@ def processEscrows(self): self.processEscrowPartialWigs() self.processEscrowPartialSigs() self.processEscrowDuplicitous() - self.processEscrowKeyState() self.processQueryNotFound() except Exception as ex: # log diagnostics errors etc @@ -4391,7 +4503,7 @@ def processEscrowOutOfOrders(self): self.db.putEvt(dgkey, serder.raw) self.db.addOoe(snKey(pre, sn), serder.dig) where: - serder is Serder instance of event + serder is SerderKERI instance of event sigers is list of Siger instance for event pre is str qb64 of identifier prefix of event sn is int sequence number of event @@ -4442,7 +4554,7 @@ def processEscrowOutOfOrders(self): raise ValidationError("Missing escrowed evt at dig = {}." "".format(bytes(edig))) - eserder = Serder(raw=bytes(eraw)) # escrowed event + eserder = serdering.SerderKERI(raw=bytes(eraw)) # escrowed event # get sigs and attach sigs = self.db.getSigs(dgKey(pre, bytes(edig))) @@ -4524,7 +4636,7 @@ def processEscrowPartialSigs(self): .db.putEvt(dgkey, serder.raw) .db.addPse(snKey(pre, sn), serder.digb) where: - serder is Serder instance of event + serder is SerderKERI instance of event sigers is list of Siger instance for event pre is str qb64 of identifier prefix of event sn is int sequence number of event @@ -4577,7 +4689,7 @@ def processEscrowPartialSigs(self): raise ValidationError("Missing escrowed evt at dig = {}." "".format(bytes(edig))) - eserder = Serder(raw=bytes(eraw)) # escrowed event + eserder = serdering.SerderKERI(raw=bytes(eraw)) # escrowed event # get sigs and attach sigs = self.db.getSigs(dgkey) if not sigs: # otherwise its a list of sigs @@ -4589,28 +4701,28 @@ def processEscrowPartialSigs(self): "dig = {}.".format(bytes(edig))) # seal source (delegator issuer if any) - seqner = saider = None + delseqner = delsaider = None couple = self.db.getPde(dgkey) if couple is not None: - seqner, saider = deSourceCouple(couple) + delseqner, delsaider = deSourceCouple(couple) elif eserder.ked["t"] in (Ilks.dip, Ilks.drt,): if eserder.pre in self.kevers: delpre = self.kevers[eserder.pre].delegator else: delpre = eserder.ked["di"] - anchor = dict(i=eserder.ked["i"], s=eserder.sn, d=eserder.said) - srdr = self.db.findAnchoringEvent(pre=delpre, anchor=anchor) + seal = dict(i=eserder.ked["i"], s=eserder.snh, d=eserder.said) + srdr = self.db.findAnchoringSealEvent(pre=delpre, seal=seal) if srdr is not None: - seqner = coring.Seqner(sn=srdr.sn) - saider = srdr.saider - couple = seqner.qb64b + saider.qb64b + delseqner = coring.Seqner(sn=srdr.sn) + delsaider = coring.Saider(qb64=srdr.said) + couple = delseqner.qb64b + delsaider.qb64b self.db.putPde(dgkey, couple) # process event sigers = [Siger(qb64b=bytes(sig)) for sig in sigs] self.processEvent(serder=eserder, sigers=sigers, - seqner=seqner, saider=saider) + delseqner=delseqner, delsaider=delsaider) # If process does NOT validate sigs or delegation seal (when delegated), # but there is still one valid signature then process will @@ -4639,7 +4751,7 @@ def processEscrowPartialSigs(self): self.db.delPse(snKey(pre, sn), edig) # removes one escrow at key val if eserder is not None and eserder.ked["t"] in (Ilks.dip, Ilks.drt,): - self.cues.append(dict(kin="psUnescrow", serder=eserder)) + self.cues.push(dict(kin="psUnescrow", serder=eserder)) if logger.isEnabledFor(logging.DEBUG): logger.exception("Kevery unescrowed: %s\n", ex.args[0]) @@ -4654,7 +4766,7 @@ def processEscrowPartialSigs(self): self.db.delPde(dgkey) # remove escrow if any if eserder is not None and eserder.ked["t"] in (Ilks.dip, Ilks.drt,): - self.cues.append(dict(kin="psUnescrow", serder=eserder)) + self.cues.push(dict(kin="psUnescrow", serder=eserder)) logger.info("Kevery unescrow succeeded in valid event: " "event=\n%s\n", json.dumps(eserder.ked, indent=1)) @@ -4684,7 +4796,7 @@ def processEscrowPartialWigs(self): .db.putEvt(dgkey, serder.raw) .db.addPwe(snKey(pre, sn), serder.digb) where: - serder is Serder instance of event + serder is SerderKERI instance of event wigers is list of Siger instance for of witnesses of event pre is str qb64 of identifier prefix of event sn is int sequence number of event @@ -4736,7 +4848,7 @@ def processEscrowPartialWigs(self): raise ValidationError("Missing escrowed evt at dig = {}." "".format(bytes(edig))) - eserder = Serder(raw=bytes(eraw)) # escrowed event + eserder = serdering.SerderKERI(raw=bytes(eraw)) # escrowed event # get sigs sigs = self.db.getSigs(dgKey(pre, bytes(edig))) # list of sigs @@ -4767,12 +4879,13 @@ def processEscrowPartialWigs(self): wigers = [Siger(qb64b=bytes(wig)) for wig in wigs] # seal source (delegator issuer if any) - seqner = saider = None + delseqner = delsaider = None couple = self.db.getPde(dgKey(pre, bytes(edig))) if couple is not None: - seqner, saider = deSourceCouple(couple) + delseqner, delsaider = deSourceCouple(couple) - self.processEvent(serder=eserder, sigers=sigers, wigers=wigers, seqner=seqner, saider=saider) + self.processEvent(serder=eserder, sigers=sigers, wigers=wigers, + delseqner=delseqner, delsaider=delsaider) # If process does NOT validate wigs then process will attempt # to re-escrow and then raise MissingWitnessSignatureError @@ -5048,7 +5161,7 @@ def processEscrowUnverNonTrans(self): raise ValidationError("Invalid receipted evt reference" " at pre={} sn={:x}".format(pre, sn)) - serder = Serder(raw=bytes(raw)) # receipted event + serder = serdering.SerderKERI(raw=bytes(raw)) # receipted event # compare digs if rsaider.qb64b != serder.saidb: @@ -5177,7 +5290,7 @@ def processQueryNotFound(self): raise ValidationError("Missing escrowed evt at dig = {}." "".format(bytes(edig))) - eserder = Serder(raw=bytes(eraw)) # escrowed event + eserder = serdering.SerderKERI(raw=bytes(eraw)) # escrowed event # get sigs and attach sigs = self.db.getSigs(dgKey(pre, bytes(edig))) @@ -5260,7 +5373,7 @@ def _processEscrowFindUnver(self, pre, sn, rsaider, wiger=None, cigar=None): for dig in self.db.getPwesIter(key=snKey(pre, sn)): # search entries dig = bytes(dig) # database dig of receipted event # get the escrowed event using database dig in .Pwes - serder = Serder(raw=bytes(self.db.getEvt(dgKey(pre, dig)))) # receipted event + serder = serdering.SerderKERI(raw=bytes(self.db.getEvt(dgKey(pre, dig)))) # receipted event # compare digs to ensure database dig and rdiger (receipt's dig) match if rsaider.qb64b != dig: continue # not match keep looking @@ -5431,7 +5544,7 @@ def processEscrowUnverTrans(self): raise ValidationError("Invalid receipted evt reference " "at pre={} sn={:x}".format(pre, sn)) - serder = Serder(raw=bytes(raw)) # receipted event + serder = serdering.SerderKERI(raw=bytes(raw)) # receipted event # compare digs if esaider.qb64b != serder.saidb: @@ -5457,7 +5570,7 @@ def processEscrowUnverTrans(self): # retrieve last event itself of receipter sraw = self.db.getEvt(key=dgKey(pre=sprefixer.qb64b, dig=bytes(sdig))) # assumes db ensures that sraw must not be none because sdig was in KE - sserder = Serder(raw=bytes(sraw)) + sserder = serdering.SerderKERI(raw=bytes(sraw)) if not sserder.compare(said=ssaider.qb64): # seal dig not match event # this unescrows raise ValidationError("Bad chit seal at sn = {} for rct = {}." @@ -5540,7 +5653,7 @@ def processEscrowDuplicitous(self): self.db.putEvt(dgkey, serder.raw) self.db.addLde(snKey(pre, sn), serder.digb) where: - serder is Serder instance of event + serder is SerderKERI instance of event sigers is list of Siger instance for event pre is str qb64 of identifier prefix of event sn is int sequence number of event @@ -5590,7 +5703,7 @@ def processEscrowDuplicitous(self): raise ValidationError("Missing escrowed evt at dig = {}." "".format(bytes(edig))) - eserder = Serder(raw=bytes(eraw)) # escrowed event + eserder = serdering.SerderKERI(raw=bytes(eraw)) # escrowed event # get sigs and attach sigs = self.db.getSigs(dgKey(pre, bytes(edig))) @@ -5676,7 +5789,7 @@ def loadEvent(db, preb, dig): if not (raw := db.getEvt(key=dgkey)): raise ValueError("Missing event for dig={}.".format(dig)) - serder = coring.Serder(raw=bytes(raw)) + serder = serdering.SerderKERI(raw=bytes(raw)) event["ked"] = serder.ked sn = serder.sn @@ -5694,7 +5807,7 @@ def loadEvent(db, preb, dig): event["signatures"] = dsigs # add witness state at this event - wits = db.wits.get(dgkey) if serder.est else [] + wits = db.wits.get(dgkey) if serder.estive else [] event["witnesses"] = [wit.qb64 for wit in wits] # add indexed witness signatures to attachments diff --git a/src/keri/core/parsing.py b/src/keri/core/parsing.py index 730fadf36..e3f18bb9f 100644 --- a/src/keri/core/parsing.py +++ b/src/keri/core/parsing.py @@ -6,15 +6,15 @@ """ import logging +import traceback from collections import namedtuple from dataclasses import dataclass, astuple -from .coring import (Ilks, CtrDex, Counter, Seqner, Siger, Cigar, IdxSigDex, - Dater, Verfer, Prefixer, Serder, Saider, Pather, Idents, - Sadder, ) +from .coring import (Ilks, CtrDex, Counter, Seqner, Siger, Cigar, + Dater, Verfer, Prefixer, Saider, Pather, Protos ) +from . import serdering from .. import help from .. import kering -from ..vc.proving import Creder logger = help.ogler.getLogger() @@ -287,9 +287,9 @@ def _transIdxSigGroups(self, ctr, ims, cold=Colds.txt, pipelined=False): cold=cold, abort=pipelined) ictr = yield from self._extractor(ims=ims, - klas=Counter, - cold=cold, - abort=pipelined) + klas=Counter, + cold=cold, + abort=pipelined) if ictr.code != CtrDex.ControllerIdxSigs: raise kering.UnexpectedCountCodeError("Wrong " "count code={}.Expected code={}." @@ -304,7 +304,6 @@ def _transIdxSigGroups(self, ctr, ims, cold=Colds.txt, pipelined=False): yield prefixer, seqner, saider, isigers - def _nonTransReceiptCouples(self, ctr, ims, cold=Colds.txt, pipelined=False): """ Extract attached rct couplets into list of sigvers @@ -380,7 +379,7 @@ def parse(self, ims=None, framed=None, pipeline=None, kvy=None, tvy=None, exc=No except StopIteration: break - def parseOne(self, ims=None, framed=True, pipeline=False, kvy=None, tvy=None, exc=None, rvy=None): + def parseOne(self, ims=None, framed=True, pipeline=False, kvy=None, tvy=None, exc=None, rvy=None, vry=None): """ Processes one messages from incoming message stream, ims, when provided. Otherwise process message from .ims @@ -413,7 +412,8 @@ def parseOne(self, ims=None, framed=True, pipeline=False, kvy=None, tvy=None, ex kvy=kvy, tvy=tvy, exc=exc, - rvy=rvy) + rvy=rvy, + vry=vry) while True: try: next(parsator) @@ -656,7 +656,9 @@ def parsator(self, ims=None, framed=None, pipeline=None, kvy=None, tvy=None, exc return True # should never return - def msgParsator(self, ims=None, framed=True, pipeline=False, kvy=None, tvy=None, exc=None, rvy=None, vry=None): + + def msgParsator(self, ims=None, framed=True, pipeline=False, + kvy=None, tvy=None, exc=None, rvy=None, vry=None): """ Returns generator that upon each iteration extracts and parses msg with attached crypto material (signature etc) from incoming message @@ -701,6 +703,8 @@ def msgParsator(self, ims=None, framed=True, pipeline=False, kvy=None, tvy=None, """ + serdery = serdering.Serdery(version=kering.Version) + if ims is None: ims = self.ims @@ -713,14 +717,24 @@ def msgParsator(self, ims=None, framed=True, pipeline=False, kvy=None, tvy=None, raise kering.ColdStartError("Expecting message counter tritet={}" "".format(cold)) # Otherwise its a message cold start - while True: # extract and deserialize message from ims + + while True: # extract, deserialize, and strip message from ims try: - sadder = Sadder(raw=ims) + serder = serdery.reap(ims=ims) # can set version here except kering.ShortageError as ex: # need more bytes yield - else: # extracted successfully - del ims[:sadder.size] # strip off event from front of ims - break + else: # extracted and stripped successfully + break # break out of while loop + + + #while True: # extract and deserialize message from ims + #try: + #sadder = Sadder(raw=ims) + #except kering.ShortageError as ex: # need more bytes + #yield + #else: # extracted successfully + #del ims[:sadder.size] # strip off event from front of ims + #break sigers = [] # list of Siger instances of attached indexed controller signatures wigers = [] # list of Siger instance of attached indexed witness signatures @@ -734,7 +748,9 @@ def msgParsator(self, ims=None, framed=True, pipeline=False, kvy=None, tvy=None, # List of tuples from extracted first seen replay couples frcs = [] # each converted couple is (seqner, dater) # List of tuples from extracted source seal couples (delegator or issuer) - sscs = [] # each converted couple is (seqner, diger) for delegating/issuing event + sscs = [] # each converted couple is (seqner, diger) for delegating or issuing event + # List of tuples from extracted source seal triples (issuer or issuance tel event) + ssts = [] # each converted couple is (seqner, diger) for delegating or issuing event # List of tuples from extracted SAD path sig groups from transferable identifiers sadtsgs = [] # each converted group is tuple of (path, i, s, d) quad plus list of sigs # List of tuples from extracted SAD path sig groups from non-trans identifiers @@ -897,6 +913,27 @@ def msgParsator(self, ims=None, framed=True, pipeline=False, kvy=None, tvy=None, abort=pipelined) sscs.append((seqner, saider)) + elif ctr.code == CtrDex.SealSourceTriples: + # extract attached anchoring source event information + # pre+snu+dig + # pre is prefix of event + # snu is sequence number of event + # dig is digest of event + for i in range(ctr.count): # extract each attached quadruple + prefixer = yield from self._extractor(ims, + klas=Prefixer, + cold=cold, + abort=pipelined) + seqner = yield from self._extractor(ims, + klas=Seqner, + cold=cold, + abort=pipelined) + saider = yield from self._extractor(ims, + klas=Saider, + cold=cold, + abort=pipelined) + ssts.append((prefixer, seqner, saider)) + elif ctr.code == CtrDex.SadPathSigGroup: path = yield from self._extractor(ims, klas=Pather, @@ -970,14 +1007,18 @@ def msgParsator(self, ims=None, framed=True, pipeline=False, kvy=None, tvy=None, "attachment group of size={}.".format(pags)) raise # no pipeline group so can't preflush, must flush stream - if sadder.ident == Idents.keri: - serder = Serder(sad=sadder) + if isinstance(serder, serdering.SerderKERI): + ilk = serder.ilk # dispatch abased on ilk - ilk = serder.ked["t"] # dispatch abased on ilk + #if sadder.proto == Protos.keri: + #serder = Serder(sad=sadder) + + #ilk = serder.ked["t"] # dispatch abased on ilk if ilk in [Ilks.icp, Ilks.rot, Ilks.ixn, Ilks.dip, Ilks.drt]: # event msg firner, dater = frcs[-1] if frcs else (None, None) # use last one if more than one - seqner, saider = sscs[-1] if sscs else (None, None) # use last one if more than one + # when present assumes this is source seal of delegating event in delegator's KEL + delseqner, delsaider = sscs[-1] if sscs else (None, None) # use last one if more than one if not sigers: raise kering.ValidationError("Missing attached signature(s) for evt " "= {}.".format(serder.ked)) @@ -985,8 +1026,8 @@ def msgParsator(self, ims=None, framed=True, pipeline=False, kvy=None, tvy=None, kvy.processEvent(serder=serder, sigers=sigers, wigers=wigers, - seqner=seqner, - saider=saider, + delseqner=delseqner, + delsaider=delsaider, firner=firner, dater=dater) @@ -995,14 +1036,15 @@ def msgParsator(self, ims=None, framed=True, pipeline=False, kvy=None, tvy=None, if trqs: kvy.processReceiptQuadruples(serder, trqs, firner=firner) - except AttributeError as e: + except AttributeError as ex: raise kering.ValidationError("No kevery to process so dropped msg" - "= {}.".format(serder.pretty())) + "= {}.".format(serder.pretty())) from ex elif ilk in [Ilks.rct]: # event receipt msg (nontransferable) if not (cigars or wigers or tsgs): raise kering.ValidationError("Missing attached signatures on receipt" "msg = {}.".format(serder.ked)) + try: if cigars: kvy.processReceipt(serder=serder, cigars=cigars) @@ -1068,58 +1110,50 @@ def msgParsator(self, ims=None, framed=True, pipeline=False, kvy=None, tvy=None, elif ilk in (Ilks.exn,): args = dict(serder=serder) - if ssgs: - pre, sigers = ssgs[-1] if ssgs else (None, None) # use last one if more than one - args["source"] = pre - args["sigers"] = sigers - - elif cigars: - args["cigars"] = cigars - - else: - raise kering.ValidationError("Missing attached exchanger signature(s) " - "to peer exchange msg = {}.".format(serder.pretty())) if pathed: args["pathed"] = pathed try: - exc.processEvent(**args) + if cigars: + exc.processEvent(cigars=cigars, **args) - except AttributeError as e: + if tsgs: + exc.processEvent(tsgs=tsgs, **args) + + except AttributeError: raise kering.ValidationError("No Exchange to process so dropped msg" "= {}.".format(serder.pretty())) elif ilk in (Ilks.vcp, Ilks.vrt, Ilks.iss, Ilks.rev, Ilks.bis, Ilks.brv): # TEL msg + # this transaction event seal in Issuer's KEL (controller of Issuer AID) seqner, saider = sscs[-1] if sscs else (None, None) # use last one if more than one try: tvy.processEvent(serder=serder, seqner=seqner, saider=saider, wigers=wigers) - except AttributeError: + except AttributeError as e: raise kering.ValidationError("No tevery to process so dropped msg" "= {}.".format(serder.pretty())) else: raise kering.ValidationError("Unexpected message ilk = {} for evt =" " {}.".format(ilk, serder.pretty())) - elif sadder.ident == Idents.acdc: - creder = Creder(sad=sadder) - args = dict(creder=creder) + elif isinstance(serder, serdering.SerderACDC): + ilk = serder.ilk # dispatch based on ilk - if sadtsgs: - args["sadsigers"] = sadtsgs - - if sadcigs: - args["sadcigars"] = sadcigs - - try: - vry.processCredential(**args) - except AttributeError as e: - raise kering.ValidationError("No verifier to process so dropped credential" - "= {}.".format(creder.pretty())) + if ilk is None: # default for ACDC + try: + prefixer, seqner, saider = ssts[-1] if ssts else (None, None, None) # use last one if more than one + vry.processCredential(creder=serder, prefixer=prefixer, seqner=seqner, saider=saider) + except AttributeError as e: + raise kering.ValidationError("No verifier to process so dropped credential" + "= {}.".format(serder.pretty())) + else: + raise kering.ValidationError("Unexpected message ilk = {} for evt =" + " {}.".format(ilk, serder.pretty())) else: - raise kering.ValidationError("Unexpected message ident = {} for evt =" - " {}.".format(sadder.ident, sadder.pretty())) + raise kering.ValidationError("Unexpected protocol type = {} for event message =" + " {}.".format(serder.proto, serder.pretty())) return True # done state diff --git a/src/keri/core/routing.py b/src/keri/core/routing.py index 2721ade84..c67092b65 100644 --- a/src/keri/core/routing.py +++ b/src/keri/core/routing.py @@ -9,7 +9,7 @@ from hio.help import decking -from . import eventing, coring +from . import eventing, coring, serdering from .. import help, kering from ..db import dbing from ..help import helping @@ -338,7 +338,7 @@ def acceptReply(self, serder, saider, route, aid, osaider=None, # retrieve last event itself of signer given sdig sraw = self.db.getEvt(key=dbing.dgKey(pre=spre, dig=bytes(sdig))) # assumes db ensures that sraw must not be none because sdig was in KE - sserder = coring.Serder(raw=bytes(sraw)) + sserder = serdering.SerderKERI(raw=bytes(sraw)) if sserder.said != ssaider.qb64: # signer's dig not match est evt raise kering.ValidationError(f"Bad trans indexed sig group at sn = " f"{seqner.sn} for reply = {serder.ked}.") @@ -400,8 +400,6 @@ def updateReply(self, *, serder, saider, dater, cigar=None, prefixer=None, sigers (list): of indexed sigs from trans endorser's key from est evt """ - # if sigers is None: - # sigers = [] keys = (saider.qb64,) self.db.sdts.put(keys=keys, val=dater) # first one idempotent self.db.rpys.put(keys=keys, val=serder) # first one idempotent @@ -490,7 +488,7 @@ def processEscrowReply(self): logger.error("Kevery unescrow attempt failed: %s\n", ex.args[0]) except Exception as ex: # other error so remove from reply escrow - self.db.rpes.remIokey(iokeys=(route, ion)) # remove escrow + self.db.rpes.rem(keys=(route, ), val=saider) # remove escrow only self.removeReply(saider) # remove escrow reply artifacts if logger.isEnabledFor(logging.DEBUG): logger.exception("Kevery unescrowed due to error: %s\n", ex.args[0]) @@ -498,12 +496,12 @@ def processEscrowReply(self): logger.error("Kevery unescrowed due to error: %s\n", ex.args[0]) else: # unescrow succeded - self.db.rpes.remIokey(iokeys=(route, ion)) # remove escrow only + self.db.rpes.rem(keys=(route, ), val=saider) # remove escrow only logger.info("Kevery unescrow succeeded for reply=\n%s\n", serder.pretty()) except Exception as ex: # log diagnostics errors etc - self.db.rpes.remIokey(iokeys=(route, ion)) # remove escrow + self.db.rpes.rem(keys=(route,), val=saider) # remove escrow only self.removeReply(saider) # remove escrow reply artifacts if logger.isEnabledFor(logging.DEBUG): logger.exception("Kevery unescrowed due to error: %s\n", ex.args[0]) diff --git a/src/keri/core/scheming.py b/src/keri/core/scheming.py index 03b7567dc..5c87667a2 100644 --- a/src/keri/core/scheming.py +++ b/src/keri/core/scheming.py @@ -12,9 +12,9 @@ import msgpack from . import coring -from .coring import MtrDex, Serials, Saider, Ids +from .coring import MtrDex, Serials, Saider, Saids from .. import help, kering -from ..kering import ValidationError, DeserializationError +from ..kering import ValidationError, DeserializeError logger = help.ogler.getLogger() @@ -86,7 +86,7 @@ def resolver(self, scer=b''): class JSONSchema: """ JSON Schema support class """ - id_ = Ids.dollar # ID Field Label + id_ = Saids.dollar # ID Field Label def __init__(self, resolver=None): """ Initialize instance @@ -126,21 +126,21 @@ def load(self, raw, kind=Serials.json): try: sed = json.loads(raw.decode("utf-8")) except Exception as ex: - raise DeserializationError("Error deserializing JSON: {} {}" + raise DeserializeError("Error deserializing JSON: {} {}" "".format(raw.decode("utf-8"), ex)) elif kind == Serials.mgpk: try: sed = msgpack.loads(raw) except Exception as ex: - raise DeserializationError("Error deserializing MGPK: {} {}" + raise DeserializeError("Error deserializing MGPK: {} {}" "".format(raw, ex)) elif kind == Serials.cbor: try: sed = cbor.loads(raw) except Exception as ex: - raise DeserializationError("Error deserializing CBOR: {} {}" + raise DeserializeError("Error deserializing CBOR: {} {}" "".format(raw, ex)) else: raise ValueError("Invalid serialization kind = {}".format(kind)) @@ -385,7 +385,7 @@ def kind(self, kind): self._raw = raw self._sed = sed self._kind = kind - self._saider = Saider(raw=self._raw, code=self._code, label=Ids.dollar) + self._saider = Saider(raw=self._raw, code=self._code, label=Saids.dollar) @property def saider(self): diff --git a/src/keri/core/serdering.py b/src/keri/core/serdering.py new file mode 100644 index 000000000..a6a464e0f --- /dev/null +++ b/src/keri/core/serdering.py @@ -0,0 +1,1669 @@ +# -*- encoding: utf-8 -*- +""" +keri.core.serdering module + +""" +import copy +import json +from collections import namedtuple + +import cbor2 as cbor +import msgpack +import pysodium +import blake3 +import hashlib + +from .. import kering +from ..kering import (ValidationError, MissingFieldError, + ShortageError, VersionError, ProtocolError, KindError, + DeserializeError, FieldError, SerializeError) +from ..kering import (Versionage, Version, Vrsn_1_0, Vrsn_1_1, + VERRAWSIZE, VERFMT, VERFULLSIZE) +from ..kering import Protos, Serials, Rever, versify, deversify, Ilks +from ..core import coring +from .coring import MtrDex, DigDex, PreDex, Saids, Digestage +from .coring import Matter, Saider, Verfer, Diger, Number, Tholder + +from .. import help +from ..help import helping + +logger = help.ogler.getLogger() + +""" +Fieldage + saids (dict): keyed by saidive field labels with values as default codes + alls (dict): keyed by all field labels including saidive ones + with values as default codes + +Example: + Fields = Labelage(saids={'d': DigDex.Blake3_256}, + alls={'v': '','d':''}) +""" +Fieldage = namedtuple("Fieldage", "saids alls") #values are dicts + + +""" +Reapage + proto (str): protocol type value of Protos examples 'KERI', 'ACDC' + major (str): single char hex string of major version number + minor (str): single char hex string of minor version number + kind (str): serialization value of Serials examples 'JSON', 'CBOR', 'MGPK' + +""" +Reapage = namedtuple("Reapage", "proto major minor kind size") + + +class Serdery: + """Serder factory class for generating serder instances by protocol type + from an incoming message stream. + + + """ + + def __init__(self, *, version=None): + """Init instance + + Parameters: + version (Versionage | None): instance supported protocol version + None means do not enforce a supported version + """ + self.version = version # default version + + + def reap(self, ims, *, version=None): + """Extract and return Serder subclass based on protocol type reaped from + version string inside serialized raw of Serder. + + Returns: + serder (Serder): instance of Serder subclass where subclass is + determined by the protocol type of its version string. + + Parameters: + ims (bytearray) of serialized incoming message stream. Assumes start + of stream is raw Serder. + version (Versionage | None): instance supported protocol version + None means do not enforce a supported version + """ + version = version if version is not None else self.version + + if len(ims) < Serder.InhaleSize: + raise ShortageError(f"Need more raw bytes for Serdery to reap.") + + match = Rever.search(ims) # Rever regex takes bytes/bytearray not str + if not match or match.start() > Serder.MaxVSOffset: + raise VersionError(f"Invalid version string for Serder raw = " + f"{ims[: Serder.InhaleSize]}.") + + reaped = Reapage(*match.group("proto", "major", "minor", "kind", "size")) + + if reaped.proto == Protos.keri.encode("utf-8"): + return SerderKERI(raw=ims, strip=True, version=version, reaped=reaped) + elif reaped.proto == Protos.acdc.encode("utf-8"): + return SerderACDC(raw=ims, strip=True, version=version, reaped=reaped) + else: + raise ProtocolError(f"Unsupported protocol type = {reaped.proto}.") + + + +class Serder: + """Serder is serializer-deserializer class for saidified over-the-wire + messages that deserializes to a field map (label value pairs) from + either a serialized field map or an unlabeled fixed field structure with + affiliated label list. The messages must include a version string field + with proto (protocol), version, kind, and size elements or equivalent + header with same elements. The messages may have an optional ilk field + that is protocol specific. Protocols that have fixed top-level fields + also perform label inclusion validation. + + Message saidification and verification may be dependent on protocol and + optionally ilk specific field label(s) and digest code type for its SAID(s). + + The base Serder class provides the common properties for all messages for + all protocols. Each subclass is protocol based and adds properties that are + required for all message ilks in a given protocol. Each protocol subclass + may have dynamically injected ilk specific properties (as descriptors) + if any. + + To support a new protocol with its ilks, add a protocol specific subclass, + override the Labels class attribute, and if necessary the .verify and + .saidify methods and define any protocol specific properties. If necessary + define ilk specific subclasses of a given protocol (ilk is packet type). + The .Labels class attributes configures the field label(s) for said + generation and verification in addition to the required fields. + + Class Attributes: + MaxVSOffset (int): Maximum Version String Offset in bytes/chars + InhaleSize (int): Minimum raw buffer size needed to inhale + Labels (dict): Protocol specific dict of field labels keyed by ilk + (packet type string value). None is default key when no ilk needed. + Each entry is a + + Properties: + raw (bytes): of serialized event only + sad (dict): self addressed data dict + proto (str): Protocolage value as protocol identifier such as KERI, ACDC + version (Versionage): protocol version (Major, Minor) + kind (str): serialization kind coring.Serials such as JSON, CBOR, MGPK, CESR + size (int): number of bytes in serialization + said (str): qb64 said of .raw given by appropriate field + saidb (bytes): qb64b of .said + ilk (str | None): packet type for this Serder if any (may be None) + + + Hidden Attributes: + ._raw is bytes of serialized event only + ._sad is key event dict + ._proto (str): Protocolage value as protocol type identifier + ._version is Versionage instance of event version + ._kind is serialization kind string value (see namedtuple coring.Serials) + supported kinds are 'json', 'cbor', 'msgpack', 'binary' + ._size is int of number of bytes in serialed event only + ._said (str): qb64 given by appropriate saidive field + + Methods: + verify() + _verify() + makify() + compare() + pretty(size: int | None ) -> str: Prettified JSON of this SAD + + ClassMethods: + _inhale() + _exhale() + + StaticMethods: + loads() + dumps() + + Note: + loads and jumps of json use str whereas cbor and msgpack use bytes + + """ + + MaxVSOffset = 12 + InhaleSize = MaxVSOffset + VERFULLSIZE # min buffer size to inhale + + Dummy = "#" # dummy spaceholder char for said. Must not be a valid Base64 char + + # should be same set of codes as in coring.DigestCodex coring.DigDex so + # .digestive property works. Use unit tests to ensure codex sets match + Digests = { + DigDex.Blake3_256: Digestage(klas=blake3.blake3, size=None, length=None), + DigDex.Blake2b_256: Digestage(klas=hashlib.blake2b, size=32, length=None), + DigDex.Blake2s_256: Digestage(klas=hashlib.blake2s, size=None, length=None), + DigDex.SHA3_256: Digestage(klas=hashlib.sha3_256, size=None, length=None), + DigDex.SHA2_256: Digestage(klas=hashlib.sha256, size=None, length=None), + DigDex.Blake3_512: Digestage(klas=blake3.blake3, size=None, length=64), + DigDex.Blake2b_512: Digestage(klas=hashlib.blake2b, size=None, length=None), + DigDex.SHA3_512: Digestage(klas=hashlib.sha3_512, size=None, length=None), + DigDex.SHA2_512: Digestage(klas=hashlib.sha512, size=None, length=None), + } + + #override in subclass to enforce specific protocol + Protocol = None # required protocol, None means any in Protos is ok + + Proto = Protos.keri # default protocol type + Vrsn = Vrsn_1_0 # default protocol version for protocol type + Kind = Serials.json # default serialization kind + + + # Nested dict keyed by protocol. + # Each protocol value is a dict keyed by ilk. + # Each ilk value is a Labelage named tuple with saids, codes and fields + # ilk value of None is default for protocols that support ilkless packets + Fields = { + Protos.keri: + { + Vrsn_1_0: + { + Ilks.icp: Fieldage(saids={Saids.d: DigDex.Blake3_256, + Saids.i: DigDex.Blake3_256,}, + alls=dict(v='', t='',d='', i='', s='0', kt='0', + k=[], nt='0', n=[], bt='0', b=[], c=[], a=[])), + Ilks.rot: Fieldage(saids={Saids.d: DigDex.Blake3_256}, + alls=dict(v='', t='',d='', i='', s='0', p='', + kt='0',k=[], nt='0', n=[], bt='0', br=[], + ba=[], a=[])), + Ilks.ixn: Fieldage({Saids.d: DigDex.Blake3_256}, + alls=dict(v='', t='',d='', i='', s='0', p='', a=[])), + Ilks.dip: Fieldage(saids={Saids.d: DigDex.Blake3_256, + Saids.i: DigDex.Blake3_256,}, + alls=dict(v='', t='',d='', i='', s='0', kt='0', + k=[], nt='0', n=[], bt='0', b=[], c=[], a=[], + di='')), + Ilks.drt: Fieldage(saids={Saids.d: DigDex.Blake3_256}, + alls=dict(v='', t='',d='', i='', s='0', p='', + kt='0',k=[], nt='0', n=[], bt='0', br=[], + ba=[], a=[])), + Ilks.rct: Fieldage(saids={}, + alls=dict(v='', t='',d='', i='', s='0')), + Ilks.qry: Fieldage(saids={Saids.d: DigDex.Blake3_256}, + alls=dict(v='', t='',d='', dt='', r='', rr='', + q={})), + Ilks.rpy: Fieldage(saids={Saids.d: DigDex.Blake3_256}, + alls=dict(v='', t='',d='', dt='', r='',a=[])), + Ilks.pro: Fieldage(saids={Saids.d: DigDex.Blake3_256}, + alls=dict(v='', t='',d='', dt='', r='', rr='', + q={})), + Ilks.bar: Fieldage(saids={Saids.d: DigDex.Blake3_256}, + alls=dict(v='', t='',d='', dt='', r='',a=[])), + Ilks.exn: Fieldage(saids={Saids.d: DigDex.Blake3_256}, + alls=dict(v='', t='', d='', i="", p="", dt='', r='',q={}, + a=[], e={})), + Ilks.vcp: Fieldage(saids={Saids.d: DigDex.Blake3_256, + Saids.i: DigDex.Blake3_256,}, + alls=dict(v='', t='',d='', i='', ii='', s='0', c=[], + bt='0', b=[], n='')), + Ilks.vrt: Fieldage(saids={Saids.d: DigDex.Blake3_256,}, + alls=dict(v='', t='',d='', i='', p='', s='0', + bt='0', br=[], ba=[])), + Ilks.iss: Fieldage(saids={Saids.d: DigDex.Blake3_256,}, + alls=dict(v='', t='',d='', i='', s='0', ri='', + dt='')), + Ilks.rev: Fieldage(saids={Saids.d: DigDex.Blake3_256,}, + alls=dict(v='', t='',d='', i='', s='0', ri='', + p='', dt='')), + Ilks.bis: Fieldage(saids={Saids.d: DigDex.Blake3_256,}, + alls=dict(v='', t='',d='', i='', ii='', s='0', ra={}, + dt='')), + Ilks.brv: Fieldage(saids={Saids.d: DigDex.Blake3_256,}, + alls=dict(v='', t='',d='', i='', s='0', p='', ra={}, + dt='')), + }, + Vrsn_1_1: + { + Ilks.icp: Fieldage(saids={Saids.d: DigDex.Blake3_256, + Saids.i: DigDex.Blake3_256,}, + alls=dict(v='', t='',d='', i='', s='0', kt='0', + k=[], nt='0', n=[], bt='0', b=[], c=[], a=[])), + Ilks.rot: Fieldage(saids={Saids.d: DigDex.Blake3_256}, + alls=dict(v='', t='',d='', i='', s='0', p='', + kt='0',k=[], nt='0', n=[], bt='0', br=[], + ba=[], c=[], a=[])), + Ilks.ixn: Fieldage({Saids.d: DigDex.Blake3_256}, + alls=dict(v='', t='',d='', i='', s='0', p='', a=[])), + Ilks.dip: Fieldage(saids={Saids.d: DigDex.Blake3_256, + Saids.i: DigDex.Blake3_256,}, + alls=dict(v='', t='',d='', i='', s='0', kt='0', + k=[], nt='0', n=[], bt='0', b=[], c=[], a=[], + di='')), + Ilks.drt: Fieldage(saids={Saids.d: DigDex.Blake3_256}, + alls=dict(v='', t='',d='', i='', s='0', p='', + kt='0',k=[], nt='0', n=[], bt='0', br=[], + ba=[], c=[], a=[])), + Ilks.rct: Fieldage(saids={}, + alls=dict(v='', t='',d='', i='', s='0')), + Ilks.qry: Fieldage(saids={Saids.d: DigDex.Blake3_256}, + alls=dict(v='', t='',d='', i='', dt='', r='', rr='', + q={})), + Ilks.rpy: Fieldage(saids={Saids.d: DigDex.Blake3_256}, + alls=dict(v='', t='',d='', i='', dt='', r='',a=[])), + Ilks.pro: Fieldage(saids={Saids.d: DigDex.Blake3_256}, + alls=dict(v='', t='',d='', i='', dt='', r='', rr='', + q={})), + Ilks.bar: Fieldage(saids={Saids.d: DigDex.Blake3_256}, + alls=dict(v='', t='',d='', i='', dt='', r='',a=[])), + Ilks.exn: Fieldage(saids={Saids.d: DigDex.Blake3_256}, + alls=dict(v='', t='', d='', i="", p="", dt='', r='', q={}, + a=[], e={})), + }, + }, + Protos.crel: + { + Vrsn_1_1: + { + Ilks.vcp: Fieldage(saids={Saids.d: DigDex.Blake3_256, + Saids.i: DigDex.Blake3_256,}, + alls=dict(v='', t='',d='', i='', ii='', s='0', c=[], + bt='0', b=[], u='')), + Ilks.vrt: Fieldage(saids={Saids.d: DigDex.Blake3_256,}, + alls=dict(v='', t='',d='', i='', p='', s='0', + bt='0', br=[], ba=[])), + Ilks.iss: Fieldage(saids={Saids.d: DigDex.Blake3_256,}, + alls=dict(v='', t='',d='', i='', s='0', ri='', + dt='')), + Ilks.rev: Fieldage(saids={Saids.d: DigDex.Blake3_256,}, + alls=dict(v='', t='',d='', i='', s='0', ri='', + p='', dt='')), + Ilks.bis: Fieldage(saids={Saids.d: DigDex.Blake3_256,}, + alls=dict(v='', t='',d='', i='', ii='', s='0', ra={}, + dt='')), + Ilks.brv: Fieldage(saids={Saids.d: DigDex.Blake3_256,}, + alls=dict(v='', t='',d='', i='', s='0', p='', ra={}, + dt='')), + }, + }, + Protos.acdc: + { + Vrsn_1_0: + { + None: Fieldage(saids={Saids.d: DigDex.Blake3_256}, + alls=dict(v='', d='', i='', s='')), + } + }, + } + + + # default ilk for each protocol at default version is zeroth ilk in dict + Ilks = dict() + for key, val in Fields.items(): + Ilks[key] = list(list(val.values())[0].keys())[0] + + + def __init__(self, *, raw=b'', sad=None, strip=False, version=Version, + reaped=None, verify=True, makify=False, + proto=None, vrsn=None, kind=None, ilk=None, saids=None): + """Deserialize raw if provided. Update properties from deserialized raw. + Verifies said(s) embedded in sad as given by labels. + When verify is True then verify said(s) in deserialized raw as + given by label(s) according to proto and ilk and code + If raw not provided then serialize .raw from sad with kind and code. + When kind not provided use kind embedded in sad['v'] version string. + When saidify is True then compute and update said(s) in sad as + given by label(s) according to proto and ilk and code. + + Parameters: + raw (bytes): serialized event + sad (dict): serializable saidified field map of message. + Ignored if raw provided + strip (bool): True means strip (delete) raw from input stream + bytearray after parsing. False means do not strip. + Assumes that raw is bytearray when strip is True. + version (Versionage | None): instance supported protocol version + None means do not enforce a supported version + reaped (Reapage | None): instance of deconstructed version string + elements. If none or empty ignore otherwise assume that raw + already had its version string extracted (reaped) into the + elements of reaped. + verify (bool): True means verify said(s) of given raw or sad. + Raises ValidationError if verification fails + Ignore when raw not provided or when raw and saidify is True + makify (bool): True means compute fields for sad including size and + saids. + proto (str | None): desired protocol type str value of Protos + If None then its extracted from sad or uses default .Proto + vrsn (Versionage | None): instance desired protocol version + If None then its extracted from sad or uses default .Vrsn + kind (str None): serialization kind string value of Serials + supported kinds are 'json', 'cbor', 'msgpack', 'binary' + If None then its extracted from sad or uses default .Kind + ilk (str | None): desired ilk packet type str value of Ilks + If None then its extracted from sad or uses default .Ilk + saids (dict): of keyed by label of codes for saidive fields to + override defaults given in .Fields for a given ilk. + If None then use defaults + Code assignment for each saidive field in desending priority: + - the code provided in saids when not None + - the code extracted from sad[said label] when valid CESR + - the code provided in .Fields...saids + + + """ + + if raw: # deserialize raw using property setter + # self._inhale works because it only references class attributes + sad, proto, vrsn, kind, size = self._inhale(raw=raw, + version=version, + reaped=reaped) + self._raw = bytes(raw[:size]) # crypto ops require bytes not bytearray + self._sad = sad + self._proto = proto + self._vrsn = vrsn + self._kind = kind + self._size = size + # primary said field label + try: + label = list(self.Fields[self.proto][self.vrsn][self.ilk].saids.keys())[0] + if label not in self._sad: + raise FieldError(f"Missing primary said field in {self._sad}.") + self._said = self._sad[label] # not verified + except Exception as ex: + self._said = None # no saidive field + + if strip: #only when raw is bytearray + try: + del raw[:self._size] + except TypeError: + pass # ignore if bytes + + if verify: # verify the said(s) provided in raw + try: + self._verify() # raises exception when not verify + except Exception as ex: + logger.error("Invalid raw for Serder %s\n%s", + self.pretty(), ex.args[0]) + raise ValidationError(f"Invalid raw for Serder = " + f"{self._sad}. {ex.args[0]}") from ex + + elif sad or makify: # serialize sad into raw or make sad + if makify: # recompute properties and said(s) and reset sad + # makify resets sad, raw, proto, version, kind, and size + self.makify(sad=sad, version=version, + proto=proto, vrsn=vrsn, kind=kind, ilk=ilk, saids=saids) + + else: + # self._exhale works because it only access class attributes + raw, sad, proto, vrsn, kind, size = self._exhale(sad=sad, + version=version) + self._raw = raw + self._sad = sad + self._proto = proto + self._vrsn = vrsn + self._kind = kind + self._size = size + # primary said field label + try: + label = list(self.Fields[self.proto][self.vrsn][self.ilk].saids.keys())[0] + if label not in self._sad: + raise DeserializeError(f"Missing primary said field in {self._sad}.") + self._said = self._sad[label] # not verified + except Exception: + self._said = None # no saidive field + + if verify: # verify the said(s) provided in sad + try: + self._verify() # raises exception when not verify + except Exception as ex: + logger.error("Invalid sad for Serder %s\n%s", + self.pretty(), ex.args[0]) + raise ValidationError(f"Invalid sad for Serder =" + f"{self._sad}.") from ex + + else: + raise ValueError("Improper initialization need raw or sad or makify.") + + + + def verify(self): + """Verifies said(s) in sad against raw + Override for protocol and ilk specific verification behavior. Especially + for inceptive ilks that have more than one said field like a said derived + identifier prefix. + + Returns: + verify (bool): True if said(s) verify. False otherwise + """ + try: + self._verify() + except Exception as ex: # log validation error here + logger.error("Invalid Serder: %s\n for %s\n", + ex.args[0], self.pretty()) + + return False + + return True + + + def _verify(self): + """Verifies said(s) in sad against raw + Override for protocol and ilk specific verification behavior. Especially + for inceptive ilks that have more than one said field like a said derived + identifier prefix. + + Raises a ValidationError (or subclass) if any verification fails + + """ + if self.Protocol and self.proto != self.Protocol: + raise ValidationError(f"Expected protocol = {self.Protocol}, got " + f"{self.proto} instead.") + + if self.proto not in self.Fields: + raise ValidationError(f"Invalid protocol type = {self.proto}.") + + if self.ilk not in self.Fields[self.proto][self.vrsn]: + raise ValidationError(f"Invalid packet type (ilk) = {self.ilk} for" + f"protocol = {self.proto}.") + + fields = self.Fields[self.proto][self.vrsn][self.ilk] # get labelage + # ensure all required fields in alls are in sad + alls = fields.alls # dict of all field labels with default values + keys = list(self._sad) # get list of keys of self.sad + for key in list(keys): # make copy to mutate + if key not in alls: + del keys[keys.index(key)] # remove non required fields + + if list(alls.keys()) != keys: # forces ordering of labels in .sad + raise MissingFieldError(f"Missing one or more required fields from" + f"= {list(alls.keys())} in sad = " + f"{self._sad}.") + + # said field labels are not order dependent with respect to all fields + # in sad so use set() to test inclusion + saids = copy.copy(fields.saids) # get copy of saidive field labels and defaults values + if not (set(saids.keys()) <= set(alls.keys())): + raise MissingFieldError(f"Missing one or more required said fields" + f" from {list(saids.keys())} in sad = " + f"{self._sad}.") + + sad = self.sad # make shallow copy so don't clobber original .sad + for label in saids.keys(): + try: # replace default code with code of value from sad + saids[label] = Matter(qb64=sad[label]).code + except Exception as ex: + if saids[label] in DigDex: # digestive but invalid + raise ValidationError(f"Invalid said field '{label}' in sad\n" + f" = {self._sad}.") from ex + + if saids[label] in DigDex: # if digestive then replace with dummy + sad[label] = self.Dummy * len(sad[label]) + + + raw = self.dumps(sad, kind=self.kind) # serialize dummied sad copy + + for label, code in saids.items(): + if code in DigDex: # subclass override if non digestive allowed + klas, size, length = self.Digests[code] # digest algo size & length + ikwa = dict() # digest algo class initi keyword args + if size: + ikwa.update(digest_size=size) # optional digest_size + dkwa = dict() # digest method keyword args + if length: + dkwa.update(length=length) + dig = Matter(raw=klas(raw, **ikwa).digest(**dkwa), code=code).qb64 + if dig != self._sad[label]: # compare to original + raise ValidationError(f"Invalid said field '{label}' in sad" + f" = {self._sad}, should be {dig}.") + sad[label] = dig + + raw = self.dumps(sad, kind=self.kind) + if raw != self.raw: + raise ValidationError(f"Invalid round trip of {sad} != \n" + f"{self.sad}.") + # verified successfully since no exception + + + def makify(self, sad, *, version=None, + proto=None, vrsn=None, kind=None, ilk=None, saids=None): + """Makify given sad dict makes the versions string and computes the said + field values and sets associated properties: + raw, sad, proto, version, kind, size + + Override for protocol and ilk specific saidification behavior. Especially + for inceptive ilks that have more than one said field like a said derived + identifier prefix. + + Default prioritization. + Use method parameter if not None + Else use provided version string if valid + Otherwise use class attribute + + + Parameters: + sad (dict): serializable saidified field map of message. + Ignored if raw provided + version (Versionage): instance supported protocol version + None means do not enforce version + proto (str | None): desired protocol type str value of Protos + If None then its extracted from sad or uses default .Proto + vrsn (Versionage | None): instance desired protocol version + If None then its extracted from sad or uses default .Vrsn + kind (str None): serialization kind string value of Serials + supported kinds are 'json', 'cbor', 'msgpack', 'binary' + If None then its extracted from sad or uses default .Kind + ilk (str | None): desired ilk packet type str value of Ilks + If None then its extracted from sad or uses default .Ilk + saids (dict): of keyed by label of codes for saidive fields to + override defaults given in .Fields for a given ilk. + If None then use defaults + Code assignment for each saidive field in desending priority: + - the code provided in saids when not None + - the code extracted from sad[said label] when valid CESR + - the code provided in .Fields...saids + """ + sproto = svrsn = skind = silk = None + if sad and 'v' in sad: # attempt to get from vs in sad + try: # extract version string elements as defaults if provided + sproto, svrsn, skind, _ = deversify(sad["v"], version=version) + except ValueError as ex: + pass + else: + silk = sad.get('t') # if not in get returns None which may be valid + + if proto is None: + proto = sproto if sproto is not None else self.Proto + + if vrsn is None: + vrsn = svrsn if svrsn is not None else self.Vrsn + + if kind is None: + kind = skind if skind is not None else self.Kind + + if ilk is None: + ilk = silk if silk is not None else self.Ilks[proto] + + + if proto not in self.Fields: + raise SerializeError(f"Invalid protocol type = {proto}.") + + + if self.Protocol and proto != self.Protocol: + raise SerializeError(f"Expected protocol = {self.Protocol}, got " + f"{proto} instead.") + + if version is not None and vrsn != version: + raise SerializeError(f"Expected version = {version}, got " + f"{vrsn.major}.{vrsn.minor}.") + + if kind not in Serials: + raise SerializeError(f"Invalid serialization kind = {kind}") + + + if ilk not in self.Fields[proto][vrsn]: + raise SerializeError(f"Invalid packet type (ilk) = {ilk} for" + f"protocol = {proto}.") + + fields = self.Fields[proto][vrsn][ilk] # get Fieldage of fields + + if not sad: # empty or None so create from defaults + sad = {} + for label, value in fields.alls.items(): + if helping.nonStringIterable(value): # copy iterable defaults + value = copy.copy(value) + sad[label] = value + + if 't' in sad: # packet type (ilk) requried so set value to ilk + sad['t'] = ilk + + # ensure all required fields in alls are in sad + alls = fields.alls # all field labels + for label, value in alls.items(): # ensure provided sad as all required fields + if label not in sad: # supply default + if helping.nonStringIterable(value): # copy iterable defaults + value = copy.copy(value) + sad[label] = value + + keys = list(sad) # get list of keys of self.sad + for key in list(keys): # make copy to mutate + if key not in alls: + del keys[keys.index(key)] # remove non required fields + + if list(alls.keys()) != keys: # ensure ordering of fields matches alls + raise SerializeError(f"Mismatch one or more of all required fields " + f" = {list(alls.keys())} in sad = {sad}.") + + # said field labels are not order dependent with respect to all fields + # in sad so use set() to test inclusion + _saids = copy.copy(fields.saids) # get copy of defaults + if not (set(_saids.keys()) <= set(alls.keys())): + raise SerializeError(f"Missing one or more required said fields " + f"from {list(_saids.keys())} in sad = {sad}.") + + # override saidive defaults + for label in _saids: + if saids and label in saids: # use parameter override + _saids[label] = saids[label] + else: + try: # use sad field override + _saids[label] = Matter(qb64=sad[label]).code + except Exception: + pass # no override + + if _saids[label] in DigDex: # if digestive then fill with dummy + sad[label] = self.Dummy * Matter.Sizes[_saids[label]].fs + + + if 'v' not in sad: # ensures that 'v' is always required by .Labels + raise SerializeError(f"Missing requires version string field 'v'" + f" in sad = {sad}.") + + sad['v'] = self.Dummy * VERFULLSIZE # ensure size of vs + + raw = self.dumps(sad, kind) # get size of fully dummied sad + size = len(raw) + + # generate new version string with correct size + vs = versify(proto=proto, version=vrsn, kind=kind, size=size) + sad["v"] = vs # update version string in sad + + # now have correctly sized version string in sad + # now compute saidive digestive field values using sized dummied sad + raw = self.dumps(sad, kind=kind) # serialize sized dummied sad + + for label, code in _saids.items(): + if code in DigDex: # subclass override if non digestive allowed + klas, dsize, dlen = self.Digests[code] # digest algo size & length + ikwa = dict() # digest algo class initi keyword args + if dsize: + ikwa.update(digest_size=dsize) # optional digest_size + dkwa = dict() # digest method keyword args + if dlen: + dkwa.update(length=dlen) + dig = Matter(raw=klas(raw, **ikwa).digest(**dkwa), code=code).qb64 + sad[label] = dig + + raw = self.dumps(sad, kind=kind) # compute final raw + + self._raw = raw + self._sad = sad + self._proto = proto + self._vrsn = vrsn + self._kind = kind + self._size = size + # primary said field label + try: + label = list(self.Fields[self.proto][self.vrsn][self.ilk].saids.keys())[0] + if label not in self._sad: + raise SerializeError(f"Missing primary said field in {self._sad}.") + self._said = self._sad[label] # implicitly verified + except Exception: + self._said = None # no saidive field + + + + @classmethod + def _inhale(clas, raw, version=Version, reaped=None): + """Deserializes raw. + Parses serilized event ser of serialization kind and assigns to + instance attributes and returns tuple of associated elements. + + As classmethod enables testing parsing raw serder values. This can be + called on self as well because it only ever accesses clas attributes + not instance attributes. + + Returns: tuple (sad, proto, vrsn, kind, size) where: + sad (dict): serializable attribute dict of saidified data + proto (str): value of Protos (Protocolage) protocol type + vrsn (Versionage | None): tuple of (major, minor) version ints + None means do not enforce version + kind (str): value of Serials (Serialage) serialization kind + + Parameters: + raw (bytes): serialized sad message + version (Versionage): instance supported protocol version + reaped (Reapage | None): instance of deconstructed version string + elements. If none or empty ignore otherwise assume that raw + already had its version string extracted (reaped) into the + elements of reaped. + + Note: + loads and jumps of json use str whereas cbor and msgpack use bytes + Assumes only supports Version + + """ + if reaped: + proto, major, minor, kind, size = reaped # tuple unpack + else: + if len(raw) < clas.InhaleSize: + raise ShortageError(f"Need more raw bytes for Serder to inhale.") + + match = Rever.search(raw) # Rever regex takes bytes/bytearray not str + if not match or match.start() > clas.MaxVSOffset: + raise VersionError(f"Invalid version string in raw = " + f"{raw[:clas.InhaleSize]}.") + + proto, major, minor, kind, size = match.group("proto", + "major", + "minor", + "kind", + "size") + + proto = proto.decode("utf-8") + if proto not in Protos: + raise ProtocolError(f"Invalid protocol type = {proto}.") + + vrsn = Versionage(major=int(major, 16), minor=int(minor, 16)) + if version is not None and vrsn != version: + raise VersionError(f"Expected version = {version}, got " + f"{vrsn.major}.{vrsn.minor}.") + + kind = kind.decode("utf-8") + if kind not in Serials: + raise KindError(f"Invalid serialization kind = {kind}.") + + size = int(size, 16) + if len(raw) < size: + raise ShortageError(f"Need more bytes.") + + sad = clas.loads(raw=raw, size=size, kind=kind) + + if "v" not in sad: + raise FieldError(f"Missing version string field in {sad}.") + + return sad, proto, vrsn, kind, size + + + @staticmethod + def loads(raw, size=None, kind=Serials.json): + """Utility static method to handle deserialization by kind + + Returns: + sad (dict | list): deserialized dict or list. Assumes attribute + dict of saidified data. + + Parameters: + raw (bytes |bytearray): raw serialization to deserialze as dict + size (int): number of bytes to consume for the deserialization. + If None then consume all bytes in raw + kind (str): value of Serials (Serialage) serialization kind + "JSON", "MGPK", "CBOR" + """ + if kind == Serials.json: + try: + sad = json.loads(raw[:size].decode("utf-8")) + except Exception as ex: + raise DeserializeError(f"Error deserializing JSON: " + f"{raw[:size].decode('utf-8')}") from ex + + elif kind == Serials.mgpk: + try: + sad = msgpack.loads(raw[:size]) + except Exception as ex: + raise DeserializeError(f"Error deserializing MGPK: " + f"{raw[:size].decode('utf-8')}") from ex + + elif kind == Serials.cbor: + try: + sad = cbor.loads(raw[:size]) + except Exception as ex: + raise DeserializeError(f"Error deserializing CBOR: " + f"{raw[:size].decode('utf-8')}") from ex + + else: + raise DeserializeError(f"Invalid deserialization kind: {kind}") + + return sad + + + @classmethod + def _exhale(clas, sad, version=None): + """Serializes sad given kind and version and sets the serialized size + in the version string. + + As classmethod enables bootstrap of valid sad dict that has correct size + in version string. This obviates sizeify. This can be called on self as + well because it only ever accesses clas attributes not instance attributes. + + Returns tuple of (raw, proto, kind, sad, vrsn) where: + raw (str): serialized event as bytes of kind + proto (str): protocol type as value of Protocolage + kind (str): serialzation kind as value of Serialage + sad (dict): modified serializable attribute dict of saidified data + vrsn (Versionage): tuple value (major, minor) + + Parameters: + sad (dict): serializable attribute dict of saidified data + version (Versionage | None): supported protocol version for message + None means do not enforce a supported version + + + """ + if "v" not in sad: + raise SerializeError(f"Missing version string field in {sad}.") + + # extract elements so can replace size element but keep others + proto, vrsn, kind, size = deversify(sad["v"], version=version) + + raw = clas.dumps(sad, kind) + size = len(raw) + + # generate new version string with correct size + vs = versify(proto=proto, version=vrsn, kind=kind, size=size) + + # find location of old version string inside raw + match = Rever.search(raw) # Rever's regex takes bytes + if not match or match.start() > 12: + raise SerializeError(f"Invalid version string in raw = {raw}.") + fore, back = match.span() # start and end positions of version string + + # replace old version string in raw with new one + raw = b'%b%b%b' % (raw[:fore], vs.encode("utf-8"), raw[back:]) + if size != len(raw): # substitution messed up + raise SerializeError(f"Malformed size of raw in version string == {vs}") + sad["v"] = vs # update sad + + return raw, sad, proto, vrsn, kind, size + + + @staticmethod + def dumps(sad, kind=Serials.json): + """Utility static method to handle serialization by kind + + Returns: + raw (bytes): serialization of sad dict using serialization kind + + Parameters: + sad (dict | list)): serializable dict or list to serialize + kind (str): value of Serials (Serialage) serialization kind + "JSON", "MGPK", "CBOR" + """ + if kind == Serials.json: + raw = json.dumps(sad, separators=(",", ":"), + ensure_ascii=False).encode("utf-8") + + elif kind == Serials.mgpk: + raw = msgpack.dumps(sad) + + elif kind == Serials.cbor: + raw = cbor.dumps(sad) + else: + raise SerializeError(f"Invalid serialization kind = {kind}") + + return raw + + + def compare(self, said=None): + """Utility method to allow comparison of own .said digest of .raw + with some other purported said of .raw + + Returns: + success (bool): True if said matches self.saidb via string + equality. Converts said to bytes if unicode + + + Parameters: + said (bytes | str): qb64b or qb64 digest to compare with .said + """ + if said is not None: + if hasattr(said, "encode"): + said = said.encode('utf-8') # makes bytes + + return said == self.saidb # str match bool + + else: + raise ValidationError(f"Uncomparable saids.") + + + + def pretty(self, *, size=None): + """Utility method to pretty print .sad as JSON str. + Returns: + pretty (str): JSON of .sad with pretty formatting + + Pararmeters: + size (int | None): size limit. None means not limit. + Enables protection against syslog error when + exceeding UDP MTU (max trans unit) for syslog applications. + Guaranteed IPv4 MTU is 576, and IPv6 MTU is 1280. + Most broadband routers have an UDP MTU set to 1454. + Must include not just payload but UDP/IP header in + MTU calculation. So must leave room for either UDP/IpV4 or + the bigger UDP/IPv6 header. + Except for old IoT hardware, modern implementations all + support IPv6 so 1024 is usually a safe value for payload. + """ + return json.dumps(self._sad, indent=1)[:size] + + + @property + def raw(self): + """raw property getter + Returns: + raw (bytes): serialized version + """ + return self._raw + + + @property + def sad(self): + """sad property getter + Returns: + sad (dict): serializable attribute dict (saidified data) + """ + return dict(self._sad) # return copy + + + @property + def kind(self): + """kind property getter + Returns: + kind (str): value of Serials (Serialage)""" + return self._kind + + + @property + def proto(self): + """proto property getter + protocol identifier type value of Protocolage such as 'KERI' or 'ACDC' + + Returns: + proto (str): Protocolage value as protocol type + """ + return self._proto + + + @property + def vrsn(self): + """vrsn (version) property getter + + Returns: + vrsn (Versionage): instance of protocol version for this Serder + """ + return self._vrsn + + @property + def version(self): + """version property getter alias of .vrsn + + Returns: + version (Versionage): instance of protocol version for this Serder + """ + return self.vrsn + + + @property + def size(self): + """size property getter + Returns: + size (int): number of bytes in .raw + """ + return self._size + + + @property + def said(self): + """said property getter + Returns: + said (str): qb64 + """ + if not self.Fields[self.proto][self.vrsn][self.ilk].saids.keys() and 'd' in self._sad: + return self._sad['d'] # special case for non-saidive messages like rct + return self._said + + + @property + def saidb(self): + """saidb property getter + Returns: + saidb (bytes): qb64b of said of .saider + """ + return self.said.encode("utf-8") if self.said is not None else None + + + @property + def ilk(self): + """ilk property getter + Returns: + ilk (str): pracket type given by sad['t'] if any + """ + return self._sad.get('t') # returns None if 't' not in sad + + + +class SerderKERI(Serder): + """SerderKERI is Serder subclass with Labels for KERI packet types (ilks) and + properties for exposing field values of KERI messages + + See docs for Serder + """ + #override in subclass to enforce specific protocol + Protocol = Protos.keri # required protocol, None means any in Protos is ok + Proto = Protos.keri # default protocol type + + + + def _verify(self, **kwa): + """Verifies said(s) in sad against raw + Override for protocol and ilk specific verification behavior. Especially + for inceptive ilks that have more than one said field like a said derived + identifier prefix. + + Raises a ValidationError (or subclass) if any verification fails + + """ + super(SerderKERI, self)._verify(**kwa) + + allkeys = list(self.Fields[self.proto][self.vrsn][self.ilk].alls.keys()) + keys = list(self.sad.keys()) + if allkeys != keys: + raise ValidationError(f"Invalid top level field list. Expected " + f"{allkeys} got {keys}.") + + if (self.vrsn.major < 2 and self.vrsn.minor < 1 and + self.ilk in (Ilks.qry, Ilks.rpy, Ilks.pro, Ilks.bar, Ilks.exn)): + pass + else: # verify pre + try: + code = Matter(qb64=self.pre).code + except Exception as ex: + raise ValidationError(f"Invalid identifier prefix = " + f"{self.pre}.") from ex + + if self.ilk in (Ilks.dip, Ilks.drt): + idex = DigDex # delegatee must be digestive prefix + else: + idex = PreDex # non delegatee may be non digest + + if code not in idex: + raise ValidationError(f"Invalid identifier prefix code = {code}.") + + # non-transferable pre validations + if code in [PreDex.Ed25519N, PreDex.ECDSA_256r1N, PreDex.ECDSA_256k1N]: + if self.ndigs: + raise ValidationError(f"Non-transferable code = {code} with" + f" non-empty nxt = {self.ndigs}.") + + if self.backs: + raise ValidationError("Non-transferable code = {code} with" + f" non-empty backers = {self.backs}.") + + if self.seals: + raise ValidationError("Non-transferable code = {code} with" + f" non-empty seals = {self.seals}.") + + if self.ilk in (Ilks.dip): # validate delpre + try: + code = Matter(qb64=self.delpre).code + except Exception as ex: + raise ValidationError(f"Invalid delegator prefix = " + f"{self.delpre}.") from ex + + if code not in PreDex: # delegator must be valid prefix code + raise ValidationError(f"Invalid delegator prefix code = {code}.") + + + @property + def estive(self): # establishative + """ Returns True if Serder represents an establishment event """ + return (self._sad["t"] in (Ilks.icp, Ilks.rot, Ilks.dip, Ilks.drt) + if "t" in self._sad else False) + + + @property + def ked(self): + """ + Returns: + ked (dict): key event dict property getter. Alias for .sad + """ + return self.sad + + + @property + def pre(self): + """ + Returns: + pre (str): qb64 of .sad["i"] identifier prefix property getter + """ + return self._sad.get("i") + + + @property + def preb(self): + """ + Returns: + preb (bytes): qb64b of .pre identifier prefix property getter as bytes + """ + return self.pre.encode("utf-8") if self.pre is not None else None + + + @property + def sner(self): + """Number instance of sequence number, sner property getter + + Returns: + (Number): of ._sad["s"] hex number str converted + """ + # auto converts hex num str to int + return Number(num=self._sad["s"]) if 's' in self._sad else None + + + @property + def sn(self): + """Sequence number, sn property getter + Returns: + sn (int): of .sner.num from .sad["s"] + """ + return self.sner.num if self.sner is not None else None + + + @property + def snh(self): + """Sequence number hex str, snh property getter + Returns: + snh (hex str): of .sner.numh from .sad["s"] + """ + return self.sner.numh if self.sner is not None else None + + + @property + def seals(self): + """Seals property getter + + Returns: + seals (list): from ._sad["a"] + """ + return self._sad.get("a") + + #Properties of inceptive Serders ilks in (icp, dip) and version2 estive serders + + @property + def traits(self): + """Traits list property getter (config traits) + + Returns: + traits (list): from ._sad["c"] + """ + return self._sad.get("c") + + + #Properties of estive Serders ilks in (icp, rot, dip, drt) + @property + def tholder(self): + """Tholder property getter + + Returns: + tholder (Tholder): instance as converted from ._sad['kt'] + or None if missing. + + """ + return Tholder(sith=self._sad["kt"]) if "kt" in self._sad else None + + + @property + def keys(self): + """Returns list of qb64 keys from ._sad['k']. + One for each key. + keys property getter + """ + return self._sad.get("k") + + + @property + def verfers(self): + """Returns list of Verfer instances as converted from ._sad['k']. + One for each key. + verfers property getter + """ + keys = self._sad.get("k") + return [Verfer(qb64=key) for key in keys] if keys is not None else None + + + @property + def ntholder(self): + """Returns Tholder instance as converted from ._sad['nt'] or None if missing. + + """ + return Tholder(sith=self._sad["nt"]) if "nt" in self._sad else None + + + @property + def ndigs(self): + """ + Returns: + (list): digs + """ + if self.vrsn.major < 2 and self.vrsn.minor < 1 and self.ilk == Ilks.vcp: + return None + + return self._sad.get("n") + + @property + def ndigers(self): + """NDigers property getter + + Returns: + ndigers (list[Diger]): instance as converted from ._sad['n']. + One for each next key digests. + """ + if self.vrsn.major < 2 and self.vrsn.minor < 1 and self.ilk == Ilks.vcp: + return None + + digs = self._sad.get("n") + return [Diger(qb64=dig) for dig in digs] if digs is not None else None + + + @property + def bner(self): # toader + """ + bner (Number of backer TOAD threshold of accountable duplicity property getter + Returns: + (Number): of ._sad["bt"] hex number str converted. Auto converts + hex num str to int + """ + return Number(num=self._sad["bt"]) if 'bt' in self._sad else None + + + @property + def bn(self): + """ + bn (backer TOAD number) property getter + Returns: + bn (int): of .bner.num from .ked["bt"] + """ + return self.bner.num if self.bner is not None else None + + + # properties for incentive Serders like icp, dip + @property + def backs(self): + """Backers property getter + + Returns: + backs (list[str]): aids qb64 from ._sad['b']. + One for each backer (witness). + + """ + return self._sad.get("b") + + + @property + def berfers(self): + """Berfers property getter + Returns list of Verfer instances as converted from ._sad['b']. + One for each backer (witness). + + """ + baks = self._sad.get("b") + return [Verfer(qb64=bak) for bak in baks] if baks is not None else None + + + # properties for priorative Serders like ixn rot drt + + @property + def prior(self): + """Prior property getter + Returns: + prior (str): said qb64 of prior event from ._sad['p']. + + """ + return self._sad.get("p") + + + @property + def priorb(self): + """Priorb bytes property getter + Returns: + priorb (str): said qb64b of prior event from ._sad['p']. + + """ + return self.prior.encode("utf-8") if self.prior is not None else None + + + # properties for rotative Serders like rot drt + + @property + def cuts(self): + """Cuts property getter + Returns list of aids of instances as converted from ._sad['br']. + One for each backer (witness) to be cut (removed). + + """ + return self._sad.get("br") + + + @property + def adds(self): + """Adds property getter + Returns list of aids of instances as converted from ._sad['ba']. + One for each backer (witness) to be added. + + """ + return self._sad.get("ba") + + + #Properties for delegated Serders ilks in (dip, drt) + + @property + def delpre(self): + """ + Returns: + delpre (str): qb64 of .sad["di"] delegator ID prefix property getter + """ + return self._sad.get("di") + + + @property + def delpreb(self): + """ + Returns: + delpreb (bytes): qb64b of .delpre property getter as bytes + """ + return self.delpre.encode("utf-8") if self.delpre is not None else None + + #Propertives for dated Serders, qry, rpy, pro, bar, exn + + @property + def stamp(self): + """ + Returns: + stamp (str): date-time-stamp sad["dt"]. RFC-3339 profile of ISO-8601 + datetime of creation of message or data + """ + return self._sad.get("dt") + + + #Properties for exn exchange + + + #Properties for vcp (registry inception event) + @property + def uuid(self): + """uuid property getter + + Returns: + uuid (str): qb64 of .sad["u"] salty nonce + """ + return self._sad.get("u") + + @property + def nonce(self): + """ + should be deprecated + + Returns: + nonce (str): alias for .uuid property + """ + if self.vrsn.major < 2 and self.vrsn.minor < 1 and self.ilk == Ilks.vcp: + return self._sad.get("n") + else: + return self.uuid + + +class SerderCREL(Serder): + """SerderCREL is Serder subclass with Labels for CREL packet types (ilks) and + properties for exposing field values of CREL messages + Container Registry Event Log for issuance, revocation, etc registries of + ACDC + + See docs for Serder + """ + #override in subclass to enforce specific protocol + Protocol = Protos.crel # required protocol, None means any in Protos is ok + Proto = Protos.crel # default protocol type + Vrsn = Vrsn_1_1 # default protocol version for protocol type + + + def _verify(self, **kwa): + """Verifies said(s) in sad against raw + Override for protocol and ilk specific verification behavior. Especially + for inceptive ilks that have more than one said field like a said derived + identifier prefix. + + Raises a ValidationError (or subclass) if any verification fails + + """ + super(SerderCREL, self)._verify(**kwa) + + try: + code = Matter(qb64=self.issuer).code + except Exception as ex: + raise ValidationError(f"Invalid issuer AID = " + f"{self.issuer}.") from ex + + if code not in PreDex: + raise ValidationError(f"Invalid issuer AID code = {code}.") + + + @property + def issuer(self): + """ + Returns: + issuer (str): qb64 of .sad["i"] issuer AID property getter + """ + return self._sad.get('i') + + + @property + def issuerb(self): + """ + Returns: + issuerb (bytes): qb64b of .issuer property getter as bytes + """ + return self.issuer.encode("utf-8") if self.issuer is not None else None + + +class SerderACDC(Serder): + """SerderACDC is Serder subclass with Labels for ACDC packet types (ilks) and + properties for exposing field values of ACDC messages + + See docs for Serder + """ + #override in subclass to enforce specific protocol + Protocol = Protos.acdc # required protocol, None means any in Protos is ok + Proto = Protos.acdc # default protocol type + + + + def _verify(self, **kwa): + """Verifies said(s) in sad against raw + Override for protocol and ilk specific verification behavior. Especially + for inceptive ilks that have more than one said field like a said derived + identifier prefix. + + Raises a ValidationError (or subclass) if any verification fails + + """ + super(SerderACDC, self)._verify(**kwa) + + try: + code = Matter(qb64=self.issuer).code + except Exception as ex: + raise ValidationError(f"Invalid issuer AID = " + f"{self.issuer}.") from ex + + if code not in PreDex: + raise ValidationError(f"Invalid issuer AID code = {code}.") + + @property + def uuid(self): + """uuid property getter + Optional fields return None when not present + Returns: + uuid (str | None): qb64 of .sad["u"] salty nonce + """ + return self._sad.get("u") + + + @property + def uuidb(self): + """uuid property getter (uuid bytes) + Optional fields return None when not present + Returns: + uuidb (bytes | None): qb64b of .sad["u"] salty nonce as bytes + """ + return self.uuid.encode("utf-8") if self.uuid is not None else None + + + @property + def issuer(self): + """issuer property getter (issuer AID) + Optional fields return None when not present + Returns: + issuer (str | None): qb64 of .sad["i"] issuer AID + """ + return self._sad.get('i') + + + @property + def issuerb(self): + """issuerb property getter (issuer AID bytes) + Optional fields return None when not present + Returns: + issuerb (bytes | None): qb64b of .issuer AID as bytes + """ + return self.issuer.encode("utf-8") if self.issuer is not None else None + + + @property + def regi(self): + """regi property getter (registry identifier SAID) + Optional fields return None when not present + Returns: + regi (str | None): qb64 of .sad["ri"] registry SAID + """ + return self._sad.get('ri') + + + @property + def regib(self): + """regib property getter (registry identifier SAID bytes) + Optional fields return None when not present + Returns: + regib (bytes | None): qb64b of .issuer AID as bytes + """ + return self.issuer.encode("utf-8") if self.issuer is not None else None + + + @property + def schema(self): + """schema block or SAID property getter + Optional fields return None when not present + Returns: + schema (dict | str | None): from ._sad["s"] + """ + return self._sad.get('s') + + + @property + def attrib(self): + """attrib block or SAID property getter (attribute) + Optional fields return None when not present + Returns: + attrib (dict | str | None): from ._sad["a"] + """ + return self._sad.get("a") + + + @property + def issuee(self): + """ise property getter (issuee AID) + Optional fields return None when not present + Returns: + issuee (str | None): qb64 of .sad["a"]["i"] issuee AID + """ + try: + return self.attrib.get['i'] + except: + return None + + + @property + def issueeb(self): + """isrb property getter (issuee AID bytes) + Optional fields return None when not present + Returns: + issueeb (bytes | None): qb64b of .issuee AID as bytes + """ + return self.issuee.encode("utf-8") if self.issuee is not None else None + + + @property + def attagg(self): + """Attagg block property getter (attribute aggregate) + Optional fields return None when not present + Returns: + attagg (dict | str): from ._sad["A"] + """ + return self._sad.get("A") + + + @property + def edge(self): + """Edge block property getter + Optional fields return None when not present + Returns: + edge (dict | str): from ._sad["e"] + """ + return self._sad.get("e") + + + @property + def rule(self): + """Rule block property getter + Optional fields return None when not present + + Returns: + rule (dict | str): from ._sad["r"] + """ + return self._sad.get("r") # or {} # need to fix logic so can remove or since optional + + # ToDo Schemer property getter. Schemer object should change name to Schemar diff --git a/src/keri/db/basing.py b/src/keri/db/basing.py index 06ecfdf33..d4263fc91 100644 --- a/src/keri/db/basing.py +++ b/src/keri/db/basing.py @@ -21,10 +21,14 @@ import os import shutil +from collections import namedtuple from contextlib import contextmanager from dataclasses import dataclass, asdict, field -from typing import Optional +import json + +import cbor2 as cbor +import msgpack import lmdb from ordered_set import OrderedSet as oset @@ -33,9 +37,11 @@ from . import dbing, koming, subing from .. import kering -from ..core import coring, eventing, parsing +from ..core import coring, eventing, parsing, serdering from .. import help +from ..help import helping + logger = help.ogler.getLogger() @@ -44,7 +50,7 @@ class dbdict(dict): """ Subclass of dict that has db as attribute and employs read through cache from db Baser.stts of kever states to reload kever from state in database - if not in memory as dict item + when not found in memory as dict item. """ __slots__ = ('db') # no .__dict__ just for db reference @@ -58,10 +64,10 @@ def __getitem__(self, k): except KeyError as ex: if not self.db: raise ex # reraise KeyError - if (state := self.db.states.get(keys=k)) is None: + if (ksr := self.db.states.get(keys=k)) is None: raise ex # reraise KeyError try: - kever = eventing.Kever(state=state, db=self.db) + kever = eventing.Kever(state=ksr, db=self.db) except kering.MissingEntryError: # no kel event for keystate raise ex # reraise KeyError self.__setitem__(k, kever) @@ -78,12 +84,171 @@ def __contains__(self, k): return True def get(self, k, default=None): + """Override of dict get method + + Parameters: + k (str): key for dict + default: default value to return if not found + + Returns: + kever: converted from underlying dict or database + + """ if not super(dbdict, self).__contains__(k): return default else: return self.__getitem__(k) +@dataclass +class RawRecord: + """RawRecord is base class for dataclasses that provides private utility + methods for representing the dataclass as some other format like dict, + json bytes, cbor bytes, mgpk bytes as a raw format. Typically uses case + is to transform dataclass into dict or serialization of its transformation + into dict so that it can be included in messages or stored in a database. + """ + + @classmethod + def _fromdict(cls, d: dict): + """returns instance of clas initialized from dict d """ + return helping.datify(cls, d) + + + def __iter__(self): + return iter(asdict(self)) + + + def _asdict(self): + """Returns dict version of record""" + return helping.dictify(self) + + + def _asjson(self): + """Returns json bytes version of record""" + return json.dumps(self._asdict(), + separators=(",", ":"), + ensure_ascii=False).encode("utf-8") + + + def _ascbor(self): + """Returns cbor bytes version of record""" + return cbor.dumps(self._asdict()) + + + def _asmgpk(self): + """Returns mgpk bytes version of record""" + return msgpack.dumps(self._asdict()) + + +@dataclass +class StateEERecord(RawRecord): + """ + Corresponds to StateEstEvent namedtuple used as sub record in KeyStateRecord + for latest establishment event associated with current key state + + Attributes: + s (str): sequence number of latest est evt lowercase hex no leading zeros + d (str): SAID qb64 of latest est evt + br (list[str]): backer aids qb64 remove list (cuts) from latest est event + ba (list[str]): backer aids qb64 add list (adds) from latest est event + """ + s: str ='0' # sequence number of latest event in KEL as hex str + d: str ='' # latest event digest qb64 + br: list = field(default_factory=list) # backer AID qb64 remove (cut) list + ba: list = field(default_factory=list) # backer AID qb64 add list + + +@dataclass +class KeyStateRecord(RawRecord): # baser.state + """ + Key State information keyed by Identifier Prefix of associated KEL. + For local AIDs that correspond to Habs this is the Hab AID. + (see baser.state at 'stts') + + Attributes: + vn (list[int]): version number [major, minor] + i (str): identifier prefix qb64 + s (str): sequence number of latest event in KEL as hex str + p (str): prior event digest qb64 + d (str): latest event digest qb64 + f (str): first seen ordinal number of latest event in KEL as hex str + dt (str): datetime iso-8601 of key state record update, usually now + et (str): latest event packet type + kt (str): signing threshold sith + k (list[str]): signing keys qb64 + nt (str): next prerotated threshold sith + n (list[str]): pre-rotation keys qb64 + bt (str): backer threshold hex num + b (list[str]): backer aids qb64 + c (list[str]): config traits + ee (StateEERecord): instance + corresponds to StateEstEvent namedtuple + s = sn of latest est event as lowercase hex string no leading zeros, + d = SAID digest qb64 of latest establishment event + br = backer (witness) remove list (cuts) from latest est event + ba = backer (witness) add list (adds) from latest est event + di (str): delegator aid qb64 or empty str if not delegated + + Note: the seal anchor dict 'a' field is not included in the state notice + because it may be verbose and would impede the main purpose of a notic which + is to trigger the download of the latest events, which would include the + anchored seals. + + """ + vn: list[int] = field(default_factory=list) # version number [major, minor] round trip serializable + i: str ='' # identifier prefix qb64 + s: str ='0' # sequence number of latest event in KEL as hex str + p: str ='' # prior event digest qb64 + d: str ='' # latest event digest qb64 + f: str ='0' # first seen ordinal number of latest event in KEL as hex str + dt: str = '' # datetime of creation of state + et: str = '' # latest evt packet type (ilk) + kt: str = '0' # signing threshold sith + k: list[str] = field(default_factory=list) # signing key list qb64 + nt: str = '0' # next rotation threshold nsith + n: list[str] = field(default_factory=list) # next rotation key digest list qb64 + bt: str = '0' # backer threshold hex num str + b: list = field(default_factory=list) # backer AID list qb64 + c: list[str] = field(default_factory=list) # config trait list + ee: StateEERecord = field(default_factory=StateEERecord) + di: str = '' # delegator aid qb64 if any otherwise empty '' str + + + + +@dataclass +class HabitatRecord: # baser.habs + """ + Habitat application state information keyed by habitat name (baser.habs) + + Attributes: + hid (str): identifier prefix of hab qb64 + mid (str | None): group member identifier qb64 when hid is group + smids (list | None): group signing member identifiers qb64 when hid is group + rmids (list | None): group signing member identifiers qb64 when hid is group + watchers: (list[str]) = list of id prefixes qb64 of watchers + + + """ + hid: str # hab own identifier prefix qb64 + mid: str | None = None # group member identifier qb64 when hid is group + smids: list | None = None # group signing member ids when hid is group + rmids: list | None = None # group rotating member ids when hid is group + sid: str | None = None # Signify identifier qb64 when hid is Signify + watchers: list[str] = field(default_factory=list) # id prefixes qb64 of watchers + + +@dataclass +class TopicsRecord: # baser.tops + """ + Tracks the last message topic index retrieved from the witness mailbox + Database Key is the identifier prefix of the witness that is storing + events in a mailbox. (baser.tops) + """ + topics: dict + + @dataclass class OobiQueryRecord: # information for responding to OOBI query """ @@ -132,79 +297,6 @@ class OobiRecord: urls: list = None -@dataclass -class HabitatRecord: # baser.habs - """ - Habitat application state information keyed by habitat name (baser.habs) - - Attributes: - hid (str): identifier prefix of hab qb64 - mid (str | None): group member identifier qb64 when hid is group - smids (list | None): group signing member identifiers qb64 when hid is group - rmids (list | None): group signing member identifiers qb64 when hid is group - watchers: (list[str]) = list of id prefixes qb64 of watchers - - ToDo: NRR - May need to save midxs for interact event signing by .mhab because - merfers and migers and mindices are not provided. Reserve members of - group do not participate in signing so must either ignore or raise error - if asked to sign interaction event. - - #midxs: tuple[int, int] | None = None # mid index tuple (csi, pni) - - """ - hid: str # hab own identifier prefix qb64 - mid: str | None = None # group member identifier qb64 when hid is group - smids: list | None = None # group signing member ids when hid is group - rmids: list | None = None # group rotating member ids when hid is group - watchers: list[str] = field(default_factory=list) # id prefixes qb64 of watchers - - -@dataclass -class RotateRecord: - """ - Tracks requests to perform multisig rotation during lifecycle of a rotation - Provides psuedo event for which group consensus must be obtained prior to - committing group rotation event to group KEL in local db - - Attributes: - date (str | None): datetime of rotation - smids (list): group signing member identifiers qb64 - smsns (list): of group signing member seq nums of last est evt as hex str - rmids (list): group rotating member identifiers qb64 - rmsns (list): of group rotating member seq nums of last est evt as hex strs - sn (str | None ): at or after proposed seq num of group est event as hex str - isith (str | list | None): current signing threshold - nsith (str | list | None): next signing threshold - toad (int | None): threshold of accountable duplicity - cuts (list | None): list of backers to remove qb64 - adds (list | None): list of backers to add qb64 - data (list | None): seals in rotation event - - """ - date: str | None = None # datetime of rotation - smids: list[str] = field(default_factory=list) # group signing member ids qb64 - smsns: list[str] = field(default_factory=list) # group signing member last est evt sns hex str - rmids: list[str] = field(default_factory=list) # group rotating member ids qb64 - rmsns: list[str] = field(default_factory=list) # group rotating member last est evt sns hex str - sn: str | None = None # at or after proposed seq num of group est event as hex str - isith: str | list | None = None # current signing threshold - nsith: str | list | None = None # next signing threshold - toad: int | None = None # threshold of accountable duplicity - cuts: list[str] | None = None # list of backers to remove qb64 - adds: list[str] | None = None # list of backers to add qb64 - data: list | None = None # seals - - -@dataclass -class TopicsRecord: # baser.tops - """ - Tracks the last message topic index retrieved from the witness mailbox - Database Key is the identifier prefix of the witness that is storing - events in a mailbox. (baser.tops) - """ - topics: dict - @dataclass class EndpointRecord: # baser.ends @@ -287,7 +379,7 @@ class EndAuthRecord: # nested as field value in baser.locs controller id, cid, and a role. used to lookup authorization in end authN database with keyspace given by (cid.role.eid) where cid is the authorizing controller for the eid (endpoint id) at the given role. - The cid is usually a transferable identifer with a KEL but may be non-trans. + The cid is usually a transferable identifier with a KEL but may be non-trans. The eid is usually a nontransferable identifier when its used for roles witness or watcher but may be transferable for other roles such as controller, judge, juror, public watcher, or registrar. @@ -414,7 +506,7 @@ class Baser(dbing.LMDBer): .evts is named sub DB whose values are serialized events dgKey - DB is keyed by identifer prefix plus digest of serialized event + DB is keyed by identifier prefix plus digest of serialized event Only one value per DB key is allowed .fels is named sub DB of first seen event log table (FEL) of digests @@ -431,7 +523,7 @@ class Baser(dbing.LMDBer): the datetime when the event was first escrosed and then later first seen by log. Used for escrows timeouts and extended validation. dgKey - DB is keyed by identifer prefix plus digest of serialized event + DB is keyed by identifier prefix plus digest of serialized event Value is ISO 8601 datetime stamp bytes .aess is named sub DB of authorizing event source seal couples @@ -442,12 +534,12 @@ class Baser(dbing.LMDBer): dgKey Values are couples used to lookup authorizer's source event in .kels sub DB - DB is keyed by identifer prefix plus digest of key event + DB is keyed by identifier prefix plus digest of key event Only one value per DB key is allowed .sigs is named sub DB of fully qualified indexed event signatures dgKey - DB is keyed by identifer prefix plus digest of serialized event + DB is keyed by identifier prefix plus digest of serialized event More than one value per DB key is allowed .wigs is named sub DB of indexed witness signatures of event @@ -455,7 +547,7 @@ class Baser(dbing.LMDBer): The index is the offset of the witness into the witness list of the most recent establishment event wrt the receipted event. dgKey - DB is keyed by identifer prefix plus digest of serialized event + DB is keyed by identifier prefix plus digest of serialized event More than one value per DB key is allowed .rcts is named sub DB of event receipt couplets from nontransferable @@ -463,13 +555,13 @@ class Baser(dbing.LMDBer): These are: non-transferale prefix plus non-indexed event signature by that prefix. dgKey - DB is keyed by identifer prefix plus digest of serialized event + DB is keyed by identifier prefix plus digest of serialized event More than one value per DB key is allowed .ures is named sub DB of unverified event receipt escrowed triples from non-transferable signers. Each triple is concatenation of fully qualified items. These are: receipted event digest, - non-transferable receiptor identfier prefix, + non-transferable receiptor identifier prefix, plus nonindexed receipt event signature by that prefix. snKey DB is keyed by receipted event controller prefix plus sn @@ -484,7 +576,7 @@ class Baser(dbing.LMDBer): When latest establishment event is multisig then there will be multiple quadruples one per signing key, each a dup at same db key. dgKey - DB is keyed by identifer prefix plus digest of serialized event + DB is keyed by identifier prefix plus digest of serialized event More than one value per DB key is allowed .vres is named sub DB of unverified event validator receipt escrowed @@ -495,21 +587,21 @@ class Baser(dbing.LMDBer): When latest establishment event is multisig then there will be multiple quadruples one per signing key, each a dup at same db key. dgKey - DB is keyed by identifer prefix plus digest of serialized event + DB is keyed by identifier prefix plus digest of serialized event More than one value per DB key is allowed .kels is named sub DB of key event log tables that map sequence numbers to serialized event digests. snKey Values are digests used to lookup event in .evts sub DB - DB is keyed by identifer prefix plus sequence number of key event + DB is keyed by identifier prefix plus sequence number of key event More than one value per DB key is allowed .pses is named sub DB of partially signed escrowed event tables that map sequence numbers to serialized event digests. snKey Values are digests used to lookup event in .evts sub DB - DB is keyed by identifer prefix plus sequence number of key event + DB is keyed by identifier prefix plus sequence number of key event More than one value per DB key is allowed .pdes is named sub DB of partially delegated escrowed couples @@ -519,14 +611,14 @@ class Baser(dbing.LMDBer): issuing) source event. dgKey Values are couples used to lookup source event in .kels sub DB - DB is keyed by identifer prefix plus digest of key event + DB is keyed by identifier prefix plus digest of key event Only one value per DB key is allowed .pwes is named sub DB of partially witnessed escrowed event tables that map sequence numbers to serialized event digests. snKey Values are digests used to lookup event in .evts sub DB - DB is keyed by identifer prefix plus sequence number of key event + DB is keyed by identifier prefix plus sequence number of key event More than one value per DB key is allowed .uwes is named sub DB of unverified event indexed escrowed couples from @@ -549,21 +641,21 @@ class Baser(dbing.LMDBer): Values are digests used to lookup event in .evts, .sigs and .dtss sub DBs. snKey - DB is keyed by identifer prefix plus sequence number of key event + DB is keyed by identifier prefix plus sequence number of key event More than one value per DB key is allowed - .dels is named sub DB of deplicitous event log tables that map sequence numbers + .dels is named sub DB of duplicitous event log tables that map sequence numbers to serialized event digests. snKey Values are digests used to lookup event in .evts sub DB - DB is keyed by identifer prefix plus sequence number of key event + DB is keyed by identifier prefix plus sequence number of key event More than one value per DB key is allowed - .ldes is named sub DB of likely deplicitous escrowed event tables + .ldes is named sub DB of likely duplicitous escrowed event tables that map sequence numbers to serialized event digests. snKey Values are digests used to lookup event in .evts sub DB - DB is keyed by identifer prefix plus sequence number of key event + DB is keyed by identifier prefix plus sequence number of key event More than one value per DB key is allowed .fons is named subDB instance of MatterSuber that maps @@ -580,6 +672,11 @@ class Baser(dbing.LMDBer): key is habitat name str value is serialized HabitatRecord dataclass + .nmsp is named subDB instance of Komer that maps habitat namespaces and names to habitat + application state. Includes habitat identifier prefix + key is habitat namespace + b'\x00' + name str + value is serialized HabitatRecord dataclass + .sdts (sad date-time-stamp) named subDB instance of CesrSuber that that maps SAD SAID to Dater instance's CESR serialization of ISO-8601 datetime @@ -644,6 +741,11 @@ class Baser(dbing.LMDBer): key is group identifier prefix value is serialized GroupIdentifier dataclass + .mpids is named subDB instance of CesrIoSetSuber mapping payload SAID (of 'e' block) + to the SAID of the `exn` messages is was contained in. This aggregates + identical message bodies across participants in group multisig body trying + to reach concensus on events or credentials. + Properties: kevers (dbdict): read through cache of kevers of states for KELs in db @@ -724,8 +826,11 @@ def reopen(self, **kwa): # events as ordered by first seen ordinals self.fons = subing.CesrSuber(db=self, subkey='fons.', klas=coring.Seqner) - # Kever state - self.states = subing.SerderSuber(db=self, subkey='stts.') # key states + # Kever state made of KeyStateRecord key states + self.states = koming.Komer(db=self, + schema=KeyStateRecord, + subkey='stts.') + self.wits = subing.CesrIoSetSuber(db=self, subkey="wits.", klas=coring.Prefixer) # habitat application state keyed by habitat name, includes prefix @@ -733,6 +838,11 @@ def reopen(self, **kwa): subkey='habs.', schema=HabitatRecord, ) + # habitat application state keyed by habitat namespace + b'\x00' + name, includes prefix + self.nmsp = koming.Komer(db=self, + subkey='nmsp.', + schema=HabitatRecord, ) + # SAD support datetime stamps and signatures indexed and not-indexed # all sad sdts (sad datetime serializations) maps said to date-time self.sdts = subing.CesrSuber(db=self, subkey='sdts.', klas=coring.Dater) @@ -768,7 +878,7 @@ def reopen(self, **kwa): # maps key=cid.role.eid to val=said of end reply self.lans = subing.CesrSuber(db=self, subkey='lans.', klas=coring.Saider) - # service endpoint identifer (eid) auths keyed by controller cid.role.eid + # service endpoint identifier (eid) auths keyed by controller cid.role.eid # data extracted from reply /end/role/add or /end/role/cut self.ends = koming.Komer(db=self, subkey='ends.', schema=EndpointRecord, ) @@ -784,14 +894,6 @@ def reopen(self, **kwa): subkey='witm.', schema=TopicsRecord, ) - # group local witness escrow - self.glwe = koming.Komer(db=self, subkey='glwe.', - schema=RotateRecord) - - # group partial member aid escrow - self.gpae = koming.Komer(db=self, subkey='gpae.', - schema=RotateRecord) - # group partial signature escrow self.gpse = subing.CatCesrIoSetSuber(db=self, subkey='gpse.', klas=(coring.Seqner, coring.Saider)) @@ -814,6 +916,9 @@ def reopen(self, **kwa): # exchange messages self.exns = subing.SerderSuber(db=self, subkey="exns.") + # Forward pointer to a provided reply message + self.erpy = subing.CesrSuber(db=self, subkey="erpy.", klas=coring.Saider) + # exchange messages self.sxns = subing.SerderSuber(db=self, subkey="sxns.") @@ -821,10 +926,8 @@ def reopen(self, **kwa): self.esigs = subing.CesrIoSetSuber(db=self, subkey='esigs.', klas=coring.Siger) # exchange message signatures - self.ecigs = subing.CesrIoSetSuber(db=self, subkey='ecigs.', klas=coring.Cigar) - - # exchange source prefix - self.esrc = subing.CesrSuber(db=self, subkey='esrc.', klas=coring.Prefixer) + self.ecigs = subing.CatCesrIoSetSuber(db=self, subkey='ecigs.', + klas=(coring.Verfer, coring.Cigar)) # exchange pathed attachments self.epath = subing.IoSetSuber(db=self, subkey=".epath") @@ -843,26 +946,12 @@ def reopen(self, **kwa): self.kdts = subing.CesrSuber(db=self, subkey='kdts.', klas=coring.Dater) # all key state messages. Maps key state said to serialization. ksns are - # versioned sads ( with version string) so use Serder to deserialize and + # KeyStateRecords so use ._asdict or ._asjson as appropriate # use .kdts, .ksgs, and .kcgs for datetimes and signatures - self.ksns = subing.SerderSuber(db=self, subkey='ksns.') - - # all key state ksgs (ksn indexed signature serializations) maps ksn quadkeys - # given by quadruple (saider.qb64, prefixer.qb64, seqner.q64, diger.qb64) - # of reply and trans signer's key state est evt to val Siger for each - # signature. - self.ksgs = subing.CesrIoSetSuber(db=self, subkey='ksgs.', klas=coring.Siger) - - # all key state kcgs (ksn non-indexed signature serializations) maps ksn SAID - # to couple (Verfer, Cigar) of nontrans signer of signature in Cigar - # nontrans qb64 of Prefixer is same as Verfer - self.kcgs = subing.CatCesrIoSetSuber(db=self, subkey='kcgs.', - klas=(coring.Verfer, coring.Cigar)) - - # all key state escrows indices of partially signed ksn messages. Maps - # route in reply to single (Saider,) of escrowed ksn. - # Routes such as /ksn/{aid} - self.knes = subing.CesrIoSetSuber(db=self, subkey='knes', klas=coring.Saider) + self.ksns = koming.Komer(db=self, + schema=KeyStateRecord, + subkey='ksns.') + #self.ksns = subing.SerderSuber(db=self, subkey='ksns.') # key state SAID database for successfully saved key state notices # maps key=(prefix, aid) to val=said of key state @@ -920,7 +1009,7 @@ def reopen(self, **kwa): self.schema = subing.SchemerSuber(db=self, subkey='schema.') - # Field values for contact information for remote identfiers. Keyed by prefix/field + # Field values for contact information for remote identifiers. Keyed by prefix/field self.cfld = subing.Suber(db=self, subkey="cfld.") @@ -932,9 +1021,34 @@ def reopen(self, **kwa): # Transferable signatures on contact data self.ccigs = subing.CesrSuber(db=self, subkey='ccigs.', klas=coring.Cigar) - # Chunked image data for contact information for remote identfiers + # Chunked image data for contact information for remote identifiers self.imgs = self.env.open_db(key=b'imgs.') + # Delegation escrow dbs # + # delegated partial witness escrow + self.dpwe = subing.SerderSuber(db=self, subkey='dpwe.') + + # delegated unanchored escrow + self.dune = subing.SerderSuber(db=self, subkey='dune.') + + # completed group multisig + self.cdel = subing.CesrSuber(db=self, subkey='cdel.', + klas=coring.Saider) + + # public keys mapped to the AID and event seq no they appeared in + self.pubs = subing.CatCesrIoSetSuber(db=self, subkey="pubs.", + klas=(coring.Prefixer, coring.Seqner)) + + # next key digests mapped to the AID and event seq no they appeared in + self.digs = subing.CatCesrIoSetSuber(db=self, subkey="digs.", + klas=(coring.Prefixer, coring.Seqner)) + + # multisig sig embed payload SAID mapped to containing exn messages across group multisig participants + self.meids = subing.CesrIoSetSuber(db=self, subkey="meids.", klas=coring.Saider) + + # multisig sig embed payload SAID mapped to group multisig participants AIDs + self.maids = subing.CesrIoSetSuber(db=self, subkey="maids.", klas=coring.Prefixer) + self.reload() return self.env @@ -946,9 +1060,10 @@ def reload(self): """ removes = [] for keys, data in self.habs.getItemIter(): - if (state := self.states.get(keys=data.hid)) is not None: + if (ksr := self.states.get(keys=data.hid)) is not None: try: - kever = eventing.Kever(state=state, db=self, + kever = eventing.Kever(state=ksr, + db=self, prefixes=self.prefixes, local=True) except kering.MissingEntryError as ex: # no kel event for keystate @@ -962,6 +1077,27 @@ def reload(self): for keys in removes: # remove bare .habs records self.habs.rem(keys=keys) + # Load namespaced Habs + removes = [] + for keys, data in self.nmsp.getItemIter(): + if (ksr := self.states.get(keys=data.hid)) is not None: + try: + kever = eventing.Kever(state=ksr, + db=self, + prefixes=self.prefixes, + local=True) + except kering.MissingEntryError as ex: # no kel event for keystate + removes.append(keys) # remove from .habs + continue + self.kevers[kever.prefixer.qb64] = kever + self.prefixes.add(kever.prefixer.qb64) + elif data.mid is None: # in .habs but no corresponding key state and not a group so remove + removes.append(keys) # no key state or KEL event for .hab record + + for keys in removes: # remove bare .habs records + self.nmsp.rem(keys=keys) + + def clean(self): """ Clean database by creating re-verified cleaned cloned copy @@ -1046,6 +1182,7 @@ def clean(self): if os.path.exists(copy.path): shutil.rmtree(copy.path) + def clonePreIter(self, pre, fn=0): """ Returns iterator of first seen event messages with attachments for the @@ -1062,6 +1199,7 @@ def clonePreIter(self, pre, fn=0): continue # skip this event yield msg + def cloneAllPreIter(self, key=b''): """ Returns iterator of first seen event messages with attachments for all @@ -1080,6 +1218,7 @@ def cloneAllPreIter(self, key=b''): continue # skip this event yield msg + def cloneEvtMsg(self, pre, fn, dig): """ Clones Event as Serialized CESR Message with Body and attached Foot @@ -1114,7 +1253,7 @@ def cloneEvtMsg(self, pre, fn, dig): for wig in wigs: atc.extend(wig) - # add authorizer (delegator/issure) source seal event couple to attachments + # add authorizer (delegator/issuer) source seal event couple to attachments couple = self.getAes(dgkey) if couple is not None: atc.extend(coring.Counter(code=coring.CtrDex.SealSourceCouples, @@ -1153,7 +1292,15 @@ def cloneEvtMsg(self, pre, fn, dig): msg.extend(atc) return msg + def cloneDelegation(self, kever): + """ + Recursively clone delegation chain from AID of Kever if one exits. + + Parameters: + kever (Kever): Kever from which to clone the delegator's AID. + + """ if kever.delegated: dkever = self.kevers[kever.delegator] yield from self.cloneDelegation(dkever) @@ -1161,31 +1308,192 @@ def cloneDelegation(self, kever): for dmsg in self.clonePreIter(pre=kever.delegator, fn=0): yield dmsg - def findAnchoringEvent(self, pre, anchor): + + def findAnchoringSealEvent(self, pre, seal, sn=0): + """ + Search through a KEL for the event that contains a specific anchored + SealEvent type of provided seal but in dict form and is also fully + witnessed. Searchs from sn forward (default = 0).Searches all events in + KEL of pre including disputed and/or superseded events. + Returns the Serder of the first event with the anchored SealEvent seal, + None if not found + + + Parameters: + pre (bytes|str): identifier of the KEL to search + seal (dict): dict form of Seal of any type SealEvent to find in anchored + seals list of each event + sn (int): beginning sn to search + + """ + if tuple(seal.keys()) != eventing.SealEvent._fields: # wrong type of seal + return None + + seal = eventing.SealEvent(**seal) #convert to namedtuple + + for evt in self.getEvtPreIter(pre=pre, sn=sn): # includes disputed & superseded + srdr = serdering.SerderKERI(raw=evt.tobytes()) + for eseal in srdr.seals or []: + if tuple(eseal.keys()) == eventing.SealEvent._fields: + eseal = eventing.SealEvent(**eseal) # convert to namedtuple + if seal == eseal and self.fullyWitnessed(srdr): + return srdr + return None + + + def findAnchoringSeal(self, pre, seal, sn=0): + """ + Search through a KEL for the event that contains an anchored + Seal with same Seal type as provided seal but in dict form. + Searchs from sn forward (default = 0). Only searches last event at any + sn therefore does not search any disputed or superseded events. + Returns the Serder of the first event with the anchored Seal seal, + None if not found + + Parameters: + pre (bytes|str): identifier of the KEL to search + seal (dict): dict form of Seal of any type to find in anchored + seals list of each event + sn (int): beginning sn to search + + """ + # create generic Seal namedtuple class using keys from provided seal dict + Seal = namedtuple('Seal', seal.keys()) # matching type + + for evt in self.getEvtLastPreIter(pre=pre, sn=sn): # only last evt at sn + srdr = serdering.SerderKERI(raw=evt.tobytes()) + for eseal in srdr.seals or []: + if tuple(eseal.keys()) == Seal._fields: # same type of seal + eseal = Seal(**eseal) #convert to namedtuple + if seal == eseal and self.fullyWitnessed(srdr): + return srdr + return None + + + + def findAnchoringSealEventClone(self, pre, seal): """ - Search through a KEL for the event that contains a specific anchor. - Returns the Serder of the first event with the anchor, None if not found + Search through a KEL for the event that contains a specific anchored + SealEvent type of provided seal but in dict form. + Returns the Serder of the first event with the anchored SealEvent seal, + None if not found + Searchs from inception forward Parameters: pre is qb64 identifier of the KEL to search - anchor is dict of anchor to find + seal is dict form of SealEvent to find in anchored seals list of each event """ - for evt in self.clonePreIter(pre=pre): - srdr = coring.Serder(raw=evt) - if "a" in srdr.ked: - ancs = srdr.ked["a"] - for anc in ancs: - spre = anc["i"] - ssn = int(anc["s"]) - sdig = anc["d"] + if tuple(seal.keys()) != eventing.SealEvent._fields: # wrong type of seal + return None + #raise ValueError(f"Expected SealEvent got {seal}.") + + seal = eventing.SealEvent(**seal) #convert to namedtuple + + # getEvtPreIter getEvtLastPreIter - if spre == anchor["i"] and ssn == int(anchor["s"]) \ - and anchor["d"] == sdig and self.fullyWitnessed(srdr): + for evt in self.clonePreIter(pre=pre): # all events including superseded + srdr = serdering.SerderKERI(raw=evt) + for eseal in srdr.seals or []: + if tuple(eseal.keys()) == eventing.SealEvent._fields: + eseal = eventing.SealEvent(**eseal) #convert to namedtuple + if seal == eseal and self.fullyWitnessed(srdr): return srdr + #spre = anc["i"] + #ssn = int(anc["s"], 16) + #sdig = anc["d"] + + #if spre == seal["i"] and ssn == int(seal["s"], 16) \ + #and seal["d"] == sdig and self.fullyWitnessed(srdr): + #return srdr return None + + def findAnchoringSealClone(self, pre, seal): + """ + Search through a KEL for the event that contains an anchored + Seal with same Seal type as provided seal but in dict form. + Returns the Serder of the first event with the anchored Seal seal, + None if not found + Searchs from inception forward + + Parameters: + pre is qb64 identifier of the KEL to search + seal is dict form of Seal of any type to find in anchored seals list of each event + + """ + # create generic Seal namedtuple class using keys from provided seal dict + Seal = namedtuple('Seal', seal.keys()) # matching type + + # getEvtPreIter getEvtLastPreIter + + for evt in self.clonePreIter(pre=pre): # all events including superseded + srdr = serdering.SerderKERI(raw=evt) + for eseal in srdr.seals or []: + if tuple(eseal.keys()) == Seal._fields: # same type of seal + eseal = Seal(**eseal) #convert to namedtuple + if seal == eseal and self.fullyWitnessed(srdr): + return srdr + return None + + + def signingMembers(self, pre: str): + """ Find signing members of a multisig group aid. + + Using the pubs index to find members of a signing group + + Parameters: + pre (str): qb64 identifier prefix to find members + + Returns: + list: qb64 identifier prefixes of signing members for provided aid + + """ + members = [] + if pre not in self.kevers: + return members + + kever = self.kevers[pre] + for verfer in kever.verfers: + if (couples := self.pubs.get(keys=(verfer.qb64,))) is None: + continue + + for couple in couples: + prefixer, seqner = couple + if prefixer.qb64 != pre: # Rule out aid being queried + members.append(prefixer.qb64) + + return members + + + def rotationMembers(self, pre: str): + """ Find rotation members of a multisig group aid. + + Using the digs index to lookup member pres of a group aid + + Parameters: + pre (str): qb64 identifier prefix to find members + + Returns: + list: qb64 identifier prefixes of rotation members for provided aid + """ + members = [] + if pre not in self.kevers: + return members + + kever = self.kevers[pre] + for diger in kever.ndigers: + if (couples := self.digs.get(keys=(diger.qb64,))) is None: + continue + + for couple in couples: + prefixer, seqner = couple + if prefixer.qb64 != pre: # Rule out aid being queried + members.append(prefixer.qb64) + + return members + def fullyWitnessed(self, serder): """ Verify the witness threshold on the event @@ -1231,11 +1539,11 @@ def resolveVerifiers(self, pre=None, sn=0, dig=None): # retrieve last event itself of receipter est evt from sdig sraw = self.getEvt(key=dbing.dgKey(pre=prefixer.qb64b, dig=bytes(sdig))) # assumes db ensures that sraw must not be none because sdig was in KE - sserder = coring.Serder(raw=bytes(sraw)) + sserder = serdering.SerderKERI(raw=bytes(sraw)) if dig is not None and not sserder.compare(said=dig): # endorser's dig not match event raise kering.ValidationError("Bad proof sig group at sn = {}" " for ksn = {}." - "".format(sn, sserder.ked)) + "".format(sn, sserder.sad)) verfers = sserder.verfers tholder = sserder.tholder @@ -1273,6 +1581,7 @@ def getEvt(self, key): """ return self.getVal(self.evts, key) + def delEvt(self, key): """ Use dgKey() @@ -1281,6 +1590,61 @@ def delEvt(self, key): """ return self.delVal(self.evts, key) + + def getEvtPreIter(self, pre, sn=0): + """ + Returns iterator of event messages without attachments + in sn order from the KEL of identifier prefix pre. + Essentially a replay of all event messages without attachments + for each sn from the KEL of pre including superseded duplicates + + Parameters: + pre (bytes|str): identifier prefix + sn (int): sequence number (default 0) to begin interation + """ + if hasattr(pre, 'encode'): + pre = pre.encode("utf-8") + + for dig in self.getKelIter(pre, sn=sn): + try: + + dgkey = dbing.dgKey(pre, dig) # get message + if not (raw := self.getEvt(key=dgkey)): + raise kering.MissingEntryError("Missing event for dig={}.".format(dig)) + + except Exception: + continue # skip this event + + yield raw # event message + + + def getEvtLastPreIter(self, pre, sn=0): + """ + Returns iterator of event messages without attachments + in sn order from the KEL of identifier prefix pre. + Essentially a replay of all event messages without attachments + for each sn from the KEL of pre including superseded duplicates + + Parameters: + pre (bytes|str): identifier prefix + sn (int): sequence number (default 0) to begin interation + """ + if hasattr(pre, 'encode'): + pre = pre.encode("utf-8") + + for dig in self.getKelLastIter(pre, sn=sn): + try: + + dgkey = dbing.dgKey(pre, dig) # get message + if not (raw := self.getEvt(key=dgkey)): + raise kering.MissingEntryError("Missing event for dig={}.".format(dig)) + + except Exception: + continue # skip this event + + yield raw # event message + + def putFe(self, key, val): """ Use fnKey() @@ -1348,6 +1712,7 @@ def getFelItemPreIter(self, pre, fn=0): """ return self.getAllOrdItemPreIter(db=self.fels, pre=pre, on=fn) + def getFelItemAllPreIter(self, key=b''): """ Returns iterator of all (pre, fn, dig) triples in first seen order for @@ -1935,7 +2300,8 @@ def delKes(self, key): """ return self.delIoVals(self.kels, key) - def getKelIter(self, pre): + + def getKelIter(self, pre, sn=0): """ Returns iterator of all dup vals in insertion order for all entries with same prefix across all sequence numbers without gaps. Stops if @@ -1943,39 +2309,47 @@ def getKelIter(self, pre): Assumes that key is combination of prefix and sequence number given by .snKey(). + db .kels values are digests used to lookup event in .evts sub DB + Raises StopIteration Error when empty. Duplicates are retrieved in insertion order. db is opened as named sub db with dupsort=True Parameters: - pre is bytes of itdentifier prefix prepended to sn in key + pre (bytes | str): of itdentifier prefix prepended to sn in key within sub db's keyspace + sn (int): initial sequence number to begin at """ if hasattr(pre, "encode"): pre = pre.encode("utf-8") # convert str to bytes - return self.getIoValsAllPreIter(self.kels, pre) + return self.getIoValsAllPreIter(self.kels, pre, on=sn) + - def getKelBackIter(self, pre, fn): + def getKelBackIter(self, pre, sn=0): """ Returns iterator of all dup vals in insertion order for all entries with same prefix across all sequence numbers without gaps in decreasing - order starting with first sequence number fn. Stops if encounters gap. + order starting with first sequence number sn. Stops if encounters gap. Assumes that key is combination of prefix and sequence number given by .snKey(). + db .kels values are digests used to lookup event in .evts sub DB + Raises StopIteration Error when empty. Duplicates are retrieved in insertion order. db is opened as named sub db with dupsort=True Parameters: - pre is bytes of itdentifier prefix prepended to sn in key + pre (bytes | str): of itdentifier prefix prepended to sn in key within sub db's keyspace + sn (int): """ if hasattr(pre, "encode"): pre = pre.encode("utf-8") # convert str to bytes - return self.getIoValsAllPreBackIter(self.kels, pre, fn) + return self.getIoValsAllPreBackIter(self.kels, pre, sn) - def getKelEstIter(self, pre): + + def getKelLastIter(self, pre, sn=0): """ Returns iterator of last one of dup vals at each key in insertion order for all entries with same prefix across all sequence numbers without gaps. @@ -1983,17 +2357,21 @@ def getKelEstIter(self, pre): Assumes that key is combination of prefix and sequence number given by .snKey(). + db .kels values are digests used to lookup event in .evts sub DB + Raises StopIteration Error when empty. Duplicates are retrieved in insertion order. db is opened as named sub db with dupsort=True Parameters: - pre is bytes of itdentifier prefix prepended to sn in key + pre (bytes | str): of itdentifier prefix prepended to sn in key within sub db's keyspace + sn (int); sequence number to being iteration """ if hasattr(pre, "encode"): pre = pre.encode("utf-8") # convert str to bytes - return self.getIoValLastAllPreIter(self.kels, pre) + return self.getIoValLastAllPreIter(self.kels, pre, on=sn) + def putPses(self, key, vals): """ diff --git a/src/keri/db/dbing.py b/src/keri/db/dbing.py index 500623e65..d3831e214 100644 --- a/src/keri/db/dbing.py +++ b/src/keri/db/dbing.py @@ -417,7 +417,11 @@ def putVal(self, db, key, val): val is bytes of value to be written """ with self.env.begin(db=db, write=True, buffers=True) as txn: - return (txn.put(key, val, overwrite=False)) + try: + return (txn.put(key, val, overwrite=False)) + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") def setVal(self, db, key, val): @@ -432,7 +436,11 @@ def setVal(self, db, key, val): val is bytes of value to be written """ with self.env.begin(db=db, write=True, buffers=True) as txn: - return (txn.put(key, val)) + try: + return (txn.put(key, val)) + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") def getVal(self, db, key): @@ -446,7 +454,11 @@ def getVal(self, db, key): """ with self.env.begin(db=db, write=False, buffers=True) as txn: - return( txn.get(key)) + try: + return(txn.get(key)) + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") def delVal(self, db, key): @@ -459,7 +471,11 @@ def delVal(self, db, key): key is bytes of key within sub db's keyspace """ with self.env.begin(db=db, write=True, buffers=True) as txn: - return (txn.delete(key)) + try: + return (txn.delete(key)) + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") def cnt(self, db): @@ -1108,7 +1124,11 @@ def delIoSetIokey(self, db, iokey): iokey (bytes): actual key with ordinal key suffix """ with self.env.begin(db=db, write=True, buffers=True) as txn: - return txn.delete(iokey) + try: + return txn.delete(iokey) + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{iokey}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") # For subdbs that support duplicates at each key (dupsort==True) @@ -1130,8 +1150,12 @@ def putVals(self, db, key, vals): """ with self.env.begin(db=db, write=True, buffers=True) as txn: result = True - for val in vals: - result = result and txn.put(key, val, dupdata=True) + try: + for val in vals: + result = result and txn.put(key, val, dupdata=True) + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") return result @@ -1158,7 +1182,11 @@ def addVal(self, db, key, val): result = False if val not in dups: with self.env.begin(db=db, write=True, buffers=True) as txn: - result = txn.put(key, val, dupdata=True) + try: + result = txn.put(key, val, dupdata=True) + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") return result @@ -1177,8 +1205,12 @@ def getVals(self, db, key): with self.env.begin(db=db, write=False, buffers=True) as txn: cursor = txn.cursor() vals = [] - if cursor.set_key(key): # moves to first_dup - vals = [val for val in cursor.iternext_dup()] + try: + if cursor.set_key(key): # moves to first_dup + vals = [val for val in cursor.iternext_dup()] + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") return vals @@ -1196,9 +1228,13 @@ def getValLast(self, db, key): with self.env.begin(db=db, write=False, buffers=True) as txn: cursor = txn.cursor() val = None - if cursor.set_key(key): # move to first_dup - if cursor.last_dup(): # move to last_dup - val = cursor.value() + try: + if cursor.set_key(key): # move to first_dup + if cursor.last_dup(): # move to last_dup + val = cursor.value() + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") return val @@ -1216,9 +1252,13 @@ def getValsIter(self, db, key): with self.env.begin(db=db, write=False, buffers=True) as txn: cursor = txn.cursor() vals = [] - if cursor.set_key(key): # moves to first_dup - for val in cursor.iternext_dup(): - yield val + try: + if cursor.set_key(key): # moves to first_dup + for val in cursor.iternext_dup(): + yield val + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") def cntVals(self, db, key): @@ -1232,8 +1272,12 @@ def cntVals(self, db, key): with self.env.begin(db=db, write=False, buffers=True) as txn: cursor = txn.cursor() count = 0 - if cursor.set_key(key): # moves to first_dup - count = cursor.count() + try: + if cursor.set_key(key): # moves to first_dup + count = cursor.count() + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") return count @@ -1263,6 +1307,7 @@ def cntValsAllPre(self, db, pre, on=0): return count + def delVals(self, db, key, val=b''): """ Deletes all values at key in db if val=b'' else deletes the dup @@ -1275,7 +1320,11 @@ def delVals(self, db, key, val=b''): val is bytes of dup val at key to delete """ with self.env.begin(db=db, write=True, buffers=True) as txn: - return (txn.delete(key, val)) + try: + return (txn.delete(key, val)) + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") # For subdbs that support insertion order preserving duplicates at each key. @@ -1309,9 +1358,13 @@ def putIoVals(self, db, key, vals): with self.env.begin(db=db, write=True, buffers=True) as txn: idx = 0 cursor = txn.cursor() - if cursor.set_key(key): # move to key if any - if cursor.last_dup(): # move to last dup - idx = 1 + int(bytes(cursor.value()[:32]), 16) # get last index as int + try: + if cursor.set_key(key): # move to key if any + if cursor.last_dup(): # move to last dup + idx = 1 + int(bytes(cursor.value()[:32]), 16) # get last index as int + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") for val in vals: if val not in dups: @@ -1330,6 +1383,12 @@ def addIoVal(self, db, key, val): Actual value written include prepended proem ordinal Assumes DB opened with dupsort=True + Because lmdb is lexocographic an insertion ordering proem is prepended to + all values that makes lexocographic order that same as insertion order + Duplicates are ordered as a pair of key plus value so prepending prefix + to each value changes duplicate ordering. Proem is 17 characters long. + With 16 character hex string followed by '.'. + Parameters: db is opened named sub db with dupsort=False key is bytes of key within sub db's keyspace @@ -1345,6 +1404,12 @@ def getIoVals(self, db, key): Removes prepended proem ordinal from each val before returning Assumes DB opened with dupsort=True + Because lmdb is lexocographic an insertion ordering proem is prepended to + all values that makes lexocographic order that same as insertion order + Duplicates are ordered as a pair of key plus value so prepending prefix + to each value changes duplicate ordering. Proem is 17 characters long. + With 16 character hex string followed by '.'. + Parameters: db is opened named sub db with dupsort=True key is bytes of key within sub db's keyspace @@ -1353,10 +1418,14 @@ def getIoVals(self, db, key): with self.env.begin(db=db, write=False, buffers=True) as txn: cursor = txn.cursor() vals = [] - if cursor.set_key(key): # moves to first_dup - # slice off prepended ordering proem - vals = [val[33:] for val in cursor.iternext_dup()] - return vals + try: + if cursor.set_key(key): # moves to first_dup + # slice off prepended ordering proem + vals = [val[33:] for val in cursor.iternext_dup()] + return vals + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") def getIoValsIter(self, db, key): @@ -1366,6 +1435,12 @@ def getIoValsIter(self, db, key): Removes prepended proem ordinal from each val before returning Assumes DB opened with dupsort=True + Because lmdb is lexocographic an insertion ordering proem is prepended to + all values that makes lexocographic order that same as insertion order + Duplicates are ordered as a pair of key plus value so prepending prefix + to each value changes duplicate ordering. Proem is 17 characters long. + With 16 character hex string followed by '.'. + Parameters: db is opened named sub db with dupsort=True key is bytes of key within sub db's keyspace @@ -1374,9 +1449,13 @@ def getIoValsIter(self, db, key): with self.env.begin(db=db, write=False, buffers=True) as txn: cursor = txn.cursor() vals = [] - if cursor.set_key(key): # moves to first_dup - for val in cursor.iternext_dup(): - yield val[33:] # slice off prepended ordering proem + try: + if cursor.set_key(key): # moves to first_dup + for val in cursor.iternext_dup(): + yield val[33:] # slice off prepended ordering proem + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") def getIoValLast(self, db, key): @@ -1394,10 +1473,14 @@ def getIoValLast(self, db, key): with self.env.begin(db=db, write=False, buffers=True) as txn: cursor = txn.cursor() val = None - if cursor.set_key(key): # move to first_dup - if cursor.last_dup(): # move to last_dup - val = cursor.value()[33:] # slice off prepended ordering proem - return val + try: + if cursor.set_key(key): # move to first_dup + if cursor.last_dup(): # move to last_dup + val = cursor.value()[33:] # slice off prepended ordering proem + return val + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") def getIoItemsNext(self, db, key=b"", skip=True): @@ -1479,12 +1562,16 @@ def cntIoVals(self, db, key): with self.env.begin(db=db, write=False, buffers=True) as txn: cursor = txn.cursor() count = 0 - if cursor.set_key(key): # moves to first_dup - count = cursor.count() + try: + if cursor.set_key(key): # moves to first_dup + count = cursor.count() + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") return count - def delIoVals(self,db, key): + def delIoVals(self, db, key): """ Deletes all values at key in db if key present. Returns True If key exists @@ -1495,7 +1582,11 @@ def delIoVals(self,db, key): """ with self.env.begin(db=db, write=True, buffers=True) as txn: - return (txn.delete(key)) + try: + return (txn.delete(key)) + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") def delIoVal(self, db, key, val): @@ -1529,18 +1620,23 @@ def delIoVal(self, db, key, val): with self.env.begin(db=db, write=True, buffers=True) as txn: cursor = txn.cursor() - if cursor.set_key(key): # move to first_dup - for proval in cursor.iternext_dup(): # value with proem - if val == proval[33:]: # strip of proem - return cursor.delete() + try: + if cursor.set_key(key): # move to first_dup + for proval in cursor.iternext_dup(): # value with proem + if val == proval[33:]: # strip of proem + return cursor.delete() + except lmdb.BadValsizeError as ex: + raise KeyError(f"Key: `{key}` is either empty, too big (for lmdb)," + " or wrong DUPFIXED size. ref) lmdb.BadValsizeError") return False - def getIoValsAllPreIter(self, db, pre): + def getIoValsAllPreIter(self, db, pre, on=0): """ Returns iterator of all dup vals in insertion order for all entries - with same prefix across all sequence numbers in increasing order without gaps - starting with zero. Stops if gap or different pre. + with same prefix across all ordinal numbers in increasing order + without gaps between ordinal numbers + starting with on, default 0. Stops if gap or different pre. Assumes that key is combination of prefix and sequence number given by .snKey(). Removes prepended proem ordinal from each val before returning @@ -1549,25 +1645,34 @@ def getIoValsAllPreIter(self, db, pre): Duplicates are retrieved in insertion order. + Because lmdb is lexocographic an insertion ordering proem is prepended to + all values that makes lexocographic order that same as insertion order + Duplicates are ordered as a pair of key plus value so prepending prefix + to each value changes duplicate ordering. Proem is 17 characters long. + With 16 character hex string followed by '.'. + Parameters: db is opened named sub db with dupsort=True - pre is bytes of itdentifier prefix prepended to sn in key + pre (bytes | str): of itdentifier prefix prepended to sn in key within sub db's keyspace + on (int): ordinal number to begin iteration at """ with self.env.begin(db=db, write=False, buffers=True) as txn: cursor = txn.cursor() - key = snKey(pre, cnt:=0) + key = snKey(pre, cnt:=on) while cursor.set_key(key): # moves to first_dup for val in cursor.iternext_dup(): # slice off prepended ordering prefix yield val[33:] key = snKey(pre, cnt:=cnt+1) - def getIoValsAllPreBackIter(self, db, pre, fn): + + def getIoValsAllPreBackIter(self, db, pre, on=0): """ Returns iterator of all dup vals in insertion order for all entries with same prefix across all sequence numbers in decreasing order without gaps - starting with fn as first sequence number. + between ordinals at a given pre. + Starting with on (default = 0) as begining ordinal number or sequence number. Stops if gap or different pre. Assumes that key is combination of prefix and sequence number given by .snKey(). @@ -1577,15 +1682,21 @@ def getIoValsAllPreBackIter(self, db, pre, fn): Duplicates are retrieved in insertion order. + Because lmdb is lexocographic an insertion ordering proem is prepended to + all values that makes lexocographic order that same as insertion order + Duplicates are ordered as a pair of key plus value so prepending prefix + to each value changes duplicate ordering. Proem is 17 characters long. + With 16 character hex string followed by '.'. + Parameters: db is opened named sub db with dupsort=True pre is bytes of identifier prefix prepended to sn in key within sub db's keyspace - fn is first + on (int): is ordinal number to begin iteration """ with self.env.begin(db=db, write=False, buffers=True) as txn: cursor = txn.cursor() - key = snKey(pre, cnt := fn) + key = snKey(pre, cnt := on) # set_key returns True if exact key else false while cursor.set_key(key): # moves to first_dup if valid key for val in cursor.iternext_dup(): @@ -1594,11 +1705,11 @@ def getIoValsAllPreBackIter(self, db, pre, fn): key = snKey(pre, cnt:=cnt-1) - def getIoValLastAllPreIter(self, db, pre): + def getIoValLastAllPreIter(self, db, pre, on=0): """ Returns iterator of last only of dup vals of each key in insertion order for all entries with same prefix across all sequence numbers in increasing order - without gaps starting with zero. Stops if gap or different pre. + without gaps starting with on (default = 0). Stops if gap or different pre. Assumes that key is combination of prefix and sequence number given by .snKey(). Removes prepended proem ordinal from each val before returning @@ -1607,26 +1718,38 @@ def getIoValLastAllPreIter(self, db, pre): Duplicates are retrieved in insertion order. + Because lmdb is lexocographic an insertion ordering proem is prepended to + all values that makes lexocographic order that same as insertion order + Duplicates are ordered as a pair of key plus value so prepending prefix + to each value changes duplicate ordering. Proem is 17 characters long. + With 16 character hex string followed by '.'. + Parameters: db is opened named sub db with dupsort=True pre is bytes of itdentifier prefix prepended to sn in key within sub db's keyspace + on (int): ordinal number to being iteration """ with self.env.begin(db=db, write=False, buffers=True) as txn: cursor = txn.cursor() - key = snKey(pre, cnt:=0) + key = snKey(pre, cnt:=on) while cursor.set_key(key): # moves to first_dup if cursor.last_dup(): # move to last_dup yield cursor.value()[33:] # slice off prepended ordering prefix key = snKey(pre, cnt:=cnt+1) - def getIoValsAnyPreIter(self, db, pre): + def getIoValsAnyPreIter(self, db, pre, on=0): """ Returns iterator of all dup vals in insertion order for any entries - with same prefix across all sequence numbers in order including gaps. + with same prefix across all ordinal numbers in order including gaps + between ordinals at a given pre. Staring with on (default = 0). Stops when pre is different. + + Duplicates that may be deleted such as duplicitous event logs need + to be able to iterate across gaps in ordinal number. + Assumes that key is combination of prefix and sequence number given by .snKey(). Removes prepended proem ordinal from each val before returning @@ -1644,18 +1767,17 @@ def getIoValsAnyPreIter(self, db, pre): db is opened named sub db with dupsort=True pre is bytes of itdentifier prefix prepended to sn in key within sub db's keyspace + on (int): beginning ordinal number to start iteration """ with self.env.begin(db=db, write=False, buffers=True) as txn: cursor = txn.cursor() - key = snKey(pre, cnt:=0) + key = snKey(pre, cnt:=on) while cursor.set_range(key): # moves to first dup of key >= key key = cursor.key() # actual key front, back = bytes(key).split(sep=b'.', maxsplit=1) - if front != pre: + if front != pre: # set range may skip pre if none break for val in cursor.iternext_dup(): yield val[33:] # slice off prepended ordering prefix cnt = int(back, 16) key = snKey(pre, cnt:=cnt+1) - - diff --git a/src/keri/db/escrowing.py b/src/keri/db/escrowing.py index 0e3d4a946..03ea429ef 100644 --- a/src/keri/db/escrowing.py +++ b/src/keri/db/escrowing.py @@ -27,11 +27,17 @@ def __init__(self, db, subkey, timeout=3600): # all ksn kdts (key state datetime serializations) maps said to date-time self.daterdb = subing.CesrSuber(db=self.db, subkey=subkey + '-dts.', klas=coring.Dater) - # all key state messages. Maps key state said to serialization. ksns are + # all reply messages that holdkey state messages. + # Maps replay messages that hold key state said to serialization. ksns are # versioned sads ( with version string) so use Serder to deserialize and # use .kdts, .ksgs, and .kcgs for datetimes and signatures self.serderdb = subing.SerderSuber(db=self.db, subkey=subkey + '-sns.') + # RegStateRecords used as basis for registry state notices in replies + #self.rsrdb = koming.Komer(db=self.db, + #schema=viring.RegStateRecord, + #subkey=subkey + '-sns.') + # all key state ksgs (ksn indexed signature serializations) maps ksn quadkeys # given by quadruple (saider.qb64, subkeyer.qb64, seqner.q64, diger.qb64) # of reply and trans signer's key state est evt to val Siger for each @@ -165,7 +171,7 @@ def escrowStateNotice(self, *, typ, pre, aid, serder, saider, dater, cigars=None return self.escrowdb.put(keys=(typ, pre, aid), vals=[saider]) # overwrite - def updateState(self, aid, serder, saider, dater): + def updateReply(self, aid, serder, saider, dater): """ Update Reply SAD in database given by by serder and associated databases for attached cig couple or sig quadruple. @@ -183,7 +189,7 @@ def updateState(self, aid, serder, saider, dater): self.daterdb.put(keys=keys, val=dater) # first one idempotent self.serderdb.pin(keys=keys, val=serder) # first one idempotent # Add source of ksn to the key... (source AID, ksn AID) - self.saiderdb.pin(keys=(serder.pre, aid), val=saider) # overwrite + self.saiderdb.pin(keys=(serder.sad["a"]["i"], aid), val=saider) # overwrite def removeState(self, saider): if saider: diff --git a/src/keri/db/koming.py b/src/keri/db/koming.py index a9c1f3323..a4987563a 100644 --- a/src/keri/db/koming.py +++ b/src/keri/db/koming.py @@ -281,6 +281,26 @@ def get(self, keys: Union[str, Iterable]): return (self.deserializer(self.db.getVal(db=self.sdb, key=self._tokey(keys)))) + def getDict(self, keys: Union[str, Iterable]): + """ + Gets dictified val at keys + + Parameters: + keys (tuple): of key strs to be combined in order to form key + + Returns: + val (dict): + None if no entry at keys + + Usage: + Use walrus operator to catch and raise missing entry + if (val := mydb.get(keys)) is None: + raise ExceptionHere + use val here + """ + val = self.get(keys) + return helping.dictify(val) if val is not None else None + def rem(self, keys: Union[str, Iterable]): """ diff --git a/src/keri/db/subing.py b/src/keri/db/subing.py index 664394a59..754cc553a 100644 --- a/src/keri/db/subing.py +++ b/src/keri/db/subing.py @@ -43,7 +43,7 @@ from .. import help from ..help.helping import nonStringIterable -from ..core import coring, scheming +from ..core import coring, scheming, serdering from . import dbing logger = help.ogler.getLogger() @@ -204,7 +204,7 @@ def __init__(self, db: dbing.LMDBer, *, super(Suber, self).__init__(db=db, subkey=subkey, dupsort=False, **kwa) - def put(self, keys: Union[str, Iterable], val: Union[bytes, str]): + def put(self, keys: Union[str, Iterable], val: Union[bytes, str, any]): """ Puts val at key made from keys. Does not overwrite @@ -1041,21 +1041,28 @@ def getItemIter(self, keys: Union[str, Iterable]=b"", class SerderSuber(Suber): """ - Sub class of Suber where data is serialized Serder instance - Automatically serializes and deserializes using Serder methods + Sub class of Suber where data is serialized Serder Subclass instance + given by .klas + Automatically serializes and deserializes using .klas Serder methods """ - def __init__(self, *pa, **kwa): + def __init__(self, *pa, + klas: Type[serdering.Serder] = serdering.SerderKERI, + **kwa): """ - Parameters: + Inherited Parameters: db (dbing.LMDBer): base db subkey (str): LMDB sub database key + + Parameters: + klas (Type[serdering.Serder]): Class reference to subclass of Serder """ super(SerderSuber, self).__init__(*pa, **kwa) + self.klas = klas - def put(self, keys: Union[str, Iterable], val: coring.Serder): + def put(self, keys: Union[str, Iterable], val: serdering.SerderKERI): """ Puts val at key made from keys. Does not overwrite @@ -1072,7 +1079,7 @@ def put(self, keys: Union[str, Iterable], val: coring.Serder): val=val.raw)) - def pin(self, keys: Union[str, Iterable], val: coring.Serder): + def pin(self, keys: Union[str, Iterable], val: serdering.SerderKERI): """ Pins (sets) val at key made from keys. Overwrites. @@ -1107,7 +1114,7 @@ def get(self, keys: Union[str, Iterable]): """ val = self.db.getVal(db=self.sdb, key=self._tokey(keys)) - return coring.Serder(raw=bytes(val)) if val is not None else None + return self.klas(raw=bytes(val)) if val is not None else None def rem(self, keys: Union[str, Iterable]): @@ -1139,7 +1146,7 @@ def getItemIter(self, keys: Union[str, Iterable]=b""): """ for iokey, val in self.db.getTopItemIter(db=self.sdb, key=self._tokey(keys)): - yield self._tokeys(iokey), coring.Serder(raw=bytes(val)) + yield self._tokeys(iokey), self.klas(raw=bytes(val)) class SchemerSuber(Suber): diff --git a/src/keri/end/ending.py b/src/keri/end/ending.py index d8670b81f..9e65ab7f0 100644 --- a/src/keri/end/ending.py +++ b/src/keri/end/ending.py @@ -10,6 +10,7 @@ import re import sys +from http_sfv import Dictionary from ordered_set import OrderedSet as oset from collections import namedtuple from collections.abc import Mapping @@ -22,6 +23,7 @@ from .. import kering from ..app import habbing from ..core import coring +from ..help import helping logger = help.ogler.getLogger() @@ -45,6 +47,11 @@ # Signature HTTP header support Signage = namedtuple("Signage", "markers indexed signer ordinal digest kind") +DEFAULTHEADERS = ('(created)', '(request-target)') + +Inputage = namedtuple("Inputage", "name fields created keyid alg expires nonce context") + + OOBI_URL_TEMPLATE = "/oobi/{cid}/{role}" OOBI_RE = re.compile('\\A/oobi/(?P[^/]+)/(?P[^/]+)(?:/(?P[^/]+))?\\Z', re.IGNORECASE) DOOBI_RE = re.compile('\\A/oobi/(?P[^/]+)\\Z', re.IGNORECASE) @@ -213,7 +220,7 @@ def designature(value): if "indexed" not in items: raise ValueError("Missing indexed field in Signature header signage.") - indexed = items["indexed"] not in kering.FALSY # make bool + indexed = items["indexed"] not in kering.FALSEY # make bool del items["indexed"] if "signer" in items: @@ -253,6 +260,138 @@ def designature(value): return signages +def normalize(param): + return param.strip() + + +def siginput(name, method, path, headers, fields, hab=None, signers=None, expires=None, nonce=None, alg=None, + keyid=None, context=None): + """ Create an HTTP Signature-Input Header + Parameters: + context (str): Optional implementation specific context for the signature + keyid (str): Optional key identifier used to sign the request + alg (str): Algorithm used when generating the signature + nonce (str): Uniqque salty nonce for signing the request + expires (str): iso8601 formated date string indicating exiration of header signature + signers (list): Optional signer objects used to sign the values + hab (Hab): Optional Hab used to sign the values. One of signers or Hab is required + fields (str): Fields in request to sign. Includes special fields as well as Header fields + headers (dict): HTTP request headers + path (str): HTTP request path + method (str): HTTP request method (POST, GET, PUT, etc) + name (str): name of item + + Returns: + header (dict): {'Signature-Input': 'value'} where value is RFC8941 compliant + (Structured Field Values for HTTP) formatted str of of Signature Input group. + sigers (Unqualified): unqualified base64 encoded signature + + """ + items = [] + ifields = [] + + # Create Signature Base, start with the fields and + for field in fields: + if field.startswith("@"): + if field == "@method": + items.append(f'"{field}": {method}') + ifields.append(field) + elif field == "@path": + items.append(f'"{field}": {path}') + ifields.append(field) + + else: + field = field.lower() + if field not in headers: + continue + + ifields.append(field) + value = normalize(headers[field]) + items.append(f'"{field}": {value}') + + sid = Dictionary() + sid[name] = ifields + now = helping.nowUTC() + sid[name].params['created'] = int(now.timestamp()) + + values = [f"({' '.join(ifields)})", f"created={int(now.timestamp())}"] + if expires is not None: + values.append(f"expires={expires}") + sid[name].params['expires'] = expires + if nonce is not None: + values.append(f"nonce={nonce}") + sid[name].params['nonce'] = nonce + if keyid is not None: + values.append(f"keyid={keyid}") + sid[name].params['keyid'] = keyid + if context is not None: + values.append(f"context={context}") + sid[name].params['context'] = context + if alg is not None: + values.append(f"alg={alg}") + sid[name].params['alg'] = alg + + params = ';'.join(values) + + items.append(f'"@signature-params: {params}"') + ser = "\n".join(items).encode("utf-8") + + if hab: + sigers = hab.sign(ser=ser, + verfers=hab.kever.verfers, + indexed=False) + else: + sigers = [] + for signer in signers: + sigers.append(signer.sign(ser)) # assigns .verfer to cigar + + return {'Signature-Input': f"{str(sid)}"}, sigers[0] # join all signature input value strs + + +def desiginput(value): + """ Verify the signature header based on values as identified in signature-input header + + Parameters: + value (Request): falcon request object + + Returns: + + """ + sid = Dictionary() + sid.parse(value) + + siginputs = [] + for name, svfields in sid.items(): + fields = [i.value for i in svfields] + if "created" not in svfields.params: + raise ValueError("missing required `created` field from signature input") + created = svfields.params["created"] + if "expires" in svfields.params: + expires = svfields.params["expires"] + else: + expires = None + if "nonce" in svfields.params: + nonce = svfields.params["nonce"] + else: + nonce = None + if "alg" in svfields.params: + alg = svfields.params["alg"] + else: + alg = None + if "keyid" in svfields.params: + keyid = svfields.params["keyid"] + else: + keyid = None + if "context" in svfields.params: + context = svfields.params["context"] + else: + context = None + + siginputs.append(Inputage(name=name, fields=fields, created=created, expires=expires, nonce=nonce, alg=alg, + keyid=keyid, context=context)) + return siginputs + + # Falcon reource endpoints class PointEnd(base.Tymee): @@ -421,7 +560,6 @@ def on_get(self, req, rep, aid=None, role=None, eid=None): eid: qb64 identifier prefix of participant in role """ - if aid is None: if self.default is None: rep.status = falcon.HTTP_NOT_FOUND @@ -435,6 +573,10 @@ def on_get(self, req, rep, aid=None, role=None, eid=None): return kever = self.hby.kevers[aid] + if not self.hby.db.fullyWitnessed(kever.serder): + rep.status = falcon.HTTP_NOT_FOUND + return + owits = oset(kever.wits) if kever.prefixer.qb64 in self.hby.prefixes: # One of our identifiers hab = self.hby.habs[kever.prefixer.qb64] diff --git a/src/keri/kering.py b/src/keri/kering.py index fdd611146..f56bc4023 100644 --- a/src/keri/kering.py +++ b/src/keri/kering.py @@ -3,15 +3,126 @@ Generic Constants and Classes """ import sys +import re from collections import namedtuple -FALSY = (False, 0, "?0", "no", "false", "False", "off") +FALSEY = (False, 0, None, "?0", "no", "false", "False", "off") TRUTHY = (True, 1, "?1", "yes" "true", "True", 'on') -Versionage = namedtuple("Versionage", "major minor") +# Serialization Kinds +Serialage = namedtuple("Serialage", 'json mgpk cbor') +Serials = Serialage(json='JSON', mgpk='MGPK', cbor='CBOR') + +# Protocol Types +Protocolage = namedtuple("Protocolage", "keri crel acdc") +Protos = Protocolage(keri="KERI", crel="CREL", acdc="ACDC", ) +Versionage = namedtuple("Versionage", "major minor") Version = Versionage(major=1, minor=0) # KERI Protocol Version +Vrsn_1_0 = Versionage(major=1, minor=0) # KERI Protocol Version Specific +Vrsn_1_1 = Versionage(major=1, minor=1) # KERI Protocol Version Specific + +VERRAWSIZE = 6 # hex characters in raw serialization size in version string +# "{:0{}x}".format(300, 6) # make num char in hex a variable +# '00012c' +VERFMT = "{}{:x}{:x}{}{:0{}x}_" # version format string +VERFULLSIZE = 17 # number of characters in full version string + +VEREX = b'(?P[A-Z]{4})(?P[0-9a-f])(?P[0-9a-f])(?P[A-Z]{4})(?P[0-9a-f]{6})_' +Rever = re.compile(VEREX) # compile is faster + + +def versify(proto=Protos.keri, version=Version, kind=Serials.json, size=0): + """ + Returns version string + """ + if proto not in Protos: + raise ValueError("Invalid message identifier = {}".format(proto)) + #version = version if version else Version + if kind not in Serials: + raise ValueError("Invalid serialization kind = {}".format(kind)) + + return VERFMT.format(proto, version[0], version[1], kind, size, VERRAWSIZE) + + +def deversify(vs, version=None): + """ + Returns: tuple(proto, kind, version, size) Where: + proto (str): value is protocol type identifier one of Protos (Protocolage) + acdc='ACDC', keri='KERI' + kind (str): value is serialization kind, one of Serials + json='JSON', mgpk='MGPK', cbor='CBOR' + vrsn (tuple): version tuple of type Versionage + size (int): raw size in bytes + + Parameters: + vs (str): version string to extract from + version (Versionage | None): supported version. None means do not check + for supported version. + + Uses regex match to extract: + protocol type + protocol version tuple + serialization kind + serialization size + """ + match = Rever.match(vs.encode("utf-8")) # match takes bytes + if match: + proto, major, minor, kind, size = match.group("proto", + "major", + "minor", + "kind", + "size") + proto = proto.decode("utf-8") + vrsn = Versionage(major=int(major, 16), minor=int(minor, 16)) + kind = kind.decode("utf-8") + + if proto not in Protos: + raise ValueError("Invalid message identifier = {}".format(proto)) + if version is not None and vrsn != version: + raise ValueError(f"Expected version = {version}, got " + f"{vrsn.major}.{vrsn.minor}.") + if kind not in Serials: + raise ValueError("Invalid serialization kind = {}".format(kind)) + size = int(size, 16) + return proto, vrsn, kind, size + + raise ValueError("Invalid version string = {}".format(vs)) + +""" +ilk is short for packet or message type for a given protocol + icp = incept, inception + rot = rotate, rotation + ixn = interact, interaction + dip = delcept, delegated inception + drt = deltate, delegated rotation + rct = receipt + ksn = state, key state notice + qry = query + rpy = reply + exn = exchange + exp = expose, sealed data exposition + vcp = vdr incept, verifiable data registry inception + vrt = vdr rotate, verifiable data registry rotation + iss = vc issue, verifiable credential issuance + rev = vc revoke, verifiable credential revocation + bis = backed vc issue, registry-backed transaction event log credential issuance + brv = backed vc revoke, registry-backed transaction event log credential revocation +""" + +# KERI protocol packet (message) types +Ilkage = namedtuple("Ilkage", ('icp rot ixn dip drt rct qry rpy exn ' + 'pro bar vcp vrt iss rev bis brv ')) + +Ilks = Ilkage(icp='icp', rot='rot', ixn='ixn', dip='dip', drt='drt', + rct='rct', + qry='qry', rpy='rpy', exn='exn', pro='pro', bar='bar', + vcp='vcp', vrt='vrt', iss='iss', rev='rev', bis='bis', brv='brv') + +# note ksn is not actual standalone message but is embedded in exn msg when sent +# over the wire. But keep ilk for legacy reasons. + SEPARATOR = "\r\n\r\n" SEPARATOR_BYTES = SEPARATOR.encode("utf-8") @@ -20,9 +131,38 @@ Schemage = namedtuple("Schemage", 'tcp http https') Schemes = Schemage(tcp='tcp', http='http', https='https') -Rolage = namedtuple("Rolage", 'controller witness registrar watcher judge juror peer mailbox') +Rolage = namedtuple("Rolage", 'controller witness registrar watcher judge juror peer mailbox agent') Roles = Rolage(controller='controller', witness='witness', registrar='registrar', - watcher='watcher', judge='judge', juror='juror', peer='peer', mailbox="mailbox") + watcher='watcher', judge='judge', juror='juror', peer='peer', mailbox="mailbox", agent="agent") + + +ICP_LABELS = ["v", "t", "d", "i", "s", "kt", "k", "nt", "n", + "bt", "b", "c", "a"] +DIP_LABELS = ["v", "d", "i", "s", "t", "kt", "k", "nt", "n", + "bt", "b", "c", "a", "di"] +ROT_LABELS = ["v", "d", "i", "s", "t", "p", "kt", "k", "nt", "n", + "bt", "br", "ba", "a"] +DRT_LABELS = ["v", "d", "i", "s", "t", "p", "kt", "k", "nt", "n", + "bt", "br", "ba", "a"] +IXN_LABELS = ["v", "d", "i", "s", "t", "p", "a"] + +#KSN_LABELS = ["v", "d", "i", "s", "p", "d", "f", "dt", "et", "kt", "k", "nt", "n", + #"bt", "b", "c", "ee", "di"] + +RPY_LABELS = ["v", "d", "t", "d", "dt", "r", "a"] + +VCP_LABELS = ["v", "d", "i", "s", "t", "bt", "b", "c"] +VRT_LABELS = ["v", "d", "i", "s", "t", "p", "bt", "b", "ba", "br"] + +ISS_LABELS = ["v", "i", "s", "t", "ri", "dt"] +BIS_LABELS = ["v", "i", "s", "t", "ra", "dt"] + +REV_LABELS = ["v", "i", "s", "t", "p", "dt"] +BRV_LABELS = ["v", "i", "s", "t", "ra", "p", "dt"] + +TSN_LABELS = ["v", "i", "s", "d", "ii", "a", "et", "bt", "b", "c", "br", "ba"] +CRED_TSN_LABELS = ["v", "i", "s", "d", "ri", "a", "ra"] + class KeriError(Exception): """ @@ -188,6 +328,15 @@ class InvalidVarRawSizeError(InvalidSizeError): raise InvalidRawSizeError("error message") """ +# Errors serializing messages + +class SerializeError(KeriError): + """ + Message creation and serialization errors + + Usage: + raise MessageError("error message") + """ @@ -199,6 +348,13 @@ class ValidationError(KeriError): raise ValidationError("error message") """ +class MissingFieldError(ValidationError): + """ + Missing a required element or field of message + Usage: + raise MissingElementError("error message") + """ + class MissingSignatureError(ValidationError): """ @@ -393,12 +549,20 @@ class VersionError(ExtractionError): raise VersionError("error message") """ +class ProtocolError(ExtractionError): + """ + Bad or Unsupported Protocol type -class DeserializationError(ExtractionError): + Usage: + raise ProtocolError("error message") """ - Error deserializing message + +class KindError(ExtractionError): + """ + Bad or Unsupported Serialization Kind + Usage: - raise DeserializationError("error message") + raise KindError("error message") """ @@ -408,12 +572,36 @@ class ConversionError(ExtractionError): Usage: raise ConversionError("error message") + + """ + +class DeserializeError(ExtractionError): + """ + Error deserializing message + Usage: + raise DeserializeError("error message") + """ + + +class FieldError(DeserializeError): + """ + Deserialized field error + Usage: + raise FieldError("error message") + + """ + +class ElementError(DeserializeError): + """ + Deserialized element error + Usage: + raise ElementError("error message") """ class DerivationCodeError(ExtractionError): """ - Derivation Code cryppto material conversion errors + Derivation Code crypto material conversion errors Usage: raise DerivationCodeError("error message") """ @@ -442,6 +630,9 @@ class UnexpectedOpCodeError(DerivationCodeError): raise DerivationCodeError("error message") """ + + + # Other errors class ExchangeError(KeriError): diff --git a/src/keri/peer/exchanging.py b/src/keri/peer/exchanging.py index ece4caaa8..fc1b3c234 100644 --- a/src/keri/peer/exchanging.py +++ b/src/keri/peer/exchanging.py @@ -6,11 +6,11 @@ import logging from datetime import timedelta -from hio.base import doing from hio.help import decking -from .. import help -from ..core import eventing, coring +from .. import help, kering +from ..app import habbing +from ..core import eventing, coring, serdering from ..help import helping from ..kering import ValidationError, MissingSignatureError @@ -19,39 +19,33 @@ logger = help.ogler.getLogger() -class Exchanger(doing.DoDoer): +class Exchanger: """ Peer to Peer KERI message Exchanger. """ - def __init__(self, db, handlers, local=False, cues=None, delta=ExchangeMessageTimeWindow, **kwa): + def __init__(self, hby, handlers, cues=None, delta=ExchangeMessageTimeWindow): """ Initialize instance Parameters: - db (Baser): database environment - handler(list): list of Handlers capable of responding to exn messages - local (bool): True means local event that should not process behavior and always persist event + hby (Haberyu): database environment + handlers(list): list of Handlers capable of responding to exn messages cues (Deck): of Cues i.e. notices of requests needing response delta (timedelta): message timeout window """ - self.db = db - self.kevers = self.db.kevers + self.hby = hby + self.kevers = self.hby.db.kevers self.delta = delta - self.local = local self.routes = dict() self.cues = cues if cues is not None else decking.Deck() # subclass of deque - doers = [] for handler in handlers: if handler.resource in self.routes: raise ValidationError("unable to register behavior {}, it has already been registered" "".format(handler.resource)) self.routes[handler.resource] = handler - doers.append(handler) - - super(Exchanger, self).__init__(doers=doers, **kwa) def addHandler(self, handler): if handler.resource in self.routes: @@ -59,61 +53,54 @@ def addHandler(self, handler): "".format(handler.resource)) self.routes[handler.resource] = handler - self.doers.append(handler) - def processEvent(self, serder, source=None, sigers=None, cigars=None, **kwargs): + def processEvent(self, serder, tsgs=None, cigars=None, **kwargs): """ Process one serder event with attached indexed signatures representing a Peer to Peer exchange message. Parameters: serder (Serder): instance of event to process - source (Prefixer): identifier prefix of event sender - sigers (list): of Siger instances of attached controller indexed sigs + tsgs (list): tuples (quadruples) of form + (prefixer, seqner, diger, [sigers]) where: + prefixer is pre of trans endorser + seqner is sequence number of trans endorser's est evt for keys for sigs + diger is digest of trans endorser's est evt for keys for sigs + [sigers] is list of indexed sigs from trans endorser's keys from est evt cigars (list): of Cigar instances of attached non-trans sigs """ route = serder.ked["r"] - payload = serder.ked["a"] - # dts = serder.ked["dt"] - modifiers = serder.ked["q"] if 'q' in serder.ked else dict() + sender = serder.ked["i"] pathed = kwargs["pathed"] if "pathed" in kwargs else [] - if not self.local and route not in self.routes: - raise AttributeError("unregistered route {} for exchange message = {}" - "".format(route, serder.pretty())) - behavior = self.routes[route] if route in self.routes else None + if tsgs is not None: + for prefixer, seqner, ssaider, sigers in tsgs: # iterate over each tsg + if sender != prefixer.qb64: # sig not by aid + raise MissingSignatureError(f"Exchange process: skipped signature not from aid=" + f"{sender}, from {prefixer.qb64} on exn msg=\n{serder.pretty()}\n") + + if prefixer.qb64 not in self.kevers or self.kevers[prefixer.qb64].sn < seqner.sn: + if self.escrowPSEvent(serder=serder, tsgs=tsgs, pathed=pathed): + self.cues.append(dict(kin="query", q=dict(r="logs", pre=prefixer.qb64, sn=seqner.snh))) + raise MissingSignatureError(f"Unable to find sender {prefixer.qb64} in kevers" + f" for evt = {serder.ked}.") + + # Verify the signatures are valid and that the signature threshold as of the signing event is met + tholder, verfers = self.hby.db.resolveVerifiers(pre=prefixer.qb64, sn=seqner.sn, dig=ssaider.qb64) + _, indices = eventing.verifySigs(serder.raw, sigers, verfers) + + if not tholder.satisfy(indices): # We still don't have all the sigers, need to escrow + if self.escrowPSEvent(serder=serder, tsgs=tsgs, pathed=pathed): + self.cues.append(dict(kin="query", q=dict(r="logs", pre=prefixer.qb64, sn=seqner.snh))) + raise MissingSignatureError(f"Not enough signatures in {indices}" + f" for evt = {serder.ked}.") - # delta = behavior.delta if behavior.delta is not None else self.delta - # delta = self.delta - # msgDt = helping.fromIso8601(dts) - # now = helping.nowUTC() - - # if now - msgDt > delta: - # raise ValidationError("message received outside time window with delta {} message={}" - # "".format(delta, serder.pretty())) - - if source is not None and sigers is not None: - if source.qb64 not in self.kevers: - if self.escrowPSEvent(serder=serder, source=source, sigers=sigers, pathed=pathed): - self.cues.append(dict(kin="query", q=dict(r="ksn", pre=source.qb64))) - raise MissingSignatureError(f"Unable to find sender {source.qb64} in kevers" - f" for evt = {serder.ked}.") - - kever = self.kevers[source.qb64] - tholder, verfers = self.db.resolveVerifiers(pre=source.qb64, sn=kever.lastEst.s) - - # Verify provided sigers using verfers - ssigers, indices = eventing.verifySigs(raw=serder.raw, sigers=sigers, verfers=verfers) - if not tholder.satisfy(indices): # at least one but not enough - psigers = self.db.esigs.get(keys=(serder.said,)) - if self.escrowPSEvent(serder=serder, source=source, sigers=sigers, pathed=pathed): - self.cues.append(dict(kin="query", q=dict(r="ksn", pre=source.qb64))) - raise MissingSignatureError("Failure satisfying sith = {} on sigs for {}" - " for evt = {}.".format(tholder.sith, - [siger.qb64 for siger in sigers], - serder.ked)) elif cigars is not None: for cigar in cigars: + if sender != cigar.verfer.qb64: # cig not by aid + raise MissingSignatureError(" process: skipped cig not from aid=" + "%s on exn msg=\n%s\n", sender, serder.pretty()) + if not cigar.verfer.verify(cigar.raw, serder.raw): # cig not verify raise MissingSignatureError("Failure satisfying exn on cigs for {}" " for evt = {}.".format(cigar, @@ -122,46 +109,33 @@ def processEvent(self, serder, source=None, sigers=None, cigars=None, **kwargs): raise MissingSignatureError("Failure satisfying exn, no cigs or sigs" " for evt = {}.".format(serder.ked)) - a = coring.Pather(path=["a"]) + e = coring.Pather(path=["e"]) attachments = [] - for pattach in pathed: + for p in pathed: + pattach = bytearray(p) pather = coring.Pather(qb64b=pattach, strip=True) - if pather.startswith(a): - np = pather.strip(a) + if pather.startswith(e): + np = pather.strip(e) attachments.append((np, pattach)) - # Always persis local events and events where the behavior has indicated persistence is required - if self.local or (hasattr(behavior, 'persist') and behavior.persist): - try: - self.logEvent(serder, [pathed for (_, pathed) in attachments], sigers, cigars) - except Exception as ex: - print(ex) - - # Do not execute behavior for local events, just validate and save - if not self.local: - msg = dict( - payload=payload, - modifiers=modifiers, - pre=source, - serder=serder, - attachments=attachments - ) - - behavior.msgs.append(msg) + # Perform behavior specific verification, think IPEX chaining requirements + try: + if not behavior.verify(serder=serder, attachments=attachments): + logger.info(f"exn event for route {route} failed behavior verfication. exn={serder.ked}") + return - def processResponseIter(self): - """ Iterate through cues and yields one or more responses for each cue. + except AttributeError: + logger.info(f"Behavior for {route} missing or does not have verify for exn={serder.ked}") - """ - responses = [] - for _, behavior in self.routes.items(): # get responses from all behaviors - while behavior.cues: - cue = behavior.cues.popleft() - responses.append(cue) + # Always persis events + self.logEvent(serder, pathed, tsgs, cigars) + self.cues.append(dict(kin="saved", said=serder.said)) - while responses: # iteratively process each response in responses - msg = responses.pop(0) - yield msg + # Execute any behavior specific handling, not sure if this should be different than verify + try: + behavior.handle(serder=serder, attachments=attachments) + except AttributeError: + logger.info(f"Behavior for {route} missing or does not have handle for exn={serder.ked}") def processEscrow(self): """ Process all escrows for `exn` messages @@ -169,32 +143,50 @@ def processEscrow(self): """ self.processEscrowPartialSigned() - def escrowPSEvent(self, serder, source, sigers, pathed): + def escrowPSEvent(self, serder, tsgs, pathed): """ Escrow event that does not have enough signatures. Parameters: serder (Serder): instance of event - source (Prefixer): of the origin of the exn - sigers (list): of Siger instances of indexed controller sigs + tsgs (list): quadlet of prefixer seqner, saider, sigers pathed (list): list of bytes of attached paths """ dig = serder.said - for siger in sigers: - self.db.esigs.add(keys=(dig,), val=siger) - self.db.epath.pin(keys=(dig,), vals=[bytes(p) for p in pathed]) - self.db.esrc.put(keys=(dig,), val=source) - return self.db.epse.put(keys=(dig,), val=serder) + for prefixer, seqner, ssaider, sigers in tsgs: # iterate over each tsg + quadkeys = (serder.said, prefixer.qb64, f"{seqner.sn:032x}", ssaider.qb64) + for siger in sigers: + self.hby.db.esigs.add(keys=quadkeys, val=siger) + + self.hby.db.epath.pin(keys=(dig,), vals=[bytes(p) for p in pathed]) + return self.hby.db.epse.put(keys=(dig,), val=serder) def processEscrowPartialSigned(self): """ Process escrow of partially signed messages """ - for (dig,), serder in self.db.epse.getItemIter(): - sigers = self.db.esigs.get(keys=(dig,)) - source = self.db.esrc.get(keys=(dig,)) - pathed = [bytearray(p.encode("utf-8")) for p in self.db.epath.get(keys=(dig,))] + for (dig,), serder in self.hby.db.epse.getItemIter(): + tsgs = [] + klases = (coring.Prefixer, coring.Seqner, coring.Saider) + args = ("qb64", "snh", "qb64") + sigers = [] + old = None # empty keys + for keys, siger in self.hby.db.esigs.getItemIter(keys=(dig, "")): + quad = keys[1:] + if quad != old: # new tsg + if sigers: # append tsg made for old and sigers + prefixer, seqner, saider = helping.klasify(sers=old, klases=klases, args=args) + + tsgs.append((prefixer, seqner, saider, sigers)) + sigers = [] + old = quad + sigers.append(siger) + if sigers and old: + prefixer, seqner, saider = helping.klasify(sers=old, klases=klases, args=args) + tsgs.append((prefixer, seqner, saider, sigers)) + + pathed = [bytearray(p.encode("utf-8")) for p in self.hby.db.epath.get(keys=(dig,))] try: - self.processEvent(serder=serder, source=source, sigers=sigers, pathed=pathed) + self.processEvent(serder=serder, tsgs=tsgs, pathed=pathed) except MissingSignatureError as ex: if logger.isEnabledFor(logging.DEBUG): @@ -202,44 +194,107 @@ def processEscrowPartialSigned(self): else: logger.info("Exchange partially signed failed: %s\n", ex.args[0]) except Exception as ex: - self.db.epse.rem(dig) - self.db.esigs.rem(dig) - self.db.esrc.rem(dig) + self.hby.db.epse.rem(dig) + self.hby.db.esigs.rem(dig) if logger.isEnabledFor(logging.DEBUG): logger.info("Exchange partially signed unescrowed: %s\n", ex.args[0]) else: logger.info("Exchange partially signed unescrowed: %s\n", ex.args[0]) else: - self.db.epse.rem(dig) - self.db.esigs.rem(dig) - self.db.esrc.rem(dig) + self.hby.db.epse.rem(dig) + self.hby.db.esigs.rem(dig) logger.info("Exchanger unescrow succeeded in valid exchange: " "creder=\n%s\n", serder.pretty()) - def logEvent(self, serder, pathed=None, sigers=None, cigars=None): + def logEvent(self, serder, pathed=None, tsgs=None, cigars=None): dig = serder.said + pdig = serder.ked['p'] pathed = pathed or [] - sigers = sigers or [] + tsgs = tsgs or [] cigars = cigars or [] - for siger in sigers: - self.db.esigs.add(keys=(dig,), val=siger) + for prefixer, seqner, ssaider, sigers in tsgs: # iterate over each tsg + quadkeys = (serder.said, prefixer.qb64, f"{seqner.sn:032x}", ssaider.qb64) + for siger in sigers: + self.hby.db.esigs.add(keys=quadkeys, val=siger) for cigar in cigars: - self.db.esigs.add(keys=(dig,), val=cigar) + self.hby.db.ecigs.add(keys=(dig,), val=(cigar.verfer, cigar)) + + saider = coring.Saider(qb64=serder.said) + self.hby.db.epath.pin(keys=(dig,), vals=[bytes(p) for p in pathed]) + if pdig: + self.hby.db.erpy.pin(keys=(pdig,), val=saider) + + self.hby.db.exns.put(keys=(dig,), val=serder) + + def lead(self, hab, said): + """ Determines is current member represented by hab is the lead of an exn message + + Lead is the signer of the exn with the lowest signing index + + Parameters: + hab (Hab): Habitat for sending of exchange message represented by SAID + said (str): qb64 SAID of exchange message + + Returns: + bool: True means hab is the lead + + """ + if not isinstance(hab, habbing.GroupHab): + return True + + keys = [verfer.qb64 for verfer in hab.kever.verfers] + tsgs = eventing.fetchTsgs(self.hby.db.esigs, coring.Saider(qb64=said)) + if not tsgs: # otherwise it contains a list of sigs + return False + + (_, _, _, sigers) = tsgs[0] + windex = min([siger.index for siger in sigers]) + + # True if Elected to send an EXN to its recipient + return hab.mhab.kever.verfers[0].qb64 == keys[windex] - self.db.epath.pin(keys=(dig,), vals=[bytes(p) for p in pathed]) - self.db.exns.put(keys=(dig,), val=serder) + def complete(self, said): + """ + Args: + said (str): qb64 said of exchange message to check status -def exchange(route, payload, date=None, modifiers=None, version=coring.Version, kind=coring.Serials.json): + Returns: + bool: True means exchange message is has been saved + """ + serder = self.hby.db.exns.get(keys=(said,)) + if not serder: + return False + else: + if serder.said != said: + raise kering.ValidationError(f"invalid exchange escrowed event {serder.said}-{said}") + + return True + + +def exchange(route, + payload, + sender, + recipient=None, + date=None, + dig=None, + modifiers=None, + embeds=None, + version=coring.Version, + kind=coring.Serials.json): """ Create an `exn` message with the specified route and payload Parameters: route (str): to destination route of the message - payload (Optional(dict, list)): body of message to deliver to route + payload (list | dict): body of message to deliver to route + sender (str): qb64 AID of sender of the exn + recipient (str) optional qb64 AID recipient of exn date (str): Iso8601 formatted date string to use for this request + dig (str) qb64 SAID of previous event if any modifiers (dict): equivalent of query string of uri, modifiers for the request that are not part of the payload + embeds (dict): named embeded KERI event CESR stream with attachments version (Version): is Version instance kind (Serials): is serialization kind @@ -247,18 +302,196 @@ def exchange(route, payload, date=None, modifiers=None, version=coring.Version, vs = coring.versify(version=version, kind=kind, size=0) ilk = eventing.Ilks.exn dt = date if date is not None else helping.nowIso8601() + p = dig if dig is not None else "" + embeds = embeds if embeds is not None else {} + + e = dict() + end = bytearray() + for label, msg in embeds.items(): + serder = coring.Sadder(raw=msg) + e[label] = serder.ked + atc = bytes(msg[serder.size:]) + if not atc: + continue + + pathed = bytearray() + pather = coring.Pather(path=["e", label]) + pathed.extend(pather.qb64b) + pathed.extend(atc) + end.extend(coring.Counter(code=coring.CtrDex.PathedMaterialQuadlets, + count=(len(pathed) // 4)).qb64b) + end.extend(pathed) + + if e: + e["d"] = "" + _, e = coring.Saider.saidify(sad=e, label=coring.Saids.d) + + attrs = dict( + ) + + if recipient is not None: + attrs['i'] = recipient + + attrs |= payload ked = dict(v=vs, t=ilk, d="", + i=sender, + p=p, dt=dt, r=route, - q=modifiers, - a=payload - ) + q=modifiers if modifiers is not None else {}, # q field required + a=attrs, + e=e) + _, ked = coring.Saider.saidify(sad=ked) + return serdering.SerderKERI(sad=ked), end # return serialized ked - if modifiers is None: - del ked["q"] - return eventing.Serder(ked=ked) # return serialized ked +def cloneMessage(hby, said): + """ Load and verify signatures on message exn + + Parameters: + hby (Habery): database environment from which to clone message + said (str): qb64 SAID of message exn to load + + Returns: + tuple: (serder, list) of message exn and pathed signatures on embedded attachments + + """ + exn = hby.db.exns.get(keys=(said,)) + if exn is None: + return None, None + + verify(hby=hby, serder=exn) + + pathed = dict() + e = coring.Pather(path=["e"]) + for p in hby.db.epath.get(keys=(exn.said,)): + pb = bytearray(p.encode("utf-8")) + pather = coring.Pather(qb64b=pb, strip=True) + if pather.startswith(e): + np = pather.strip(e) + nesting(np.path, pathed, pb) + + return exn, pathed + + +def serializeMessage(hby, said, pipelined=False): + atc = bytearray() + + exn = hby.db.exns.get(keys=(said,)) + if exn is None: + return None, None + + atc.extend(exn.raw) + + tsgs, cigars = verify(hby=hby, serder=exn) + + if len(tsgs) > 0: + for (prefixer, seqner, saider, sigers) in tsgs: + atc.extend(coring.Counter(coring.CtrDex.TransIdxSigGroups, count=1).qb64b) + atc.extend(prefixer.qb64b) + atc.extend(seqner.qb64b) + atc.extend(saider.qb64b) + + atc.extend(coring.Counter(code=coring.CtrDex.ControllerIdxSigs, count=len(sigers)).qb64b) + for siger in sigers: + atc.extend(siger.qb64b) + + if len(cigars) > 0: + atc.extend(coring.Counter(code=coring.CtrDex.NonTransReceiptCouples, count=len(cigars)).qb64b) + for cigar in cigars: + if cigar.verfer.code not in coring.NonTransDex: + raise ValueError("Attempt to use tranferable prefix={} for " + "receipt.".format(cigar.verfer.qb64)) + atc.extend(cigar.verfer.qb64b) + atc.extend(cigar.qb64b) + + # Smash the pathed components on the end + for p in hby.db.epath.get(keys=(exn.said,)): + atc.extend(coring.Counter(code=coring.CtrDex.PathedMaterialQuadlets, + count=(len(p) // 4)).qb64b) + atc.extend(p.encode("utf-8")) + + msg = bytearray() + + if pipelined: + if len(atc) % 4: + raise ValueError("Invalid attachments size={}, nonintegral" + " quadlets.".format(len(atc))) + msg.extend(coring.Counter(code=coring.CtrDex.AttachedMaterialQuadlets, + count=(len(atc) // 4)).qb64b) + + msg.extend(atc) + return msg + + +def nesting(paths, acc, val): + if len(paths) == 0: + return val + else: + first_value = paths[0] + nacc = dict() + acc[first_value] = nesting(paths[1:], nacc, val) + return acc + + +def verify(hby, serder): + """ Verify that the signatures in the database are valid for the provided exn + + Parameters: + hby (Habery): database environment from which to verify message + serder (Serder): exn serder to load and verify signatures for + + Returns: + bool: True means threshold satisfyig signatures were loaded and verified successfully + + """ + tsgs = [] + klases = (coring.Prefixer, coring.Seqner, coring.Saider) + args = ("qb64", "snh", "qb64") + sigers = [] + old = None # empty keys + for keys, siger in hby.db.esigs.getItemIter(keys=(serder.said, "")): + quad = keys[1:] + if quad != old: # new tsg + if sigers: # append tsg made for old and sigers + prefixer, seqner, saider = helping.klasify(sers=old, klases=klases, args=args) + + tsgs.append((prefixer, seqner, saider, sigers)) + sigers = [] + old = quad + sigers.append(siger) + if sigers and old: + prefixer, seqner, saider = helping.klasify(sers=old, klases=klases, args=args) + tsgs.append((prefixer, seqner, saider, sigers)) + + accepted = False + for prefixer, seqner, ssaider, sigers in tsgs: + if prefixer.qb64 not in hby.kevers or hby.kevers[prefixer.qb64].sn < seqner.sn: + raise MissingSignatureError(f"Unable to find sender {prefixer.qb64} in kevers" + f" for evt = {serder.ked}.") + + # Verify the signatures are valid and that the signature threshold as of the signing event is met + tholder, verfers = hby.db.resolveVerifiers(pre=prefixer.qb64, sn=seqner.sn, dig=ssaider.qb64) + _, indices = eventing.verifySigs(serder.raw, sigers, verfers) + + if not tholder.satisfy(indices): # We still don't have all the sigers, need to escrow + raise MissingSignatureError(f"Not enough signatures in {indices}" + f" for evt = {serder.ked}.") + accepted = True + + cigars = hby.db.ecigs.get(keys=(serder.said,)) + for cigar in cigars: + if not cigar.verfer.verify(cigar.raw, serder.raw): # cig not verify + raise MissingSignatureError("Failure satisfying exn on cigs for {}" + " for evt = {}.".format(cigar, + serder.ked)) + accepted = True + + if not accepted: + raise MissingSignatureError(f"No valid signatures stored for evt = {serder.ked}") + + return tsgs, cigars diff --git a/src/keri/vc/protocoling.py b/src/keri/vc/protocoling.py index d1e21c04c..12f4c991b 100644 --- a/src/keri/vc/protocoling.py +++ b/src/keri/vc/protocoling.py @@ -3,222 +3,142 @@ keri.vc.handling module """ -import json - -from hio.base import doing -from hio.help import decking +import os +from collections import namedtuple from .. import help -from ..app import signing from ..peer import exchanging logger = help.ogler.getLogger() +Ipexage = namedtuple("Ipexage", 'apply offer agree grant admit spurn') +Ipex = Ipexage(apply="apply", offer="offer", agree="agree", grant="grant", admit="admit", spurn="spurn") +PreviousRoutes = { + Ipex.offer: (Ipex.apply,), + Ipex.agree: (Ipex.offer,), + Ipex.grant: (Ipex.agree,), + Ipex.admit: (Ipex.grant,), + Ipex.spurn: (Ipex.apply, Ipex.offer, Ipex.agree, Ipex.grant), +} -class ApplyHandler(doing.Doer): - """ - { - "v": "KERI10JSON00011c_", // KERI Version String - "t": "exn", // peer to peer message ilk - "d": "EvLi9I4T6tiIEi4IxZtQy8S7ec5SZYwKJnUBPIgYs5Ks", - "dt": "2020-08-22T17:50:12.988921+00:00" - "r": "/credential/apply" - "a" { - "s": "EFgnk_c08WmZGgv9_mpldibRuqFMTQN-rAgtD-TCOwbs", - "a": { - "LEI": "254900OPPU84GM83MG36" - } - } //embedded credential_submission, may contain credential_fullfilment responding to presentation_def above - }-AABAA1o61PgMhwhi89FES_vwYeSbbWnVuELV_jv7Yv6f5zNiOLnj1ZZa4MW2c6Z_vZDt55QUnLaiaikE-d_ApsFEgCA - """ +class IpexHandler: + """ Processor of `exn` IPEX messages. - resource = "/credential/apply" + """ - def __init__(self, hby, rgy, verifier, name, cues=None, **kwa): + def __init__(self, resource, hby, notifier): """ Initialize instance Parameters: - hab (Habitat): credential wallet that will hold the issued credentials - verifier (Verifier): Local credential verifier used to verify and save any issued credential - name (str): local alias of issuer to use for issuing credential - issuerCues (Optional(decking.Deck)): outbound cue messages for issuer - cues (Optional(decking.Deck)): outbound cue messages - **kwa (dict): keyword arguments passed to DoDoer + resource (str): route of messages for this handler + hby (Habery): local identifier environment + notifier (Notifier): outbound notifications """ + self.resource = resource self.hby = hby - self.rgy = rgy - self.verifier = verifier - self.name = name - self.issuer = None - self.cues = cues if cues is not None else decking.Deck() - - self.msgs = decking.Deck() - - super(ApplyHandler, self).__init__(**kwa) + self.notifier = notifier - def do(self, tymth, tock=0.0, **opts): - """ Handle incoming messages by parsing and verifiying the credential and storing it in the wallet + def verify(self, serder, attachments=None): + """ Do route specific processsing of IPEX protocol exn messages Parameters: - tymth (function): injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock (float): injected initial tock value + serder (Serder): Serder of the IPEX protocol exn message + attachments (list): list of tuples of pather, CESR SAD path attachments to the exn event - Messages: - payload is dict representing the body of a /credential/issue message - pre is qb64 identifier prefix of sender - sigers is list of Sigers representing the sigs on the /credential/issue message - verfers is list of Verfers of the keys used to sign the message + Returns: + bool: True means the exn passed behaviour specific verification for IPEX protocol messages """ - self.wind(tymth) - self.tock = tock - yield self.tock - - while True: - while self.msgs: - msg = self.msgs.popleft() - recipientIdentifier = msg["pre"] - print(recipientIdentifier) - - yield self.tock - - yield self.tock - - -class IssueHandler(doing.DoDoer): - """ Sample class that handles a credential Issue `exn` message. - - By default, this handler verifies the credential with the provided verifier. - The incoming message must have the following format: - - { - "vc" [ - { - "vc": { - "v": "KERI10JSON00011c_", //KERI Version String - "x": "EeyJ0eXBlIjogWyJWZXJpZmlhYmxlQ3JlZGVudGlhbCI", // Identifier prefix of the Schema - "d": { - "type": [ - "EeyJ0eXBlIjogWyJWZXJpZmlhYmxlQ3JlZGVudGlhbCI" - ], - "id": "did:keri:EeyJ0eXBlIjogWyJWZXJpZmlhYmxlQ3JlZGVudGlhbCI", - "issuer": "did:keri:EchZLZUFqtBGRWMh3Ur_iKucjsrFcxU7AjfCPko9CkEA", - "issuanceDate": "2021-06-09T17:35:54.169967+00:00", - "credentialSubject": { - "id": "did:keri:did:keri:Efaavv0oadfghasdfn443fhbyyr4v", - "lei": "254900OPPU84GM83MG36" - }, - "credentialSchema": { - "id": "" - "type": "" - }, - "credentialStatus": { - "id": "", - "type": "" - } - } - }, // embedded verifiable credential - "proof": "-AABAA1o61PgMhwhi89FES_vwYeSbbWnVuELV_jv7Yv6f5zNiOLnj1ZZa4MW2c6Z_vZDt55QUnLaiaikE - -d_ApsFEgCA-GAB0AAAAAAAAAAAAAAAAAAAAABQEchZLZUFqtBGRWMh3Ur_iKucjsrFcxU7AjfCPko9CkEA" - } - ] //list of verifiable credentials - } + route = serder.ked['r'] + dig = serder.ked['p'] - """ + match route.split("/"): + case["", "ipex", Ipex.apply]: + if not dig: # Apply messages can only start an IPEX exchange + return True + case["", "ipex", verb] if verb in (Ipex.offer, Ipex.grant): + if not dig: # This is an offer, agree or grant opening an IPEX exchange, no prior + return True - resource = "/credential/issue" + pserder, _ = exchanging.cloneMessage(self.hby, said=dig) + if pserder is None: # previous reference message does not exist + return False - def __init__(self, hby, rgy, notifier, **kwa): - """ Initialize instance + proute = pserder.ked['r'] + pverb = os.path.basename(os.path.normpath(proute)) - Parameters: - hab (Habitat): local identifier environment - wallet (Wallet) credential wallet that will hold the issued credentials - ims (Optional(bytearray)): inbound message stream to process - notifier (Notifier): outbound notifications - **kwa (dict): keyword arguments passed to DoDoer + # Use established PreviousRoutes to determine if this response is valid + if pverb not in PreviousRoutes[verb]: + return False - """ - self.hby = hby - self.rgy = rgy - self.notifier = notifier - self.msgs = decking.Deck() - self.cues = decking.Deck() + return self.response(pserder) is None # Make sure we don't have a response already + + case["", "ipex", verb] if verb in (Ipex.admit, Ipex.agree, Ipex.spurn): + if not dig: # Admit and Spurn messages can NOT start an IPEX exchange + return False + + pserder, _ = exchanging.cloneMessage(self.hby, said=dig) + if pserder is None: # previous reference message does not exist + return False - doers = [doing.doify(self.msgDo)] + proute = pserder.ked['r'] + pverb = os.path.basename(os.path.normpath(proute)) - super(IssueHandler, self).__init__(doers=doers, **kwa) + # Use established PreviousRoutes to determine if this response is valid + if pverb not in PreviousRoutes[verb]: + return False - def msgDo(self, tymth, tock=0.0): - """ Handle incoming messages by parsing and verifiying the credential and storing it in the wallet + return self.response(pserder) is None # Make sure we don't have a response already + + return False + + def response(self, serder): + """ Return the IPEX exn message sent as a response to the provided serder, if any Parameters: - tymth (function): injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock (float): injected initial tock value + serder (Serder): IPEX exn message to check for a response - Messages: - payload (dict): representing the body of a /credential/issue message - pre (qb64): identifier prefix of sender - sigers (list): of Sigers representing the sigs on the /credential/issue message - verfers (list): of Verfers of the keys used to sign the message + Returns: """ - # enter context - self.wind(tymth) - self.tock = tock - yield self.tock - - while True: - while self.msgs: - msg = self.msgs.popleft() - payload = msg["payload"] - - if "i" not in payload or "s" not in payload or "a" not in payload: - print(f"invalid credential issuance message, i, s and a are required fields. evt=: " - f"{payload}") - continue - - iaid = payload["i"] - ssaid = payload["s"] - csaid = payload["a"] - - data = dict( - r='/credential/issue', - issuer=dict( - i=iaid - ), - schema=dict( - d=ssaid - ), - credential=dict( - d=csaid - ) - ) - - creder = self.rgy.reger.creds.get(csaid) - if creder is not None: - data["credential"]["sad"] = creder.crd - - # TODO: Get schema resolver and Organizer to load schema and contact info if any. - self.notifier.add(attrs=data) - yield self.tock - - yield self.tock - - -def credentialIssueExn(hab, issuer, schema, said): - """ + saider = self.hby.db.erpy.get(keys=(serder.said,)) + if saider: + rserder, _ = exchanging.cloneMessage(self.hby, saider.qb64) # Clone previous so we reverify the sigs + return rserder + + return None + + def handle(self, serder, attachments=None): + """ Do route specific processsing of IPEX protocol exn messages + + Parameters: + serder (Serder): Serder of the IPEX protocol exn message + attachments (list): list of tuples of pather, CESR SAD path attachments to the exn event + + """ + attrs = serder.ked["a"] + + data = dict( + r=f"/exn{serder.ked['r']}", + d=serder.said, + m=attrs["m"] + ) + + self.notifier.add(attrs=data) + + +def ipexApplyExn(hab, recp, message, schema, attrs): + """ Apply for an ACDC Parameters: hab(Hab): identifier environment for issuer of credential - issuer (str): qb64 AID of the issuer of the credential - schema (str): qb64 SAID of JSON schema of credential being issued - said (str): qb64 SAID of credentiual being issued + recp (str): qb64 AID of recipient + message(str): Human readable message regarding the credential application + schema (any): schema or its SAID + attrs (any): attribute field label list Returns: Serder: credential issuance exn peer to peer message @@ -226,221 +146,186 @@ def credentialIssueExn(hab, issuer, schema, said): """ data = dict( - i=issuer, + m=message, s=schema, - a=said, + a=attrs, + i=recp ) - exn = exchanging.exchange(route="/credential/issue", payload=data) - ims = hab.endorse(serder=exn, last=True, pipelined=False) + exn, end = exchanging.exchange(route="/ipex/apply", payload=data, sender=hab.pre) + ims = hab.endorse(serder=exn, last=False, pipelined=False) del ims[:exn.size] + ims.extend(end) return exn, ims -class PresentationRequestHandler(doing.Doer): - """ Processor for a presentation request - - Processor for a credential request with input descriptors in the payload used to - match saved credentials based on a schema. The payload of the request is expected to - have the following format: - - { - ""submission_requirements": [{ - "name": "Proof of LEI", - "rule": "pick", - "count": 1, - "from": "A" - }] - "input_descriptors": [ - { - "x":"EckOnHB11J4H9q16I3tN8DdpNXnCiP5QJQ7yvkWqTDdA", - "group": ["A"], - } - ], - "format": { - "cesr": { - "proof_type": ["Ed25519Signature2018"] - } - } - } +def ipexOfferExn(hab, message, acdc, apply=None): + """ Offer a metadata ACDC - """ + Parameters: + hab(Hab): identifier environment for issuer of credential + message(str): Human readable message regarding the credential offer + acdc (any): metadata ACDC or its SAID + apply (Serder): optional IPEX exn apply message that this offer is response to. - resource = "/presentation/request" + Returns: + Serder: credential issuance exn peer to peer message + bytes: attachments for exn message + + """ + data = dict( + m=message + ) - def __init__(self, hby, notifier, cues=None, **kwa): - """ Create an `exn` request handler for processing credential presentation requests + embeds = dict( + acdc=acdc + ) - Parameters - hab (Habitat): is the environment - wallet (Wallet): is the wallet holding the credentials to present - cues (Optional(decking.Deck)): outbound response cue for this handler + kwa = dict() + if apply is not None: + kwa["dig"] = apply.said - """ - self.hby = hby - self.msgs = decking.Deck() - self.cues = cues if cues is not None else decking.Deck() - self.notifier = notifier + exn, end = exchanging.exchange(route="/ipex/offer", payload=data, sender=hab.pre, embeds=embeds, **kwa) + ims = hab.endorse(serder=exn, last=False, pipelined=False) + del ims[:exn.size] + ims.extend(end) - super(PresentationRequestHandler, self).__init__(**kwa) + return exn, ims - def do(self, tymth, tock=0.0, **opts): - """ Process presentation request message with sender identifier, sigs and verfers - Parameters: - tymth (function): injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock (float): injected initial tock value +def ipexAgreeExn(hab, message, offer): + """ Agree an offer - Messages: - payload (dict): representing the body of a /presentation/request message - pre (qb64): identifier prefix of sender - sigers (list): of Sigers representing the sigs on the /presentation/request message - verfers (list): of Verfers of the keys used to sign the message + Parameters: + hab(Hab): identifier environment for issuer of credential + message(str): Human readable message regarding the credential agreement + offer (Serder): IPEX exn offer message that this offer is response to. - """ - self.wind(tymth) - self.tock = tock - yield self.tock - - while True: - while self.msgs: - msg = self.msgs.popleft() - payload = msg["payload"] - if "i" not in payload and "s" not in payload and "n" not in payload: - print(f"invalid credential request message, one of i, s and n are required fields. evt=: " - f"{payload}") - continue - - data = dict( - r='/presentation/request', - issuer={}, - schema={}, - credential={} - ) - - if "i" in payload: - data["issuer"] = dict( - i=payload["i"] - ) - if "s" in payload: - data["schema"] = dict( - n=payload["s"] - ) - if "i" in payload: - data["credential"] = dict( - n=payload["n"] - ) - - self.notifier.add(attrs=data) - - yield self.tock - - yield self.tock - - -class PresentationProofHandler(doing.Doer): - """ Processor for responding to presentation proof peer to peer message. - - The payload of the message is expected to have the following format: + Returns: + Serder: credential issuance exn peer to peer message + bytes: attachments for exn message """ + data = dict( + m=message + ) - resource = "/presentation" + exn, end = exchanging.exchange(route="/ipex/agree", payload=data, sender=hab.pre, dig=offer.said) + ims = hab.endorse(serder=exn, last=False, pipelined=False) + del ims[:exn.size] + ims.extend(end) - def __init__(self, notifier, cues=None, **kwa): - """ Initialize instance + return exn, ims - Parameters: - cues (decking.Deck): outbound cue messages - proofs (decking.Deck): inbound proof request `exn` messages - **kwa (dict): keyword arguments passes to super Doer - """ - self.msgs = decking.Deck() - self.notifier = notifier - self.cues = cues if cues is not None else decking.Deck() +def ipexGrantExn(hab, recp, message, acdc, iss=None, anc=None, agree=None, dt=None): + """ Disclose an ACDC + + Parameters: + hab(Hab): identifier environment for issuer of credential + recp (str) qb64 AID of recipient of GRANT message + message(str): Human readable message regarding the credential disclosure + acdc (bytes): CESR stream of serialized ACDC with attachments + iss (bytes): serialized TEL issuance event + anc (bytes): serialized anchoring event in the KEL, either ixn or rot + agree (Serder): optional IPEX exn agree message that this grant is response to. + dt (str): Iso8601 formatted date string to use for this request + + Returns: + Serder: credential issuance exn peer to peer message + bytes: attachments for exn message - super(PresentationProofHandler, self).__init__(**kwa) + """ + data = dict( + m=message, + i=recp, + ) - def do(self, tymth, tock=0.0, **opts): - """ Handle incoming messages by parsing and verifying the credential and storing it in the wallet + embeds = dict( + acdc=acdc, + ) - Parameters: - tymth (function): injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock (float): injected initial tock value + if iss is not None: + embeds['iss'] = iss - Messages: - payload is dict representing the body of a /credential/issue message - pre is qb64 identifier prefix of sender - sigers is list of Sigers representing the sigs on the /credential/issue message - verfers is list of Verfers of the keys used to sign the message + if anc is not None: + embeds['anc'] = anc + kwa = dict() + if agree is not None: + kwa['dig'] = agree.said + + exn, end = exchanging.exchange(route="/ipex/grant", payload=data, sender=hab.pre, embeds=embeds, date=dt, **kwa) + ims = hab.endorse(serder=exn, last=False, pipelined=False) + del ims[:exn.size] + ims.extend(end) + + return exn, ims - """ - self.wind(tymth) - self.tock = tock - yield self.tock - - while True: - while self.msgs: - msg = self.msgs.popleft() - payload = msg["payload"] - - if "i" not in payload or "s" not in payload or "n" not in payload: - print(f"invalid credential presentation message, i, s and n are required fields. evt=: " - f"{payload}") - continue - - iaid = payload["i"] - ssaid = payload["s"] - csaid = payload["n"] - - data = dict( - r='/presentation', - issuer=dict( - i=iaid - ), - schema=dict( - n=ssaid - ), - credential=dict( - n=csaid - ) - ) - self.notifier.add(attrs=data) - yield - - -def presentationExchangeExn(hab, reger, said): - """ Create a presentation exchange. - - Create presentation exchange body containing the credential and event logs - needed to provide proof of holding a valid credential + +def ipexAdmitExn(hab, message, grant): + """ Admit a disclosure Parameters: - hab (Hab): is the environment database - reger (Registry): is the credential registry database - said (str): qb64 SAID of the credential to present + hab(Hab): identifier environment for issuer of credential + message(str): Human readable message regarding the admission + grant (Serder): IPEX grant exn message serder Returns: - dict: presentation dict for credential + Serder: credential issuance exn peer to peer message + bytes: attachments for exn message """ - creder = reger.creds.get(said) - if creder is None: - raise ValueError(f"unable to find credential {said} to present") + data = dict( + m=message, + ) + + exn, end = exchanging.exchange(route="/ipex/admit", payload=data, sender=hab.pre, dig=grant.said) + ims = hab.endorse(serder=exn, last=False, pipelined=False) + del ims[:exn.size] + ims.extend(end) + + return exn, ims + + +def ipexSpurnExn(hab, message, spurned): + """ Reject an application, offer or agreement + + Parameters: + hab(Hab): identifier environment for issuer of credential + message(str): Human readable message regarding the admission + spurned (Serder): apply, offer, agree or grant received + Returns: + Serder: credential issuance exn peer to peer message + bytes: attachments for exn message + + """ data = dict( - i=creder.issuer, - s=creder.schema, - n=said, + m=message ) - exn = exchanging.exchange(route="/presentation", payload=data) - ims = hab.endorse(serder=exn, last=True, pipelined=False) + exn, end = exchanging.exchange(route="/ipex/spurn", payload=data, sender=hab.pre, dig=spurned.said) + ims = hab.endorse(serder=exn, last=False, pipelined=False) del ims[:exn.size] + ims.extend(end) return exn, ims + + +def loadHandlers(hby, exc, notifier): + """ Load handlers for the IPEX protocol + + Parameters: + hby (Habery): Database and keystore for environment + exc (Exchanger): Peer-to-peer message router + notifier (Notifier): outbound notifications + + """ + exc.addHandler(IpexHandler(resource="/ipex/apply", hby=hby, notifier=notifier)) + exc.addHandler(IpexHandler(resource="/ipex/offer", hby=hby, notifier=notifier)) + exc.addHandler(IpexHandler(resource="/ipex/agree", hby=hby, notifier=notifier)) + exc.addHandler(IpexHandler(resource="/ipex/grant", hby=hby, notifier=notifier)) + exc.addHandler(IpexHandler(resource="/ipex/admit", hby=hby, notifier=notifier)) + exc.addHandler(IpexHandler(resource="/ipex/spurn", hby=hby, notifier=notifier)) diff --git a/src/keri/vc/proving.py b/src/keri/vc/proving.py index d28a15b06..e724648a5 100644 --- a/src/keri/vc/proving.py +++ b/src/keri/vc/proving.py @@ -8,7 +8,7 @@ from typing import Union from .. import help -from ..core import coring +from ..core import coring, serdering from ..core.coring import (Serials, versify) from ..db import subing from ..kering import Version @@ -30,28 +30,27 @@ def credential(schema, rules=None, version=Version, kind=Serials.json): - """ Returns Credentialer of new credential - - Creates SAD for credential and Saidifyies it before creation. + """Utility function to create an ACDC. Creates dict SAD for credential from + parameters and Saidifyies it before creation. Parameters: schema (SAID): of schema for this credential issuer (str): qb64 identifier prefix of the issuer status (str): qb64 said of the credential registry - recipient (str): qb64 identifier prefix of the recipient + recipient (Option[str|None]): qb64 identifier prefix of the recipient data (dict): of the values being assigned to the subject of this credential private (bool): apply nonce used for privacy preserving ACDC salt (string): salt for nonce - source (Optional[dict,list]): of source credentials to which this credential is chained - rules (list): ACDC rules section for credential + source (dict | list): of source credentials to which this credential is chained + rules (dict | list): ACDC rules section for credential version (Version): version instance kind (Serials): serialization kind Returns: - Creder: credential instance + SerderACDC: credential instance """ - vs = versify(ident=coring.Idents.acdc, version=version, kind=kind, size=0) + vs = versify(proto=coring.Protos.acdc, version=version, kind=kind, size=0) vc = dict( v=vs, @@ -91,184 +90,9 @@ def credential(schema, if rules is not None: vc["r"] = rules - _, sad = coring.Saider.saidify(sad=subject, kind=kind, label=coring.Ids.d) + _, sad = coring.Saider.saidify(sad=subject, kind=kind, label=coring.Saids.d) vc["a"] = sad _, vc = coring.Saider.saidify(sad=vc) - return Creder(ked=vc) - - -class Creder(coring.Sadder): - """ Creder is for creating ACDC chained credentials - - Sub class of Sadder that adds credential specific validation and properties - - Inherited Properties: - .raw is bytes of serialized event only - .ked is key event dict - .kind is serialization kind string value (see namedtuple coring.Serials) - .version is Versionage instance of event version - .size is int of number of bytes in serialed event only - .diger is Diger instance of digest of .raw - .dig is qb64 digest from .diger - .digb is qb64b digest from .diger - .verfers is list of Verfers converted from .ked["k"] - .werfers is list of Verfers converted from .ked["b"] - .tholder is Tholder instance from .ked["kt'] else None - .sn is int sequence number converted from .ked["s"] - .pre is qb64 str of identifier prefix from .ked["i"] - .preb is qb64b bytes of identifier prefix from .ked["i"] - .said is qb64 of .ked['d'] if present - .saidb is qb64b of .ked['d'] of present - - Properties: - .crd (dict): synonym for .ked - .issuer (str): qb64 identifier prefix of credential issuer - .schema (str): qb64 SAID of JSONSchema for credential - .subject (str): qb64 identifier prefix of credential subject - .status (str): qb64 identifier prefix of issuance / revocation registry - - """ - - def __init__(self, raw=b'', ked=None, kind=None, sad=None, code=coring.MtrDex.Blake3_256): - """ Creates a serializer/deserializer for a ACDC Verifiable Credential in CESR Proof Format - - Requires either raw or (crd and kind) to load credential from serialized form or in memory - - Parameters: - raw (bytes): is raw credential - ked (dict): is populated credential - kind (is serialization kind - sad (Sadder): is clonable base class - code (MtrDex): is hashing codex - - """ - super(Creder, self).__init__(raw=raw, ked=ked, kind=kind, sad=sad, code=code) - - if self._ident != coring.Idents.acdc: - raise ValueError("Invalid ident {}, must be ACDC".format(self._ident)) - - @property - def crd(self): - """ issuer property getter""" - return self._ked - - @property - def issuer(self): - """ issuer property getter""" - return self._ked["i"] - - @property - def schema(self): - """ schema property getter""" - return self._ked["s"] - - @property - def subject(self): - """ subject property getter""" - return self._ked["a"] - - @property - def status(self): - """ status property getter""" - if "ri" in self._ked: - return self._ked["ri"] - else: - return None - - @property - def chains(self): - return self._ked["e"] if "e" in self._ked else {} - - -class CrederSuber(subing.Suber): - """ Data serialization for Creder - - Sub class of Suber where data is serialized Creder instance - Automatically serializes and deserializes using Creder methods - - """ - - def __init__(self, *pa, **kwa): - """ - Parameters: - db (dbing.LMDBer): base db - subkey (str): LMDB sub database key - - """ - super(CrederSuber, self).__init__(*pa, **kwa) - - def put(self, keys: Union[str, Iterable], val: Creder): - """ Puts val at key made from keys. Does not overwrite - - Parameters: - keys (tuple): of key strs to be combined in order to form key - val (Creder): instance - - Returns: - bool: True If successful, False otherwise, such as key - already in database. - """ - return (self.db.putVal(db=self.sdb, - key=self._tokey(keys), - val=val.raw)) - - def pin(self, keys: Union[str, Iterable], val: Creder): - """ Pins (sets) val at key made from keys. Overwrites. - - Parameters: - keys (tuple): of key strs to be combined in order to form key - val (Creder): instance - - Returns: - bool: True If successful. False otherwise. - """ - return (self.db.setVal(db=self.sdb, - key=self._tokey(keys), - val=val.raw)) - - def get(self, keys: Union[str, Iterable]): - """ Gets Credentialer at keys - - Parameters: - keys (tuple): of key strs to be combined in order to form key - - Returns: - Creder: instance at keys - None: if no entry at keys - - Usage: - Use walrus operator to catch and raise missing entry - if (creder := mydb.get(keys)) is None: - raise ExceptionHere - use creder here - - """ - val = self.db.getVal(db=self.sdb, key=self._tokey(keys)) - return Creder(raw=bytes(val)) if val is not None else None - - def rem(self, keys: Union[str, Iterable]): - """ Removes entry at keys - - Parameters: - keys (tuple): of key strs to be combined in order to form key - - Returns: - bool: True if key exists so delete successful. False otherwise - """ - return self.db.delVal(db=self.sdb, key=self._tokey(keys)) - - def getItemIter(self, keys: Union[str, Iterable] = b""): - """ Return iterator over the all the items in subdb - - Parameters: - keys (tuple): of key strs to be combined in order to form key - - Returns: - iterator: of tuples of keys tuple and val coring.Serder for - each entry in db - - """ - for key, val in self.db.getTopItemIter(db=self.sdb, key=self._tokey(keys)): - yield self._tokeys(key), Creder(raw=bytes(val)) + return serdering.SerderACDC(sad=vc) diff --git a/src/keri/vc/walleting.py b/src/keri/vc/walleting.py index 46c0bf55e..f4ea25b46 100644 --- a/src/keri/vc/walleting.py +++ b/src/keri/vc/walleting.py @@ -45,8 +45,8 @@ def getCredentials(self, schema=None): creds = [] for saider in saiders: - creder, sadsigers, sadcigars = self.reger.cloneCred(said=saider.qb64) - creds.append((creder, sadsigers, sadcigars)) + creder, prefixer, seqner, saider = self.reger.cloneCred(said=saider.qb64) + creds.append((creder, prefixer, seqner, saider)) return creds @@ -68,7 +68,7 @@ def __init__(self, hby, verifier, **kwa): self.verifier = verifier doers = [doing.doify(self.escrowDo)] - self.witq = agenting.WitnessInquisitor(hby=hby, klas=agenting.TCPWitnesser) + self.witq = agenting.WitnessInquisitor(hby=hby, klas=agenting.TCPMessenger) super(WalletDoer, self).__init__(doers=doers, **kwa) diff --git a/src/keri/vdr/credentialing.py b/src/keri/vdr/credentialing.py index 28e4e0a53..b9b8fc223 100644 --- a/src/keri/vdr/credentialing.py +++ b/src/keri/vdr/credentialing.py @@ -5,20 +5,19 @@ VC issuer support """ -from ordered_set import OrderedSet as oset - from hio.base import doing from hio.help import decking from keri.vdr import viring from .. import kering, help -from ..app import agenting, signing, forwarding -from ..core import parsing, coring, scheming -from ..core.coring import Seqner, MtrDex, Serder +from ..app import agenting +from ..app.habbing import GroupHab +from ..core import parsing, coring, scheming, serdering +from ..core.coring import Seqner, MtrDex from ..core.eventing import SealEvent, TraitDex from ..db import dbing from ..db.dbing import snKey, dgKey -from ..vc import proving, protocoling +from ..vc import proving from ..vdr import eventing from ..vdr.viring import Reger @@ -86,6 +85,19 @@ def makeRegistry(self, name, prefix, **kwa): return reg + def makeSignifyRegistry(self, name, prefix, regser): + hab = self.hby.habs[prefix] + if hab is None: + raise kering.ConfigurationError(f"Unknown prefix {prefix} for creating Registry {name}") + + reg = SignifyRegistry(hab=hab, name=name, reger=self.reger, tvy=self.tvy, psr=self.psr, cues=self.cues) + + reg.make(regser=regser) + + self.regs[reg.regk] = reg + + return reg + def registryByName(self, name): if regrec := self.reger.regs.get(name): return self.regs[regrec.registryKey] if regrec.registryKey in self.regs else None @@ -137,7 +149,7 @@ def do(self, tymth, tock=0.0, **opts): yield self.tock -class Registry: +class BaseRegistry: """ Issuer provides encapsulation of creating a Verifiable Credential Registry with issuance and revocation of VCs against that registry. @@ -167,51 +179,11 @@ def __init__(self, hab, reger, tvy, psr, name="test", regk=None, cues=None): self.cues = cues if cues is not None else decking.Deck() self.regk = regk self.regd = None + self.vcp = None self.cnfg = [] self.inited = False - def make(self, *, nonce=None, noBackers=True, baks=None, toad=None, estOnly=False): - """ Delayed initialization of Issuer. - - Actual initialization of Issuer from properties or loaded from .reger. Should - only be called after .hab is initied. - - Parameters: - nonce (str) qb64 random seed for credential registries - noBackers (boolean): True to allow specification of TEL specific backers - baks (list): initial list of backer prefixes qb64 for VCs in the Registry - toad (str): hex of witness threshold - estOnly (boolean): True for forcing rotation events for every TEL event. - - """ - baks = baks if baks is not None else [] - - self.cnfg = [TraitDex.NoBackers] if noBackers else [] - if estOnly: - self.cnfg.append(TraitDex.EstOnly) - - pre = self.hab.pre - - regser = eventing.incept(pre, - baks=baks, - toad=toad, - nonce=nonce, - cnfg=self.cnfg, - code=MtrDex.Blake3_256) - self.regk = regser.pre - self.regd = regser.said - self.registries.add(self.regk) - self.reger.regs.put(keys=self.name, - val=viring.RegistryRecord(registryKey=self.regk, prefix=pre)) - - try: - self.tvy.processEvent(serder=regser) - except kering.MissingAnchorError: - logger.info("Credential registry missing anchor for inception = {}".format(regser.ked)) - - self.inited = True - @property def tevers(self): """ tevers property @@ -249,6 +221,86 @@ def regser(self): def registries(self): return self.reger.registries + def processEvent(self, serder): + """ Process registry events + + Parameters: + serder (Serder): Registry TEL event to process + + """ + + try: + self.tvy.processEvent(serder=serder) + except kering.MissingAnchorError: + logger.info("Credential registry missing anchor for inception = {}".format(serder.ked)) + + def anchorMsg(self, pre, regd, seqner, saider): + """ Create key event with seal to serder anchored as data. + + Performs a rotation or interaction event for single sig or multiple sig identifier + to anchor the provide registry event. Inserts outbound cues for external processing + of resulting events or multisig handling. + + Parameters: + pre (str): registry event identifier + regd (str): registry event SAID + seqner (Seqner): sequence number of anchoring event + saider (Saider): SAID of the anchoring event + + """ + + key = dgKey(pre, regd) + sealet = seqner.qb64b + saider.qb64b + self.reger.putAnc(key, sealet) + + +class Registry(BaseRegistry): + """ + + """ + + def make(self, *, nonce=None, noBackers=True, baks=None, toad=None, estOnly=False, vcp=None): + """ Delayed initialization of Issuer. + + Actual initialization of Issuer from properties or loaded from .reger. Should + only be called after .hab is initied. + + Parameters: + nonce (str) qb64 random seed for credential registries + noBackers (boolean): True to allow specification of TEL specific backers + baks (list): initial list of backer prefixes qb64 for VCs in the Registry + toad (str): hex of witness threshold + estOnly (boolean): True for forcing rotation events for every TEL event. + vcp (Serder): optional vcp event serder if configured outside the Registry + + """ + pre = self.hab.pre + + if vcp is None: + baks = baks if baks is not None else [] + + self.cnfg = [TraitDex.NoBackers] if noBackers else [] + if estOnly: + self.cnfg.append(TraitDex.EstOnly) + + self.vcp = eventing.incept(pre, + baks=baks, + toad=toad, + nonce=nonce, + cnfg=self.cnfg, + code=MtrDex.Blake3_256) + else: + self.vcp = vcp + + self.regk = self.vcp.pre + self.regd = self.vcp.said + self.registries.add(self.regk) + self.reger.regs.put(keys=self.name, + val=viring.RegistryRecord(registryKey=self.regk, prefix=pre)) + + self.processEvent(serder=self.vcp) + self.inited = True + def rotate(self, toad=None, cuts=None, adds=None): """ Rotate backer list for registry @@ -273,11 +325,7 @@ def rotate(self, toad=None, cuts=None, adds=None): adds=adds, cuts=cuts) - try: - self.tvy.processEvent(serder=serder) - except kering.MissingAnchorError: - logger.info("Credential registry missing anchor for inception = {}".format(serder.ked)) - + self.processEvent(serder=serder) return serder def issue(self, said, dt=None): @@ -295,14 +343,13 @@ def issue(self, said, dt=None): if self.noBackers: serder = eventing.issue(vcdig=said, regk=self.regk, dt=dt) else: - serder = eventing.backerIssue(vcdig=said, regk=self.regk, regsn=self.regi, regd=self.regser.saider.qb64, + serder = eventing.backerIssue(vcdig=said, + regk=self.regk, + regsn=self.regi, + regd=self.regser.said, dt=dt) - try: - self.tvy.processEvent(serder=serder) - except kering.MissingAnchorError: - logger.info("Credential registry missing anchor for inception = {}".format(serder.ked)) - + self.processEvent(serder=serder) return serder def revoke(self, said, dt=None): @@ -324,39 +371,113 @@ def revoke(self, said, dt=None): raise kering.ValidationError("Invalid revoke of {} that has not been issued " "pre={}.".format(vci, self.regk)) ievt = self.reger.getTvt(dgKey(pre=vci, dig=vcser)) - iserder = Serder(raw=bytes(ievt)) + iserder = serdering.SerderKERI(raw=bytes(ievt)) #Serder(raw=bytes(ievt)) if self.noBackers: serder = eventing.revoke(vcdig=vci, regk=self.regk, dig=iserder.said, dt=dt) else: - serder = eventing.backerRevoke(vcdig=vci, regk=self.regk, regsn=self.regi, regd=self.regser.saider.qb64, + serder = eventing.backerRevoke(vcdig=vci, + regk=self.regk, + regsn=self.regi, + regd=self.regser.said, dig=iserder.said, dt=dt) - try: - self.tvy.processEvent(serder=serder) - except kering.MissingAnchorError: - logger.info("Credential registry missing anchor for inception = {}".format(serder.ked)) + self.processEvent(serder=serder) + return serder + + +class SignifyRegistry(BaseRegistry): + + def make(self, *, regser): + """ Delayed initialization of Issuer. + + Actual initialization of Issuer from properties or loaded from .reger. Should + only be called after .hab is initied. + + Parameters: + regser (Serder): Regsitry inception event + + """ + pre = self.hab.pre + self.regk = regser.pre + self.regd = regser.said + self.registries.add(self.regk) + self.reger.regs.put(keys=self.name, + val=viring.RegistryRecord(registryKey=self.regk, prefix=pre)) + + self.processEvent(serder=regser) + self.inited = True + + def rotate(self, serder): + """ Rotate backer list for registry + + Parameters: + serder (Serder): Regsitry inception event + + Returns: + boolean: True if rotation is successful + + """ + if self.noBackers: + raise ValueError("Attempt to rotate registry {} that does not support backers".format(self.regk)) + + if serder.ked['s'] != self.regi + 1: + raise ValueError(f"Invalid sequence number {serder.ked['s']}") + + self.processEvent(serder=serder) return serder - def anchorMsg(self, pre, regd, seqner, saider): - """ Create key event with seal to serder anchored as data. + def issue(self, said, dt=None): + """ Create and process an iss or bis message event - Performs a rotation or interaction event for single sig or multiple sig identifier - to anchor the provide registry event. Inserts outbound cues for external processing - of resulting events or multisig handling. + Parameters: + said (str): qb64 SAID of credential to issue + dt (str): iso8601 formatted date time string of issuance + + Returns: + boolean: True if issuance is successful + + """ + + if self.noBackers: + serder = eventing.issue(vcdig=said, regk=self.regk, dt=dt) + else: + serder = eventing.backerIssue(vcdig=said, regk=self.regk, regsn=self.regi, regd=self.regser.said, + dt=dt) + + self.processEvent(serder=serder) + return serder + + def revoke(self, said, dt=None): + """ Perform revocation of credential + + Create and process rev or brv message event Parameters: - pre (str): registry event identifier - regd (str): registry event SAID - seqner (Seqner): sequence number of anchoring event - saider (Saider): SAID of the anchoring event + said (str): qb64 SAID of the credential to revoke + dt (str): iso8601 formatted date time string of revocation + + Returns: + boolean: True if revocation is successful. """ + vci = said + vcser = self.reger.getTel(snKey(pre=vci, sn=0)) + if vcser is None: + raise kering.ValidationError("Invalid revoke of {} that has not been issued " + "pre={}.".format(vci, self.regk)) + ievt = self.reger.getTvt(dgKey(pre=vci, dig=vcser)) + iserder = serdering.serderACDC(raw=bytes(ievt)) # Serder(raw=bytes(ievt)) - key = dgKey(pre, regd) - sealet = seqner.qb64b + saider.qb64b - self.reger.putAnc(key, sealet) + if self.noBackers: + serder = eventing.revoke(vcdig=vci, regk=self.regk, dig=iserder.said, dt=dt) + else: + serder = eventing.backerRevoke(vcdig=vci, regk=self.regk, regsn=self.regi, regd=self.regser.said, + dig=iserder.said, dt=dt) + + self.processEvent(serder=serder) + return serder class Registrar(doing.DoDoer): @@ -372,146 +493,107 @@ def __init__(self, hby, rgy, counselor): super(Registrar, self).__init__(doers=doers) - def incept(self, name, pre, conf=None, smids=None, rmids=None): + def incept(self, iserder, anc): """ Parameters: - name (str): human readable name for the registry - pre (str): qb64 identifier prefix of issuing identifier in control of this registry - conf (dict): configuration information for the registry (noBackers, estOnly) - smids (list): group signing member ids qb64 in the anchoring event - need to contribute current signing key - rmids (list): group rotating member ids in the anchoring event - need to contribute digest of next rotating key + iserder (SerderKERI): Serder object of TEL iss event + anc (SerderKERI): Serder object of anchoring event Returns: Registry: created registry """ - conf = conf if conf is not None else {} # default config if none specified - estOnly = "estOnly" in conf and conf["estOnly"] - hab = self.hby.habs[pre] - - registry = self.rgy.makeRegistry(name=name, prefix=pre, **conf) - + registry = self.rgy.regs[iserder.pre] + hab = registry.hab rseq = coring.Seqner(sn=0) - rseal = SealEvent(registry.regk, "0", registry.regd) - rseal = dict(i=rseal.i, s=rseal.s, d=rseal.d) - if not hab.group: - if estOnly: - hab.rotate(data=[rseal]) - else: - hab.interact(data=[rseal]) + if not isinstance(hab, GroupHab): # not a multisig group seqner = coring.Seqner(sn=hab.kever.sner.num) - saider = hab.kever.serder.saider - registry.anchorMsg(pre=registry.regk, regd=registry.regd, seqner=seqner, saider=saider) + saider = coring.Saider(qb64=hab.kever.serder.said) + registry.anchorMsg(pre=iserder.pre, + regd=iserder.said, + seqner=seqner, + saider=saider) print("Waiting for TEL event witness receipts") - self.witDoer.msgs.append(dict(pre=pre, sn=seqner.sn)) + self.witDoer.msgs.append(dict(pre=anc.pre, sn=seqner.sn)) self.rgy.reger.tpwe.add(keys=(registry.regk, rseq.qb64), val=(hab.kever.prefixer, seqner, saider)) else: - s, r = hab.members() - smids = smids if smids is not None else s - rmids = rmids if rmids is not None else r - prefixer, seqner, saider = self.multisigIxn(hab, rseal) - self.counselor.start(prefixer=prefixer, seqner=seqner, saider=saider, - mid=hab.mhab.pre, smids=smids, rmids=rmids) + sn = anc.sn + said = anc.said - print("Waiting for TEL registry vcp event mulisig anchoring event") - self.rgy.reger.tmse.add(keys=(registry.regk, rseq.qb64, registry.regd), val=(prefixer, seqner, saider)) + prefixer = coring.Prefixer(qb64=hab.pre) + seqner = coring.Seqner(sn=sn) + saider = coring.Saider(qb64=said) - return registry + self.counselor.start(prefixer=prefixer, seqner=seqner, saider=saider, ghab=hab) + print("Waiting for TEL registry vcp event mulisig anchoring event") + self.rgy.reger.tmse.add(keys=(registry.regk, rseq.qb64, registry.regd), val=(prefixer, seqner, saider)) - def issue(self, regk, said, dt=None, smids=None, rmids=None): + def issue(self, creder, iserder, anc): """ Create and process the credential issuance TEL events on the given registry Parameters: - regk (str): qb64 identifier prefix of the credential registry - said (str): qb64 SAID of the credential to issue - dt (str): iso8601 formatted date string of issuance date - smids (list): group signing member ids qb64 in the anchoring event - need to contribute current signing key - rmids (list): group rotating member ids qb64 in the anchoring event - need to contribute digest of next rotating key + creder (SerderACDC): credential to issue + iserder (SerderKERI): Serder object of TEL iss event + anc (SerderKERI): Serder object of anchoring event + """ + regk = creder.regi registry = self.rgy.regs[regk] hab = registry.hab - iserder = registry.issue(said=said, dt=dt) - vcid = iserder.ked["i"] rseq = coring.Seqner(snh=iserder.ked["s"]) - rseal = SealEvent(vcid, rseq.snh, iserder.said) - rseal = dict(i=rseal.i, s=rseal.s, d=rseal.d) - - if not hab.group: # not a multisig group - if registry.estOnly: - hab.rotate(data=[rseal]) - else: - hab.interact(data=[rseal]) + if not isinstance(hab, GroupHab): # not a multisig group seqner = coring.Seqner(sn=hab.kever.sner.num) - saider = hab.kever.serder.saider + saider = coring.Saider(qb64=hab.kever.serder.said) registry.anchorMsg(pre=vcid, regd=iserder.said, seqner=seqner, saider=saider) print("Waiting for TEL event witness receipts") self.witDoer.msgs.append(dict(pre=hab.pre, sn=seqner.sn)) self.rgy.reger.tpwe.add(keys=(vcid, rseq.qb64), val=(hab.kever.prefixer, seqner, saider)) - return vcid, rseq.sn else: # multisig group hab - s, r = hab.members() - smids = smids if smids is not None else s - rmids = rmids if rmids is not None else r - prefixer, seqner, saider = self.multisigIxn(hab, rseal) - self.counselor.start(prefixer=prefixer, seqner=seqner, saider=saider, - mid=hab.mhab.pre, smids=smids, rmids=rmids) + sn = anc.sn + said = anc.said + + prefixer = coring.Prefixer(qb64=hab.pre) + seqner = coring.Seqner(sn=sn) + saider = coring.Saider(qb64=said) + + self.counselor.start(prefixer=prefixer, seqner=seqner, saider=saider, ghab=hab) print(f"Waiting for TEL iss event mulisig anchoring event {seqner.sn}") self.rgy.reger.tmse.add(keys=(vcid, rseq.qb64, iserder.said), val=(prefixer, seqner, saider)) - return vcid, rseq.sn - def revoke(self, regk, said, dt=None, smids=None, rmids=None): + def revoke(self, creder, rserder, anc): """ Create and process the credential revocation TEL events on the given registry Parameters: - regk (str): qb64 identifier prefix of the credential registry - said (str): qb64 SAID of the credential to issue - dt (str): iso8601 formatted date string of issuance date - smids (list): group signing member ids (multisig) in the anchoring event - need to contribute digest of current signing key - rmids (list | None): group rotating member ids (multisig) in the anchoring event - need to contribute digest of next rotating key + creder (Creder): credential to issue + rserder (Serder): Serder object of TEL rev event + anc (Serder): Serder object of anchoring event """ + + regk = creder.regi registry = self.rgy.regs[regk] hab = registry.hab - state = registry.tever.vcState(vci=said) - if state is None or state.ked["et"] not in (coring.Ilks.iss, coring.Ilks.rev): - raise kering.ValidationError(f"credential {said} not is correct state for revocation") - - rserder = registry.revoke(said=said, dt=dt) - vcid = rserder.ked["i"] rseq = coring.Seqner(snh=rserder.ked["s"]) - rseal = SealEvent(vcid, rseq.snh, rserder.said) - rseal = dict(i=rseal.i, s=rseal.s, d=rseal.d) - - if not hab.group: - if registry.estOnly: - hab.rotate(data=[rseal]) - else: - hab.interact(data=[rseal]) + if not isinstance(hab, GroupHab): # not a multisig group seqner = coring.Seqner(sn=hab.kever.sner.num) - saider = hab.kever.serder.saider + saider = coring.Saider(qb64=hab.kever.serder.said) registry.anchorMsg(pre=vcid, regd=rserder.said, seqner=seqner, saider=saider) print("Waiting for TEL event witness receipts") @@ -520,37 +602,47 @@ def revoke(self, regk, said, dt=None, smids=None, rmids=None): self.rgy.reger.tpwe.add(keys=(vcid, rseq.qb64), val=(hab.kever.prefixer, seqner, saider)) return vcid, rseq.sn else: - s, r = hab.members() - smids = smids if smids is not None else s - rmids = rmids if rmids is not None else r - prefixer, seqner, saider = self.multisigIxn(hab, rseal) - self.counselor.start(prefixer=prefixer, seqner=seqner, saider=saider, - mid=hab.mhab.pre, smids=smids, rmids=rmids) + sn = anc.sn + said = anc.said + + prefixer = coring.Prefixer(qb64=hab.pre) + seqner = coring.Seqner(sn=sn) + saider = coring.Saider(qb64=said) + + self.counselor.start(prefixer=prefixer, seqner=seqner, saider=saider, ghab=hab) print(f"Waiting for TEL rev event mulisig anchoring event {seqner.sn}") self.rgy.reger.tmse.add(keys=(vcid, rseq.qb64, rserder.said), val=(prefixer, seqner, saider)) return vcid, rseq.sn - @staticmethod def multisigIxn(hab, rseal): ixn = hab.interact(data=[rseal]) - gserder = coring.Serder(raw=ixn) + serder = serdering.SerderKERI(raw=bytes(ixn)) - sn = gserder.sn - said = gserder.said + sn = serder.sn + said = serder.said prefixer = coring.Prefixer(qb64=hab.pre) seqner = coring.Seqner(sn=sn) saider = coring.Saider(qb64=said) - return prefixer, seqner, saider + return ixn, prefixer, seqner, saider def complete(self, pre, sn=0): + """ Determine if registry event (inception, issuance, revocation, etc.) is finished validation + + Parameters: + pre (str): qb64 identifier of registry event + sn (int): integer sequence number of regsitry event + + Returns: + bool: True means event has completed and is commited to database + """ + seqner = coring.Seqner(sn=sn) said = self.rgy.reger.ctel.get(keys=(pre, seqner.qb64)) - return said is not None - + return said is not None and self.witPub.sent(said=pre) def escrowDo(self, tymth, tock=1.0): """ Process escrows of group multisig identifiers waiting to be compeleted. @@ -578,7 +670,6 @@ def escrowDo(self, tymth, tock=1.0): self.processEscrows() yield 0.5 - def processEscrows(self): """ Process credential registry anchors: @@ -588,7 +679,6 @@ def processEscrows(self): self.processMultisigEscrow() self.processDiseminationEscrow() - def processWitnessEscrow(self): """ Process escrow of group multisig events that do not have a full compliment of receipts @@ -661,7 +751,7 @@ def processDiseminationEscrow(self): print(f"Sending TEL events to witnesses") # Fire and forget the TEL event to the witnesses. Consumers will have to query # to determine when the Witnesses have received the TEL events. - self.witPub.msgs.append(dict(pre=prefixer.qb64, msg=tevt)) + self.witPub.msgs.append(dict(pre=prefixer.qb64, said=regk, msg=tevt)) self.rgy.reger.ctel.put(keys=(regk, rseq.qb64), val=saider) # idempotent @@ -672,8 +762,7 @@ def __init__(self, hby, rgy, registrar, verifier): self.rgy = rgy self.registrar = registrar self.verifier = verifier - self.postman = forwarding.Postman(hby=hby) - doers = [self.postman, doing.doify(self.escrowDo)] + doers = [doing.doify(self.escrowDo)] super(Credentialer, self).__init__(doers=doers) @@ -723,7 +812,7 @@ def validate(self, creder): bool: true if credential is valid against a known schema """ - schema = creder.crd['s'] + schema = creder.sad['s'] scraw = self.verifier.resolver.resolve(schema) if not scraw: raise kering.ConfigurationError("Credential schema {} not found. It must be loaded with data oobi before " @@ -737,56 +826,32 @@ def validate(self, creder): return True - def issue(self, creder, smids=None, rmids=None): + def issue(self, creder, serder): """ Issue the credential creder and handle witness propagation and communication Args: creder (Creder): Credential object to issue - smids (list[str] | None): optional group signing member ids for multisig - need to contributed current signing key - rmids (list[str] | None): optional group rotating member ids for multisig + serder (Serder): KEL or TEL anchoring event need to contribute digest of next rotating key """ - regk = creder.crd["ri"] - registry = self.rgy.regs[regk] - hab = registry.hab - if hab.group: - s, r = hab.members() - smids = smids if smids is not None else s - rmids = rmids if rmids is not None else r - - dt = creder.subject["dt"] if "dt" in creder.subject else None - - vcid, seq = self.registrar.issue(regk=registry.regk, said=creder.said, - dt=dt, smids=smids, rmids=rmids) - - rseq = coring.Seqner(sn=seq) - if hab.group: - craw = signing.ratify(hab=hab, serder=creder) - atc = bytearray(craw[creder.size:]) - others = list(oset(smids + (rmids or []))) - others.remove(hab.mhab.pre) + # escrow waiting for other signatures + prefixer = coring.Prefixer(qb64=serder.pre) + seqner = coring.Seqner(sn=serder.sn) - print(f"Sending signed credential to {others} other participants") - for recpt in others: - self.postman.send(src=hab.mhab.pre, dest=recpt, topic="multisig", serder=creder, attachment=atc) - - # escrow waiting for other signatures - self.rgy.reger.cmse.put(keys=(creder.said, rseq.qb64), val=creder) - else: - craw = signing.ratify(hab=hab, serder=creder) - - # escrow waiting for registry anchors to be complete - self.rgy.reger.crie.put(keys=(creder.said, rseq.qb64), val=creder) - - parsing.Parser().parse(ims=craw, vry=self.verifier) + self.rgy.reger.cmse.put(keys=(creder.said, seqner.qb64), val=creder) + try: + self.verifier.processCredential(creder=creder, prefixer=prefixer, seqner=seqner, + saider=coring.Saider(qb64=serder.said)) + except kering.MissingRegistryError: + pass def processCredentialMissingSigEscrow(self): for (said, snq), creder in self.rgy.reger.cmse.getItemIter(): rseq = coring.Seqner(qb64=snq) + if not self.registrar.complete(pre=said, sn=rseq.sn): + continue - # Look for the saved saider saider = self.rgy.reger.saved.get(keys=said) if saider is None: continue @@ -794,131 +859,11 @@ def processCredentialMissingSigEscrow(self): # Remove from this escrow self.rgy.reger.cmse.rem(keys=(said, snq)) - hab = self.hby.habs[creder.issuer] - kever = hab.kever # place in escrow to diseminate to other if witnesser and if there is an issuee - self.rgy.reger.crie.put(keys=(creder.said, rseq.qb64), val=creder) - - - def processCredentialIssuedEscrow(self): - for (said, snq), creder in self.rgy.reger.crie.getItemIter(): - rseq = coring.Seqner(qb64=snq) - - if not self.registrar.complete(pre=said, sn=rseq.sn): - continue - - saider = self.rgy.reger.saved.get(keys=said) - if saider is None: - continue - - issr = creder.issuer - regk = creder.status - - print("Credential issuance complete, sending to recipient") - if "i" in creder.subject: - recp = creder.subject["i"] - - hab = self.hby.habs[issr] - if hab.group: - sender = hab.mhab.pre - else: - sender = issr - - ikever = self.hby.db.kevers[issr] - for msg in self.hby.db.cloneDelegation(ikever): - serder = coring.Serder(raw=msg) - atc = msg[serder.size:] - self.postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) - - for msg in self.hby.db.clonePreIter(pre=issr): - serder = coring.Serder(raw=msg) - atc = msg[serder.size:] - self.postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) - - if regk is not None: - for msg in self.verifier.reger.clonePreIter(pre=regk): - serder = coring.Serder(raw=msg) - atc = msg[serder.size:] - self.postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) - - for msg in self.verifier.reger.clonePreIter(pre=creder.said): - serder = coring.Serder(raw=msg) - atc = msg[serder.size:] - self.postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) - - sources = self.verifier.reger.sources(self.hby.db, creder) - for source, atc in sources: - regk = source.status - vci = source.said - - issr = source.crd["i"] - ikever = self.hby.db.kevers[issr] - for msg in self.hby.db.cloneDelegation(ikever): - serder = coring.Serder(raw=msg) - atc = msg[serder.size:] - self.postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) - - for msg in self.hby.db.clonePreIter(pre=issr): - serder = coring.Serder(raw=msg) - atc = msg[serder.size:] - self.postman.send(src=sender, dest=recp, topic="credential", serder=serder, - attachment=atc) - - for msg in self.verifier.reger.clonePreIter(pre=regk): - serder = coring.Serder(raw=msg) - atc = msg[serder.size:] - self.postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) - - for msg in self.verifier.reger.clonePreIter(pre=vci): - serder = coring.Serder(raw=msg) - atc = msg[serder.size:] - self.postman.send(src=sender, dest=recp, topic="credential", serder=serder, - attachment=atc) - - serder, sadsigs, sadcigs = self.rgy.reger.cloneCred(source.said) - atc = signing.provision(serder=source, sadcigars=sadcigs, sadsigers=sadsigs) - del atc[:serder.size] - self.postman.send(src=sender, dest=recp, topic="credential", serder=source, attachment=atc) - - serder, sadsigs, sadcigs = self.rgy.reger.cloneCred(creder.said) - atc = signing.provision(serder=creder, sadcigars=sadcigs, sadsigers=sadsigs) - del atc[:serder.size] - self.postman.send(src=sender, dest=recp, topic="credential", serder=creder, attachment=atc) - - exn, atc = protocoling.credentialIssueExn(hab=hab, issuer=issr, schema=creder.schema, said=creder.said) - self.postman.send(src=sender, dest=recp, topic="credential", serder=exn, attachment=atc) - - # Escrow until postman has successfully sent the notification - self.rgy.reger.crse.put(keys=(exn.said,), val=creder) - else: - # Credential complete, mark it in the database - self.rgy.reger.ccrd.put(keys=(said,), val=creder) - - self.rgy.reger.crie.rem(keys=(said, snq)) - - - def processCredentialSentEscrow(self): - """ - Process Postman cues to ensure that the last message (exn notification) has - been sent before declaring the credential complete - - """ - for (said,), creder in self.rgy.reger.crse.getItemIter(): - found = False - while self.postman.cues: - cue = self.postman.cues.popleft() - if cue["said"] == said: - found = True - break - - if found: - self.rgy.reger.crse.rem(keys=(said,)) - self.rgy.reger.ccrd.put(keys=(creder.said,), val=creder) - + self.rgy.reger.ccrd.put(keys=(said,), val=creder) def complete(self, said): - return self.rgy.reger.ccrd.get(keys=(said,)) is not None and len(self.postman.evts) == 0 - + return self.rgy.reger.ccrd.get(keys=(said,)) is not None def escrowDo(self, tymth, tock=1.0): """ Process escrows of group multisig identifiers waiting to be completed. @@ -946,15 +891,12 @@ def escrowDo(self, tymth, tock=1.0): self.processEscrows() yield 0.5 - def processEscrows(self): """ Process credential registry anchors: """ - self.processCredentialIssuedEscrow() self.processCredentialMissingSigEscrow() - self.processCredentialSentEscrow() def sendCredential(hby, hab, reger, postman, creder, recp): @@ -964,83 +906,104 @@ def sendCredential(hby, hab, reger, postman, creder, recp): hby: hab: reger: - postman: + postman (StreamPoster): poster to stream credential with creder: recp: Returns: """ - if hab.group: + if isinstance(hab, GroupHab): sender = hab.mhab.pre else: sender = hab.pre - sendArtifacts(hby, reger, postman, creder, sender, recp) + sendArtifacts(hby, reger, postman, creder, recp) sources = reger.sources(hby.db, creder) for source, atc in sources: - sendArtifacts(hby, reger, postman, source, sender, recp) + sendArtifacts(hby, reger, postman, source, recp) + postman.send(serder=source, attachment=atc) - serder, sadsigs, sadcigs = reger.cloneCred(source.said) - atc = signing.provision(serder=source, sadcigars=sadcigs, sadsigers=sadsigs) - del atc[:serder.size] - postman.send(src=sender, dest=recp, topic="credential", serder=source, attachment=atc) + serder, prefixer, seqner, saider = reger.cloneCred(creder.said) + atc = bytearray(coring.Counter(coring.CtrDex.SealSourceTriples, count=1).qb64b) + atc.extend(prefixer.qb64b) + atc.extend(seqner.qb64b) + atc.extend(saider.qb64b) + postman.send(serder=creder, attachment=atc) - serder, sadsigs, sadcigs = reger.cloneCred(creder.said) - atc = signing.provision(serder=creder, sadcigars=sadcigs, sadsigers=sadsigs) - del atc[:serder.size] - postman.send(src=sender, dest=recp, topic="credential", serder=creder, attachment=atc) - -def sendArtifacts(hby, reger, postman, creder, sender, recp): +def sendArtifacts(hby, reger, postman, creder, recp): """ Stream credential artifacts to recipient using postman Parameters: hby: reger: - postman: + postman (StreamPoster): poster to stream credential with creder: - sender: recp: Returns: """ issr = creder.issuer - isse = creder.subject["i"] if "i" in creder.subject else None - regk = creder.status + isse = creder.attrib["i"] if "i" in creder.attrib else None + regk = creder.regi ikever = hby.db.kevers[issr] for msg in hby.db.cloneDelegation(ikever): - serder = coring.Serder(raw=msg) + serder = serdering.SerderKERI(raw=msg) atc = msg[serder.size:] - postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) + postman.send(serder=serder, attachment=atc) for msg in hby.db.clonePreIter(pre=issr): - serder = coring.Serder(raw=msg) + serder = serdering.SerderKERI(raw=msg) atc = msg[serder.size:] - postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) + postman.send(serder=serder, attachment=atc) if isse != recp: ikever = hby.db.kevers[isse] for msg in hby.db.cloneDelegation(ikever): - serder = coring.Serder(raw=msg) + serder = serdering.SerderKERI(raw=msg) atc = msg[serder.size:] - postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) + postman.send(serder=serder, attachment=atc) for msg in hby.db.clonePreIter(pre=isse): - serder = coring.Serder(raw=msg) + serder = serdering.SerderKERI(raw=msg) atc = msg[serder.size:] - postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) + postman.send(serder=serder, attachment=atc) if regk is not None: for msg in reger.clonePreIter(pre=regk): - serder = coring.Serder(raw=msg) + serder = serdering.SerderKERI(raw=msg) atc = msg[serder.size:] - postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) + postman.send(serder=serder, attachment=atc) for msg in reger.clonePreIter(pre=creder.said): - serder = coring.Serder(raw=msg) + serder = serdering.SerderKERI(raw=msg) # coring.Serder(raw=msg) + atc = msg[serder.size:] + postman.send(serder=serder, attachment=atc) + + +def sendRegistry(hby, reger, postman, creder, sender, recp): + issr = creder.issuer + regk = creder.regi + + if regk is None: + return + + ikever = hby.db.kevers[issr] + for msg in hby.db.cloneDelegation(ikever): + serder = serdering.SerderKERI(raw=msg) # coring.Serder(raw=msg) + atc = msg[serder.size:] + postman.send(serder=serder, attachment=atc) + + for msg in hby.db.clonePreIter(pre=issr): + serder = serdering.SerderKERI(raw=msg) # coring.Serder(raw=msg) + atc = msg[serder.size:] + postman.send(serder=serder, attachment=atc) + + for msg in reger.clonePreIter(pre=regk): + serder = serdering.SerderKERI(raw=msg) # coring.Serder(raw=msg) atc = msg[serder.size:] - postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) + postman.send(serder=serder, attachment=atc) diff --git a/src/keri/vdr/eventing.py b/src/keri/vdr/eventing.py index 5f45dbec8..063ebaad7 100644 --- a/src/keri/vdr/eventing.py +++ b/src/keri/vdr/eventing.py @@ -8,16 +8,17 @@ import json import logging +from dataclasses import asdict from math import ceil from ordered_set import OrderedSet as oset from hio.help import decking from keri import kering -from keri.core import coring from .. import core from .. import help -from ..core.coring import (MtrDex, Serder, Serials, versify, Prefixer, +from ..core import serdering, coring +from ..core.coring import (MtrDex, Serials, versify, Prefixer, Ilks, Seqner, Verfer) from ..core.eventing import SealEvent, ample, TraitDex, verifySigs, validateSN from ..db import basing, dbing @@ -25,22 +26,12 @@ from ..help import helping from ..kering import (MissingWitnessSignatureError, Version, MissingAnchorError, ValidationError, OutOfOrderError, LikelyDuplicitousError) -from ..vdr.viring import Reger +from ..kering import (VCP_LABELS, VRT_LABELS, ISS_LABELS, BIS_LABELS, REV_LABELS, + BRV_LABELS, TSN_LABELS, CRED_TSN_LABELS) +from ..vdr import viring logger = help.ogler.getLogger() -VCP_LABELS = ["v", "i", "s", "t", "bt", "b", "c"] -VRT_LABELS = ["v", "i", "s", "t", "p", "bt", "b", "ba", "br"] - -ISS_LABELS = ["v", "i", "s", "t", "ri", "dt"] -BIS_LABELS = ["v", "i", "s", "t", "ra", "dt"] - -REV_LABELS = ["v", "i", "s", "t", "p", "dt"] -BRV_LABELS = ["v", "i", "s", "t", "ra", "p", "dt"] - -TSN_LABELS = ["v", "i", "s", "d", "ii", "a", "et", "bt", "b", "c", "br", "ba"] -CRED_TSN_LABELS = ["v", "i", "s", "d", "ri", "a", "ra"] - def incept( pre, @@ -50,7 +41,7 @@ def incept( cnfg=None, version=Version, kind=Serials.json, - code=None, + code=MtrDex.Blake3_256, ): """ Returns serder of credential registry inception (vcp) message event @@ -114,11 +105,9 @@ def incept( n=nonce # nonce of random bytes to make each registry unique ) - prefixer = Prefixer(ked=ked, code=code, allows=[MtrDex.Blake3_256]) # Derive AID from ked and code - ked["i"] = prefixer.qb64 # update pre element in ked with pre qb64 - ked["d"] = prefixer.qb64 - - return Serder(ked=ked) # return serialized ked + serder = serdering.SerderKERI(sad=ked, makify=True) + serder._verify() # raises error if fails verifications + return serder def rotate( @@ -216,9 +205,10 @@ def rotate( br=cuts, # list of qb64 may be empty ba=adds, # list of qb64 may be empty ) - _, ked = coring.Saider.saidify(sad=ked) - return Serder(ked=ked) # return serialized ked + serder = serdering.SerderKERI(sad=ked, makify=True) + serder._verify() # raises error if fails verifications + return serder def issue( @@ -257,8 +247,9 @@ def issue( if dt is not None: ked["dt"] = dt - _, ked = coring.Saider.saidify(sad=ked) - return Serder(ked=ked) # return serialized ked + serder = serdering.SerderKERI(sad=ked, makify=True) + serder._verify() # raises error if fails verifications + return serder def revoke( @@ -306,7 +297,9 @@ def revoke( _, ked = coring.Saider.saidify(sad=ked) - return Serder(ked=ked) # return serialized ked + serder = serdering.SerderKERI(sad=ked, makify=True) + serder._verify() # raises error if fails verifications + return serder def backerIssue( @@ -341,7 +334,7 @@ def backerIssue( isn = 0 ilk = Ilks.bis - seal = SealEvent(regk, regsn, regd) + seal = SealEvent(regk, "{:x}".format(regsn), regd) ked = dict(v=vs, # version string t=ilk, @@ -357,7 +350,9 @@ def backerIssue( if dt is not None: ked["dt"] = dt - return Serder(ked=ked) # return serialized ked + serder = serdering.SerderKERI(sad=ked, makify=True) + serder._verify() # raises error if fails verifications + return serder def backerRevoke( @@ -394,7 +389,7 @@ def backerRevoke( isn = 1 ilk = Ilks.brv - seal = SealEvent(regk, regsn, regd) + seal = SealEvent(regk, "{:x}".format(regsn), regd) ked = dict(v=vs, t=ilk, @@ -410,7 +405,9 @@ def backerRevoke( if dt is not None: ked["dt"] = dt - return Serder(ked=ked) # return serialized ked + serder = serdering.SerderKERI(sad=ked, makify=True) + serder._verify() # raises error if fails verifications + return serder def state(pre, @@ -418,19 +415,18 @@ def state(pre, sn, ri, eilk, - br, - ba, - a, dts=None, # default current datetime toad=None, # default based on wits wits=None, # default to [] cnfg=None, # default to [] version=Version, - kind=Serials.json, ): """ - Returns serder of key state notification message. - Utility function to automate creation of key state events. + Utility function to create a RegStateRecord of state notice of a given + Registry Event Log (REL) + + Returns: + rsr: (RegStateRecord): instance Parameters: pre (str): identifier prefix qb64 @@ -438,9 +434,7 @@ def state(pre, said (str): digest of latest event ri (str): qb64 AID of credential registry eilk (str): message type (ilk) oflatest event - br (list): witness remove list (cuts) - ba (list): witness add list (adds) - a (dict): key event anchor data + a (dict): key event anchored seal data dts (str) ISO 8601 formated current datetime toad (int): int of witness threshold wits (list): list of witness prefixes qb64 @@ -466,14 +460,12 @@ def state(pre, "n": "EZ-i0d8JZAoTNZH3ULvaU6JR2nmwyYAfSVPzhzS6b5CM", "bt": "1", "b": ["DnmwyYAfSVPzhzS6b5CMZ-i0d8JZAoTNZH3ULvaU6JR2"], - "br": ["Dd8JZAoTNZH3ULvaU6JR2nmwyYAfSVPzhzS6b5CMZ-i0"], - "ba": ["DnmwyYAfSVPzhzS6b5CMZ-i0d8JZAoTNZH3ULvaU6JR2"] "di": "EYAfSVPzhzS6b5CMaU6JR2nmwyZ-i0d8JZAoTNZH3ULv", - "c": ["eo"], + "c": ["EO"], } """ - vs = versify(version=version, kind=kind, size=0) + #vs = versify(version=version, kind=kind, size=0) if sn < 0: raise ValueError("Negative sn = {} in key state.".format(sn)) @@ -506,30 +498,19 @@ def state(pre, cnfg = cnfg if cnfg is not None else [] - if len(oset(br)) != len(br): # duplicates in cuts - raise ValueError("Invalid cuts = {} in latest est event, has duplicates" - ".".format(br)) - - if len(oset(ba)) != len(ba): # duplicates in adds - raise ValueError("Invalid adds = {} in latest est event, has duplicates" - ".".format(ba)) - - ksd = dict(v=vs, # version string - i=ri, # qb64 SAID of the registry + rsr = viring.RegStateRecord( + vn=list(version), # version number as list [major, minor] + i=ri, # qb64 registry SAID s="{:x}".format(sn), # lowercase hex string no leading zeros d=said, ii=pre, dt=dts, et=eilk, - a=a, bt="{:x}".format(toad), # hex string no leading zeros lowercase - br=br, - ba=ba, b=wits, # list of qb64 may be empty - c=cnfg, # list of config ordered mappings may be empty + c=cnfg if cnfg is not None else [], ) - - return Serder(ked=ksd) # return serialized ksd + return rsr # return RegStateRecord use asdict(rsr) to get dict version def vcstate(vcpre, @@ -554,8 +535,8 @@ def vcstate(vcpre, sn (int): sequence number of latest event ri (str): registry identifier ra (dict): optional registry seal for registries with backers - eilk (str): is message type (ilk) oflatest event - a (dict): is seal for anchor to key event log + eilk (str): is message type (ilk) of latest event + a (dict): is seal for anchor in KEL dts (str): iso8601 formatted date string of state version (Version): is KERI version instance kind (str): is serialization kind @@ -576,8 +557,6 @@ def vcstate(vcpre, } """ - vs = versify(version=version, kind=kind, size=0) - if sn < 0: raise ValueError("Negative sn = {} in key state.".format(sn)) @@ -590,18 +569,18 @@ def vcstate(vcpre, if ra is None: ra = dict() - ksd = dict(v=vs, # version string - i=vcpre, # qb64 prefix - s="{:x}".format(sn), # lowercase hex string no leading zeros - d=said, - ri=ri, - ra=ra, - a=a, - dt=dts, - et=eilk, - ) + vsr = viring.VcStateRecord(vn=list(version), # version string + i=vcpre, # qb64 prefix + s="{:x}".format(sn), # lowercase hex string no leading zeros + d=said, + ri=ri, + ra=ra, + a=a, + dt=dts, + et=eilk, + ) - return Serder(ked=ksd) # return serialized ksd + return vsr # return vc state record data class def query(regk, @@ -659,7 +638,7 @@ def query(regk, class Tever: """ - Tever is KERI transaction event verifier class + Tever is KERI/ACDC transaction event log verifier class Only supports current version VERSION Has the following public attributes and properties: @@ -689,15 +668,16 @@ class Tever: """ NoBackers = False - def __init__(self, cues=None, stt=None, serder=None, seqner=None, saider=None, bigers=None, db=None, - reger=None, noBackers=None, estOnly=None, regk=None, local=False): + def __init__(self, cues=None, rsr=None, serder=None, seqner=None, saider=None, + bigers=None, db=None, reger=None, noBackers=None, estOnly=None, + regk=None, local=False): """ Create incepting tever and state from registry inception serder Create incepting tever and state from registry inception serder Parameters: serder (Serder): instance of registry inception event - stt (Serder): transaction state notice state message Serder + rsr (RegStateRecord): transaction state notice state message Serder seqner (Seqner): issuing event sequence number from controlling KEL. saider (Saider): issuing event said from controlling KEL. bigers (list): list of Siger instances of indexed backer signatures of @@ -717,17 +697,17 @@ def __init__(self, cues=None, stt=None, serder=None, seqner=None, saider=None, b """ - if not (stt or serder): + if not (rsr or serder): raise ValueError("Missing required arguments. Need state or serder") - self.reger = reger if reger is not None else Reger() + self.reger = reger if reger is not None else viring.Reger() self.cues = cues if cues is not None else decking.Deck() self.db = db if db is not None else basing.Baser(reopen=True) self.local = True if local else False - if stt: # preload from state - self.reload(stt) + if rsr: # preload from state + self.reload(rsr) return self.version = serder.version @@ -764,57 +744,45 @@ def __init__(self, cues=None, stt=None, serder=None, seqner=None, saider=None, b self.regk = self.prefixer.qb64 - def reload(self, ksn): + def reload(self, rsr): """ Reload Tever attributes (aka its state) from state serder Reload Tever attributes (aka its state) from state serder Parameters: - ksn (Serder): instance of key stat notice 'ksn' message body + rsr (RegStateRecord): instance of key stat notice 'ksn' message body """ - for k in TSN_LABELS: - if k not in ksn.ked: - raise ValidationError("Missing element = {} from {} event." - " evt = {}.".format(k, Ilks.ksn, - ksn.pretty())) + ked = asdict(rsr) - self.version = ksn.version - self.pre = ksn.ked["ii"] - self.regk = ksn.ked["i"] + self.version = rsr.vn + self.pre = ked["ii"] + self.regk = ked["i"] self.prefixer = Prefixer(qb64=self.regk) - self.sn = ksn.sn - self.ilk = ksn.ked["et"] - self.toad = int(ksn.ked["bt"], 16) - self.baks = ksn.ked["b"] - self.cuts = ksn.ked["br"] - self.adds = ksn.ked["ba"] + self.sn = int(ked['s'], 16) + self.ilk = ked["et"] + self.toad = int(ked["bt"], 16) + self.baks = ked["b"] - self.noBackers = True if TraitDex.NoBackers in ksn.ked["c"] else False - self.estOnly = True if TraitDex.EstOnly in ksn.ked["c"] else False + self.noBackers = True if TraitDex.NoBackers in ked["c"] else False + self.estOnly = True if TraitDex.EstOnly in ked["c"] else False if (raw := self.reger.getTvt(key=dgKey(pre=self.prefixer.qb64, - dig=ksn.ked['d']))) is None: + dig=ked['d']))) is None: raise kering.MissingEntryError("Corresponding event for state={} not found." - "".format(ksn.pretty())) - self.serder = Serder(raw=bytes(raw)) + "".format(ked)) + self.serder = serdering.SerderKERI(raw=bytes(raw)) - def state(self, kind=Serials.json): - """ Returns Serder instance of current transaction state notification message - - Returns Serder instance of current transaction state notification message of this - credential registry. - - Parameters: - kind (str): serialization kind for message json, cbor, mgpk + def state(self): #state(self, kind=Serials.json) + """ Returns RegStateRecord of state notice of given Registry Event Log + (REL) Returns: - Serder: event message Serder instance + rsr: (RegStateRecord): instance for this Tever + """ - br = self.cuts - ba = self.adds cnfg = [] if self.noBackers: @@ -832,13 +800,11 @@ def state(self, kind=Serials.json): ri=self.regk, dts=None, eilk=self.ilk, - a=dict(s=seqner.sn, d=diger.qb64), - br=br, - ba=ba, + #a=dict(s=seqner.sn, d=diger.qb64), toad=self.toad, wits=self.baks, cnfg=cnfg, - kind=kind + #kind=kind ) ) @@ -940,6 +906,7 @@ def update(self, serder, seqner=None, saider=None, bigers=None): if self.noBackers is True: raise ValidationError("invalid rotation evt {} against backerless registry {}". format(ked, self.regk)) + toad, baks, cuts, adds = self.rotate(serder, sn=sn) bigers = self.valAnchorBigs(serder=serder, @@ -974,6 +941,7 @@ def update(self, serder, seqner=None, saider=None, bigers=None): else: # unsupported event ilk so discard raise ValidationError("Unsupported ilk = {} for evt = {}.".format(ilk, ked)) + def rotate(self, serder, sn): """ Process registry management TEL, non-inception events (vrt) @@ -990,8 +958,16 @@ def rotate(self, serder, sn): """ ked = serder.ked + ilk = ked["t"] dig = ked["p"] + # XXXX should there be validation of labels here + labels = VRT_LABELS # assumes ilk == Ilks.vrt + #for k in labels: + #if k not in ked: + #raise ValidationError("Missing element = {} from {} event for " + #"evt = {}.".format(k, ilk, ked)) + if serder.pre != self.prefixer.qb64: raise ValidationError("Mismatch event aid prefix = {} expecting" " = {} for evt = {}.".format(ked["i"], @@ -1004,7 +980,7 @@ def rotate(self, serder, sn): if not self.serder.compare(said=dig): # prior event dig not match raise ValidationError("Mismatch event dig = {} with state dig" " = {} for evt = {}.".format(ked["p"], - self.serder.saider.qb64, + self.serder.said, ked)) witset = oset(self.baks) @@ -1158,11 +1134,11 @@ def revoke(self, serder, seqner, saider, sn, bigers=None): raise ValidationError("revoke without issue... probably have to escrow") ievt = bytes(ievt) - iserder = Serder(raw=ievt) + iserder = serdering.SerderKERI(raw=ievt) if not iserder.compare(said=ked["p"]): # prior event dig not match raise ValidationError("Mismatch event dig = {} with state dig" " = {} for evt = {}.".format(ked["p"], - self.serder.saider.qb64, + self.serder.said, ked)) if ilk in (Ilks.rev,): # simple revoke @@ -1177,7 +1153,7 @@ def revoke(self, serder, seqner, saider, sn, bigers=None): "".format(serder.ked)) self.logEvent(pre=vci, sn=sn, serder=serder, seqner=seqner, saider=saider) - self.cues.append(dict(kin="revoked", serder=serder)) + self.cues.push(dict(kin="revoked", serder=serder)) elif ilk in (Ilks.brv,): # backer revoke if self.noBackers is True: @@ -1193,7 +1169,7 @@ def revoke(self, serder, seqner, saider, sn, bigers=None): baks=baks) self.logEvent(pre=vci, sn=sn, serder=serder, seqner=seqner, saider=saider, bigers=bigers) - self.cues.append(dict(kin="revoked", serder=serder)) + self.cues.push(dict(kin="revoked", serder=serder)) else: raise ValidationError("Unsupported ilk = {} for evt = {}.".format(ilk, ked)) @@ -1221,7 +1197,7 @@ def vcState(self, vci): dgkey = dbing.dgKey(vci, vcdig) # get message raw = self.reger.getTvt(key=dgkey) - serder = coring.Serder(raw=bytes(raw)) + serder = serdering.SerderKERI(raw=bytes(raw)) if self.noBackers: vcilk = Ilks.iss if len(digs) == 1 else Ilks.rev @@ -1279,7 +1255,7 @@ def logEvent(self, pre, sn, serder, seqner, saider, bigers=None, baks=None): if hasattr(pre, "encode"): pre = pre.encode("utf-8") # convert str to bytes - dig = serder.saider.qb64b + dig = serder.saidb key = dgKey(pre, dig) sealet = seqner.qb64b + saider.qb64b self.reger.putAnc(key, sealet) @@ -1384,7 +1360,7 @@ def verifyAnchor(self, serder, seqner=None, saider=None): else: raw = bytes(raw) - eserder = Serder(raw=raw) # deserialize event raw + eserder = serdering.SerderKERI(raw=raw) # deserialize event raw if eserder.said != saider.qb64: return False @@ -1477,7 +1453,7 @@ def getBackerState(self, ked): if revt is None: raise ValidationError("have to escrow this somewhere") - rserder = Serder(raw=bytes(revt)) + rserder = serdering.SerderKERI(raw=bytes(revt)) # the backer threshold at this event in mgmt TEL rtoad = rserder.ked["bt"] @@ -1520,7 +1496,7 @@ def __init__(self, reger=None, db=None, local=False, lax=False, cues=None, rvy=N """ self.db = db if db is not None else basing.Baser(reopen=True) # default name = "main" self.rvy = rvy - self.reger = reger if reger is not None else Reger() + self.reger = reger if reger is not None else viring.Reger() self.local = True if local else False # local vs nonlocal restrictions self.lax = True if lax else False self.cues = cues if cues is not None else decking.Deck() @@ -1544,7 +1520,7 @@ def registries(self): return self.reger.registries def processEvent(self, serder, seqner=None, saider=None, wigers=None): - """ Process one event serder with attached indexde signatures sigers + """ Process one event serder with attached indexed signatures sigers Validates event against current state of registry or credential, creating registry on inception events and processing change in state to credential or registry for @@ -1567,7 +1543,6 @@ def processEvent(self, serder, seqner=None, saider=None, wigers=None): regk = self.registryKey(serder) pre = serder.pre ked = serder.ked - said = serder.said sn = ked["s"] ilk = ked["t"] @@ -1623,8 +1598,8 @@ def processEvent(self, serder, seqner=None, saider=None, wigers=None): esn = tever.vcSn(pre) sno = 0 if esn is None else esn + 1 - if not serder.saider.verify(sad=serder.ked): - raise ValidationError("Invalid SAID {} for event {}".format(said, serder.ked)) + #if not serder.saider.verify(sad=serder.sad): + #raise ValidationError("Invalid SAID {} for event {}".format(said, serder.ked)) if sn > sno: # sn later than sno so out of order escrow # escrow out-of-order event @@ -1665,29 +1640,30 @@ def processQuery(self, serder, source=None, sigers=None, cigars=None): if route == "tels": mgmt = qry["ri"] + src = qry["src"] cloner = self.reger.clonePreIter(pre=mgmt, fn=0) # create iterator at 0 - msgs = bytearray() # outgoing messages + msgs = list() # outgoing messages for msg in cloner: - msgs.extend(msg) + msgs.append(msg) if vci := qry["i"]: cloner = self.reger.clonePreIter(pre=vci, fn=0) # create iterator at 0 for msg in cloner: - msgs.extend(msg) + msgs.append(msg) if msgs: - self.cues.append(dict(kin="replay", dest=source, msgs=msgs)) + self.cues.append(dict(kin="replay", src=src, dest=source.qb64, msgs=msgs)) elif route == "tsn": ri = qry["ri"] if ri in self.tevers: tever = self.tevers[ri] tsn = tever.state() - self.cues.push(dict(kin="reply", route="/tsn/registry", data=tsn.ked, dest=source)) + self.cues.push(dict(kin="reply", route="/tsn/registry", data=asdict(tsn), dest=source)) if vcpre := qry["i"]: tsn = tever.vcState(vcpre=vcpre) - self.cues.push(dict(kin="reply", route="/tsn/credential", data=tsn.ked, dest=source)) + self.cues.push(dict(kin="reply", route="/tsn/credential", data=asdict(tsn), dest=source)) else: raise ValidationError("invalid query message {} for evt = {}".format(ilk, ked)) @@ -1767,19 +1743,12 @@ def processReplyRegistryTxnState(self, *, serder, saider, route, cigars=None, ts data = serder.ked["a"] dater = coring.Dater(dts=serder.ked["dt"]) - tserder = coring.Serder(ked=data) - - for k in TSN_LABELS: - if k not in tserder.ked: - raise ValidationError("Missing element = {} from {} msg." - " tsn = {}.".format(k, Ilks.tsn, - serder.pretty())) + rsr = viring.RegStateRecord(**data) # fetch from serder to process - ked = tserder.ked - regk = tserder.pre - pre = ked["ii"] - sn = tserder.sn + regk = rsr.i + pre = rsr.ii + sn = int(rsr.s, 16) if pre not in self.kevers: if self.reger.txnsb.escrowStateNotice(typ="registry-mae", pre=regk, aid=aid, serder=serder, saider=saider, @@ -1789,12 +1758,12 @@ def processReplyRegistryTxnState(self, *, serder, saider, route, cigars=None, ts raise kering.MissingAnchorError("Failure verify event = {} ".format(serder.ked)) # Load backers from either tsn or Kever of issuer - cnfg = ked["c"] + cnfg = rsr.c if TraitDex.NoBackers in cnfg: kevers = self.kevers[pre] baks = kevers.wits else: - baks = ked["b"] + baks = rsr.b wats = set() for _, habr in self.db.habs.getItemIter(): @@ -1807,13 +1776,13 @@ def processReplyRegistryTxnState(self, *, serder, saider, route, cigars=None, ts aid not in baks and \ aid not in wats: raise kering.UntrustedKeyStateSource("transaction state notice for {} from untrusted source {} " - .format(tserder.pre, aid)) + .format(rsr.i, aid)) if regk in self.tevers: tever = self.tevers[regk] - if tserder.sn < tever.sn: + if int(rsr.s, 16) < tever.sn: raise ValidationError("Skipped stale transaction state at sn {} for {}." - "".format(tserder.sn, tserder.pre)) + "".format(rsr.s, rsr.i)) keys = (regk, aid,) osaider = self.reger.txnsb.current(keys=keys) # get old said if any @@ -1823,7 +1792,7 @@ def processReplyRegistryTxnState(self, *, serder, saider, route, cigars=None, ts aid=aid, osaider=osaider, cigars=cigars, tsgs=tsgs) if not accepted: - raise kering.UnverifiedReplyError(f"Unverified reply.") + raise kering.UnverifiedReplyError(f"Unverified registry txn state reply.") ldig = self.reger.getTel(key=snKey(pre=regk, sn=sn)) # retrieve dig of last event at sn. @@ -1833,21 +1802,21 @@ def processReplyRegistryTxnState(self, *, serder, saider, route, cigars=None, ts dater=dater, cigars=cigars, tsgs=tsgs): self.cues.append(dict(kin="telquery", q=dict(ri=regk))) - raise kering.OutOfOrderTxnStateError("Out of order txn state={}.".format(ked)) + raise kering.OutOfOrderTxnStateError("Out of order txn state={}.".format(rsr)) - tsaider = coring.Saider(qb64=ked["d"]) + tsaider = coring.Saider(qb64=rsr.d) ldig = bytes(ldig) # retrieve last event itself of signer given sdig sraw = self.reger.getTvt(key=dgKey(pre=regk, dig=ldig)) # assumes db ensures that sraw must not be none because sdig was in KE - sserder = Serder(raw=bytes(sraw)) + sserder = serdering.SerderKERI(raw=bytes(sraw)) if sserder.said != tsaider.qb64: # mismatch events problem with replay raise ValidationError("Mismatch keystate at sn = {} with db." - "".format(ked["s"])) + "".format(rsr.s)) - self.reger.txnsb.updateState(aid=aid, serder=tserder, saider=tsaider, dater=dater) - self.cues.append(dict(kin="txnStateSaved", serder=tserder)) + self.reger.txnsb.updateReply(aid=aid, serder=serder, saider=tsaider, dater=dater) + self.cues.append(dict(kin="txnStateSaved", record=rsr)) def processReplyCredentialTxnState(self, *, serder, saider, route, cigars=None, tsgs=None, **kwargs): """ Process one reply message for key state = /tsn/registry @@ -1905,22 +1874,16 @@ def processReplyCredentialTxnState(self, *, serder, saider, route, cigars=None, data = serder.ked["a"] dater = coring.Dater(dts=serder.ked["dt"]) - tserder = coring.Serder(ked=data) - for k in CRED_TSN_LABELS: - if k not in tserder.ked: - raise ValidationError("Missing element = {} from {} msg." - " tsn = {}.".format(k, Ilks.tsn, - serder.pretty())) + vsr = viring.VcStateRecord(**data) # fetch from serder to process - ked = tserder.ked - regk = tserder.ked["ri"] - vci = tserder.pre - sn = tserder.sn - ra = tserder.ked["ra"] + regk = vsr.ri + vci = vsr.i + sn = int(vsr.s, 16) + ra = vsr.ra if 's' in ra: - regsn = ra["s"] + regsn = int(ra["s"], 16) else: regsn = 0 @@ -1959,7 +1922,7 @@ def processReplyCredentialTxnState(self, *, serder, saider, route, cigars=None, aid not in baks and \ aid not in wats: raise kering.UntrustedKeyStateSource("transaction state notice for {} from untrusted source {} " - .format(tserder.pre, aid)) + .format(vsr.i, aid)) keys = (vci, aid,) osaider = self.reger.txnsb.current(keys=keys) # get old said if any @@ -1969,7 +1932,7 @@ def processReplyCredentialTxnState(self, *, serder, saider, route, cigars=None, aid=aid, osaider=osaider, cigars=cigars, tsgs=tsgs) if not accepted: - raise kering.UnverifiedReplyError(f"Unverified reply.") + raise kering.UnverifiedReplyError(f"Unverified credential state reply.") ldig = self.reger.getTel(key=snKey(pre=vci, sn=sn)) # retrieve dig of last event at sn. @@ -1979,25 +1942,25 @@ def processReplyCredentialTxnState(self, *, serder, saider, route, cigars=None, saider=saider, dater=dater, cigars=cigars, tsgs=tsgs): self.cues.append(dict(kin="telquery", q=dict(ri=regk, i=vci))) - raise kering.OutOfOrderTxnStateError("Out of order txn state={}.".format(ked)) + raise kering.OutOfOrderTxnStateError("Out of order txn state={}.".format(vsr)) - tsaider = coring.Saider(qb64=ked["d"]) + tsaider = coring.Saider(qb64=vsr.d) ldig = bytes(ldig) # retrieve last event itself of signer given sdig sraw = self.reger.getTvt(key=dgKey(pre=vci, dig=ldig)) # assumes db ensures that sraw must not be none because sdig was in KE - sserder = Serder(raw=bytes(sraw)) + sserder = serdering.SerderKERI(raw=bytes(sraw)) if sn < sserder.sn: raise ValidationError("Stale txn state at sn = {} with db." - "".format(ked["s"])) + "".format(vsr.s)) if sserder.said != tsaider.qb64: # mismatch events problem with replay raise ValidationError("Mismatch txn state at sn = {} with db." - "".format(ked["s"])) + "".format(vsr.s)) - self.reger.txnsb.updateState(aid=aid, serder=tserder, saider=tsaider, dater=dater) - self.cues.append(dict(kin="txnStateSaved", serder=tserder)) + self.reger.txnsb.updateReply(aid=aid, serder=serder, saider=tsaider, dater=dater) + self.cues.append(dict(kin="txnStateSaved", record=vsr)) @staticmethod def registryKey(serder): @@ -2093,7 +2056,7 @@ def processEscrowOutOfOrders(self): raise ValidationError("Missing escrowed evt at dig = {}." "".format(bytes(digb))) - tserder = Serder(raw=bytes(traw)) # escrowed event + tserder = serdering.SerderKERI(raw=bytes(traw)) # escrowed event bigers = None if tibs := self.reger.getTibs(key=dgkey): @@ -2160,7 +2123,7 @@ def processEscrowAnchorless(self): raise ValidationError("Missing escrowed evt at dig = {}." "".format(bytes(digb))) - tserder = Serder(raw=bytes(traw)) # escrowed event + tserder = serdering.SerderKERI(raw=bytes(traw)) # escrowed event bigers = None if tibs := self.reger.getTibs(key=dgkey): diff --git a/src/keri/vdr/verifying.py b/src/keri/vdr/verifying.py index 2c17089c0..d7fcb245b 100644 --- a/src/keri/vdr/verifying.py +++ b/src/keri/vdr/verifying.py @@ -12,9 +12,7 @@ from hio.help import decking from .. import help, kering -from ..app import signing from ..core import parsing, coring, scheming -from .. import core from ..help import helping from ..vdr import eventing from ..vdr.viring import Reger @@ -44,7 +42,7 @@ def __init__(self, hby, reger=None, creds=None, cues=None, expiry=36000000000): """ self.hby = hby - self.reger = reger if reger is not None else Reger(name=self.hby.name, temp=True) + self.reger = reger if reger is not None else Reger(name=self.hby.name, temp=self.hby.temp) self.creds = creds if creds is not None else decking.Deck() # subclass of deque self.cues = cues if cues is not None else decking.Deck() # subclass of deque self.CredentialExpiry = expiry @@ -69,7 +67,6 @@ def setup(self): self.inited = True - @property def tevers(self): """ Returns .db.tevers @@ -89,7 +86,7 @@ def processMessages(self, creds=None): while creds: self.processCredential(**creds.pull()) - def processCredential(self, creder, sadsigers=None, sadcigars=None): + def processCredential(self, creder, prefixer, seqner, saider): """ Credential data and signature(s) verification Verify the data of the credential against the schema, the SAID of the credential and @@ -97,36 +94,34 @@ def processCredential(self, creder, sadsigers=None, sadcigars=None): Parameters: creder (Creder): that contains the credential to process - sadsigers (list): sad path signatures from transferable identifier - sadcigars (list): sad path signatures from non-transferable identifier + prefixer (Prefixer): prefix of source anchoring KEL or TEL event + seqner (Seqner): sequence number of source anchoring KEL or TEL event + saider (Saider): SAID of source anchoring KEL or TEL event """ - regk = creder.status + regk = creder.regi vcid = creder.said schema = creder.schema - prov = creder.chains - - sadcigars = sadcigars if sadcigars is not None else [] - sadsigers = sadsigers if sadsigers is not None else [] + prov = creder.edge if creder.edge is not None else {} if regk not in self.tevers: # registry event not found yet - if self.escrowMRE(creder, sadsigers, sadcigars): - self.cues.append(dict(kin="telquery", q=dict(ri=regk, i=vcid))) + if self.escrowMRE(creder, prefixer, seqner, saider): + self.cues.append(dict(kin="telquery", q=dict(ri=regk, i=vcid, issr=creder.issuer))) raise kering.MissingRegistryError("registry identifier {} not in Tevers".format(regk)) state = self.tevers[regk].vcState(vcid) if state is None: # credential issuance event not found yet - if self.escrowMRE(creder, sadsigers, sadcigars): + if self.escrowMRE(creder, prefixer, seqner, saider): self.cues.append(dict(kin="telquery", q=dict(ri=regk, i=vcid))) raise kering.MissingRegistryError("credential identifier {} not in Tevers".format(vcid)) dtnow = helping.nowUTC() - dte = helping.fromIso8601(state.ked["dt"]) + dte = helping.fromIso8601(state.dt) if (dtnow - dte) > datetime.timedelta(seconds=self.CredentialExpiry): - if self.escrowMRE(creder, sadsigers, sadcigars): + if self.escrowMRE(creder, prefixer, seqner, saider): self.cues.append(dict(kin="telquery", q=dict(ri=regk, i=vcid))) raise kering.MissingRegistryError("credential identifier {} is out of date".format(vcid)) - elif state.ked["et"] in (coring.Ilks.rev, coring.Ilks.brv): # no escrow, credential has been revoked + elif state.et in (coring.Ilks.rev, coring.Ilks.brv): # no escrow, credential has been revoked logger.error("credential {} in registrying is not in issued state".format(vcid, regk)) # Log this and continue instead of the previous exception so we save a revoked credential. # raise kering.InvalidCredentialStateError("...")) @@ -134,7 +129,7 @@ def processCredential(self, creder, sadsigers=None, sadcigars=None): # Verify the credential against the schema scraw = self.resolver.resolve(schema) if not scraw: - if self.escrowMSE(creder, sadsigers, sadcigars): + if self.escrowMSE(creder, prefixer, seqner, saider): self.cues.append(dict(kin="query", q=dict(r="schema", said=schema))) raise kering.MissingSchemaError("schema {} not in cache".format(schema)) @@ -147,41 +142,6 @@ def processCredential(self, creder, sadsigers=None, sadcigars=None): raise kering.FailedSchemaValidationError("Credential {} is not valid against schema {}: {}" .format(creder.said, schema, ex)) - for (pather, cigar) in sadcigars: - if not cigar.verfer.verify(cigar.raw, creder.raw): # cig not verify - self.escrowPSC(creder, sadsigers, sadcigars) - raise kering.MissingSignatureError("Failure satisfying credential on sigs for {}" - " for evt = {}.".format(cigar, - creder.crd)) - - rooted = False - for (pather, prefixer, seqner, saider, sigers) in sadsigers: - if pather.bext != "-": - continue - - rooted = True - if prefixer.qb64 not in self.hby.kevers or self.hby.kevers[prefixer.qb64].sn < seqner.sn: - if self.escrowMIE(creder, sadsigers, sadcigars): - self.cues.append(dict(kin="query", q=dict(pre=prefixer.qb64, sn=seqner.sn))) - raise kering.MissingIssuerError("issuer identifier {} not in Kevers".format(prefixer.qb64)) - - # Verify the signatures are valid and that the signature threshold as of the signing event is met - tholder, verfers = self.hby.db.resolveVerifiers(pre=prefixer.qb64, sn=seqner.sn, dig=saider.qb64) - _, indices = core.eventing.verifySigs(creder.raw, sigers, verfers) - - if not tholder.satisfy(indices): # We still don't have all the sigers, need to escrow - self.escrowPSC(creder, sadsigers, sadcigars) - raise kering.MissingSignatureError("Failure satisfying credential sith = {} on sigs for {}" - " for evt = {}.".format(tholder.sith, - [siger.qb64 for siger in sigers], - creder.crd)) - if not rooted: - print("Missing root signature for ", vcid) - raise kering.MissingSignatureError("No root signature on credential with paths {}" - " for evt = {}.".format([pather.bext for (pather, _, _, _, _) - in sadsigers], - creder.crd)) - if isinstance(prov, list): edges = prov elif isinstance(prov, dict): @@ -195,101 +155,76 @@ def processCredential(self, creder, sadsigers=None, sadcigars=None): if label in ('d', 'o'): # SAID or Operator of this edge block continue nodeSaid = node["n"] - state = self.verifyChain(nodeSaid) + op = node['o'] if 'o' in node else None + state = self.verifyChain(nodeSaid, op, creder.issuer) if state is None: - self.escrowMCE(creder, sadsigers, sadcigars) + self.escrowMCE(creder, prefixer, seqner, saider) self.cues.append(dict(kin="proof", said=nodeSaid)) raise kering.MissingChainError("Failure to verify credential {} chain {}({})" .format(creder.said, label, nodeSaid)) dtnow = helping.nowUTC() - dte = helping.fromIso8601(state.ked["dt"]) + dte = helping.fromIso8601(state.dt) if (dtnow - dte) > datetime.timedelta(seconds=self.CredentialExpiry): - self.escrowMCE(creder, sadsigers, sadcigars) + self.escrowMCE(creder, prefixer, seqner, saider) self.cues.append(dict(kin="query", q=dict(r="tels", pre=nodeSaid))) raise kering.MissingChainError("Failure to verify credential {} chain {}({})" .format(creder.said, label, nodeSaid)) - elif state.ked["et"] in (coring.Ilks.rev, coring.Ilks.brv): + elif state.et in (coring.Ilks.rev, coring.Ilks.brv): raise kering.RevokedChainError("Failure to verify credential {} chain {}({})" .format(creder.said, label, nodeSaid)) else: # VcStatus == VcStates.Issued logger.info("Successfully validated credential chain {} for credential {}" .format(label, creder.said)) - self.saveCredential(creder, sadsigers, sadcigars) - msg = signing.provision(creder, sadsigers=sadsigers, sadcigars=sadcigars) - self.cues.append(dict(kin="saved", creder=creder, msg=msg)) - - def escrowPSC(self, creder, sadsigers, sadcigars): - """ Credential Partial Signature Escrow - - Parameters: - creder (Creder): that contains the credential to process - sadsigers (list): sad path signatures from transferable identifier - sadcigars (list): sad path signatures from non-transferable identifier - - """ - key = creder.saider.qb64b - - self.reger.logCred(creder, sadsigers, sadcigars) - return self.reger.pse.put(keys=key, val=coring.Dater()) + self.saveCredential(creder, prefixer, seqner, saider) + self.cues.append(dict(kin="saved", creder=creder)) - def escrowMRE(self, creder, sadsigers, sadcigars): + def escrowMRE(self, creder, prefixer, seqner, saider): """ Missing Registry Escrow Parameters: creder (Creder): that contains the credential to process - sadsigers (list): sad path signatures from transferable identifier - sadcigars (list): sad path signatures from non-transferable identifier + prefixer (Prefixer): prefix (AID or TEL) of event anchoring credential + seqner (Seqner): sequence number of event anchoring credential + saider (Diger) digest of anchoring event for credential """ - key = creder.saider.qb64b + key = creder.said - self.reger.logCred(creder, sadsigers, sadcigars) + self.reger.logCred(creder, prefixer, seqner, saider) return self.reger.mre.put(keys=key, val=coring.Dater()) - def escrowMIE(self, creder, sadsigers, sadcigars): - """ Missing Issuer Escrow - - Parameters: - creder (Creder): that contains the credential to process - sadsigers (list): sad path signatures from transferable identifier - sadcigars (list): sad path signatures from non-transferable identifier - - """ - key = creder.saider.qb64b - - self.reger.logCred(creder, sadsigers, sadcigars) - return self.reger.mie.put(keys=key, val=coring.Dater()) - - def escrowMCE(self, creder, sadsigers, sadcigars): - """ Missing Issuer Escrow + def escrowMCE(self, creder, prefixer, seqner, saider): + """ Missing Chain Escrow Parameters: creder (Creder): that contains the credential to process - sadsigers (list): sad path signatures from transferable identifier - sadcigars (list): sad path signatures from non-transferable identifier + prefixer (Prefixer): prefix (AID or TEL) of event anchoring credential + seqner (Seqner): sequence number of event anchoring credential + saider (Diger) digest of anchoring event for credential """ - key = creder.saider.qb64b + key = creder.said - self.reger.logCred(creder, sadsigers, sadcigars) + self.reger.logCred(creder, prefixer, seqner, saider) return self.reger.mce.put(keys=key, val=coring.Dater()) - def escrowMSE(self, creder, sadsigers, sadcigars): + def escrowMSE(self, creder, prefixer, seqner, saider): """ Missing Credential Schema Escrow Parameters: creder (Creder): that contains the credential to process - sadsigers (list): sad path signatures from transferable identifier - sadcigars (list): sad path signatures from non-transferable identifier + prefixer (Prefixer): prefix (AID or TEL) of event anchoring credential + seqner (Seqner): sequence number of event anchoring credential + saider (Diger) digest of anchoring event for credential """ - key = creder.saider.qb64b + key = creder.said - self.reger.logCred(creder, sadsigers, sadcigars) + self.reger.logCred(creder, prefixer, seqner, saider) return self.reger.mse.put(keys=key, val=coring.Dater()) def processEscrows(self): @@ -299,8 +234,6 @@ def processEscrows(self): self._processEscrow(self.reger.mce, self.TimeoutMRI, kering.MissingChainError) self._processEscrow(self.reger.mse, self.TimeoutMRI, kering.MissingSchemaError) - self._processEscrow(self.reger.pse, self.TimeoutPSE, kering.MissingSignatureError) - self._processEscrow(self.reger.mie, self.TimeoutMRI, kering.MissingIssuerError) self._processEscrow(self.reger.mre, self.TimeoutMRE, kering.MissingRegistryError) def _processEscrow(self, db, timeout, etype: Type[Exception]): @@ -313,7 +246,7 @@ def _processEscrow(self, db, timeout, etype: Type[Exception]): """ for (said,), dater in db.getItemIter(): - creder, sadsigers, sadcigars = self.reger.cloneCred(said) + creder, prefixer, seqner, saider = self.reger.cloneCred(said) try: @@ -322,12 +255,12 @@ def _processEscrow(self, db, timeout, etype: Type[Exception]): if (dtnow - dte) > datetime.timedelta(seconds=timeout): # escrow stale so raise ValidationError which unescrows below logger.info("Verifier unescrow error: Stale event escrow " - " at said = %s\n", bytes(said)) + " at said = %s\n", said) raise kering.ValidationError("Stale event escrow " - "at said = {}.".format(bytes(said))) + "at said = {}.".format(said)) - self.processCredential(creder, sadsigers, sadcigars) + self.processCredential(creder, prefixer, seqner, saider) except etype as ex: if logger.isEnabledFor(logging.DEBUG): @@ -346,28 +279,29 @@ def _processEscrow(self, db, timeout, etype: Type[Exception]): logger.info("Verifier unescrow succeeded in valid group op: " "creder=\n%s\n", creder.pretty()) - def saveCredential(self, creder, sadsigers, sadcigars): + def saveCredential(self, creder, prefixer, seqner, saider): """ Write the credential and associated indicies to the database Parameters: creder (Creder): that contains the credential to process - sadsigers (list): sad path signatures from transferable identifier - sadcigars (list): sad path signatures from non-transferable identifier + prefixer (Prefixer): prefix (AID or TEL) of event anchoring credential + seqner (Seqner): sequence number of event anchoring credential + saider (Diger) digest of anchoring event for credential """ - self.reger.logCred(creder, sadsigers, sadcigars) + self.reger.logCred(creder, prefixer, seqner, saider) schema = creder.schema.encode("utf-8") issuer = creder.issuer.encode("utf-8") # Look up indicies - saider = creder.saider + saider = coring.Saider(qb64=creder.said) self.reger.saved.pin(keys=saider.qb64b, val=saider) self.reger.issus.add(keys=issuer, val=saider) self.reger.schms.add(keys=schema, val=saider) - if 'i' in creder.subject: - subject = creder.subject["i"].encode("utf-8") + if 'i' in creder.attrib: + subject = creder.attrib["i"].encode("utf-8") self.reger.subjs.add(keys=subject, val=saider) def query(self, pre, regk, vcid, *, dt=None, dta=None, dtb=None, **kwa): @@ -379,12 +313,13 @@ def query(self, pre, regk, vcid, *, dt=None, dta=None, dtb=None, **kwa): hab = self.hby.habs[pre] return hab.endorse(serder, last=True) - def verifyChain(self, nodeSaid): + def verifyChain(self, nodeSaid, op, issuer): """ Verifies the node credential at the end of an edge Parameters: - nodeSubject(str): qb64 of node credential subject nodeSaid: (str): qb64 SAID of node credential + op(str): edge operator + issuer (str) qb64 AID of issuer Returns: Serder: transaction event state notification message @@ -395,14 +330,28 @@ def verifyChain(self, nodeSaid): return None creder = self.reger.creds.get(keys=nodeSaid) - iss = self.reger.subjs.get(keys=creder.subject['i']) - if iss is None: - return None - if creder.status not in self.tevers: + if op not in ['I2I', 'DI2I', 'NI2I']: + op = 'I2I' if 'i' in creder.attrib else 'NI2I' + + if op != 'NI2I': + if 'i' not in creder.attrib: + return None + + iss = self.reger.subjs.get(keys=creder.attrib['i']) + if iss is None: + return None + + if op == 'I2I' and issuer != creder.attrib['i']: + return None + + if op == "DI2I": + raise NotImplementedError() + + if creder.regi not in self.tevers: return None - tever = self.tevers[creder.status] + tever = self.tevers[creder.regi] state = tever.vcState(nodeSaid) if state is None: diff --git a/src/keri/vdr/viring.py b/src/keri/vdr/viring.py index 99cb34802..01a1ce676 100644 --- a/src/keri/vdr/viring.py +++ b/src/keri/vdr/viring.py @@ -8,59 +8,61 @@ A special purpose Verifiable Data Registry (VDR) """ -from dataclasses import dataclass +from dataclasses import dataclass, field, asdict from ordered_set import OrderedSet as oset from ..db import koming, subing, escrowing from .. import kering from ..app import signing -from ..core import coring -from ..db import dbing +from ..core import coring, serdering +from ..db import dbing, basing +from ..db.dbing import snKey from ..help import helping from ..vc import proving +from ..vdr import eventing -class RegerDict(dict): +class rbdict(dict): """ Reger backed read through cache for registry state - Subclass of dict that has db as attribute and employs read through cache - from db Baser.stts of kever states to reload kever from state in database - if not in memory as dict item + Subclass of dict that has db and reger as attributes and employs read + through cache from db Reger.stts of registry states to reload tever from + state in database when not found in memory as dict item. """ - __slots__ = ('reger', 'db', 'klas') # no .__dict__ just for db reference + __slots__ = ('db', 'reger') # no .__dict__ just for db reference def __init__(self, *pa, **kwa): - super(RegerDict, self).__init__(*pa, **kwa) + super(rbdict, self).__init__(*pa, **kwa) self.db = None self.reger = None def __getitem__(self, k): - from ..vdr import eventing + try: - return super(RegerDict, self).__getitem__(k) + return super(rbdict, self).__getitem__(k) except KeyError as ex: if not self.db or not self.reger: raise ex # reraise KeyError - if (state := self.reger.states.get(keys=k)) is None: + if (rsr := self.reger.states.get(keys=k)) is None: raise ex # reraise KeyError try: - tever = eventing.Tever(stt=state, db=self.db, reger=self.reger) + tever = eventing.Tever(rsr=rsr, db=self.db, reger=self.reger) except kering.MissingEntryError: # no kel event for keystate raise ex # reraise KeyError - super(RegerDict, self).__setitem__(k, tever) + super(rbdict, self).__setitem__(k, tever) return tever def __setitem__(self, key, item): - super(RegerDict, self).__setitem__(key, item) + super(rbdict, self).__setitem__(key, item) self.reger.states.pin(keys=key, val=item.state()) def __delitem__(self, key): - super(RegerDict, self).__delitem__(key) + super(rbdict, self).__delitem__(key) self.reger.states.rem(keys=key) def __contains__(self, k): - if not super(RegerDict, self).__contains__(k): + if not super(rbdict, self).__contains__(k): try: self.__getitem__(k) return True @@ -70,17 +72,17 @@ def __contains__(self, k): return True def get(self, k, default=None): - """ Override of dict get method + """Override of dict get method - Args: + Parameters: k (str): key for dict default: default value to return if not found Returns: - Serder: value from underlying dict or database + tever: converted from underlying dict or database """ - if not super(RegerDict, self).__contains__(k): + if not super(rbdict, self).__contains__(k): return default else: return self.__getitem__(k) @@ -94,6 +96,70 @@ class RegistryRecord: prefix: str +@dataclass +class RegStateRecord(basing.RawRecord): # reger.state + """ + Registry Event Log (REL) State information + + (see reger.state at 'stts' for database that holds these records keyed by + Registry SAID, i field) + + Attributes: + vn (list[int]): version number [major, minor] + i (str): registry SAID qb64 (registry inception event SAID) + s (str): sequence number of latest event in KEL as hex str + d (str): latest registry event digest qb64 + ii (str): registry issuer identifier aid qb64 + dt (str): datetime iso-8601 of registry state record update, usually now + et (str): event packet type (ilk) + bt (str): backer threshold hex num + b (list[str]): backer aids qb64 + c (list[str]): config traits + + Note: the seal anchor dict 'a' field is not included in the state notice + because it may be verbose and would impede the main purpose of a notice which + is to trigger the download of the latest events, which would include the + anchored seals. + + rsr = viring.RegStateRecord( + vn=list(version), # version number as list [major, minor] + i=ri, # qb64 registry SAID + s="{:x}".format(sn), # lowercase hex string no leading zeros + d=said, + ii=pre, + dt=dts, + et=eilk, + bt="{:x}".format(toad), # hex string no leading zeros lowercase + b=wits, # list of qb64 may be empty + c=cnfg if cnfg is not None else [], + ) + + """ + vn: list[int] = field(default_factory=list) # version number [major, minor] round trip serializable + i: str = '' # identifier prefix qb64 + s: str = '0' # sequence number of latest event in KEL as hex str + d: str = '' # latest event digest qb64 + ii: str = '' # issuer identifier of registry aid qb64 + dt: str = '' # datetime of update of state record + et: str = '' # TEL evt packet type (ilk) + bt: str = '0' # backer threshold hex num str + b: list = field(default_factory=list) # backer AID list qb64 + c: list[str] = field(default_factory=list) # config trait list + + +@dataclass +class VcStateRecord(basing.RawRecord): + vn: list[str] = field(default_factory=list) # version number [major, minor] round trip serializable + i: str = '' # identifier prefix qb64 + s: str = '0' # sequence number of latest event in KEL as hex str + d: str = '' # latest event digest qb64 + ri: str = '' # registry identifier of registry aid qb64 + ra: dict = field(default_factory=dict) # registry anchor for registry with backers + a: dict = field(default_factory=dict) # seal for anchor in KEL + dt: str = '' # datetime of update of state record + et: str = '' # TEL evt packet type (ilk) + + def openReger(name="test", **kwa): """ Returns contextmanager generated by openLMDB but with Baser instance @@ -106,7 +172,7 @@ def openReger(name="test", **kwa): class Reger(dbing.LMDBer): - """ Vaser sets up named sub databases for VIR + """ Reger sets up named sub databases for TEL registry Attributes: see superclass LMDBer for inherited attributes @@ -114,43 +180,43 @@ class Reger(dbing.LMDBer): .tvts is named sub DB whose values are serialized TEL events dgKey - DB is keyed by identifer prefix plus digest of serialized event + DB is keyed by identifier prefix plus digest of serialized event Only one value per DB key is allowed .tels is named sub DB of transaction event log tables that map sequence numbers to serialized event digests. snKey Values are digests used to lookup event in .tvts sub DB - DB is keyed by identifer prefix plus sequence number of tel event + DB is keyed by identifier prefix plus sequence number of tel event Only one value per DB key is allowed .tibs is named sub DB of indexed backer signatures of event Backers always have nontransferable indetifier prefixes. The index is the offset of the backer into the backer list of the anchored management event wrt the receipted event. dgKey - DB is keyed by identifer prefix plus digest of serialized event + DB is keyed by identifier prefix plus digest of serialized event More than one value per DB key is allowed .oots is named sub DB of out of order escrowed event tables that map sequence numbers to serialized event digests. snKey Values are digests used to lookup event in .tvts sub DB - DB is keyed by identifer prefix plus sequence number of key event + DB is keyed by identifier prefix plus sequence number of key event Only one value per DB key is allowed .baks is named sub DB of ordered list of backers at given point in management TEL. dgKey - DB is keyed by identifer prefix plus digest of serialized event + DB is keyed by identifier prefix plus digest of serialized event More than one value per DB key is allowed .twes is named sub DB of partially witnessed escrowed event tables that map sequence numbers to serialized event digests. snKey Values are digests used to lookup event in .tvts sub DB - DB is keyed by identifer prefix plus sequence number of tel event + DB is keyed by identifier prefix plus sequence number of tel event Only one value per DB key is allowed .taes is named sub DB of anchorless escrowed event tables that map sequence numbers to serialized event digests. snKey Values are digests used to lookup event in .tvts sub DB - DB is keyed by identifer prefix plus sequence number of tel event + DB is keyed by identifier prefix plus sequence number of tel event Only one value per DB key is allowed .ancs is a named sub DB of anchors to KEL events. Quadlet Each quadruple is concatenation of four fully qualified items @@ -160,7 +226,7 @@ class Reger(dbing.LMDBer): When latest establishment event is multisig then there will be multiple quadruples one per signing key, each a dup at same db key. dgKey - DB is keyed by identifer prefix plus digest of serialized event + DB is keyed by identifier prefix plus digest of serialized event Only one value per DB key is allowed .regs is named subDB instance of Komer that maps registry names to registry keys @@ -204,7 +270,7 @@ def __init__(self, headDirPath=None, reopen=True, **kwa): self.registries = oset() if "db" in kwa: - self._tevers = RegerDict() + self._tevers = rbdict() self._tevers.reger = self # assign db for read thorugh cache of kevers self._tevers.db = kwa["db"] else: @@ -215,7 +281,8 @@ def __init__(self, headDirPath=None, reopen=True, **kwa): @property def tevers(self): - """ Returns .db.kevers + """ Returns ._tevers + tevers getter """ return self._tevers @@ -242,10 +309,20 @@ def reopen(self, **kwa): self.taes = self.env.open_db(key=b'taes.') self.tets = subing.CesrSuber(db=self, subkey='tets.', klas=coring.Dater) - self.states = subing.SerderSuber(db=self, subkey='stts.') # key states + # Registry state made of RegStateRecord. + # Each registry has registry event log keyed by registry identifier + self.states = koming.Komer(db=self, + schema=RegStateRecord, + subkey='stts.') + #self.states = subing.SerderSuber(db=self, subkey='stts.') # registry event state # Holds the credential - self.creds = proving.CrederSuber(db=self, subkey="creds.") + self.creds = subing.SerderSuber(db=self, subkey="creds.", klas=serdering.SerderACDC) + + # database of anchors to credentials. prefix is either AID with direct credential + # anchor or TEL event AID (same as credential SAID) when credential uses revocation registry + self.cancs = subing.CatCesrSuber(db=self, subkey='cancs.', + klas=(coring.Prefixer, coring.Seqner, coring.Saider)) # all sad path ssgs (sad pathed indexed signature serializations) maps SAD quinkeys # given by quintuple (saider.qb64, path, prefixer.qb64, seqner.q64, diger.qb64) @@ -268,12 +345,8 @@ def reopen(self, **kwa): # Index of credentials by schema self.schms = subing.CesrDupSuber(db=self, subkey='schms.', klas=coring.Saider) - # Partially signed credential escrow - self.pse = subing.CesrSuber(db=self, subkey='pse.', klas=coring.Dater) # Missing reegistry escrow self.mre = subing.CesrSuber(db=self, subkey='mre.', klas=coring.Dater) - # Missing issuer escrow - self.mie = subing.CesrSuber(db=self, subkey='mie.', klas=coring.Dater) # Broken chain escrow self.mce = subing.CesrSuber(db=self, subkey='mce.', klas=coring.Dater) # Missing schema escrow @@ -303,25 +376,20 @@ def reopen(self, **kwa): self.ctel = subing.CesrSuber(db=self, subkey='ctel.', klas=coring.Saider) - # Credential Issuance Escrow - self.crie = proving.CrederSuber(db=self, subkey="drie.") - - # Credential Sent Escrow - self.crse = proving.CrederSuber(db=self, subkey="crse.") - # Credential Missing Signature Escrow - self.cmse = proving.CrederSuber(db=self, subkey="cmse.") + self.cmse = subing.SerderSuber(db=self, subkey="cmse.", klas=serdering.SerderACDC) # Completed Credentials - self.ccrd = proving.CrederSuber(db=self, subkey="ccrd.") + self.ccrd = subing.SerderSuber(db=self, subkey="ccrd.", klas=serdering.SerderACDC) return self.env - def cloneCreds(self, saids): + def cloneCreds(self, saids, db): """ Returns fully expanded credential with chained credentials attached. Parameters: saids (list): of Saider objects: + db (Baser): baser object to load schema Returns: list: fully hydrated credentials with full chains provided @@ -330,10 +398,18 @@ def cloneCreds(self, saids): creds = [] for saider in saids: key = saider.qb64 - creder, sadsigers, sadcigars = self.cloneCred(said=key) + creder, prefixer, seqner, asaider = self.cloneCred(said=key) + atc = bytearray(signing.serialize(creder, prefixer, seqner, saider)) + del atc[0:creder.size] + + iss = bytearray(self.cloneTvtAt(pre=prefixer.qb64, sn=seqner.sn)) + iserder = serdering.SerderKERI(raw=iss) + issatc = bytes(iss[iserder.size:]) + + del iss[0:iserder.size] chainSaids = [] - for k, p in creder.chains.items(): + for k, p in (creder.edge.items() if creder.edge is not None else {}): if k == "d": continue @@ -341,50 +417,61 @@ def cloneCreds(self, saids): continue chainSaids.append(coring.Saider(qb64=p["n"])) - chains = self.cloneCreds(chainSaids) + chains = self.cloneCreds(chainSaids, db) - regk = creder.status + regk = creder.regi status = self.tevers[regk].vcState(saider.qb64) + schemer = db.schema.get(creder.schema) + cred = dict( - sad=creder.crd, + sad=creder.sad, + atc=atc.decode("utf-8"), + iss=iserder.sad, + issatc=issatc.decode("utf-8"), pre=creder.issuer, - sadsigers=[dict( - path=pather.bext, + schema=schemer.sed, + chains=chains, + status=asdict(status), + anchor=dict( pre=prefixer.qb64, sn=seqner.sn, - d=saider.qb64 - ) for (pather, prefixer, seqner, saider, sigers) in sadsigers], - sadcigars=[dict(path=pather.bext, cigar=cigar.qb64) for (pather, cigar) in sadcigars], - chains=chains, - status=status.ked, + d=asaider.qb64 + ) ) + ctr = coring.Counter(qb64b=iss, strip=True) + if ctr.code == coring.CtrDex.AttachedMaterialQuadlets: + ctr = coring.Counter(qb64b=iss, strip=True) + + if ctr.code == coring.CtrDex.SealSourceCouples: + coring.Seqner(qb64b=iss, strip=True) + saider = coring.Saider(qb64b=iss) + + anc = db.cloneEvtMsg(pre=creder.issuer, fn=0, dig=saider.qb64b) + aserder = serdering.SerderKERI(raw=anc) + ancatc = bytes(anc[aserder.size:]) + cred['anc'] = aserder.sad + cred['ancatc'] = ancatc.decode("utf-8"), + creds.append(cred) + return creds - def logCred(self, creder, sadsigers=None, sadcigars=None): + def logCred(self, creder, prefixer, seqner, saider): """ Save the base credential and seals (est evt+sigs quad) with no indices. Parameters: creder (Creder): that contains the credential to process - sadsigers (list): sad path signatures from transferable identifier - sadcigars (list): sad path signatures from non-transferable identifier + prefixer (Prefixer): prefix (AID or TEL) of event anchoring credential + seqner (Seqner): sequence number of event anchoring credential + saider (Diger) digest of anchoring event for credential """ - key = creder.saider.qb64b + key = creder.said + self.cancs.pin(keys=key, val=[prefixer, seqner, saider]) self.creds.put(keys=key, val=creder) - if sadcigars: - for (pather, cigar) in sadcigars: - keys = (creder.saider.qb64, pather.qb64) - self.spcgs.put(keys=keys, vals=[(cigar.verfer, cigar)]) - if sadsigers: # want sn in numerical order so use hex - for (pather, prefixer, seqner, saider, sigers) in sadsigers: - quinkeys = (creder.saider.qb64, pather.qb64, prefixer.qb64, f"{seqner.sn:032x}", saider.qb64) - for siger in sigers: - self.spsgs.add(keys=quinkeys, val=siger) - - def cloneCred(self, said, root=None): + def cloneCred(self, said): """ Load base credential and CESR proof signatures from database. Base credential and all signatures are returned from the credential @@ -394,44 +481,12 @@ def cloneCred(self, said, root=None): Parameters: said(str or bytes): qb64 SAID of credential - root (Optional(Pather)): a target path transposition location for all signatures """ creder = self.creds.get(keys=(said,)) - sadcigars = [] # transferable signature groups - sadsigers = [] # transferable signature groups - - for keys, cigar in self.spcgs.getItemIter(keys=(creder.saider.qb64, "")): - pather = coring.Pather(qb64=keys[1]) - if root is not None: - pather = pather.root(root) - sadcigars.append((pather, cigar)) - - klases = (coring.Pather, coring.Prefixer, coring.Seqner, coring.Saider) - args = ("qb64", "qb64", "snh", "qb64") - sigers = [] - old = None # empty keys - for keys, siger in self.spsgs.getItemIter(keys=(creder.saider.qb64, "")): - quad = keys[1:] - if quad != old: # new tsg - if sigers: # append tsg made for old and sigers - pather, prefixer, seqner, saider = helping.klasify(sers=old, klases=klases, args=args) - if root is not None: - pather = pather.root(root) - - sadsigers.append((pather, prefixer, seqner, saider, sigers)) - sigers = [] - old = quad - sigers.append(siger) - if sigers and old: - pather, prefixer, seqner, saider = helping.klasify(sers=old, klases=klases, args=args) - if root is not None: - pather = pather.root(root) - - sadsigers.append((pather, prefixer, seqner, saider, sigers)) - - return creder, sadsigers, sadcigars + prefixer, seqner, saider = self.cancs.get(keys=(said,)) + return creder, prefixer, seqner, saider def clonePreIter(self, pre, fn=0): """ Iterator of first seen event messages @@ -452,37 +507,46 @@ def clonePreIter(self, pre, fn=0): pre = pre.encode("utf-8") for fn, dig in self.getTelItemPreIter(pre, fn=fn): - msg = bytearray() # message - atc = bytearray() # attachments - dgkey = dbing.dgKey(pre, dig) # get message - if not (raw := self.getTvt(key=dgkey)): - raise kering.MissingEntryError("Missing event for dig={}.".format(dig)) - msg.extend(raw) - - # add indexed backer signatures to attachments - if tibs := self.getTibs(key=dgkey): - atc.extend(coring.Counter(code=coring.CtrDex.WitnessIdxSigs, - count=len(tibs)).qb64b) - for tib in tibs: - atc.extend(tib) - - # add authorizer (delegator/issure) source seal event couple to attachments - couple = self.getAnc(dgkey) - if couple is not None: - atc.extend(coring.Counter(code=coring.CtrDex.SealSourceCouples, - count=1).qb64b) - atc.extend(couple) - - # prepend pipelining counter to attachments - if len(atc) % 4: - raise ValueError("Invalid attachments size={}, nonintegral" - " quadlets.".format(len(atc))) - pcnt = coring.Counter(code=coring.CtrDex.AttachedMaterialQuadlets, - count=(len(atc) // 4)).qb64b - msg.extend(pcnt) - msg.extend(atc) + msg = self.cloneTvt(pre, dig) yield msg + def cloneTvtAt(self, pre, sn=0): + snkey = dbing.snKey(pre, sn) + dig = self.getTel(key=snkey) + return self.cloneTvt(pre, dig) + + def cloneTvt(self, pre, dig): + msg = bytearray() # message + atc = bytearray() # attachments + dgkey = dbing.dgKey(pre, dig) # get message + if not (raw := self.getTvt(key=dgkey)): + raise kering.MissingEntryError("Missing event for dig={}.".format(dig)) + msg.extend(raw) + + # add indexed backer signatures to attachments + if tibs := self.getTibs(key=dgkey): + atc.extend(coring.Counter(code=coring.CtrDex.WitnessIdxSigs, + count=len(tibs)).qb64b) + for tib in tibs: + atc.extend(tib) + + # add authorizer (delegator/issure) source seal event couple to attachments + couple = self.getAnc(dgkey) + if couple is not None: + atc.extend(coring.Counter(code=coring.CtrDex.SealSourceCouples, + count=1).qb64b) + atc.extend(couple) + + # prepend pipelining counter to attachments + if len(atc) % 4: + raise ValueError("Invalid attachments size={}, nonintegral" + " quadlets.".format(len(atc))) + pcnt = coring.Counter(code=coring.CtrDex.AttachedMaterialQuadlets, + count=(len(atc) // 4)).qb64b + msg.extend(pcnt) + msg.extend(atc) + return msg + def sources(self, db, creder): """ Returns raw bytes of any source ('e') credential that is in our database @@ -494,7 +558,7 @@ def sources(self, db, creder): list: credential sources as resolved from `e` in creder.crd """ - chains = creder.chains + chains = creder.edge if creder.edge is not None else {} saids = [] for key, source in chains.items(): if key == 'd': @@ -507,11 +571,14 @@ def sources(self, db, creder): sources = [] for said in saids: - screder, sadsigers, sadcigars = self.cloneCred(said=said) + screder, prefixer, seqner, saider = self.cloneCred(said=said) + + atc = bytearray(coring.Counter(coring.CtrDex.SealSourceTriples, count=1).qb64b) + atc.extend(prefixer.qb64b) + atc.extend(seqner.qb64b) + atc.extend(saider.qb64b) - craw = signing.provision(serder=screder, sadsigers=sadsigers, sadcigars=sadcigars) - del craw[screder.size:] - sources.append((screder, craw)) + sources.append((screder, atc)) sources.extend(self.sources(db, screder)) return sources diff --git a/tests/app/cli/test_kli_commands.py b/tests/app/cli/test_kli_commands.py index caac806a2..1fcfd566f 100644 --- a/tests/app/cli/test_kli_commands.py +++ b/tests/app/cli/test_kli_commands.py @@ -17,7 +17,8 @@ def test_standalone_kli_commands(helpers, capsys): assert os.path.isdir("/usr/local/var/keri/ks/test") is False parser = multicommand.create_parser(commands) - args = parser.parse_args(["init", "--name", "test", "--nopasscode", "--salt", habbing.SALT]) + salt = coring.Salter(raw=b'0123456789abcdef').qb64 + args = parser.parse_args(["init", "--name", "test", "--nopasscode", "--salt", salt]) assert args.handler is not None doers = args.handler(args) @@ -46,7 +47,7 @@ def test_standalone_kli_commands(helpers, capsys): directing.runController(doers=doers) # Create transferable identifier - args = parser.parse_args(["incept", "--name", "test", "--alias", "trans", "--file", + args = parser.parse_args(["incept", "--name", "test", "--alias", "trans", "--transferable", "--file", os.path.join(TEST_DIR, "transferable-sample.json")]) assert args.handler is not None doers = args.handler(args) @@ -133,7 +134,7 @@ def test_standalone_kli_commands(helpers, capsys): # Skipping sign and verify, they rely on console output. # Establishment Only - args = parser.parse_args(["incept", "--name", "test", "--alias", "est-only", "--file", + args = parser.parse_args(["incept", "--name", "test", "--alias", "est-only", "--transferable", "--file", os.path.join(TEST_DIR, "estonly-sample.json")]) assert args.handler is not None doers = args.handler(args) @@ -243,7 +244,6 @@ def test_standalone_kli_commands(helpers, capsys): ' "partially-signed-events": [],\n' ' "likely-duplicitous-events": [],\n' ' "missing-registry-escrow": [],\n' - ' "missing-issuer-escrow": [],\n' ' "broken-chain-escrow": [],\n' ' "missing-schema-escrow": []\n' '}\n') @@ -257,7 +257,8 @@ def test_incept_and_rotate_opts(helpers, capsys): assert os.path.isdir("/usr/local/var/keri/ks/test-opts") is False parser = multicommand.create_parser(commands) - args = parser.parse_args(["init", "--name", "test-opts", "--nopasscode", "--salt", habbing.SALT]) + salt = coring.Salter(raw=b'0123456789abcdef').qb64 + args = parser.parse_args(["init", "--name", "test-opts", "--nopasscode", "--salt", salt]) assert args.handler is not None doers = args.handler(args) @@ -266,14 +267,14 @@ def test_incept_and_rotate_opts(helpers, capsys): with existing.existingHby("test-opts") as hby: assert os.path.isdir(hby.db.path) is True - args = parser.parse_args(["incept", "--name", "test-opts", "--alias", "trans-args", "--transferable", "True"]) + args = parser.parse_args(["incept", "--name", "test-opts", "--alias", "trans-args", "--transferable"]) assert args.handler is not None # Attempt to incept without required arg isith with pytest.raises(ValueError): args.handler(args) # Incept with command line arguments - args = parser.parse_args(["incept", "--name", "test-opts", "--alias", "trans-args", "--transferable", "True", + args = parser.parse_args(["incept", "--name", "test-opts", "--alias", "trans-args", "--transferable", "--isith", "1", "--icount", "1", "--nsith", "1", "--ncount", "1", "--toad", "0"]) assert args.handler is not None doers = args.handler(args) diff --git a/tests/app/test_agenting.py b/tests/app/test_agenting.py index ac046a2a8..244549fd3 100644 --- a/tests/app/test_agenting.py +++ b/tests/app/test_agenting.py @@ -8,7 +8,7 @@ from hio.base import doing, tyming from keri import kering -from keri.core import coring +from keri.core import coring, serdering from keri.core.coring import Counter, CtrDex, Seqner from keri.help import nowIso8601 from keri.app import habbing, indirecting, agenting, directing @@ -173,7 +173,7 @@ def testDo(self, tymth, tock=0.0): while True: raw = reger.getTvt(dbing.dgKey(serder.preb, serder.saidb)) if raw: - found = coring.Serder(raw=bytes(raw)) + found = serdering.SerderKERI(raw=bytes(raw)) if found and serder.pre == found.pre: break yield self.tock diff --git a/tests/app/test_challenging.py b/tests/app/test_challenging.py new file mode 100644 index 000000000..c1b0f73cd --- /dev/null +++ b/tests/app/test_challenging.py @@ -0,0 +1,26 @@ +# -*- encoding: utf-8 -*- +""" +tests.app.challenging module + +""" + +from keri.app import habbing, challenging, signaling +from keri.peer import exchanging + + +def test_challenge_handler(): + with habbing.openHab(name="test", temp=True) as (hby, hab): + + signaler = signaling.Signaler() + handler = challenging.ChallengeHandler(db=hab.db, signaler=signaler) + + payload = dict(i=hab.pre, words=["the", "test", "words", "that", "are", "not", "sufficient"]) + exn, _ = exchanging.exchange(route="/challenge/response", payload=payload, sender=hab.pre) + + handler.handle(serder=exn) + + assert len(signaler.signals) == 1 + saids = hab.db.reps.get(keys=(hab.pre,)) + + assert len(saids) == 1 + assert saids[0].qb64 == exn.said diff --git a/tests/app/test_credentials.py b/tests/app/test_credentials.py deleted file mode 100644 index 1320af16a..000000000 --- a/tests/app/test_credentials.py +++ /dev/null @@ -1,269 +0,0 @@ -# -*- encoding: utf-8 -*- -""" -tests.app.kiwiing module - -""" -import json -import os - -import falcon -from falcon import testing -from hio.base import doing - -from keri import kering -from keri.app import habbing, kiwiing, grouping, indirecting, directing, booting, notifying -from keri.core import scheming, coring, eventing, parsing -from keri.db import basing -from keri.vc import proving -from keri.vdr import credentialing, verifying -from tests.app import test_grouping - -TEST_DIR = os.path.dirname(os.path.abspath(__file__)) - - -def loadApp(hby, rgy, verifier, notifier): - app = falcon.App() - - counselor = grouping.Counselor(hby=hby) - registrar = credentialing.Registrar(hby=hby, rgy=rgy, counselor=counselor) - credentialer = credentialing.Credentialer(hby=hby, rgy=rgy, registrar=registrar, verifier=verifier) - mbx = indirecting.MailboxDirector(hby=hby, topics=["/receipt", "/replay", "/credential", "/multisig"], - verifier=verifier) - servery = booting.Servery(port=1234) - doers = kiwiing.loadEnds(hby=hby, - rgy=rgy, - notifier=notifier, - signaler=notifier.signaler, - verifier=verifier, - app=app, path="/", - registrar=registrar, - credentialer=credentialer, - servery=servery, - bootConfig=dict(), - counselor=counselor) - doers.extend([counselor, registrar, credentialer, mbx]) - return app, doers - - -def loadSchema(db): - filepath = os.path.join(TEST_DIR, "schema.json") - with open(filepath) as f: - sed = json.load(f) - _, sad = coring.Saider.saidify(sed, label=coring.Ids.dollar) - - schemer = scheming.Schemer(sed=sed) - assert schemer.said == 'EFgnk_c08WmZGgv9_mpldibRuqFMTQN-rAgtD-TCOwbs' - db.schema.pin(keys=(schemer.said,), val=schemer) - - -def createMbxEndRole(hab, cid, eid, url): - keys = (cid, kering.Roles.mailbox, eid) - - ender = basing.EndpointRecord(allowed=True) # create new record - hab.db.ends.pin(keys=keys, val=ender) - httplocer = basing.LocationRecord(url=url) # create new record - lockeys = (eid, kering.Schemes.http) - hab.db.locs.pin(keys=lockeys, val=httplocer) # overwrite - - -class TestDoer(doing.DoDoer): - - def __init__(self, wanHby, hby1, hab1, hby2, hab2, hby3, hab3, recp): - self.hab1 = hab1 - self.hab2 = hab2 - self.hab3 = hab3 - self.recp = recp - - wanDoers = indirecting.setupWitness(alias="wan", hby=wanHby, tcpPort=5632, httpPort=5642) - wanHab = wanHby.habByName("wan") - # Verify the group identifier was incepted properly and matches the identifiers - assert wanHab.pre == 'BOigXdxpp1r43JhO--czUTwrCXzoWrIwW8i41KWDlr8s' - assert hab1.mhab.pre == 'EH__mobl7NDyyQCB1DoLK-OPSueraPtZAlWEjfOYkaba' - assert hab2.mhab.pre == 'EJPlLivjjHWkkSpvUTT7iewTlG_TolGIpUbAxsK8Dslu' - assert hab3.mhab.pre == 'ECKuCwnnPA3z212QjiWewHv2jQwArMu7HPRBUSXOSqKv' - gid = 'EERn_laF0qwP8zTBGL86LbF84J0Yh2IvQSRskH3BZZiy' - assert hab1.pre == hab2.pre == hab3.pre == gid - assert hab1.name == "test_group1" - assert hab2.name == "test_group2" - assert hab3.name == "test_group3" - - kev1 = eventing.Kevery(db=hab1.db, lax=True, local=False) - kev2 = eventing.Kevery(db=hab2.db, lax=True, local=False) - kev3 = eventing.Kevery(db=hab3.db, lax=True, local=False) - - ricp = recp.makeOwnEvent(sn=0) - parsing.Parser().parse(ims=bytearray(ricp), kvy=kev1) - parsing.Parser().parse(ims=bytearray(ricp), kvy=kev2) - parsing.Parser().parse(ims=bytearray(ricp), kvy=kev3) - - # Set up mailbox end role for each participant using wan - for hab in [hab1, hab2, hab3]: - createMbxEndRole(hab1, hab.mhab.pre, wanHab.pre, "http://127.0.0.1:5642/") - createMbxEndRole(hab2, hab.mhab.pre, wanHab.pre, "http://127.0.0.1:5642/") - createMbxEndRole(hab3, hab.mhab.pre, wanHab.pre, "http://127.0.0.1:5642/") - - createMbxEndRole(hab1, recp.pre, wanHab.pre, "http://127.0.0.1:5642/") - createMbxEndRole(hab2, recp.pre, wanHab.pre, "http://127.0.0.1:5642/") - createMbxEndRole(hab3, recp.pre, wanHab.pre, "http://127.0.0.1:5642/") - - # Create Regery for each participant - self.rgy1 = credentialing.Regery(hby=hby1, name="test_1", temp=True) - self.rgy2 = credentialing.Regery(hby=hby2, name="test_2", temp=True) - self.rgy3 = credentialing.Regery(hby=hby3, name="test_3", temp=True) - - self.notifier1 = notifying.Notifier(hby=hby1) - self.verifier1 = verifying.Verifier(hby=hby1, reger=self.rgy1.reger) - self.notifier2 = notifying.Notifier(hby=hby2) - self.verifier2 = verifying.Verifier(hby=hby2, reger=self.rgy2.reger) - self.notifier3 = notifying.Notifier(hby=hby3) - self.verifier3 = verifying.Verifier(hby=hby3, reger=self.rgy3.reger) - - # Load schema in database for each participant - loadSchema(hby1.db) - loadSchema(hby2.db) - loadSchema(hby3.db) - - # Create falcon app loaded with kiwiing ends for each participant - self.app1, doers1 = loadApp(hby1, self.rgy1, self.verifier1, self.notifier1) - self.app2, doers2 = loadApp(hby2, self.rgy2, self.verifier2, self.notifier2) - self.app3, doers3 = loadApp(hby3, self.rgy3, self.verifier3, self.notifier3) - doers = wanDoers + doers1 + doers2 + doers3 + [doing.doify(self.escrowDo)] - self.toRemove = list(doers) - doers.extend([doing.doify(self.testDo)]) - - super(TestDoer, self).__init__(doers=doers) - - def escrowDo(self, tymth, tock=0.0): - self.wind(tymth) - self.tock = tock - yield self.tock - - while True: - # have each regery process escrows to get the now anchored TEL event committed - self.rgy1.processEscrows() - self.rgy2.processEscrows() - self.rgy3.processEscrows() - - self.verifier1.processEscrows() - self.verifier2.processEscrows() - self.verifier3.processEscrows() - - yield tock - - def testDo(self, tymth, tock=0.0): - self.wind(tymth) - self.tock = tock - yield self.tock - - client1 = testing.TestClient(self.app1) - client2 = testing.TestClient(self.app2) - client3 = testing.TestClient(self.app3) - - # Post to /registries to create a Registry for the multisig identifier - regd = dict(alias=self.hab1.name, - name="vLEI", - nonce="AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s", - baks=[], - estOnly=False, - noBackers=True, - toad=0) - b = json.dumps(regd).encode("utf-8") - response = client1.simulate_post("/registries", body=b) - assert response.status == falcon.HTTP_202 - regd["alias"] = self.hab2.name - b = json.dumps(regd).encode("utf-8") - response = client2.simulate_post("/registries", body=b) - assert response.status == falcon.HTTP_202 - regd["alias"] = self.hab3.name - b = json.dumps(regd).encode("utf-8") - response = client3.simulate_post("/registries", body=b) - assert response.status == falcon.HTTP_202 - - registry = self.rgy1.registryByName("vLEI") - while registry.regk not in self.rgy1.tevers: - yield tock - - registry = self.rgy2.registryByName("vLEI") - while registry.regk not in self.rgy3.tevers: - yield tock - - registry = self.rgy3.registryByName("vLEI") - while registry.regk not in self.rgy3.tevers: - yield tock - - # Let rotate our keys for good hygiene - rotd = dict(aids=[self.hab1.mhab.pre, self.hab2.mhab.pre, self.hab3.mhab.pre]) - b = json.dumps(rotd).encode("utf-8") - response = client1.simulate_post(f"/groups/{self.hab1.name}/rot", body=b) - assert response.status == falcon.HTTP_202 - response = client2.simulate_put(f"/groups/{self.hab2.name}/rot", body=b) - assert response.status == falcon.HTTP_202 - response = client3.simulate_put(f"/groups/{self.hab3.name}/rot", body=b) - assert response.status == falcon.HTTP_202 - - prefixer = self.hab1.kever.prefixer - seqner = coring.Seqner(sn=2) - while self.hab1.db.cgms.get(keys=(prefixer.qb64, seqner.qb64)) is None: - yield tock - assert self.hab1.kever.ilk == coring.Ilks.rot - - while self.hab2.db.cgms.get(keys=(prefixer.qb64, seqner.qb64)) is None: - yield tock - assert self.hab2.kever.ilk == coring.Ilks.rot - - while self.hab2.db.cgms.get(keys=(prefixer.qb64, seqner.qb64)) is None: - yield tock - assert self.hab3.kever.ilk == coring.Ilks.rot - - issd = dict(credentialData=dict(LEI="5493001KJTIIGC8Y1R17"), recipient=self.recp.pre, registry="vLEI", - schema="EFgnk_c08WmZGgv9_mpldibRuqFMTQN-rAgtD-TCOwbs", source={}) - b = json.dumps(issd).encode("utf-8") - response = client1.simulate_post(f"/groups/{self.hab1.name}/credentials", body=b) - assert response.status == falcon.HTTP_200 - credential = response.json - assert credential["a"]["LEI"] == "5493001KJTIIGC8Y1R17" - - issd = dict(credential=credential) - b = json.dumps(issd).encode("utf-8") - response = client2.simulate_put(f"/groups/{self.hab2.name}/credentials", body=b) - assert response.status == falcon.HTTP_200 - response = client3.simulate_put(f"/groups/{self.hab3.name}/credentials", body=b) - assert response.status == falcon.HTTP_200 - - creder = proving.Creder(ked=credential) - while not self.rgy1.reger.saved.get(creder.said): - yield tock - - # Wait for the credential endpoint to notify the completion of the credential issuance - - while len(self.notifier1.getNotes()) < 2 or len(self.notifier2.getNotes()) < 2 \ - or len(self.notifier3.getNotes()) < 2: - yield tock - - notes1 = self.notifier1.getNotes() - assert notes1[0].pad['a']['r'] == "/multisig/rot/complete" - assert notes1[1].pad['a']['r'] == "/multisig/iss/complete" - notes2 = self.notifier2.getNotes() - assert notes2[0].pad['a']['r'] == "/multisig/rot/complete" - assert notes2[1].pad['a']['r'] == "/multisig/iss/complete" - notes3 = self.notifier3.getNotes() - assert notes3[0].pad['a']['r'] == "/multisig/rot/complete" - assert notes3[1].pad['a']['r'] == "/multisig/iss/complete" - - self.remove(self.toRemove) - - return True - - -def test_multisig_issue_agent(): - salt = coring.Salter(raw=b'wann-the-witness').qb64 - with test_grouping.openMultiSig(prefix="test") as ((hby1, hab1), (hby2, hab2), (hby3, hab3)), \ - habbing.openHby(name="wan", salt=salt, temp=True) as wanHby, \ - habbing.openHab(name="recp", transferable=True) as (_, recp): - - testDoer = TestDoer(wanHby, hby1, hab1, hby2, hab2, hby3, hab3, recp) - - # Run all participants - directing.runController(doers=[testDoer], expire=60.0) - - assert testDoer.done is True diff --git a/tests/app/test_delegating.py b/tests/app/test_delegating.py index 6584f5b1d..7742a1d2b 100644 --- a/tests/app/test_delegating.py +++ b/tests/app/test_delegating.py @@ -6,12 +6,10 @@ import time from hio.base import doing, tyming -import keri.app.oobiing from keri import kering from keri.app import habbing, delegating, indirecting, agenting, notifying from keri.core import eventing, parsing, coring from keri.db import dbing -from keri.peer import exchanging def test_boatswain(seeder): @@ -20,12 +18,12 @@ def test_boatswain(seeder): habbing.openHby(name="del", salt=coring.Salter(raw=b'0123456789ghijkl').qb64) as delHby: wesDoers = indirecting.setupWitness(alias="wes", hby=wesHby, tcpPort=5634, httpPort=5644) - witDoer = agenting.WitnessReceiptor(hby=palHby) - bts = delegating.Boatswain(hby=delHby) + witDoer = agenting.Receiptor(hby=palHby) + bts = delegating.Sealer(hby=delHby) wesHab = wesHby.habByName(name="wes") - seeder.seedWitEnds(palHby.db, witHabs=[wesHab], protocols=[kering.Schemes.tcp]) - seeder.seedWitEnds(delHby.db, witHabs=[wesHab], protocols=[kering.Schemes.tcp]) + seeder.seedWitEnds(palHby.db, witHabs=[wesHab], protocols=[kering.Schemes.http]) + seeder.seedWitEnds(delHby.db, witHabs=[wesHab], protocols=[kering.Schemes.http]) opts = dict( wesHab=wesHab, @@ -78,8 +76,8 @@ def boatswain_test_do(tymth=None, tock=0.0, **opts): witDoer.msgs.append(dict(pre=palHab.pre)) while not witDoer.cues: yield tock - witDoer.cues.popleft() + witDoer.cues.popleft() msg = next(wesHab.db.clonePreIter(pre=palHab.pre)) kvy = eventing.Kevery(db=delHby.db, local=False) parsing.Parser().parseOne(ims=bytearray(msg), kvy=kvy) @@ -87,13 +85,16 @@ def boatswain_test_do(tymth=None, tock=0.0, **opts): while palHab.pre not in delHby.kevers: yield tock + proxyHab = delHby.makeHab(name="proxy", icount=1, isith='1', ncount=1, nsith='1', + wits=[wesHab.pre]) + assert proxyHab.pre == "EIQ9wnMWGxZHlontoBMp5-GPyVecLL99XrCVxmTCO22b" + delHab = delHby.makeHab(name="del", icount=1, isith='1', ncount=1, nsith='1', wits=[wesHab.pre], delpre=palHab.pre) assert delHab.pre == "EGyXT1FmEeI05xmaBsYs2H4v8bazCy-JClB21rAfvXZu" - bts.msgs.append(dict(pre=delHab.pre)) - + bts.delegation(pre=delHab.pre, proxy=proxyHab) palHab.rotate(data=[dict(i=delHab.pre, s="0", d=delHab.kever.serder.said)]) witDoer.msgs.append(dict(pre=palHab.pre)) while not witDoer.cues: @@ -114,127 +115,43 @@ def boatswain_test_do(tymth=None, tock=0.0, **opts): yield tock -def test_boatswain_proxy(): - with habbing.openHby(name="deltest", temp=True) as eeHby, \ - habbing.openHby(name="deltest", temp=True) as orHby: - orHab = orHby.makeHab("delegator", transferable=True) - assert orHab.pre == "EKL3to0Q059vtxKi7wWmaNFJ3NKE1nQsOPasRXqPzpjS" - eeHab = eeHby.makeHab("del", transferable=True, delpre=orHab.pre, - wits=["BGKVzj4ve0VSd8z_AmvhLg4lqcC_9WYX90k03q-R_Ydo", - "BAyRFMideczFZoapylLIyCjSdhtqVb31wZkRKvPfNqkw", - "BBoq68HCmYNUDgOz4Skvlu306o_NY-NrYuKAVhk3Zh9c"] - - ) - assert eeHab.pre == 'EAszJpIhVoFsTw_fqOXT7N0yQbyPS-S1LV3FGvqUVcye' - - boats = delegating.Boatswain(hby=eeHby) - phab = boats.proxy("deltest", eeHab.kever) - - assert phab.pre == 'EJXiQ33u2yXCtkH7UImC4D-RverPqvshuTlJyaAybKi4' - assert phab.kever.wits == eeHab.kever.wits - assert phab.kever.toader.num == eeHab.kever.toader.num - assert phab.kever.tholder.sith == eeHab.kever.tholder.sith - - def test_delegation_request(mockHelpingNowUTC): with habbing.openHab(name="test", temp=True) as (hby, hab): delpre = "EArzbTSWjccrTdNRsFUUfwaJ2dpYxu9_5jI2PJ-TRri0" serder = eventing.delcept(keys=["DUEFuPeaDH2TySI-wX7CY_uW5FF41LRu3a59jxg1_pMs"], delpre=delpre, ndigs=["DLONLed3zFEWa0p21fvi1Jf5-x-EoyEPqFvOki3YhP1k"]) - exn, atc = delegating.delegateRequestExn(hab=hab, delpre=delpre, ked=serder.ked) + evt = hab.endorse(serder=serder) + exn, atc = delegating.delegateRequestExn(hab=hab, delpre=delpre, evt=evt) + + assert atc == (b'-FABEIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI30AAAAAAAAAAAAAAA' + b'AAAAAAAAEIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3-AABAADECnBl' + b'0c14SVi7Keh__sd1PVhinSy-itPr33ZxvSjJYFastqXw9ZTFGNKsY6iALUk5xP3S' + b'399tJrPFe7PtuNAN') assert exn.ked["r"] == '/delegate/request' - assert exn.saidb == b'EMj7eSEtgYjkjLPwBFelUX6I2RMzSudhqdDwzgofHhGn' - assert atc == (b'-HABEIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3-AABAACf_qKy8TCn' - b'K_2xoBzBZeGRd_bzUj8WAsIXKRAy7bmf881bLLi0KyjLDmdZ4YvEd2i-aG7qn6nI' - b'9QXT8vApFtsP') + assert exn.saidb == b'EOiDc2wEmhHc7sbLG64y2gveCIRlFe4BuISaz0mlOuZz' + assert atc == (b'-FABEIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI30AAAAAAAAAAAAAAA' + b'AAAAAAAAEIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3-AABAADECnBl' + b'0c14SVi7Keh__sd1PVhinSy-itPr33ZxvSjJYFastqXw9ZTFGNKsY6iALUk5xP3S' + b'399tJrPFe7PtuNAN') data = exn.ked["a"] assert data["delpre"] == delpre - assert data["ked"] == serder.ked + embeds = exn.ked['e'] + assert embeds["evt"] == serder.ked def test_delegation_request_handler(mockHelpingNowUTC): with habbing.openHab(name="test", temp=True) as (hby, hab): - src = "EfrzbTSWjccrTdNRsFUUfwaJ2dpYxu9_5jI2PJ-TRri0" - ctrl = "EIwLgWhrDj2WI4WCiArWVAYsarrP-B48OM4T6_Wk6BLs" serder = eventing.delcept(keys=["DUEFuPeaDH2TySI-wX7CY_uW5FF41LRu3a59jxg1_pMs"], delpre=hab.pre, ndigs=["DLONLed3zFEWa0p21fvi1Jf5-x-EoyEPqFvOki3YhP1k"]) + evt = hab.endorse(serder=serder) notifier = notifying.Notifier(hby=hby) handler = delegating.DelegateRequestHandler(hby=hby, notifier=notifier) + exn, _ = delegating.delegateRequestExn(hab, hab.pre, evt=evt) - # Pass message missing keys: - handler.msgs.append(dict(name="value")) - handler.msgs.append(dict(pre=hab.kever.prefixer)) - handler.msgs.append(dict(pre=hab.kever.prefixer, payload=dict(delpre=hab.pre))) - handler.msgs.append(dict(pre=hab.kever.prefixer, payload=dict(delpre=src, ked=serder.ked))) - handler.msgs.append(dict(pre=hab.kever.prefixer, payload=dict(delpre=hab.pre, ked=serder.ked))) - limit = 1.0 - tock = 0.03125 - doist = doing.Doist(tock=tock, limit=limit, doers=[handler]) - doist.enter() - - tymer = tyming.Tymer(tymth=doist.tymen(), duration=doist.limit) - - while not tymer.expired: - doist.recur() - time.sleep(doist.tock) - - assert doist.limit == limit - doist.exit() + handler.handle(serder=exn) assert len(notifier.getNotes()) == 1 - - with habbing.openHab(name="test", temp=True) as (hby, hab): - - src = "EfrzbTSWjccrTdNRsFUUfwaJ2dpYxu9_5jI2PJ-TRri0" - ctrl = "EIwLgWhrDj2WI4WCiArWVAYsarrP-B48OM4T6_Wk6BLs" - serder = eventing.delcept(keys=["DUEFuPeaDH2TySI-wX7CY_uW5FF41LRu3a59jxg1_pMs"], delpre=hab.pre, - ndigs=["DLONLed3zFEWa0p21fvi1Jf5-x-EoyEPqFvOki3YhP1k"]) - - exn, atc = delegating.delegateRequestExn(hab=hab, delpre=hab.pre, ked=serder.ked) - - notifier = notifying.Notifier(hby=hby) - exc = exchanging.Exchanger(db=hby.db, handlers=[]) - oobiery = keri.app.oobiing.Oobiery(hby=hby) - - delegating.loadHandlers(hby=hby, exc=exc, notifier=notifier) - - ims = bytearray(exn.raw) - ims.extend(atc) - parsing.Parser().parseOne(ims=ims, exc=exc) - - limit = 1.0 - tock = 0.03125 - doist = doing.Doist(tock=tock, limit=limit, doers=[exc]) - doist.enter() - - tymer = tyming.Tymer(tymth=doist.tymen(), duration=doist.limit) - - while not tymer.expired: - doist.recur() - time.sleep(doist.tock) - - assert doist.limit == limit - doist.exit() - - notes = notifier.getNotes() - assert len(notes) == 1 - note = notes[0] - assert note.pad['a']['r'] == '/delegate/request' - assert note.pad['a']['ked'] == {'a': [], - 'b': [], - 'bt': '0', - 'c': [], - 'd': 'EAaXhAxAYiaJidAKLd4r1j_6gN3GTC-pP3UZmECnIEKv', - 'di': 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3', - 'i': 'EAaXhAxAYiaJidAKLd4r1j_6gN3GTC-pP3UZmECnIEKv', - 'k': ['DUEFuPeaDH2TySI-wX7CY_uW5FF41LRu3a59jxg1_pMs'], - 'kt': '1', - 'n': ['DLONLed3zFEWa0p21fvi1Jf5-x-EoyEPqFvOki3YhP1k'], - 'nt': '1', - 's': '0', - 't': 'dip', - 'v': 'KERI10JSON00015f_'} diff --git a/tests/app/test_forwarding.py b/tests/app/test_forwarding.py index 3c1209339..7f94ae439 100644 --- a/tests/app/test_forwarding.py +++ b/tests/app/test_forwarding.py @@ -4,20 +4,19 @@ tests.app.forwarding module """ - import time from hio.base import doing, tyming from keri.app import forwarding, habbing, indirecting, storing -from keri.core import coring, eventing, parsing +from keri.core import coring, eventing, parsing, serdering from keri.peer import exchanging def test_postman(seeder): with habbing.openHab(name="test", transferable=True, temp=True) as (hby, hab), \ habbing.openHby(name="wes", salt=coring.Salter(raw=b'wess-the-witness').qb64, temp=True) as wesHby, \ - habbing.openHby(name="repTest", temp=True) as recpHby: + habbing.openHby(name="repTest", temp=True) as recpHby: mbx = storing.Mailboxer(name="wes", temp=True) wesDoers = indirecting.setupWitness(alias="wes", hby=wesHby, mbx=mbx, tcpPort=5634, httpPort=5644) @@ -33,7 +32,7 @@ def test_postman(seeder): parsing.Parser().parse(ims=bytearray(recpIcp), kvy=wesKvy) assert recpHab.pre in wesKvy.kevers - serder = coring.Serder(raw=recpIcp) + serder = serdering.SerderKERI(raw=recpIcp) rct = wesHab.receipt(serder) kvy = eventing.Kevery(db=hab.db) @@ -42,10 +41,10 @@ def test_postman(seeder): kvy.processEscrows() assert recpHab.pre in kvy.kevers - pman = forwarding.Postman(hby=hby) + pman = forwarding.Poster(hby=hby) - exn = exchanging.exchange(route="/echo", payload=dict(msg="test")) - atc = hab.endorse(exn) + exn, _ = exchanging.exchange(route="/echo", payload=dict(msg="test"), sender=hab.pre) + atc = hab.endorse(exn, last=False) del atc[:exn.size] pman.send(src=hab.pre, dest=recpHab.pre, topic="echo", serder=exn, attachment=atc) @@ -70,7 +69,7 @@ def test_postman(seeder): msgs.append(msg) assert len(msgs) == 1 - serder = coring.Serder(raw=msgs[0]) + serder = serdering.SerderKERI(raw=msgs[0]) assert serder.ked["t"] == coring.Ilks.exn assert serder.ked["r"] == "/echo" assert serder.ked["a"] == dict(msg="test") @@ -78,22 +77,6 @@ def test_postman(seeder): def test_forward_handler(): with habbing.openHab(name="test", transferable=True, temp=True) as (hby, hab): - mbx = storing.Mailboxer() forwarder = forwarding.ForwardHandler(hby=hby, mbx=mbx) - - limit = 1.0 - tock = 0.03125 - doist = doing.Doist(tock=tock, limit=limit, doers=[forwarder]) - doist.enter() - - tymer = tyming.Tymer(tymth=doist.tymen(), duration=doist.limit) - - while not tymer.expired: - doist.recur() - time.sleep(doist.tock) - - assert doist.limit == limit - - doist.exit() - + # TODO: implement a real test here diff --git a/tests/app/test_grouping.py b/tests/app/test_grouping.py index 1df30cb2b..5df6a805b 100644 --- a/tests/app/test_grouping.py +++ b/tests/app/test_grouping.py @@ -5,12 +5,11 @@ """ from contextlib import contextmanager -import time -from hio.base import doing, tyming from keri.app import habbing, grouping, notifying -from keri.core import coring, eventing, parsing -from keri.db import dbing, basing +from keri.core import coring, eventing, parsing, serdering +from keri.vdr import eventing as veventing +from keri.db import dbing from keri.peer import exchanging @@ -50,18 +49,7 @@ def test_counselor(): # Send to Counselor to post process through escrows counselor.start(prefixer=prefixer, seqner=seqner, saider=saider, - mid=hab1.pre, smids=smids, rmids=rmids) - assert len(counselor.postman.evts) == 2 # Send my event to other participants - evt = counselor.postman.evts.popleft() - assert evt["src"] == "EOzS8kvK5AM0O9Qwub8wDVAmuetGCtUYVOQC6vpqbLQa" - assert evt["dest"] == "EHTApV7zY0866EBv6891tN19uM9TnbwpvV0JzcWu1DVY" - assert evt["serder"].raw == (b'{"v":"KERI10JSON000207_","t":"icp","d":"ENuUR3YvSR2-dFoN1zBN2p8W9BvsySnrY6g2' - b'vDS1EVAS","i":"ENuUR3YvSR2-dFoN1zBN2p8W9BvsySnrY6g2vDS1EVAS","s":"0","kt":["' - b'1/2","1/2","1/2"],"k":["DEXdkHRR2Nspj5czsFvKOa-ZnGzMMFG5MLaBle19aJ9j","DL4SF' - b'zA89ls_auIqISf4UbSQGxNPc9y8Z2UrPDZupEsM","DERxxjBQUD4nGiaioBlqg8qpkRjJLGMe67' - b'OPdVsHFarQ"],"nt":["1/2","1/2","1/2"],"n":["EKMBA8Q1uP3WshghLR_r6MjYwVEids8y' - b'Kb_03w8FOOFO","EHV8V6dj_VXvXZFUwMTT4yUy40kw5uYMXnFxoh_KZmos","EMUrvGYprwKm77' - b'Oju22TlcoAEhL9QnnYfOBFPO1IyJUn"],"bt":"0","b":[],"c":[],"a":[]}') + ghab=ghab) (seqner, saider) = hby1.db.gpse.getLast(keys=(ghab.pre,)) # Escrowed the event for sigs assert seqner.sn == 0 assert saider.qb64 == "ENuUR3YvSR2-dFoN1zBN2p8W9BvsySnrY6g2vDS1EVAS" @@ -69,7 +57,7 @@ def test_counselor(): # Sith 2 so create second signature to get past the first escrow ghab2 = hby2.makeGroupHab(group=f"{prefix}_group2", mhab=hab2, smids=smids, rmids=rmids, **inits) - evt = grouping.getEscrowedEvent(hab2.db, ghab2.pre, 0) + evt = ghab2.makeOwnInception(allowPartiallySigned=True) assert evt == (b'{"v":"KERI10JSON000207_","t":"icp","d":"ENuUR3YvSR2-dFoN1zBN2p8W' b'9BvsySnrY6g2vDS1EVAS","i":"ENuUR3YvSR2-dFoN1zBN2p8W9BvsySnrY6g2v' b'DS1EVAS","s":"0","kt":["1/2","1/2","1/2"],"k":["DEXdkHRR2Nspj5cz' @@ -88,42 +76,18 @@ def test_counselor(): val = hby1.db.gpse.getLast(keys=(ghab.pre,)) # thold met, partial sig escrow should be empty assert val is None assert counselor.complete(prefixer=prefixer, seqner=seqner, saider=saider) - counselor.postman.evts.popleft() # First Partial Rotation - smids = [hab1.pre, hab2.pre] - rmids = [hab1.pre, hab2.pre] # need to fix - counselor.rotate(ghab=ghab, smids=smids, nsith="2", isith="2", rmids=rmids, toad=0, cuts=list(), adds=list()) - rec = hby1.db.glwe.get(keys=(ghab.pre,)) - assert rec is not None - assert rec.smids == smids - assert rec.nsith == "2" - assert rec.toad == 0 - - counselor.processEscrows() # process escrows to get witness-less event to next step - rec = hby1.db.glwe.get(keys=(ghab.pre,)) - assert rec is None - assert len(counselor.postman.evts) == 1 - evt = counselor.postman.evts.popleft() - assert evt["src"] == hab1.pre - assert evt["dest"] == hab2.pre - assert evt["topic"] == "multisig" - assert evt["serder"].raw == (b'{"v":"KERI10JSON000160_","t":"rot","d":"EEX9vGqk8FJbe-pSusdW-t6dtTyPeOgtR8Cd' - b'hue6LgY7","i":"EOzS8kvK5AM0O9Qwub8wDVAmuetGCtUYVOQC6vpqbLQa","s":"1","p":"EO' - b'zS8kvK5AM0O9Qwub8wDVAmuetGCtUYVOQC6vpqbLQa","kt":"1","k":["DEbwF934m5TjdQbC1' - b'8jSmk2CcPO7xzAemzePy4LKnA_U"],"nt":"1","n":["EBOgQ1MOWQ2eWIqDuqjinhh3L3O5qHP' - b'EZ08zMICPhPTw"],"bt":"0","br":[],"ba":[],"a":[]}') - rec = hby1.db.gpae.get(keys=(ghab.pre,)) - assert rec is not None - assert rec.smids == smids - - # rotate second identifiter in group, process escrows to generate group rotation event. + hab1.rotate() hab2.rotate() - rot = hab2.makeOwnEvent(sn=1) - parsing.Parser().parse(ims=bytearray(rot), kvy=kev1) # parse rotation - counselor.processEscrows() # second identifier has rotated, second stage clear - rec = hby1.db.gpae.get(keys=(ghab.pre,)) - assert rec is None + merfers = [hab1.kever.verfers[0], hab2.kever.verfers[0]] + migers = [hab1.kever.ndigers[0], hab2.kever.ndigers[0]] + prefixer = coring.Prefixer(qb64=ghab.pre) + seqner = coring.Seqner(sn=ghab.kever.sn + 1) + rot = ghab.rotate(isith="2", nsith="2", toad=0, cuts=list(), adds=list(), verfers=merfers, digers=migers) + rserder = serdering.SerderKERI(raw=rot) + + counselor.start(ghab=ghab, prefixer=prefixer, seqner=seqner, saider=coring.Saider(qb64=rserder.said)) # partially signed group rotation val = hby1.db.gpse.get(keys=(ghab.pre,)) @@ -139,7 +103,7 @@ def test_counselor(): b',"nt":"2","n":["EBOgQ1MOWQ2eWIqDuqjinhh3L3O5qHPEZ08zMICPhPTw","EGyO8jUZpLIlA' b'CoeLmfUzvE3mnxmcU2m_nyKfSDfpxV4"],"bt":"0","br":[],"ba":[],"a":[]}') - serder = coring.Serder(raw=bytes(evt)) + serder = serdering.SerderKERI(raw=bytes(evt)) sigers = hab2.mgr.sign(serder.raw, verfers=hab2.kever.verfers, indexed=True, indices=[1], ondices=[1]) msg = eventing.messagize(serder=serder, sigers=sigers) assert msg == (b'{"v":"KERI10JSON0001be_","t":"rot","d":"EFWaDXMVIhIMpsXMOcnXhU0t' @@ -162,47 +126,23 @@ def test_counselor(): # Validate successful partial rotation nkeys = [hab1.kever.verfers[0].qb64, hab2.kever.verfers[0].qb64] - ndigs = [hab1.kever.digers[0].qb64, hab2.kever.digers[0].qb64] + ndigs = [hab1.kever.ndigers[0].qb64, hab2.kever.ndigers[0].qb64] assert ghab.kever.sn == 1 assert [verfer.qb64 for verfer in ghab.kever.verfers] == nkeys - assert [diger.qb64 for diger in ghab.kever.digers] == ndigs - - counselor.postman.evts.clear() # Clear out postman for next rotation + assert [diger.qb64 for diger in ghab.kever.ndigers] == ndigs # Second Partial Rotation - smids = [hab1.pre, hab2.pre] - rmids = [hab1.pre, hab2.pre, hab3.pre] - counselor.rotate(ghab=ghab, smids=smids, rmids=rmids, toad=0, cuts=list(), adds=list()) - rec = hby1.db.glwe.get(keys=(ghab.pre,)) - assert rec is not None - assert rec.smids == smids - assert rec.nsith is None - assert rec.toad == 0 - - counselor.processEscrows() # process escrows to get witness-less event to next step - rec = hby1.db.glwe.get(keys=(ghab.pre,)) - assert rec is None - assert len(counselor.postman.evts) == 2 - evt = counselor.postman.evts.popleft() - assert evt["src"] == hab1.pre - assert evt["dest"] == hab2.pre - assert evt["topic"] == "multisig" - assert evt["serder"].raw == (b'{"v":"KERI10JSON000160_","t":"rot","d":"EPX4RtZs7_HHlxYqV5nXC2odIvMEJJpR_BDk' - b'KZs2GnkR","i":"EOzS8kvK5AM0O9Qwub8wDVAmuetGCtUYVOQC6vpqbLQa","s":"2","p":"EE' - b'X9vGqk8FJbe-pSusdW-t6dtTyPeOgtR8Cdhue6LgY7","kt":"1","k":["DK-j3FspSlqvjM0v9' - b'nRUbgog54vminulol46VO1dDSAP"],"nt":"1","n":["EHMdUV5PuMt37ooqo1nW5DXkYC_lQXj' - b'qgXY4V7GaWrAJ"],"bt":"0","br":[],"ba":[],"a":[]}') - rec = hby1.db.gpae.get(keys=(ghab.pre,)) - assert rec is not None - assert rec.smids == smids - - # rotate second identifiter in group, process escrows to generate group rotation event. + + hab1.rotate() hab2.rotate() - rot = hab2.makeOwnEvent(sn=2) - parsing.Parser().parse(ims=bytearray(rot), kvy=kev1) # parse rotation - counselor.processEscrows() # second identifier has rotated, second stage clear - rec = hby1.db.gpae.get(keys=(ghab.pre,)) - assert rec is None + merfers = [hab1.kever.verfers[0], hab2.kever.verfers[0]] + migers = [hab1.kever.ndigers[0], hab2.kever.ndigers[0], hab3.kever.ndigers[0]] + prefixer = coring.Prefixer(qb64=ghab.pre) + seqner = coring.Seqner(sn=ghab.kever.sn + 1) + rot = ghab.rotate(isith="2", nsith="2", toad=0, cuts=list(), adds=list(), verfers=merfers, digers=migers) + rserder = serdering.SerderKERI(raw=rot) + + counselor.start(ghab=ghab, prefixer=prefixer, seqner=seqner, saider=coring.Saider(qb64=rserder.said)) # partially signed group rotation val = hby1.db.gpse.get(keys=(ghab.pre,)) @@ -219,7 +159,7 @@ def test_counselor(): b'4KrWvInrg8gW3KbcYKiGceWFtwDfxmV","EMUrvGYprwKm77Oju22TlcoAEhL9QnnYfOBFPO1IyJ' b'Un"],"bt":"0","br":[],"ba":[],"a":[]}') - serder = coring.Serder(raw=bytes(evt)) + serder = serdering.SerderKERI(raw=bytes(evt)) sigers = hab2.mgr.sign(serder.raw, verfers=hab2.kever.verfers, indexed=True, indices=[1]) msg = eventing.messagize(serder=serder, sigers=sigers) assert msg == (b'{"v":"KERI10JSON0001ed_","t":"rot","d":"EAFmW50FmBfJXp4sPnYBp51L' @@ -243,47 +183,22 @@ def test_counselor(): # Validate successful partial rotation nkeys = [hab1.kever.verfers[0].qb64, hab2.kever.verfers[0].qb64] - ndigs = [hab1.kever.digers[0].qb64, hab2.kever.digers[0].qb64, hab3.kever.digers[0].qb64] + ndigs = [hab1.kever.ndigers[0].qb64, hab2.kever.ndigers[0].qb64, hab3.kever.ndigers[0].qb64] assert ghab.kever.sn == 2 assert [verfer.qb64 for verfer in ghab.kever.verfers] == nkeys - assert [diger.qb64 for diger in ghab.kever.digers] == ndigs - - counselor.postman.evts.clear() # Clear out postman for next rotation + assert [diger.qb64 for diger in ghab.kever.ndigers] == ndigs # Third Partial Rotation with Recovery - smids = [hab1.pre, hab3.pre] - rmids = smids - counselor.rotate(ghab=ghab, smids=smids, rmids=rmids, toad=0, cuts=list(), adds=list()) - rec = hby1.db.glwe.get(keys=(ghab.pre,)) - assert rec is not None - assert rec.smids == smids - assert rec.nsith is None - assert rec.toad == 0 - - counselor.processEscrows() # process escrows to get witness-less event to next step - rec = hby1.db.glwe.get(keys=(ghab.pre,)) - assert rec is None - assert len(counselor.postman.evts) == 1 - evt = counselor.postman.evts.popleft() - assert evt["src"] == hab1.pre - assert evt["dest"] == hab3.pre - assert evt["topic"] == "multisig" - assert evt["serder"].raw == (b'{"v":"KERI10JSON000160_","t":"rot","d":"EAgOz6WCuULYu0JKkLIZvFqy8NWEiSgy0jwL' - b'KpVKo3BH","i":"EOzS8kvK5AM0O9Qwub8wDVAmuetGCtUYVOQC6vpqbLQa","s":"3","p":"EP' - b'X4RtZs7_HHlxYqV5nXC2odIvMEJJpR_BDkKZs2GnkR","kt":"1","k":["DE_7Y-c-xZXLb7Tcl' - b'Inn6Q6hRbiYuaTTDqZGmBNjvVXA"],"nt":"1","n":["ELyh1BXGM7C0jfx3x-k8f1GLx9mIRHz' - b'Fq3tiZgc9N5Vm"],"bt":"0","br":[],"ba":[],"a":[]}') - rec = hby1.db.gpae.get(keys=(ghab.pre,)) - assert rec is not None - assert rec.smids == smids - - # rotate second identifiter in group, process escrows to generate group rotation event. + hab1.rotate() hab3.rotate() - rot = hab3.makeOwnEvent(sn=1) - parsing.Parser().parse(ims=bytearray(rot), kvy=kev1) # parse rotation - counselor.processEscrows() # second identifier has rotated, second stage clear - rec = hby1.db.gpae.get(keys=(ghab.pre,)) - assert rec is None + merfers = [hab1.kever.verfers[0], hab3.kever.verfers[0]] + migers = [hab1.kever.ndigers[0], hab3.kever.ndigers[0]] + prefixer = coring.Prefixer(qb64=ghab.pre) + seqner = coring.Seqner(sn=ghab.kever.sn + 1) + rot = ghab.rotate(isith="2", nsith="2", toad=0, cuts=list(), adds=list(), verfers=merfers, digers=migers) + rserder = serdering.SerderKERI(raw=rot) + + counselor.start(ghab=ghab, prefixer=prefixer, seqner=seqner, saider=coring.Saider(qb64=rserder.said)) # partially signed group rotation val = hby1.db.gpse.get(keys=(ghab.pre,)) @@ -299,7 +214,7 @@ def test_counselor(): b',"nt":"2","n":["ELyh1BXGM7C0jfx3x-k8f1GLx9mIRHzFq3tiZgc9N5Vm","EH0h1byPWpTfi' b'MUcnk_nbeS4HEfnS_j0q2TAJAeIkFlu"],"bt":"0","br":[],"ba":[],"a":[]}') - serder = coring.Serder(raw=bytes(evt)) + serder = serdering.SerderKERI(raw=bytes(evt)) sigers = hab3.mgr.sign(serder.raw, verfers=hab3.kever.verfers, indexed=True, indices=[1], ondices=[2]) msg = eventing.messagize(serder=serder, sigers=sigers) assert msg == (b'{"v":"KERI10JSON0001be_","t":"rot","d":"EEQVk2x7-t_fnYNoOzeZppvI' @@ -334,7 +249,6 @@ def test_the_seven(): counselor = grouping.Counselor(hby=hby1) # All the Habs, this will come in handy later - habs =[hab1, hab2, hab3, hab4, hab5, hab6, hab7] # Keverys so we can process each other's inception messages. kev1 = eventing.Kevery(db=hab1.db, lax=True, local=False) kev2 = eventing.Kevery(db=hab2.db, lax=True, local=False) @@ -373,8 +287,7 @@ def test_the_seven(): saider = coring.Saider(qb64=prefixer.qb64) # Send to Counselor to post process through escrows - counselor.start(prefixer=prefixer, seqner=seqner, saider=saider, - mid=hab1.pre, smids=smids, rmids=rmids) + counselor.start(prefixer=prefixer, seqner=seqner, saider=saider, ghab=ghab) raw = (b'{"v":"KERI10JSON0003af_","t":"icp","d":"EL-f5D0esAFbZTzK9W3wtTgDmncye9IOnF0Z' b'8gRdICIU","i":"EL-f5D0esAFbZTzK9W3wtTgDmncye9IOnF0Z8gRdICIU","s":"0","kt":["' b'1/3","1/3","1/3","1/3","1/3","1/3","1/3"],"k":["DEXdkHRR2Nspj5czsFvKOa-ZnGzM' @@ -388,11 +301,6 @@ def test_the_seven(): b'IWGHla17X","EHsPjPxkY00PW0IG3n834sBYqaLGWat9KKh-7qNSvH5O","EF9BqvXiUmAMpLVtx' b'CQ0m9BD3kwlzM6hx-jrI1CAt96R","EOKRgzqsueblcnkIrJhInqlpOwq8BVZCfJ7jBJ88Rt2Q"]' b',"bt":"0","b":[],"c":[],"a":[]}') - assert len(counselor.postman.evts) == 6 # Send my event to other participants - evt = counselor.postman.evts.popleft() - assert evt["src"] == "EOzS8kvK5AM0O9Qwub8wDVAmuetGCtUYVOQC6vpqbLQa" - assert evt["dest"] == "EHTApV7zY0866EBv6891tN19uM9TnbwpvV0JzcWu1DVY" - assert evt["serder"].raw == raw (seqner, saider) = hby1.db.gpse.getLast(keys=(ghab.pre,)) # Escrowed the event for sigs assert seqner.sn == 0 assert saider.qb64 == "EL-f5D0esAFbZTzK9W3wtTgDmncye9IOnF0Z8gRdICIU" @@ -400,8 +308,8 @@ def test_the_seven(): # Get participation from everyone on inception ghab2 = hby2.makeGroupHab(group=f"{prefix}_group2", mhab=hab2, smids=smids, rmids=rmids, **inits) - evt = grouping.getEscrowedEvent(hab2.db, ghab2.pre, 0) - serd = coring.Serder(raw=bytearray(evt)) + evt = ghab2.makeOwnInception(allowPartiallySigned=True) + serd = serdering.SerderKERI(raw=bytearray(evt)) assert evt[serd.size:] == (b'-AABBBAD108k4sWtYRv8jQaRbzX6kDebjdzFNVCh3N9cOAJqXV5IzmKdi60Cr0Eu' b'MaACskw0FCi73V2VX8BgFlxO8VIK') assert serd.raw == raw @@ -409,8 +317,8 @@ def test_the_seven(): ghab3 = hby3.makeGroupHab(group=f"{prefix}_group3", mhab=hab3, smids=smids, rmids=rmids, **inits) - evt = grouping.getEscrowedEvent(hab3.db, ghab3.pre, 0) - serd = coring.Serder(raw=bytearray(evt)) + evt = ghab3.makeOwnInception(allowPartiallySigned=True) + serd = serdering.SerderKERI(raw=bytearray(evt)) assert evt[serd.size:] == (b'-AABBCD6V2UkAovhY07MrJUNb-ICddDoyLde9i0FWclxfs7jes01YUEihfgbGERF' b'dKDR4kSr4WF3AskrZOPvMuXipAgP') assert serd.raw == raw @@ -418,8 +326,8 @@ def test_the_seven(): ghab4 = hby4.makeGroupHab(group=f"{prefix}_group4", mhab=hab4, smids=smids, rmids=rmids, **inits) - evt = grouping.getEscrowedEvent(hab4.db, ghab4.pre, 0) - serd = coring.Serder(raw=bytearray(evt)) + evt = ghab4.makeOwnInception(allowPartiallySigned=True) + serd = serdering.SerderKERI(raw=bytearray(evt)) assert evt[serd.size:] == (b'-AABBDBCZuZSFWy0tFshGny1pTR47GphDljd0SShmGRpUSpBX_BeHB1tdIObizaA' b'4GMoOcZ2sOWIe6muJPF_RaoKedYE') assert serd.raw == raw @@ -427,8 +335,8 @@ def test_the_seven(): ghab5 = hby5.makeGroupHab(group=f"{prefix}_group5", mhab=hab5, smids=smids, rmids=rmids, **inits) - evt = grouping.getEscrowedEvent(hab5.db, ghab5.pre, 0) - serd = coring.Serder(raw=bytearray(evt)) + evt = ghab5.makeOwnInception(allowPartiallySigned=True) + serd = serdering.SerderKERI(raw=bytearray(evt)) assert evt[serd.size:] == (b'-AABBEBsR6_hPId3H8fFG8EfevQVji8MsLAC72MjkkRxJp3h9v1vyFS1hAGGGxno' b'F5xSHOnpBpPwjMJwOCurAa3VrNAD') assert serd.raw == raw @@ -436,8 +344,8 @@ def test_the_seven(): ghab6 = hby6.makeGroupHab(group=f"{prefix}_group6", mhab=hab6, smids=smids, rmids=rmids, **inits) - evt = grouping.getEscrowedEvent(hab6.db, ghab6.pre, 0) - serd = coring.Serder(raw=bytearray(evt)) + evt = ghab6.makeOwnInception(allowPartiallySigned=True) + serd = serdering.SerderKERI(raw=bytearray(evt)) assert evt[serd.size:] == (b'-AABBFCi5hK6Ax4aBNsdoUkh7Q_CcSWJfpwkeF68aCO34J3BDN7k483lOxiyj6pl' b'8TQIQ7VJLBkoRscUMi_mls9jbpcD') assert serd.raw == raw @@ -445,8 +353,8 @@ def test_the_seven(): ghab7 = hby7.makeGroupHab(group=f"{prefix}_group7", mhab=hab7, smids=smids, rmids=rmids, **inits) - evt = grouping.getEscrowedEvent(hab7.db, ghab7.pre, 0) - serd = coring.Serder(raw=bytearray(evt)) + evt = ghab7.makeOwnInception(allowPartiallySigned=True) + serd = serdering.SerderKERI(raw=bytearray(evt)) assert evt[serd.size:] == (b'-AABBGCtPvRj00vEfT5Po6eH50DWfBWwAcQgvBaJ7LlYT7kQswkl_r-K9Lsxi5tm' b'Pvsb2xFtcMJkFf-BxamGhFo9OOcD') assert serd.raw == raw @@ -458,50 +366,21 @@ def test_the_seven(): val = hby1.db.gpse.getLast(keys=(ghab.pre,)) # thold met, partial sig escrow should be empty assert val is None assert counselor.complete(prefixer=prefixer, seqner=seqner, saider=saider) - counselor.postman.evts.clear() # First Partial Rotation - smids = [hab1.pre, hab2.pre, hab3.pre] - rmids = [hab1.pre, hab2.pre, hab3.pre, hab4.pre, hab5.pre, hab6.pre, hab7.pre] # need to fix - counselor.rotate(ghab=ghab, isith='["1/3", "1/3", "1/3"]', - nsith='["1/3", "1/3", "1/3", "1/3", "1/3", "1/3", "1/3"]', smids=smids, - rmids=rmids, toad=0, cuts=list(), adds=list()) - - rec = hby1.db.glwe.get(keys=(ghab.pre,)) - assert rec is not None - assert rec.smids == smids - assert rec.nsith == '["1/3", "1/3", "1/3", "1/3", "1/3", "1/3", "1/3"]' - assert rec.toad == 0 - - counselor.processEscrows() # process escrows to get witness-less event to next step - rec = hby1.db.glwe.get(keys=(ghab.pre,)) - assert rec is None - assert len(counselor.postman.evts) == 6 - evt = counselor.postman.evts.popleft() - assert evt["src"] == hab1.pre - assert evt["dest"] == hab2.pre - assert evt["topic"] == "multisig" - assert evt["serder"].raw == (b'{"v":"KERI10JSON000160_","t":"rot","d":"EEX9vGqk8FJbe-pSusdW-t6dtTyPeOgtR8Cd' - b'hue6LgY7","i":"EOzS8kvK5AM0O9Qwub8wDVAmuetGCtUYVOQC6vpqbLQa","s":"1","p":"EO' - b'zS8kvK5AM0O9Qwub8wDVAmuetGCtUYVOQC6vpqbLQa","kt":"1","k":["DEbwF934m5TjdQbC1' - b'8jSmk2CcPO7xzAemzePy4LKnA_U"],"nt":"1","n":["EBOgQ1MOWQ2eWIqDuqjinhh3L3O5qHP' - b'EZ08zMICPhPTw"],"bt":"0","br":[],"ba":[],"a":[]}') - rec = hby1.db.gpae.get(keys=(ghab.pre,)) - assert rec is not None - assert rec.smids == smids - - # rotate second and third identifiter in group, process escrows to generate group rotation event. + hab1.rotate() hab2.rotate() - rot = hab2.makeOwnEvent(sn=1) - parsing.Parser().parse(ims=bytearray(rot), kvy=kev1) # parse rotation hab3.rotate() - rot = hab3.makeOwnEvent(sn=1) - parsing.Parser().parse(ims=bytearray(rot), kvy=kev1) # parse rotation - - counselor.processEscrows() # second and third (3 at 1/3) identifier has rotated, second stage clear - rec = hby1.db.gpae.get(keys=(ghab.pre,)) + merfers = [hab1.kever.verfers[0], hab2.kever.verfers[0], hab3.kever.verfers[0]] + migers = [hab1.kever.ndigers[0], hab2.kever.ndigers[0], hab3.kever.ndigers[0], hab4.kever.ndigers[0], + hab5.kever.ndigers[0], hab6.kever.ndigers[0], hab7.kever.ndigers[0]] + prefixer = coring.Prefixer(qb64=ghab.pre) + seqner = coring.Seqner(sn=ghab.kever.sn + 1) + rot = ghab.rotate(isith='["1/3", "1/3", "1/3"]', nsith='["1/3", "1/3", "1/3", "1/3", "1/3", "1/3", "1/3"]', + toad=0, cuts=list(), adds=list(), verfers=merfers, digers=migers) + rserder = serdering.SerderKERI(raw=rot) - assert rec is None + counselor.start(ghab=ghab, prefixer=prefixer, seqner=seqner, saider=coring.Saider(qb64=rserder.said)) # partially signed group rotation val = hby1.db.gpse.get(keys=(ghab.pre,)) @@ -525,7 +404,7 @@ def test_the_seven(): assert bytes(evt) == raw # Grab the group ROT event, sign with Hab2 and parse into Kev1 - serder = coring.Serder(raw=bytes(evt)) + serder = serdering.SerderKERI(raw=bytes(evt)) sigers = hab2.mgr.sign(serder.raw, verfers=hab2.kever.verfers, indexed=True, indices=[1]) msg = eventing.messagize(serder=serder, sigers=sigers) assert msg[serder.size:] == (b'-AABABAzvHN7yC3581dp9DxFXrKuXGP_62r_pzNMXL20T6RaPQASXvnBn6sKJ78z' @@ -545,52 +424,27 @@ def test_the_seven(): assert counselor.complete(prefixer=prefixer, seqner=seqner, saider=saider) # Validate successful partial rotation nkeys = [hab1.kever.verfers[0].qb64, hab2.kever.verfers[0].qb64, hab3.kever.verfers[0].qb64] - ndigs = [hab1.kever.digers[0].qb64, hab2.kever.digers[0].qb64, hab3.kever.digers[0].qb64, - hab4.kever.digers[0].qb64, hab5.kever.digers[0].qb64, hab6.kever.digers[0].qb64, - hab7.kever.digers[0].qb64] + ndigs = [hab1.kever.ndigers[0].qb64, hab2.kever.ndigers[0].qb64, hab3.kever.ndigers[0].qb64, + hab4.kever.ndigers[0].qb64, hab5.kever.ndigers[0].qb64, hab6.kever.ndigers[0].qb64, + hab7.kever.ndigers[0].qb64] assert ghab.kever.sn == 1 assert [verfer.qb64 for verfer in ghab.kever.verfers] == nkeys - assert [diger.qb64 for diger in ghab.kever.digers] == ndigs - - counselor.postman.evts.clear() # Clear out postman for next rotation + assert [diger.qb64 for diger in ghab.kever.ndigers] == ndigs # Second Partial Rotation - counselor.rotate(ghab=ghab, smids=smids, rmids=rmids, toad=0, cuts=list(), adds=list()) - rec = hby1.db.glwe.get(keys=(ghab.pre,)) - assert rec is not None - assert rec.smids == smids - assert rec.nsith is None - assert rec.toad == 0 - - counselor.processEscrows() # process escrows to get witness-less event to next step - rec = hby1.db.glwe.get(keys=(ghab.pre,)) - assert rec is None - assert len(counselor.postman.evts) == 6 - evt = counselor.postman.evts.popleft() - assert evt["src"] == hab1.pre - assert evt["dest"] == hab2.pre - assert evt["topic"] == "multisig" - assert evt["serder"].raw == (b'{"v":"KERI10JSON000160_","t":"rot","d":"EPX4RtZs7_HHlxYqV5nXC2odIvMEJJpR_BDk' - b'KZs2GnkR","i":"EOzS8kvK5AM0O9Qwub8wDVAmuetGCtUYVOQC6vpqbLQa","s":"2","p":"EE' - b'X9vGqk8FJbe-pSusdW-t6dtTyPeOgtR8Cdhue6LgY7","kt":"1","k":["DK-j3FspSlqvjM0v9' - b'nRUbgog54vminulol46VO1dDSAP"],"nt":"1","n":["EHMdUV5PuMt37ooqo1nW5DXkYC_lQXj' - b'qgXY4V7GaWrAJ"],"bt":"0","br":[],"ba":[],"a":[]}') - rec = hby1.db.gpae.get(keys=(ghab.pre,)) - assert rec is not None - assert rec.smids == smids - - # rotate second and third identifiter in group, process escrows to generate group rotation event. + hab1.rotate() hab2.rotate() - rot = hab2.makeOwnEvent(sn=2) - parsing.Parser().parse(ims=bytearray(rot), kvy=kev1) # parse rotation hab3.rotate() - rot = hab3.makeOwnEvent(sn=2) - parsing.Parser().parse(ims=bytearray(rot), kvy=kev1) # parse rotation - - counselor.processEscrows() # second and third (3 at 1/3) identifier has rotated, second stage clear - rec = hby1.db.gpae.get(keys=(ghab.pre,)) + merfers = [hab1.kever.verfers[0], hab2.kever.verfers[0], hab3.kever.verfers[0]] + migers = [hab1.kever.ndigers[0], hab2.kever.ndigers[0], hab3.kever.ndigers[0], hab4.kever.ndigers[0], + hab5.kever.ndigers[0], hab6.kever.ndigers[0], hab7.kever.ndigers[0]] + prefixer = coring.Prefixer(qb64=ghab.pre) + seqner = coring.Seqner(sn=ghab.kever.sn + 1) + rot = ghab.rotate(isith='["1/3", "1/3", "1/3"]', nsith='["1/3", "1/3", "1/3", "1/3", "1/3", "1/3", "1/3"]', + toad=0, cuts=list(), adds=list(), verfers=merfers, digers=migers) + rserder = serdering.SerderKERI(raw=rot) - assert rec is None + counselor.start(ghab=ghab, prefixer=prefixer, seqner=seqner, saider=coring.Saider(qb64=rserder.said)) # partially signed group rotation val = hby1.db.gpse.get(keys=(ghab.pre,)) @@ -615,7 +469,7 @@ def test_the_seven(): assert bytes(evt) == raw # Grab the group ROT event, sign with Hab2 and parse into Kev1 - serder = coring.Serder(raw=bytes(evt)) + serder = serdering.SerderKERI(raw=bytes(evt)) sigers = hab2.mgr.sign(serder.raw, verfers=hab2.kever.verfers, indexed=True, indices=[1]) msg = eventing.messagize(serder=serder, sigers=sigers) assert msg[serder.size:] == (b'-AABABC4sYnDXCpO87BMXO21ofqHZKntPSdEXlBPlq1H8NOHD3KV-GHGWrXyrElK' @@ -635,15 +489,13 @@ def test_the_seven(): assert counselor.complete(prefixer=prefixer, seqner=seqner, saider=saider) # Validate successful partial rotation nkeys = [hab1.kever.verfers[0].qb64, hab2.kever.verfers[0].qb64, hab3.kever.verfers[0].qb64] - ndigs = [hab1.kever.digers[0].qb64, hab2.kever.digers[0].qb64, hab3.kever.digers[0].qb64, - hab4.kever.digers[0].qb64, hab5.kever.digers[0].qb64, hab6.kever.digers[0].qb64, - hab7.kever.digers[0].qb64] + ndigs = [hab1.kever.ndigers[0].qb64, hab2.kever.ndigers[0].qb64, hab3.kever.ndigers[0].qb64, + hab4.kever.ndigers[0].qb64, hab5.kever.ndigers[0].qb64, hab6.kever.ndigers[0].qb64, + hab7.kever.ndigers[0].qb64] assert ghab.kever.sn == 2 assert [verfer.qb64 for verfer in ghab.kever.verfers] == nkeys - assert [diger.qb64 for diger in ghab.kever.digers] == ndigs - - counselor.postman.evts.clear() # Clear out postman for next rotation + assert [diger.qb64 for diger in ghab.kever.ndigers] == ndigs # Third Partial Rotation with Recovery (using 4 members not involved in previous rotations) # First we have to do a replay of all multisig AID and member AID events and get members 4 - 7 up to date @@ -658,47 +510,20 @@ def test_the_seven(): assert kev7.kevers[ghab.pre] is not None # Create a new counselor with #4 - smids = [hab4.pre, hab5.pre, hab6.pre] - rmids = smids counselor4 = grouping.Counselor(hby=hby4) - counselor4.rotate(ghab=ghab4, smids=smids, rmids=rmids, isith='["1/3", "1/3", "1/3"]', - nsith='["1/3", "1/3", "1/3"]', toad=0, cuts=list(), adds=list()) - rec = hby4.db.glwe.get(keys=(ghab4.pre,)) - assert rec is not None - assert rec.smids == smids - assert rec.nsith == '["1/3", "1/3", "1/3"]' - assert rec.toad == 0 - - counselor4.processEscrows() # process escrows to get witness-less event to next step - rec = hby4.db.glwe.get(keys=(ghab4.pre,)) - assert rec is None - assert len(counselor4.postman.evts) == 2 - evt = counselor4.postman.evts.popleft() - assert evt["src"] == hab4.pre - assert evt["dest"] == hab5.pre - assert evt["topic"] == "multisig" - assert evt["serder"].raw == (b'{"v":"KERI10JSON000160_","t":"rot","d":"EBG71ULs1iZBLHdynKBPy14M_tyO4oIeMcWd' - b'vB6lj5vj","i":"EE2KPMeOSEs9aQqRsrg5yFtzPkWusWIG0cT-D4EBqjmy","s":"1","p":"EE' - b'2KPMeOSEs9aQqRsrg5yFtzPkWusWIG0cT-D4EBqjmy","kt":"1","k":["DOKBAV-_3Z63w7yGm' - b'zu6pZCdUlpnEytbnChUhiTZGLa_"],"nt":"1","n":["EGX_K2uTEU6NOXfNo0VfhYLMrqADYHO' - b'oNk7WtT1SXOo2"],"bt":"0","br":[],"ba":[],"a":[]}') - rec = hby4.db.gpae.get(keys=(ghab4.pre,)) - assert rec is not None - assert rec.smids == smids - - # rotate second and third identifiter in group, process escrows to generate group rotation event. + hab4.rotate() hab5.rotate() - rot = hab5.makeOwnEvent(sn=1) - parsing.Parser().parse(ims=bytearray(rot), kvy=kev4) # parse rotation hab6.rotate() - rot = hab6.makeOwnEvent(sn=1) - parsing.Parser().parse(ims=bytearray(rot), kvy=kev4) # parse rotation - - counselor4.processEscrows() # second and third (3 at 1/3) identifier has rotated, second stage clear - rec = hby4.db.gpae.get(keys=(ghab4.pre,)) + merfers = [hab4.kever.verfers[0], hab5.kever.verfers[0], hab6.kever.verfers[0]] + migers = [hab4.kever.ndigers[0], hab5.kever.ndigers[0], hab6.kever.ndigers[0]] + prefixer = coring.Prefixer(qb64=ghab.pre) + seqner = coring.Seqner(sn=ghab.kever.sn + 1) + rot = ghab4.rotate(isith='["1/3", "1/3", "1/3"]', nsith='["1/3", "1/3", "1/3"]', + toad=0, cuts=list(), adds=list(), verfers=merfers, digers=migers) + rserder = serdering.SerderKERI(raw=rot) - assert rec is None + counselor4.start(ghab=ghab4, prefixer=prefixer, seqner=seqner, saider=coring.Saider(qb64=rserder.said)) # partially signed group rotation val = hby4.db.gpse.get(keys=(ghab4.pre,)) @@ -719,7 +544,7 @@ def test_the_seven(): assert bytes(evt) == raw # Grab the group ROT event, sign with Hab5 and parse into Kev4 - serder = coring.Serder(raw=bytes(evt)) + serder = serdering.SerderKERI(raw=bytes(evt)) sigers = hab5.mgr.sign(serder.raw, verfers=hab5.kever.verfers, indexed=True, indices=[1], ondices=[4]) msg = eventing.messagize(serder=serder, sigers=sigers) assert msg[serder.size:] == (b'-AAB2AABAEDSs99oM-KOhJ8q3H8lqGqPE3EvZxCHvCjZFvWHLzhqm91YlcskGqvK' @@ -740,10 +565,10 @@ def test_the_seven(): # Validate successful partial rotation nkeys = [hab4.kever.verfers[0].qb64, hab5.kever.verfers[0].qb64, hab6.kever.verfers[0].qb64] - ndigs = [hab4.kever.digers[0].qb64, hab5.kever.digers[0].qb64, hab6.kever.digers[0].qb64] + ndigs = [hab4.kever.ndigers[0].qb64, hab5.kever.ndigers[0].qb64, hab6.kever.ndigers[0].qb64] assert ghab4.kever.sn == 3 assert [verfer.qb64 for verfer in ghab4.kever.verfers] == nkeys - assert [diger.qb64 for diger in ghab4.kever.digers] == ndigs + assert [diger.qb64 for diger in ghab4.kever.ndigers] == ndigs @contextmanager @@ -809,302 +634,175 @@ def openMultiSig(prefix="test", salt=b'0123456789abcdef', temp=True, **kwa): def test_multisig_incept(mockHelpingNowUTC): with habbing.openHab(name="test", temp=True) as (hby, hab): aids = [hab.pre, "EfrzbTSWjccrTdNRsFUUfwaJ2dpYxu9_5jI2PJ-TRri0"] - exn, atc = grouping.multisigInceptExn(hab=hab, aids=aids, ked=hab.kever.serder.ked) + exn, atc = grouping.multisigInceptExn(hab=hab, smids=aids, rmids=aids, + icp=hab.makeOwnEvent(sn=hab.kever.sn)) assert exn.ked["r"] == '/multisig/icp' - assert exn.saidb == b'EEl70ZAj2v8kR8X2IkKB2tuhhYa4lHSO1UqvA3_cZK7G' - assert atc == (b'-HABEIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3-AABAAB-u_h6NLNe' - b'MVCh3k07dY7smtLV4MhGD-Fgl3IAuJOIa2IpNYGG_YsvfD4GLcv1zU1btNHmnfXm' - b'OdoKbaTOY_YH') + assert exn.saidb == b'EGDEBUZW--n-GqOOwRflzBeqoQsYWKMOQVU_1YglG-BL' + assert atc == (b'-FABEIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI30AAAAAAAAAAAAAAA' + b'AAAAAAAAEIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3-AABAAC84-o2' + b'HKwKxhL1ttzykB9zuFaGV6OpQ05b1ZJYAeBFrR7kVON1aNjpLgQCG_0bY4FUiP7F' + b'GTVDrBjuFhbeDKAH-LAa5AACAA-e-icp-AABAACihaKoLnoXxRoxGbFfOy67YSh6' + b'UxtgjT2oxupnLDz2FlhevGJKTMObbdex9f0Hqob6uTavSJvsXf5RzitskkkC') data = exn.ked["a"] - assert data["aids"] == aids - assert data["ked"] == hab.kever.serder.ked + assert data["smids"] == aids + assert "icp" in exn.ked['e'] def test_multisig_rotate(mockHelpingNowUTC): with openMultiSig(prefix="test") as ((hby1, ghab1), (_, _), (_, _)): - serder = eventing.rotate( - ghab1.pre, - dig=ghab1.kever.serder.said, - keys=[verfer.qb64 for verfer in ghab1.kever.verfers], - sn=2, - isith="2" - ) - exn, atc = grouping.multisigRotateExn(ghab=ghab1, aids=ghab1.smids, ked=serder.ked) + rot = (b'{"v":"KERI10JSON00023c_","t":"rot","d":"EGt_CZZASnY_iyB14ZXGQ4MxMtcSVW5oMHAu' + b'LM8BnqxV","i":"EL-f5D0esAFbZTzK9W3wtTgDmncye9IOnF0Z8gRdICIU","s":"3","p":"EH' + b'V57zdXq3lB3PZ4mmlOWt4SOOubIKDpcG5sSZh5jayZ","kt":["1/3","1/3","1/3"],"k":["D' + b'OKBAV-_3Z63w7yGmzu6pZCdUlpnEytbnChUhiTZGLa_","DOKFe0a-q2yyi_Yyh9wxLsSnG9e3nx' + b'vAXlgMaIFSo0YE","DKq5vZxsl7lCtFkuxSdfRRm-Edzdk_mRnh3xlVESXpck"],"nt":["1/3",' + b'"1/3","1/3"],"n":["EGX_K2uTEU6NOXfNo0VfhYLMrqADYHOoNk7WtT1SXOo2","EFl4us5uR0' + b'hCiYcW7YyOaSAo-7zp8x1uBVU2E_tmhEwj","EMyxeTiM_cH5IHUI6nummgHMeW-_1oKw7rvqlDd' + b'gha9v"],"bt":"0","br":[],"ba":[],"a":[]}') + exn, atc = grouping.multisigRotateExn(ghab=ghab1, smids=ghab1.smids, rmids=ghab1.rmids, rot=rot) assert exn.ked["r"] == '/multisig/rot' - assert exn.saidb == b'EK6QqWIn4QBCIsNZvvnLhlsMmvUomqSLXdMaWD9qF1yt' - assert atc == (b'-HABEH__mobl7NDyyQCB1DoLK-OPSueraPtZAlWEjfOYkaba-AABAABdqAoddbf9' - b'5zdqlGJAoA0M0UCJZr8uan2KUopSeEFbKS1_Wo9Lf9V8phPuzUCFzj2uh3vjc98p' - b'dsbdHamLDsAI') + assert exn.saidb == b'ENfCk9DUUck6Ixe6cYnbCbJfIsisA3H4kHPwm5Z-2Tf8' + assert atc == (b'-FABEH__mobl7NDyyQCB1DoLK-OPSueraPtZAlWEjfOYkaba0AAAAAAAAAAAAAAA' + b'AAAAAAAAEH__mobl7NDyyQCB1DoLK-OPSueraPtZAlWEjfOYkaba-AABAADChiAf' + b'iExAQ2ETkzzf7MOubXV9mL-r6fPsOI4yn348yeE5dXqdI7ddn5-wnPwNVjqqKkDp' + b'xlOEFYRiBQEbwZQC') data = exn.ked["a"] - assert data["aids"] == ghab1.smids + assert data["smids"] == ghab1.smids assert data["gid"] == ghab1.pre - assert data["ked"] == serder.ked + assert "rot" in exn.ked["e"] def test_multisig_interact(mockHelpingNowUTC): with openMultiSig(prefix="test") as ((hby1, ghab1), (_, _), (_, _)): - exn, atc = grouping.multisigInteractExn(ghab=ghab1, sn=1, aids=ghab1.smids, - data=[{"i": 1, "x": 0, "d": 2}]) + ixn = ghab1.mhab.interact() + exn, atc = grouping.multisigInteractExn(ghab=ghab1, aids=ghab1.smids, + ixn=ixn) assert exn.ked["r"] == '/multisig/ixn' - assert exn.saidb == b'EJtH2ozjqRUHmk_JsWUD8fNYHBJ2RbH9teiRFWUNNyof' - assert atc == (b'-HABEH__mobl7NDyyQCB1DoLK-OPSueraPtZAlWEjfOYkaba-AABAABb6niz5-mm' - b'TK6lcEYHwZmWC9hHGj5m-SxVlk2GB0AuLUJ-sUuQNgFNThn5yo7LAEmTuPt3yAkg' - b'pkVCjtuiqYMG') + assert exn.saidb == b'EGQ_DqGlSBx2MKJfHr6liXAngFpQ0UCtV1cdVMUtJHdN' + assert atc == (b'-FABEH__mobl7NDyyQCB1DoLK-OPSueraPtZAlWEjfOYkaba0AAAAAAAAAAAAAAA' + b'AAAAAAAAEH__mobl7NDyyQCB1DoLK-OPSueraPtZAlWEjfOYkaba-AABAAB3yX6b' + b'EXb8N63PKaMaFqijZVT5TqVtoO8q1BFnoJW3rDkNuJ9lEMpEN-44HKGtvniWZ6-d' + b'CVPS4fsEXKZAKGkB-LAa5AACAA-e-ixn-AABAABG58m7gibjdrQ8YU-8WQ8A70nc' + b'tYekYr3xdfZ5WgDQOD0bb9pI7SuuaJvzfAQisLAYQnztA82pAo1Skhf1vQwD') data = exn.ked["a"] - assert data["aids"] == ghab1.smids - assert data["sn"] == 1 + assert data["smids"] == ghab1.smids assert data["gid"] == ghab1.pre - assert data["data"] == [{"i": 1, "x": 0, "d": 2}] - - -def test_multisig_incept_handler(mockHelpingNowUTC): - with habbing.openHab(name="test0", temp=True) as (hby, hab): - - aids = [hab.pre, "EArzbTSWjccrTdNRsFUUfwaJ2dpYxu9_5jI2PJ-TRri0"] - serder = eventing.incept(keys=["DAEFuPeaDH2TySI-wX7CY_uW5FF41LRu3a59jxg1_pMs"], - ndigs=["DLONLed3zFEWa0p21fvi1Jf5-x-EoyEPqFvOki3YhP1k"]) - - notifier = notifying.Notifier(hby=hby) - handler = grouping.MultisigInceptHandler(hby=hby, notifier=notifier) - - # Pass message missing keys: - handler.msgs.append(dict(name="value")) - handler.msgs.append(dict(pre=hab.kever.prefixer)) - handler.msgs.append(dict(pre=hab.kever.prefixer, payload=dict(aids=aids))) - handler.msgs.append(dict(pre=hab.kever.prefixer, payload=dict(aids=aids, ked=serder.ked))) - - limit = 1.0 - tock = 0.03125 - doist = doing.Doist(tock=tock, limit=limit, doers=[handler]) - doist.enter() + assert "ixn" in exn.ked["e"] - tymer = tyming.Tymer(tymth=doist.tymen(), duration=doist.limit) - while not tymer.expired: - doist.recur() - time.sleep(doist.tock) +def test_multisig_registry_incept(mockHelpingNowUTC, mockCoringRandomNonce): + with openMultiSig(prefix="test") as ((hby1, ghab1), (_, _), (_, _)): + vcp = veventing.incept(ghab1.pre) + ixn = ghab1.mhab.interact(data=[dict(i=vcp.pre, s="0", d=vcp.said)]) + exn, atc = grouping.multisigRegistryInceptExn(ghab=ghab1, vcp=vcp.raw, anc=ixn, + usage="Issue vLEI Credentials") + + assert exn.ked["r"] == '/multisig/vcp' + assert exn.saidb == b'ECKiNFo7fpG4vS5tUeja3EvOqT8ctq4AW8E3HKsP7dJo' + assert atc == (b'-FABEH__mobl7NDyyQCB1DoLK-OPSueraPtZAlWEjfOYkaba0AAAAAAAAAAAAAAA' + b'AAAAAAAAEH__mobl7NDyyQCB1DoLK-OPSueraPtZAlWEjfOYkaba-AABAABh6d0m' + b'lebT57L8o2si7DfEvPCoXJP0ekPiBqkzQns3-P7dz36MPXhjNFW6xRRdUstDLAZe' + b'BEqBxBCltMpTZGsD-LAa5AACAA-e-anc-AABAAD2mK9ICW9x1-0NZGkEDOcAbZ58' + b'VWK9LOTwyN2lSfHr2zY638P1SBStoh8mjgy7nOTGMyujOXMKvF_ZDeQ_ISYA') + data = exn.ked["a"] + assert data == {'gid': 'EERn_laF0qwP8zTBGL86LbF84J0Yh2IvQSRskH3BZZiy', + 'usage': 'Issue vLEI Credentials'} + assert "vcp" in exn.ked["e"] + assert "anc" in exn.ked["e"] - assert doist.limit == limit - assert len(notifier.signaler.signals) == 1 - doist.exit() +def test_multisig_incept_handler(mockHelpingNowUTC): with habbing.openHab(name="test0", temp=True) as (hby, hab): - aids = [hab.pre, "EfrzbTSWjccrTdNRsFUUfwaJ2dpYxu9_5jI2PJ-TRri0"] - exn, atc = grouping.multisigInceptExn(hab=hab, aids=aids, ked=hab.kever.serder.ked) + exn, atc = grouping.multisigInceptExn(hab=hab, smids=aids, rmids=aids, + icp=hab.makeOwnEvent(sn=hab.kever.sn)) notifier = notifying.Notifier(hby=hby) - exc = exchanging.Exchanger(db=hby.db, handlers=[]) - grouping.loadHandlers(hby=hby, exc=exc, notifier=notifier) + mux = grouping.Multiplexor(hby=hby, notifier=notifier) + exc = exchanging.Exchanger(hby=hby, handlers=[]) + grouping.loadHandlers(exc=exc, mux=mux) ims = bytearray(exn.raw) ims.extend(atc) parsing.Parser().parseOne(ims=ims, exc=exc) + assert len(notifier.signaler.signals) == 0 - limit = 1.0 - tock = 0.03125 - doist = doing.Doist(tock=tock, limit=limit, doers=[exc]) - doist.enter() - - tymer = tyming.Tymer(tymth=doist.tymen(), duration=doist.limit) - - while not tymer.expired: - doist.recur() - time.sleep(doist.tock) - - assert doist.limit == limit - doist.exit() - - assert len(notifier.signaler.signals) == 1 + esaid = exn.ked['e']['d'] + saiders = hby.db.meids.get(keys=(esaid, )) + assert len(saiders) == 1 + assert saiders[0].qb64 == exn.said + prefixers = hby.db.maids.get(keys=(esaid,)) + assert len(prefixers) == 1 + assert prefixers[0].qb64 == exn.pre def test_multisig_rotate_handler(mockHelpingNowUTC): - with openMultiSig(prefix="test") as ((hby, ghab), (_, _), (_, _)): - - notifier = notifying.Notifier(hby=hby) - handler = grouping.MultisigRotateHandler(hby=hby, notifier=notifier) - serder = eventing.rotate( - ghab.pre, - dig=ghab.kever.serder.said, - keys=[verfer.qb64 for verfer in ghab.kever.verfers], - sn=2, - isith="2" - ) - # Pass message missing keys: - handler.msgs.append(dict(name="value")) - handler.msgs.append(dict(pre=ghab.kever.prefixer)) - handler.msgs.append(dict(pre=ghab.kever.prefixer, payload=dict(aids=ghab.smids))) - handler.msgs.append(dict(pre=ghab.kever.prefixer, payload=dict(aids=ghab.smids, ked=serder.ked, gid=ghab.pre))) - handler.msgs.append(dict(pre=ghab.mhab.kever.prefixer, payload=dict(aids=ghab.smids, gid=ghab.pre, - ked=serder.ked, smids=ghab.smids, - rmids=ghab.rmids))) - - limit = 1.0 - tock = 0.03125 - doist = doing.Doist(tock=tock, limit=limit, doers=[handler]) - doist.enter() - - tymer = tyming.Tymer(tymth=doist.tymen(), duration=doist.limit) - - while not tymer.expired: - doist.recur() - time.sleep(doist.tock) - - assert doist.limit == limit - doist.exit() - - assert len(notifier.signaler.signals) == 1 - - with openMultiSig(prefix="test") as ((hby1, ghab1), (_, _), (_, _)): - - serder = eventing.rotate( - ghab.pre, - dig=ghab.kever.serder.said, - keys=[verfer.qb64 for verfer in ghab.kever.verfers], - sn=2, - isith="2" - ) - exn, atc = grouping.multisigRotateExn(ghab=ghab1, aids=ghab1.smids, ked=serder.ked) + with openMultiSig(prefix="test") as ((hby1, ghab1), (hby2, ghab2), (_, _)): + msg = ghab1.mhab.rotate() notifier = notifying.Notifier(hby=hby1) - exc = exchanging.Exchanger(db=hby1.db, handlers=[]) - grouping.loadHandlers(hby=hby1, exc=exc, notifier=notifier) + mux = grouping.Multiplexor(hby=hby1, notifier=notifier) + exc = exchanging.Exchanger(hby=hby1, handlers=[]) + grouping.loadHandlers(exc=exc, mux=mux) + # create and send message from ghab2 + exn, atc = grouping.multisigRotateExn(ghab=ghab2, smids=ghab1.smids, rmids=ghab1.rmids, + rot=msg) ims = bytearray(exn.raw) ims.extend(atc) parsing.Parser().parseOne(ims=ims, exc=exc) - limit = 1.0 - tock = 0.03125 - doist = doing.Doist(tock=tock, limit=limit, doers=[exc]) - doist.enter() - - tymer = tyming.Tymer(tymth=doist.tymen(), duration=doist.limit) - - while not tymer.expired: - doist.recur() - time.sleep(doist.tock) - - assert doist.limit == limit - doist.exit() - + # One notification assert len(notifier.signaler.signals) == 1 + esaid = exn.ked['e']['d'] + saiders = hby1.db.meids.get(keys=(esaid, )) + assert len(saiders) == 1 + assert saiders[0].qb64 == exn.said + prefixers = hby1.db.maids.get(keys=(esaid,)) + assert len(prefixers) == 1 + assert prefixers[0].qb64 == ghab2.mhab.pre + + # Send the same message from ghab1 + exn, atc = grouping.multisigRotateExn(ghab=ghab1, smids=ghab1.smids, rmids=ghab1.rmids, + rot=msg) + ims = bytearray(exn.raw) + ims.extend(atc) + parsing.Parser().parseOne(ims=ims, exc=exc) -def test_multisig_interact_handler(mockHelpingNowUTC): - with openMultiSig(prefix="test") as ((hby, ghab), (_, _), (_, _)): - - notifier = notifying.Notifier(hby=hby) - handler = grouping.MultisigInteractHandler(hby=hby, notifier=notifier) - - # Pass message missing keys: - handler.msgs.append(dict(name="value")) - handler.msgs.append(dict(pre=ghab.kever.prefixer)) - handler.msgs.append(dict(pre=ghab.kever.prefixer, payload=dict(aids=ghab.smids))) - handler.msgs.append(dict(pre=ghab.kever.prefixer, payload=dict(aids=ghab.smids, sn=2, gid=ghab.pre))) - handler.msgs.append(dict(pre=ghab.mhab.kever.prefixer, payload=dict(aids=ghab.smids, sn=2, gid=ghab.pre))) - - limit = 1.0 - tock = 0.03125 - doist = doing.Doist(tock=tock, limit=limit, doers=[handler]) - doist.enter() - - tymer = tyming.Tymer(tymth=doist.tymen(), duration=doist.limit) - - while not tymer.expired: - doist.recur() - time.sleep(doist.tock) - - assert doist.limit == limit - doist.exit() - + # There should still only be one notification because we don't notify for our own event assert len(notifier.signaler.signals) == 1 - with openMultiSig(prefix="test") as ((hby1, ghab1), (_, _), (_, _)): + saiders = hby1.db.meids.get(keys=(esaid, )) + assert len(saiders) == 2 + assert saiders[1].qb64 == exn.said + prefixers = hby1.db.maids.get(keys=(esaid,)) + assert len(prefixers) == 2 + assert prefixers[1].qb64 == ghab1.mhab.pre - exn, atc = grouping.multisigInteractExn(ghab=ghab1, sn=1, aids=ghab1.smids, - data=[{"i": 1, "x": 0, "d": 2}]) + +def test_multisig_interact_handler(mockHelpingNowUTC): + with openMultiSig(prefix="test") as ((hby1, ghab1), (_, ghab2), (_, _)): + ixn = ghab1.mhab.interact() + exn, atc = grouping.multisigInteractExn(ghab=ghab2, aids=ghab1.smids, + ixn=ixn) notifier = notifying.Notifier(hby=hby1) - exc = exchanging.Exchanger(db=hby1.db, handlers=[]) - grouping.loadHandlers(hby=hby1, exc=exc, notifier=notifier) + mux = grouping.Multiplexor(hby=hby1, notifier=notifier) + exc = exchanging.Exchanger(hby=hby1, handlers=[]) + grouping.loadHandlers(exc=exc, mux=mux) ims = bytearray(exn.raw) ims.extend(atc) parsing.Parser().parseOne(ims=ims, exc=exc) - limit = 1.0 - tock = 0.03125 - doist = doing.Doist(tock=tock, limit=limit, doers=[exc]) - doist.enter() - - tymer = tyming.Tymer(tymth=doist.tymen(), duration=doist.limit) - - while not tymer.expired: - doist.recur() - time.sleep(doist.tock) - - assert doist.limit == limit - doist.exit() - + esaid = exn.ked['e']['d'] assert len(notifier.signaler.signals) == 1 - - -def test_pending_events(): - with habbing.openHab(name="test0", temp=True) as (hby, hab): - counselor = grouping.Counselor(hby=hby) - - rec = basing.RotateRecord( - sn=0, - isith=["1/2, 1/2, 1/2"], - nsith=["1/2, 1/2, 1/2"], - toad=3, - cuts=[], - adds=[], - data=[dict(a=1)], - date="2021-06-09T17:35:54.169967+00:00", - smids=[hab.pre] - ) - hby.db.gpae.put(keys=(hab.pre,), val=rec) - - evts = counselor.pendingEvents(hab.pre) - assert len(evts) == 1 - assert evts[0] == {'adds': [], - 'aids': ['EFPnKh_K7OrV7giJWjUVM7QIZftaCdPQnTQBOGIviMrj'], - 'cuts': [], - 'data': [{'a': 1}], - 'isith': ['1/2, 1/2, 1/2'], - 'nsith': ['1/2, 1/2, 1/2'], - 'sn': 0, - 'timestamp': '2021-06-09T17:35:54.169967+00:00', - 'toad': 3} - - rec = basing.RotateRecord( - sn=3, - isith=['1/2, 1/2, 1/2'], - nsith="1", - toad=1, - cuts=[], - adds=[], - data=[], - date="2021-06-09T17:35:54.169967+00:00", - smids=[hab.pre] - ) - hby.db.glwe.put(keys=(hab.pre,), val=rec) - evts = counselor.pendingEvents(hab.pre) - assert len(evts) == 2 - assert evts[1] == {'adds': [], - 'aids': ['EFPnKh_K7OrV7giJWjUVM7QIZftaCdPQnTQBOGIviMrj'], - 'cuts': [], - 'data': [], - 'isith': ['1/2, 1/2, 1/2'], - 'nsith': '1', - 'sn': 3, - 'timestamp': '2021-06-09T17:35:54.169967+00:00', - 'toad': 1} - - evts = counselor.pendingEvents("ABC") - assert len(evts) == 0 + saiders = hby1.db.meids.get(keys=(esaid, )) + assert len(saiders) == 1 + assert saiders[0].qb64 == exn.said + prefixers = hby1.db.maids.get(keys=(esaid,)) + assert len(prefixers) == 1 + assert prefixers[0].qb64 == ghab2.mhab.pre diff --git a/tests/app/test_habbing.py b/tests/app/test_habbing.py index b86cdb3e7..69e0d65a4 100644 --- a/tests/app/test_habbing.py +++ b/tests/app/test_habbing.py @@ -8,14 +8,17 @@ import os import shutil -from hio.base import doing, tyming +from hio.base import doing from keri import kering from keri import help from keri.app import habbing, keeping, configing +from keri.core.coring import MtrDex from keri.db import basing from keri.core import coring, eventing, parsing +from keri.help import helping from keri.peer import exchanging +from keri.vdr.eventing import incept def test_habery(): @@ -23,6 +26,7 @@ def test_habery(): Test Habery class """ # test default + default_salt = coring.Salter(raw=b'0123456789abcdef').qb64 hby = habbing.Habery(temp=True) assert hby.name == "test" assert hby.base == "" @@ -51,7 +55,7 @@ def test_habery(): assert hby.mgr.seed == "" assert hby.mgr.aeid == "" - assert hby.mgr.salt == habbing.SALT + assert hby.mgr.salt == default_salt assert hby.mgr.pidx == 1 assert hby.mgr.algo == keeping.Algos.salty assert hby.mgr.tier == coring.Tiers.low @@ -84,7 +88,7 @@ def test_habery(): assert hby.mgr.seed == seed4bran assert hby.mgr.aeid == aeid4seed - assert hby.mgr.salt == habbing.SALT + assert hby.mgr.salt == default_salt assert hby.mgr.pidx == 1 assert hby.mgr.algo == keeping.Algos.salty assert hby.mgr.tier == coring.Tiers.low @@ -103,7 +107,6 @@ def test_habery(): assert not os.path.exists(hby.db.path) assert not os.path.exists(hby.ks.path) - # test pre-create of injected resources base = "keep" name = "main" @@ -118,7 +121,7 @@ def test_habery(): cf = configing.Configer(name=name, base=base, temp=temp) cfDoer = configing.ConfigerDoer(configer=cf) conf = cf.get() - if not conf: # setup config file + if not conf: # setup config file curls = ["ftp://localhost:5620/"] iurls = [f"ftp://localhost:5621/?role={kering.Roles.peer}&name=Bob"] conf = dict(dt=help.nowIso8601(), curls=curls, iurls=iurls) @@ -126,7 +129,7 @@ def test_habery(): # setup habery hby = habbing.Habery(name=name, base=base, ks=ks, db=db, cf=cf, temp=temp, - bran=bran ) + bran=bran) hbyDoer = habbing.HaberyDoer(habery=hby) # setup doer assert hby.name == "main" @@ -140,9 +143,8 @@ def test_habery(): # run components tock = 0.03125 - limit = 1.0 + limit = 1.0 doist = doing.Doist(limit=limit, tock=tock, real=True) - tymer = tyming.Tymer(tymth=doist.tymen(), duration=doist.limit) # doist.do(doers=doers) deeds = doist.enter(doers=doers) @@ -153,7 +155,7 @@ def test_habery(): assert hby.mgr is not None assert hby.mgr.seed == seed4bran assert hby.mgr.aeid == aeid4seed - assert hby.mgr.salt == habbing.SALT + assert hby.mgr.salt == default_salt assert hby.mgr.pidx == 1 assert hby.mgr.algo == keeping.Algos.salty assert hby.mgr.tier == coring.Tiers.low @@ -161,15 +163,9 @@ def test_habery(): assert hby.rtr.routes assert hby.rvy.rtr == hby.rtr assert hby.kvy.rvy == hby.rvy - assert hby.psr.kvy == hby.kvy + assert hby.psr.kvy == hby.kvy assert hby.psr.rvy == hby.rvy - - #time.sleep(doist.tock) - #while not tymer.expired: - #doist.recur(deeds=deeds) - #time.sleep(doist.tock) - #assert doist.limit == limit # already exited? doist.exit(deeds=deeds) assert not cf.opened @@ -180,7 +176,6 @@ def test_habery(): assert not os.path.exists(db.path) assert not os.path.exists(ks.path) - # test pre-create using habery itself base = "keep" name = "main" @@ -192,13 +187,12 @@ def test_habery(): hbyDoer = habbing.HaberyDoer(habery=hby) # setup doer conf = hby.cf.get() - if not conf: # setup config file + if not conf: # setup config file curls = ["ftp://localhost:5620/"] iurls = [f"ftp://localhost:5621/?role={kering.Roles.peer}&name=Bob"] conf = dict(dt=help.nowIso8601(), curls=curls, iurls=iurls) hby.cf.put(conf) - assert hby.name == "main" assert hby.base == "keep" assert hby.temp @@ -210,9 +204,8 @@ def test_habery(): # run components tock = 0.03125 - limit = 1.0 + limit = 1.0 doist = doing.Doist(limit=limit, tock=tock, real=True) - tymer = tyming.Tymer(tymth=doist.tymen(), duration=doist.limit) # doist.do(doers=doers) deeds = doist.enter(doers=doers) @@ -223,7 +216,7 @@ def test_habery(): assert hby.mgr is not None assert hby.mgr.seed == seed4bran assert hby.mgr.aeid == aeid4seed - assert hby.mgr.salt == habbing.SALT + assert hby.mgr.salt == default_salt assert hby.mgr.pidx == 1 assert hby.mgr.algo == keeping.Algos.salty assert hby.mgr.tier == coring.Tiers.low @@ -231,14 +224,9 @@ def test_habery(): assert hby.rtr.routes assert hby.rvy.rtr == hby.rtr assert hby.kvy.rvy == hby.rvy - assert hby.psr.kvy == hby.kvy + assert hby.psr.kvy == hby.kvy assert hby.psr.rvy == hby.rvy - #time.sleep(doist.tock) - #while not tymer.expired: - #doist.recur(deeds=deeds) - #time.sleep(doist.tock) - #assert doist.limit == limit # already exited? doist.exit(deeds=deeds) assert not hby.cf.opened @@ -276,7 +264,7 @@ def test_habery(): assert hby.mgr.seed == "" assert hby.mgr.aeid == "" - assert hby.mgr.salt == habbing.SALT + assert hby.mgr.salt == default_salt assert hby.mgr.pidx == 1 assert hby.mgr.algo == keeping.Algos.salty assert hby.mgr.tier == coring.Tiers.low @@ -295,7 +283,6 @@ def test_habery(): assert not os.path.exists(hby.db.path) assert not os.path.exists(hby.ks.path) - bran = "MyPasscodeARealSecret" with habbing.openHby(bran=bran) as hby: assert hby.name == "test" @@ -326,7 +313,7 @@ def test_habery(): # test bran to seed assert hby.mgr.seed == seed4bran assert hby.mgr.aeid == aeid4seed - assert hby.mgr.salt == habbing.SALT + assert hby.mgr.salt == default_salt assert hby.mgr.pidx == 1 assert hby.mgr.algo == keeping.Algos.salty assert hby.mgr.tier == coring.Tiers.low @@ -345,7 +332,6 @@ def test_habery(): assert not os.path.exists(hby.db.path) assert not os.path.exists(hby.ks.path) - """End Test""" @@ -354,7 +340,7 @@ def test_make_load_hab_with_habery(): Test creation methods for Hab instances with Habery """ with pytest.raises(TypeError): # missing required dependencies - hab = habbing.Hab() # defaults + _ = habbing.Hab() # defaults name = "sue" suePre = 'ELF1S0jZkyQx8YtHaPLu-qyFmrkcykAiEW8twS-KPSO1' # with temp=True @@ -383,8 +369,6 @@ def test_make_load_hab_with_habery(): hab.kvy = hby.kvy # injected hab.psr = hby.psr # injected - - assert not hby.cf.opened assert not hby.db.opened assert not hby.ks.opened @@ -438,7 +422,6 @@ def test_make_load_hab_with_habery(): assert len(hby.habs) == 2 - assert not hby.cf.opened assert not hby.db.opened assert not hby.ks.opened @@ -484,7 +467,6 @@ def test_make_load_hab_with_habery(): """End Test""" - def test_hab_rotate_with_witness(): """ Reload from disk and rotate hab with witness @@ -506,7 +488,6 @@ def test_hab_rotate_with_witness(): opub = hab.kever.verfers[0].qb64 odig = hab.kever.serder.said - with habbing.openHby(name=name, base="test", temp=False) as hby: hab = hby.habByName(name) assert hab.pre == opre @@ -547,13 +528,11 @@ def test_habery_reinitialization(): opub = hab.kever.verfers[0].qb64 odig = hab.kever.serder.said - with habbing.openHby(name=name, base="test", temp=False) as hby: assert opre in hby.db.kevers # read through cache assert opre in hby.db.prefixes - # hab = habbing.Habitat(name=name, ks=ks, db=db, icount=1, temp=False) hab = hby.habByName(name) assert hab.pre == opre assert hab.prefixes is hab.db.prefixes @@ -622,44 +601,38 @@ def test_habery_reconfigure(mockHelpingNowUTC): """ # use same salter but with different path from name for each # salt = pysodium.randombytes(pysodium.crypto_pwhash_SALTBYTES) - #raw = b'\x05\xaa\x8f-S\x9a\xe9\xfaU\x9c\x02\x9c\x9b\x08Hu' - #salter = coring.Salter(raw=raw) - #salt = salter.qb64 - #assert salt == '0ABaqPLVOa6fpVnAKcmwhIdQ' + # raw = b'\x05\xaa\x8f-S\x9a\xe9\xfaU\x9c\x02\x9c\x9b\x08Hu' + # salter = coring.Salter(raw=raw) + # salt = salter.qb64 + # assert salt == '0ABaqPLVOa6fpVnAKcmwhIdQ' - salt = habbing.SALT + salt = coring.Salter(raw=b'0123456789abcdef').qb64 cname = "tam" # controller name - cbase = "main" # controller base shared + cbase = "main" # controller base shared pname = "nel" # peer name - pbase = "head" # peer base shared - + pbase = "head" # peer base shared with (habbing.openHby(name='wes', base=cbase, salt=salt) as wesHby, habbing.openHby(name='wok', base=cbase, salt=salt) as wokHby, habbing.openHby(name=cname, base=cbase, salt=salt) as tamHby, habbing.openHby(name='wat', base=cbase, salt=salt) as watHby, habbing.openHby(name=pname, base=pbase, salt=salt) as nelHby): - # witnesses first so can setup inception event for tam wsith = '1' # setup Wes's habitat nontrans wesHab = wesHby.makeHab(name="wes", isith=wsith, icount=1, transferable=False) assert not wesHab.kever.prefixer.transferable - wesKvy = eventing.Kevery(db=wesHab.db, lax=False, local=False) - wesPrs = parsing.Parser(kvy=wesKvy) # setup Wok's habitat nontrans wokHab = wokHby.makeHab(name="wok", isith=wsith, icount=1, transferable=False) assert not wokHab.kever.prefixer.transferable - wokKvy = eventing.Kevery(db=wokHab.db, lax=False, local=False) - wokPrs = parsing.Parser(kvy=wokKvy) # setup Tam's config curls = ["tcp://localhost:5620/"] iurls = [f"tcp://localhost:5621/?role={kering.Roles.peer}&name={pname}"] - assert (conf := tamHby.cf.get()) == {} + assert tamHby.cf.get() == {} conf = dict(dt=help.nowIso8601(), tam=dict(dt=help.nowIso8601(), curls=curls), iurls=iurls) tamHby.cf.put(conf) @@ -675,17 +648,14 @@ def test_habery_reconfigure(mockHelpingNowUTC): tsith = '1' # hex str of threshold int tamHab = tamHby.makeHab(name=cname, isith=tsith, icount=3, toad=2, wits=wits) assert tamHab.kever.prefixer.transferable - assert len(tamHab.iserder.werfers) == len(wits) - for werfer in tamHab.iserder.werfers: + assert len(tamHab.iserder.berfers) == len(wits) + for werfer in tamHab.iserder.berfers: assert werfer.qb64 in wits assert tamHab.kever.wits == wits assert tamHab.kever.toader.num == 2 assert tamHab.kever.sn == 0 - assert tamHab.kever.tholder.thold == 1 == int(tsith,16) + assert tamHab.kever.tholder.thold == 1 == int(tsith, 16) # create non-local kevery for Tam to process non-local msgs - tamKvy = eventing.Kevery(db=tamHab.db, lax=False, local=False) - # create non-local parer for Tam to process non-local msgs - tamPrs = parsing.Parser(kvy=tamKvy) # check tamHab.cf config setup ender = tamHab.db.ends.get(keys=(tamHab.pre, "controller", tamHab.pre)) @@ -695,14 +665,13 @@ def test_habery_reconfigure(mockHelpingNowUTC): assert locer.url == 'tcp://localhost:5620/' # setup Wat's habitat nontrans - watHab = watHby.makeHab(name="wat", isith=wsith, icount=1, transferable=False,) + watHab = watHby.makeHab(name="wat", isith=wsith, icount=1, transferable=False, ) assert not watHab.kever.prefixer.transferable - watKvy = eventing.Kevery(db=watHab.db, lax=False, local=False) # setup Nel's config curls = ["tcp://localhost:5621/"] iurls = [f"tcp://localhost:5620/?role={kering.Roles.peer}&name={cname}"] - assert (conf := nelHby.cf.get()) == {} + assert nelHby.cf.get() == {} conf = dict(dt=help.nowIso8601(), nel=dict(dt=help.nowIso8601(), curls=curls), iurls=iurls) nelHby.cf.put(conf) @@ -716,9 +685,7 @@ def test_habery_reconfigure(mockHelpingNowUTC): # setup Nel's habitat nontrans nelHab = nelHby.makeHab(name=pname, isith=wsith, icount=1, transferable=False) assert not nelHab.kever.prefixer.transferable - nelKvy = eventing.Kevery(db=nelHab.db, lax=False, local=False) # create non-local parer for Nel to process non-local msgs - nelPrs = parsing.Parser(kvy=nelKvy) assert nelHab.pre == 'BBWmLeVPY4obmPkyBGCsmysDmhbe017t6gS7v6B_ogV9' assert nelHab.kever.prefixer.code == coring.MtrDex.Ed25519N @@ -731,7 +698,6 @@ def test_habery_reconfigure(mockHelpingNowUTC): locer = nelHab.db.locs.get(keys=(nelHab.pre, kering.Schemes.tcp)) assert locer.url == 'tcp://localhost:5621/' - assert not os.path.exists(nelHby.cf.path) assert not os.path.exists(nelHby.db.path) assert not os.path.exists(nelHby.ks.path) @@ -750,37 +716,87 @@ def test_habery_reconfigure(mockHelpingNowUTC): """Done Test""" -def test_hab_exchange(mockHelpingNowUTC): - +def test_namespaced_habs(): with habbing.openHby() as hby: hab = hby.makeHab(name="test") assert hab.pre == "EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3" - data = dict(a=1, b=2, c=3) - exn = exchanging.exchange(route='/test/fwd', modifiers=dict(), - payload=data) + found = hby.habByName("test") + assert found.pre == "EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3" + + assert len(hby.habs) == 1 + assert len(hby.prefixes) == 1 - msg = hab.exchange(serder=exn, save=True) - assert msg == (b'{"v":"KERI10JSON0000ad_","t":"exn","d":"EF4u9o0M_Qs0Vkqb0vwDnrqP' - b'aQyCiI1Y_9ezNH2WtNKH","dt":"2021-01-01T00:00:00.000000+00:00","r' - b'":"/test/fwd","q":{},"a":{"a":1,"b":2,"c":3}}-HABEIaGMMWJFPmtXzn' - b'Y1IIiKDIrg-vIyge6mBl2QV8dDjI3-AABAABGv3uei98BwqLUt96_366zqonSFpf' - b'TKE4fVHBVoa6t5nVRHPRc-R7m8Ck5q7ie5SzhciTq5oT9CujANkV5UG0A') + nshab = hby.makeHab(name="test2", ns="agent") + assert nshab.pre == "EErXOolQNmKrTMKfXdQ1sj8YsgZZe4wMXZwsX-j1V6Dd" - serder = hab.db.exns.get(keys=(exn.said,)) - assert serder.ked == exn.ked + assert len(hby.habs) == 1 + assert len(hby.namespaces) == 1 + assert len(hby.prefixes) == 2 - hab = hby.makeHab(name="test1", transferable=False) - assert hab.pre == "BJZ_LF61JTCCSCIw2Q4ozE2MsbRC4m-N6-tFVlCeiZPG" - msg = hab.exchange(serder=exn, save=True) - assert msg == (b'{"v":"KERI10JSON0000ad_","t":"exn","d":"EF4u9o0M_Qs0Vkqb0vwDnrqP' - b'aQyCiI1Y_9ezNH2WtNKH","dt":"2021-01-01T00:00:00.000000+00:00","r' - b'":"/test/fwd","q":{},"a":{"a":1,"b":2,"c":3}}-CABBJZ_LF61JTCCSCI' - b'w2Q4ozE2MsbRC4m-N6-tFVlCeiZPG0BCb_ZFWN6truKuBCekbTfxeVFTQJGjjuPe' - b'uEhIDyxUvH72A_vp3lllrVnW9MPJPZHYltGfqvxXnO5jOyk2BWZgN') + found = hby.habByName(name="test2") + assert found is None + found = hby.habByName(name="test2", ns="agent") + assert found.pre == "EErXOolQNmKrTMKfXdQ1sj8YsgZZe4wMXZwsX-j1V6Dd" + found = hby.habByName(name="test", ns="agent") + assert found is None - serder = hab.db.exns.get(keys=(exn.said,)) - assert serder.ked == exn.ked + # Test a '.' in Hab name + nshab = hby.makeHab(name="test.3", ns="agent") + assert nshab.pre == "EG5FUOzW_KKVB8JGlNGoZAADDC8cZ6Jt079nLEaFnYcg" + + assert len(hby.habs) == 1 + assert len(hby.namespaces) == 1 + assert len(hby.prefixes) == 3 + ns = hby.namespaces['agent'] + assert len(ns) == 2 + + # '.' characters not allowed in namespace names + with pytest.raises(kering.ConfigurationError): + hby.makeHab(name="test", ns="agent.5") + + hby.close() + + # Test Reload of Namespace habs + name = "ns-test" + with habbing.openHby(name=name, base="test", temp=False, clear=True) as hby: + hab = hby.makeHab(name=name, icount=1) + opre = hab.pre + hab = hby.makeHab(name="test.1", icount=1) + o2pre = hab.pre + nshab = hby.makeHab(name="test", ns="agent") + atpre = nshab.pre + nshab = hby.makeHab(name="test2", ns="agent") + at2pre = nshab.pre + nshab = hby.makeHab(name="test", ns="controller") + ctpre = nshab.pre + + with habbing.openHby(name=name, base="test", temp=False) as hby: + for pre in [opre, o2pre, atpre, at2pre, ctpre]: + assert pre in hby.db.kevers # read through cache + assert pre in hby.db.prefixes + + assert len(hby.habs) == 2 + assert len(hby.db.prefixes) == 5 + + agent = hby.namespaces["agent"] + assert len(agent) == 2 + ctrl = hby.namespaces["controller"] + assert len(ctrl) == 1 + + found = hby.habByName(name=name) + assert found.pre == opre + found = hby.habByName(name="test.1") + assert found.pre == o2pre + found = hby.habByName(name="test", ns="agent") + assert found.pre == atpre + found = hby.habByName(name="test2", ns="agent") + assert found.pre == at2pre + found = hby.habByName(name="test", ns="controller") + assert found.pre == ctpre + + hby.close(clear=True) + hby.cf.close(clear=True) def test_make_other_event(): @@ -810,9 +826,142 @@ def test_make_other_event(): b'p8Sc4CcESKA-q5O0O5CmpCbSrA29UpqZnfvUagrwm8w3M1a1WJKy64OQYXIG') +def test_hab_by_pre(): + with habbing.openHby() as hby: + # Create two habs in the default namespace + hab1 = hby.makeHab(name="test1") + hab2 = hby.makeHab(name="test2") + + # Create two habs in namespace "one" + hab3 = hby.makeHab(name="test1", ns="one") + hab4 = hby.makeHab(name="test2", ns="one") + + # Create two habs in namespace "two" + hab5 = hby.makeHab(name="test1", ns="two") + hab6 = hby.makeHab(name="test2", ns="two") + + # Only habs in default namespace are in hby.habs + assert hab1.pre in hby.habs + assert hab2.pre in hby.habs + assert hab3.pre not in hby.habs + assert hab4.pre not in hby.habs + assert hab5.pre not in hby.habs + assert hab6.pre not in hby.habs + + assert hby.habByPre("EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3") is None + + assert hby.habByPre(pre=hab1.pre) == hab1 + assert hby.habByPre(pre=hab2.pre) == hab2 + assert hby.habByPre(pre=hab3.pre) == hab3 + assert hby.habByPre(pre=hab4.pre) == hab4 + assert hby.habByPre(pre=hab5.pre) == hab5 + assert hby.habByPre(pre=hab6.pre) == hab6 + + assert "one" in hby.namespaces + assert hab3.pre in hby.namespaces["one"] + assert hab4.pre in hby.namespaces["one"] + assert hab1.pre not in hby.namespaces["one"] + assert hab2.pre not in hby.namespaces["one"] + assert "two" in hby.namespaces + assert hab5.pre in hby.namespaces["two"] + assert hab6.pre in hby.namespaces["two"] + assert hab1.pre not in hby.namespaces["two"] + assert hab2.pre not in hby.namespaces["two"] + + +def test_postman_endsfor(): + with habbing.openHby(name="test", temp=True) as hby, \ + habbing.openHby(name="wes", salt=coring.Salter(raw=b'wess-the-witness').qb64, temp=True) as wesHby, \ + habbing.openHab(name="agent", temp=True) as (agentHby, agentHab): + + wesHab = wesHby.makeHab(name='wes', isith="1", icount=1, transferable=False) + assert not wesHab.kever.prefixer.transferable + # create non-local kevery for Wes to process nonlocal msgs + wesKvy = eventing.Kevery(db=wesHab.db, lax=False, local=False) + + wits = [wesHab.pre] + hab = hby.makeHab(name='cam', isith="1", icount=1, toad=1, wits=wits, ) + assert hab.kever.prefixer.transferable + assert len(hab.iserder.berfers) == len(wits) + for werfer in hab.iserder.berfers: + assert werfer.qb64 in wits + assert hab.kever.wits == wits + assert hab.kever.toader.num == 1 + assert hab.kever.sn == 0 + + kvy = eventing.Kevery(db=hab.db, lax=False, local=False) + icpMsg = hab.makeOwnInception() + rctMsgs = [] # list of receipts from each witness + parsing.Parser().parse(ims=bytearray(icpMsg), kvy=wesKvy) + assert wesKvy.kevers[hab.pre].sn == 0 # accepted event + assert len(wesKvy.cues) >= 1 # assunmes includes queued receipt cue + # better to find cue in cues and confirm exactly + rctMsg = wesHab.processCues(wesKvy.cues) # process cue returns rct msg + assert len(rctMsg) == 626 + rctMsgs.append(rctMsg) + + for msg in rctMsgs: # process rct msgs from all witnesses + parsing.Parser().parse(ims=bytearray(msg), kvy=kvy) + assert wesHab.pre in kvy.kevers + + agentIcpMsg = agentHab.makeOwnInception() + parsing.Parser().parse(ims=bytearray(agentIcpMsg), kvy=kvy) + assert agentHab.pre in kvy.kevers + + msgs = bytearray() + msgs.extend(wesHab.makeEndRole(eid=wesHab.pre, + role=kering.Roles.controller, + stamp=helping.nowIso8601())) + + msgs.extend(wesHab.makeLocScheme(url='http://127.0.0.1:8888', + scheme=kering.Schemes.http, + stamp=helping.nowIso8601())) + wesHab.psr.parse(ims=bytearray(msgs)) + + # Set up + msgs.extend(hab.makeEndRole(eid=hab.pre, + role=kering.Roles.controller, + stamp=helping.nowIso8601())) + + msgs.extend(hab.makeLocScheme(url='http://127.0.0.1:7777', + scheme=kering.Schemes.http, + stamp=helping.nowIso8601())) + hab.psr.parse(ims=msgs) + + msgs = bytearray() + msgs.extend(agentHab.makeEndRole(eid=agentHab.pre, + role=kering.Roles.controller, + stamp=helping.nowIso8601())) + + msgs.extend(agentHab.makeLocScheme(url='http://127.0.0.1:6666', + scheme=kering.Schemes.http, + stamp=helping.nowIso8601())) + + msgs.extend(hab.makeEndRole(eid=agentHab.pre, + role=kering.Roles.agent, + stamp=helping.nowIso8601())) + + msgs.extend(hab.makeEndRole(eid=agentHab.pre, + role=kering.Roles.mailbox, + stamp=helping.nowIso8601())) + + agentHab.psr.parse(ims=bytearray(msgs)) + hab.psr.parse(ims=bytearray(msgs)) + + ends = hab.endsFor(hab.pre) + assert ends == { + 'agent': { + 'EBErgFZoM3PBQNTpTuK9bax_U8HLJq1Re2RM1cdifaTJ': {'http': 'http://127.0.0.1:6666'}}, + 'controller': { + 'EGadHcyW9IfVIPrFUAa_I0z4dF8QzQAvUvfaUTJk8Jre': {'http': 'http://127.0.0.1:7777'}}, + 'mailbox': { + 'EBErgFZoM3PBQNTpTuK9bax_U8HLJq1Re2RM1cdifaTJ': {'http': 'http://127.0.0.1:6666'}}, + 'witness': { + 'BN8t3n1lxcV0SWGJIIF46fpSUqA7Mqre5KJNN3nbx3mr': {'http': 'http://127.0.0.1:8888'}} + } if __name__ == "__main__": pass test_habery() - #pytest.main(['-vv', 'test_habbing.py::test_habery_reconfigure']) + # pytest.main(['-vv', 'test_habbing.py::test_habery_reconfigure']) diff --git a/tests/app/test_httping.py b/tests/app/test_httping.py index ba1a7aadd..7987252cb 100644 --- a/tests/app/test_httping.py +++ b/tests/app/test_httping.py @@ -9,7 +9,7 @@ from falcon.testing import helpers from keri.app import habbing, httping -from keri.core import coring +from keri.core import coring, serdering from keri.vdr import credentialing, verifying @@ -77,12 +77,12 @@ def test_create_cesr_request(mockHelpingNowUTC): route="tels") client = MockClient() - httping.createCESRRequest(msg, client, path="/qry/tels") + httping.createCESRRequest(msg, client, dest=wit, path="/qry/tels") args = client.args.pop() assert args["method"] == "POST" assert args["path"] == "/qry/tels" - serder = coring.Serder(raw=args['body']) + serder = serdering.SerderKERI(raw=args['body']) assert serder.ked["t"] == coring.Ilks.qry assert serder.ked["r"] == "tels" @@ -94,7 +94,7 @@ def test_create_cesr_request(mockHelpingNowUTC): msg = hab.query(pre=hab.pre, src=wit, route="mbx", query=dict(s=0)) client = MockClient() - httping.createCESRRequest(msg, client, path="/qry/mbx") + httping.createCESRRequest(msg, client, dest=wit, path="/qry/mbx") args = client.args.pop() assert args["method"] == "POST" @@ -107,9 +107,10 @@ def test_create_cesr_request(mockHelpingNowUTC): headers = args["headers"] assert headers["Content-Type"] == "application/cesr+json" assert headers["Content-Length"] == 260 - assert headers["CESR-ATTACHMENT"] == bytearray(b'-VAj-HABEIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3-AABAAB6P97k' - b'Z3al3V3z3VstRtHRPeOrotuqZZUgBl2yHzgpGyOjAXYGinVqWLAMhdmQ089FTSAz' - b'qSTBmJzI8RvIezsJ') + assert headers["CESR-ATTACHMENT"] == bytearray( + b'-VAj-HABEIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3-AABAAB6P97k' + b'Z3al3V3z3VstRtHRPeOrotuqZZUgBl2yHzgpGyOjAXYGinVqWLAMhdmQ089FTSAz' + b'qSTBmJzI8RvIezsJ') def test_stream_cesr_request(mockHelpingNowUTC): @@ -124,12 +125,12 @@ def test_stream_cesr_request(mockHelpingNowUTC): route="tels") client = MockClient() - httping.streamCESRRequests(client, msg, path="/qry/tels") + httping.streamCESRRequests(client, msg, dest=wit, path="/qry/tels") args = client.args.pop() assert args["method"] == "POST" assert args["path"] == "/qry/tels" - serder = coring.Serder(raw=args['body']) + serder = serdering.SerderKERI(raw=args['body']) assert serder.ked["t"] == coring.Ilks.qry assert serder.ked["r"] == "tels" @@ -141,7 +142,7 @@ def test_stream_cesr_request(mockHelpingNowUTC): msg = hab.query(pre=hab.pre, src=wit, route="mbx", query=dict(s=0)) client = MockClient() - httping.streamCESRRequests(client, msg, path="/qry/mbx") + httping.streamCESRRequests(client, msg, dest=wit, path="/qry/mbx") args = client.args.pop() assert args["method"] == "POST" @@ -162,16 +163,16 @@ def test_stream_cesr_request(mockHelpingNowUTC): msgs.extend(hab.makeOwnEvent(sn=0)) client = MockClient() - httping.streamCESRRequests(client, msgs) + httping.streamCESRRequests(client, msgs, dest=wit) assert len(client.args) == 2 args = client.args.pop() assert args["method"] == "POST" assert args["path"] == "/" assert args["body"] == (b'{"v":"KERI10JSON00012b_","t":"icp","d":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2' - b'QV8dDjI3","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","s":"0","kt":"1' - b'","k":["DGmIfLmgErg4zFHfPwaDckLNxsLqc5iS_P0QbLjbWR0I"],"nt":"1","n":["EJhRr1' - b'0e5p7LVB6JwLDIcgqsISktnfe5m60O_I2zZO6N"],"bt":"0","b":[],"c":[],"a":[]}') + b'QV8dDjI3","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","s":"0","kt":"1' + b'","k":["DGmIfLmgErg4zFHfPwaDckLNxsLqc5iS_P0QbLjbWR0I"],"nt":"1","n":["EJhRr1' + b'0e5p7LVB6JwLDIcgqsISktnfe5m60O_I2zZO6N"],"bt":"0","b":[],"c":[],"a":[]}') headers = args["headers"] assert headers['Content-Length'] == 299 assert headers['Content-Type'] == 'application/cesr+json' @@ -183,9 +184,9 @@ def test_stream_cesr_request(mockHelpingNowUTC): assert args["path"] == "/" assert args["body"] == (b'{"v":"KERI10JSON000105_","t":"qry","d":"EHtaQHsKzezkQUEYjMjEv6nIf4AhhR9Zy6Av' - b'cfyGCXkI","dt":"2021-01-01T00:00:00.000000+00:00","r":"logs","rr":"","q":{"s' - b'":0,"i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","src":"BGKVzj4ve0VSd8' - b'z_AmvhLg4lqcC_9WYX90k03q-R_Ydo"}}') + b'cfyGCXkI","dt":"2021-01-01T00:00:00.000000+00:00","r":"logs","rr":"","q":{"s' + b'":0,"i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","src":"BGKVzj4ve0VSd8' + b'z_AmvhLg4lqcC_9WYX90k03q-R_Ydo"}}') headers = args["headers"] assert headers['Content-Length'] == 261 assert headers['Content-Type'] == 'application/cesr+json' diff --git a/tests/app/test_indirecting.py b/tests/app/test_indirecting.py index 035c0b04a..2c9641633 100644 --- a/tests/app/test_indirecting.py +++ b/tests/app/test_indirecting.py @@ -5,11 +5,14 @@ """ import json +import falcon +import hio import pytest +from hio.core import tcp, http from hio.help import decking from keri.app import indirecting, storing, habbing -from keri.core import coring +from keri.core import coring, serdering def test_mailbox_iter(): @@ -101,9 +104,9 @@ def test_qrymailbox_iter(): with habbing.openHab(name="test", transferable=True, temp=True) as (hby, hab): assert hab.pre == 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3' icp = hab.makeOwnInception() - icpSrdr = coring.Serder(raw=icp) + icpSrdr = serdering.SerderKERI(raw=icp) qry = hab.query(pre=hab.pre, src=hab.pre, route="/mbx") - srdr = coring.Serder(raw=qry) + srdr = serdering.SerderKERI(raw=qry) cues = decking.Deck() mbx = storing.Mailboxer(temp=True) @@ -149,6 +152,33 @@ def test_qrymailbox_iter(): next(mbi) +class MockServerTls: + def __init__(self, certify, keypath, certpath, cafilepath, port): + pass + + +class MockHttpServer: + def __init__(self, port, app, servant=None): + self.servant = servant + + +def test_createHttpServer(monkeypatch): + port = 5632 + app = falcon.App() + server = indirecting.createHttpServer(port, app) + assert isinstance(server, http.Server) + + monkeypatch.setattr(hio.core.tcp, 'ServerTls', MockServerTls) + monkeypatch.setattr(hio.core.http, 'Server', MockHttpServer) + + server = indirecting.createHttpServer(port, app, keypath='keypath', certpath='certpath', cafilepath='cafilepath') + + assert isinstance(server, MockHttpServer) + assert isinstance(server.servant, MockServerTls) + + + + if __name__ == "__main__": test_mailbox_iter() test_qrymailbox_iter() diff --git a/tests/app/test_kiwiing.py b/tests/app/test_kiwiing.py deleted file mode 100644 index 66285f507..000000000 --- a/tests/app/test_kiwiing.py +++ /dev/null @@ -1,1629 +0,0 @@ -# -*- encoding: utf-8 -*- -""" -tests.app.agent_kiwiserver module - -""" - -import json -import os - -import falcon -from falcon import testing -from hio.base import doing - -import keri.app.oobiing -from keri import kering -from keri.app import (habbing, kiwiing, grouping, booting, notifying, - signing, connecting) -from keri.app.kiwiing import MultisigEventEnd -from keri.core import eventing, parsing, coring, scheming -from keri.core.eventing import SealEvent -from keri.db import basing, dbing -from keri.vc import proving -from keri.vdr import credentialing, verifying - -from tests.app import openMultiSig - - -def test_credential_handlers(mockHelpingNowUTC, seeder): - with habbing.openHab(name="test", transferable=True) as (hby, hab), \ - habbing.openHab(name="recp", transferable=True) as (recpHby, recp): - seeder.seedSchema(hby.db) - seeder.seedSchema(recpHby.db) - - app = falcon.App() - - regery = credentialing.Regery(hby=hby, name=hab.name, temp=True) - issuer = regery.makeRegistry(name=hab.name, prefix=hab.pre) - rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() - hab.interact(data=[rseal]) - seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=hab.kever.serder.saider) - regery.processEscrows() - assert issuer.regk in regery.reger.tevers - - verifier = verifying.Verifier(hby=hby, reger=regery.reger) - - icp = recp.makeOwnEvent(sn=0) - kvy = eventing.Kevery(db=hab.db, lax=True) - parsing.Parser().parseOne(ims=bytearray(icp), kvy=kvy) - - notifier = notifying.Notifier(hby=hby) - counselor = grouping.Counselor(hby=hby) - registrar = credentialing.Registrar(hby=hby, rgy=regery, counselor=counselor) - credentialer = credentialing.Credentialer(hby=hby, rgy=regery, registrar=registrar, verifier=verifier) - - _ = kiwiing.loadEnds(hby=hby, - rgy=regery, - verifier=verifier, - notifier=notifier, - signaler=notifier.signaler, - counselor=counselor, - registrar=registrar, - credentialer=credentialer, - servery=booting.Servery(port=1234), - bootConfig=dict(), - app=app, path="/") - - client = testing.TestClient(app) - - result = client.simulate_post(path="/registries", body=b'{}') - assert result.status == falcon.HTTP_400 # Bad request, missing name - - result = client.simulate_post(path="/registries", body=b'{"name": "test"}') - assert result.status == falcon.HTTP_400 # Bad Request, missing alias - - result = client.simulate_post(path="/registries", body=b'{"name": "test", "alias": "test123"}') - assert result.status == falcon.HTTP_404 # Bad Request, invalid alias - - # Test all the parameters - result = client.simulate_post(path="/registries", - body=b'{"name": "test-full", "alias": "test",' - b' "noBackers": true, "baks": [], "toad": 0, "estOnly": false}') - assert result.status == falcon.HTTP_202 - regery.processEscrows() - - result = client.simulate_post(path="/registries", body=b'{"name": "test", "alias": "test"}') - assert result.status == falcon.HTTP_202 - regery.processEscrows() - - result = client.simulate_get(path="/registries") - assert result.status == falcon.HTTP_200 - assert len(result.json) == 3 - - schema = "ENTAoj2oNBFpaniRswwPcca9W1ElEeH2V7ahw68HV4G5" - LEI = "1234567890abcdefg" - - data = dict(LEI=LEI) - body = dict( - registry="test", - schema=schema, - recipient=recp.pre, - type="GLEIFvLEICredential", - credentialData=data, source={}, rules={} - ) - b = json.dumps(body).encode("utf-8") - result = client.simulate_post(path="/credentials/test", body=b) - assert result.status == falcon.HTTP_200 - creder = proving.Creder(ked=result.json) - regery.processEscrows() - credentialer.processEscrows() - verifier.processEscrows() - - assert regery.reger.creds.get(creder.saidb).raw == creder.raw - - # Try to revoke a credential that doesn't exist and get the appropriate error - result = client.simulate_delete(path="/credentials/test", - query_string=("registry=test&" - "said=ESRIYQwCs8z1Fu7Jc6wf1ZDSoQQbKgjW9PiC324D_MUs")) - assert result.status == falcon.HTTP_NOT_FOUND - - # Now revoke the actual credential - result = client.simulate_delete(path="/credentials/test", - query_string=("registry=test&" - f"said={creder.said}")) - assert result.status == falcon.HTTP_202 - regery.processEscrows() - credentialer.processEscrows() - - result = client.simulate_get(path="/credentials/test123", params=dict(type="issued", registry="test")) - assert result.status == falcon.HTTP_400 # Bad Request, invalid alias - - result = client.simulate_get(path="/credentials/test", params=dict(type="issued", registry="test")) - assert result.status == falcon.HTTP_200 - assert len(result.json) == 1 - sad = result.json[0]["sad"] - assert sad["d"] == creder.said - state = result.json[0]["status"] - assert state["et"] == coring.Ilks.rev - - -def test_multisig_incept(): - prefix = "ends_test" - salt = b'0123456789abcdef' - with habbing.openHab(name=f"{prefix}_1", salt=salt, transferable=True, temp=True) as (hby1, hab1), \ - habbing.openHab(name=f"{prefix}_2", salt=salt, transferable=True, temp=True) as (hby2, hab2), \ - habbing.openHab(name=f"{prefix}_3", salt=salt, transferable=True, temp=True) as (hby3, hab3): - kev1 = eventing.Kevery(db=hab1.db, lax=False, local=False) - kev2 = eventing.Kevery(db=hab2.db, lax=False, local=False) - kev3 = eventing.Kevery(db=hab3.db, lax=False, local=False) - - icp1 = hab1.makeOwnEvent(sn=0) - parsing.Parser().parse(ims=bytearray(icp1), kvy=kev2) - icp2 = hab2.makeOwnEvent(sn=0) - parsing.Parser().parse(ims=bytearray(icp2), kvy=kev1) - parsing.Parser().parse(ims=bytearray(icp1), kvy=kev3) - icp2 = hab2.makeOwnEvent(sn=0) - parsing.Parser().parse(ims=bytearray(icp2), kvy=kev1) - parsing.Parser().parse(ims=bytearray(icp2), kvy=kev3) - icp3 = hab3.makeOwnEvent(sn=0) - parsing.Parser().parse(ims=bytearray(icp3), kvy=kev1) - parsing.Parser().parse(ims=bytearray(icp3), kvy=kev2) - - assert hab1.pre == 'EL04UFX_N1fx9Vjg6GERitFpOymqiuxieTHvYal6iVEm' - assert hab2.pre == 'EMXpkB-DXmcDP_MdsyXDvZjgU8jtuUqPcfXoYq4TFLAv' - assert hab3.pre == 'EJTjsnvXUUTObBuz_jPKLUGVrq48E_AEg2ph69ZqmmUs' - - counselor = grouping.Counselor(hby=hby1) - notifier = notifying.Notifier(hby=hby1) - icpEnd = kiwiing.MultisigInceptEnd(hby=hby1, counselor=counselor, notifier=notifier) - app = falcon.App() - app.add_route("/multisig/{alias}/icp", icpEnd) - - client = testing.TestClient(app) - - # aids is required - result = client.simulate_post(path="/multisig/test/icp", body=b'{}') - assert result.status == falcon.HTTP_400 - assert result.text == "Invalid multisig group inception request, 'aids' is required'" - - # aids must include a local identifier - body = dict(group="test", aids=[hab2.pre, hab3.pre]) - b = json.dumps(body).encode("utf-8") - - result = client.simulate_post(path="/multisig/test/icp", body=b) - assert result.status == falcon.HTTP_400 - assert result.text == ('Invalid multisig group inception request, aid list must contain a local ' - "identifier'") - - # can not reuse a hab alias - body = dict(aids=[hab1.pre, hab2.pre, hab3.pre]) - b = json.dumps(body).encode("utf-8") - - result = client.simulate_post(path=f"/multisig/{prefix}_1/icp", body=b) - assert result.status == falcon.HTTP_400 - assert result.text == "Identifier alias ends_test_1 is already in use" - - body = dict( - aids=[hab1.pre, hab2.pre, hab3.pre], - transferable=True, - wits=[ - "BGKVzj4ve0VSd8z_AmvhLg4lqcC_9WYX90k03q-R_Ydo", - "BCyRFMideczFZoapylLIyCjSdhtqVb31wZkRKvPfNqkw", - "BDoq68HCmYNUDgOz4Skvlu306o_NY-NrYuKAVhk3Zh9c" - ], - toad=2, - isith='2', - nsith='2' - - ) - b = json.dumps(body).encode("utf-8") - - # Use Falcon test all to submit the request to issue a credential - client = testing.TestClient(app) - result = client.simulate_post(path="/multisig/multisig/icp", body=b) - assert result.status == falcon.HTTP_200 - assert len(icpEnd.postman.evts) == 2 - - # Incept POST endpoint initiates multisig inception by sending the ICP to all other participants - evt = icpEnd.postman.evts.popleft() - assert evt["src"] == hab1.pre - assert evt["dest"] == hab2.pre - srdr = evt["serder"] - assert srdr.ked['t'] == coring.Ilks.exn - assert srdr.ked['r'] == '/multisig/icp' - payload = json.dumps(srdr.ked["a"]).encode("utf-8") - assert payload == (b'{"aids": ["EL04UFX_N1fx9Vjg6GERitFpOymqiuxieTHvYal6iVEm", "EMXpkB-DXmcDP_Mds' - b'yXDvZjgU8jtuUqPcfXoYq4TFLAv", "EJTjsnvXUUTObBuz_jPKLUGVrq48E_AEg2ph69ZqmmUs"' - b'], "ked": {"v": "KERI10JSON000273_", "t": "icp", "d": "EDENaz23s9dl8TfUJ6drp' - b'MO_Sr2k91DSGy8-Jl7mlaDS", "i": "EDENaz23s9dl8TfUJ6drpMO_Sr2k91DSGy8-Jl7mlaDS' - b'", "s": "0", "kt": "2", "k": ["DGWoXud8dM4ubtwRBjxHZ2B3j2dblNbRN9ezjklDUaqo"' - b', "DFK7TqkMoxs_wpF5ID25RZUBb5ow93kP4r3Rkemz41Gl", "DP95wOs0R9SGIgOaC-gpWHlJt' - b'rwPvgDeP-0-VLKZhamW"], "nt": "2", "n": ["EH-uz11Ky8NEGpL7kRG2A2ef6_g4m2865G8' - b'Qbx1QCryT", "EHW59fWA4-YPCZNtau6dbm5u9v3_egguEucKgjzDu5Kr", "EMDsRWscjCxnNsd' - b'SUlcDjWklmtgeNGcuI0PG1Uc5vfQP"], "bt": "2", "b": ["BGKVzj4ve0VSd8z_AmvhLg4lq' - b'cC_9WYX90k03q-R_Ydo", "BCyRFMideczFZoapylLIyCjSdhtqVb31wZkRKvPfNqkw", "BDoq6' - b'8HCmYNUDgOz4Skvlu306o_NY-NrYuKAVhk3Zh9c"], "c": [], "a": []}}') - - evt = icpEnd.postman.evts.popleft() - assert evt["src"] == hab1.pre - assert evt["dest"] == hab3.pre - assert evt["serder"] == srdr - - # Create new end and app to represent Hab2's agent - counselor = grouping.Counselor(hby=hby2) - notifier = notifying.Notifier(hby=hby2) - icpEnd = kiwiing.MultisigInceptEnd(hby=hby2, counselor=counselor, notifier=notifier) - app = falcon.App() - app.add_route("/multisig/{alias}/icp", icpEnd) - - client = testing.TestClient(app) - - # Perform a PUT to join a group identifier inception - result = client.simulate_put(path="/multisig/multisig2/icp", body=b) - assert result.status == falcon.HTTP_200 - assert len(icpEnd.counselor.postman.evts) == 2 - evt = icpEnd.counselor.postman.evts.popleft() - assert evt["src"] == hab2.pre - assert evt["dest"] == hab1.pre - assert evt["topic"] == "multisig" - assert evt["serder"].raw == (b'{"v":"KERI10JSON000273_","t":"icp","d":"EDENaz23s9dl8TfUJ6drpMO_Sr2k91DSGy8-' - b'Jl7mlaDS","i":"EDENaz23s9dl8TfUJ6drpMO_Sr2k91DSGy8-Jl7mlaDS","s":"0","kt":"2' - b'","k":["DGWoXud8dM4ubtwRBjxHZ2B3j2dblNbRN9ezjklDUaqo","DFK7TqkMoxs_wpF5ID25R' - b'ZUBb5ow93kP4r3Rkemz41Gl","DP95wOs0R9SGIgOaC-gpWHlJtrwPvgDeP-0-VLKZhamW"],"nt' - b'":"2","n":["EH-uz11Ky8NEGpL7kRG2A2ef6_g4m2865G8Qbx1QCryT","EHW59fWA4-YPCZNta' - b'u6dbm5u9v3_egguEucKgjzDu5Kr","EMDsRWscjCxnNsdSUlcDjWklmtgeNGcuI0PG1Uc5vfQP"]' - b',"bt":"2","b":["BGKVzj4ve0VSd8z_AmvhLg4lqcC_9WYX90k03q-R_Ydo","BCyRFMideczFZ' - b'oapylLIyCjSdhtqVb31wZkRKvPfNqkw","BDoq68HCmYNUDgOz4Skvlu306o_NY-NrYuKAVhk3Zh' - b'9c"],"c":[],"a":[]}') - #assert evt["attachment"] == (b'-AABABA4LBb_ljS-dgSnh_g0fUbc1y5Q_tdh12eMAkzyRsd_Bbqy1zK05Uua-6GM' - #b'pUKP5vIo--fuD3YesuxJ7l6GbFAF') - - assert evt["attachment"] == (b'-AABBBA4LBb_ljS-dgSnh_g0fUbc1y5Q_tdh12eMAkzyRsd_Bbqy1zK05Uua-6GM' - b'pUKP5vIo--fuD3YesuxJ7l6GbFAF') - evt = icpEnd.counselor.postman.evts.popleft() - assert evt["src"] == hab2.pre - assert evt["dest"] == hab3.pre - - # Test weight threshold specification for isith and nsith - body = dict( - aids=[hab1.pre, hab2.pre, hab3.pre], - transferable=True, - wits=[ - "BGKVzj4ve0VSd8z_AmvhLg4lqcC_9WYX90k03q-R_Ydo", - "BCyRFMideczFZoapylLIyCjSdhtqVb31wZkRKvPfNqkw", - "BDoq68HCmYNUDgOz4Skvlu306o_NY-NrYuKAVhk3Zh9c" - ], - toad=2, - isith="1/3,1/3,1/3", - nsith="1/3,1/3,1/3" - - ) - b = json.dumps(body).encode("utf-8") - - # Use Falcon test all to submit the request to issue a credential - client = testing.TestClient(app) - result = client.simulate_post(path="/multisig/multisig/icp", body=b) - assert result.status == falcon.HTTP_200 - assert len(icpEnd.postman.evts) == 2 - - -def test_multisig_rotation(): - prefix = "test" - with openMultiSig(prefix="test") as ((hby1, ghab1), (hby2, ghab2), (hby3, ghab3)): - assert ghab1.pre == ghab2.pre == ghab3.pre == 'EERn_laF0qwP8zTBGL86LbF84J0Yh2IvQSRskH3BZZiy' - - app = falcon.App() - - # Start with hby1 who will initiate the rotation with a POST - counselor = grouping.Counselor(hby=hby1) - notifier = notifying.Notifier(hby=hby1) - rotEnd = MultisigEventEnd(hby=hby1, counselor=counselor, notifier=notifier) - app.add_route("/multisig/{alias}/rot", rotEnd, suffix="rot") - - client = testing.TestClient(app) - - # aids is required - result = client.simulate_post(path="/multisig/test/rot", body=b'{}') - assert result.status == falcon.HTTP_400 - assert result.text == "Invalid multisig group rotation request, 'aids' is required" - - # aids must include a local identifier - body = dict(group="test", aids=[ghab2.pre, ghab3.pre]) - b = json.dumps(body).encode("utf-8") - - result = client.simulate_post(path="/multisig/test/rot", body=b) - assert result.status == falcon.HTTP_404 - assert result.text == "Invalid multisig group rotation request alias {alias} not found" - - body = dict( - aids=[ghab1.mhab.pre, ghab2.mhab.pre, ghab3.mhab.pre], - transferable=True, - wits=[ - "BGKVzj4ve0VSd8z_AmvhLg4lqcC_9WYX90k03q-R_Ydo", - "BCyRFMideczFZoapylLIyCjSdhtqVb31wZkRKvPfNqkw", - "BDoq68HCmYNUDgOz4Skvlu306o_NY-NrYuKAVhk3Zh9c" - ], - toad=2, - isith='2', - nsith='2' - - ) - b = json.dumps(body).encode("utf-8") - - # initiate a multisig rotation with a POST - client = testing.TestClient(app) - result = client.simulate_post(path=f"/multisig/{prefix}_group1/rot", body=b) - assert result.status == falcon.HTTP_202 - - # escrow event for local witnessing - assert hby1.db.glwe.get(keys=(ghab1.pre,)) is not None - - app = falcon.App() - # Now join rotation with hby2 who will initiate the rotation with a POST - counselor = grouping.Counselor(hby=hby2) - notifier = notifying.Notifier(hby=hby2) - rotEnd = MultisigEventEnd(hby=hby2, counselor=counselor, notifier=notifier) - app.add_route("/multisig/{alias}/rot", rotEnd, suffix="rot") - client = testing.TestClient(app) - result = client.simulate_put(path=f"/multisig/{prefix}_group2/rot", body=b) - assert result.status == falcon.HTTP_202 - - # escrow event for local witnessing - glwe = hby2.db.glwe.get(keys=(ghab2.pre,)) - assert glwe is not None - # no notifications set if joining - assert len(rotEnd.postman.evts) == 0 - - -def test_multisig_interaction(): - prefix = "test" - with openMultiSig(prefix="test") as ((hby1, ghab1), (hby2, ghab2), (hby3, ghab3)): - assert ghab1.pre == ghab2.pre == ghab3.pre == 'EERn_laF0qwP8zTBGL86LbF84J0Yh2IvQSRskH3BZZiy' - - app = falcon.App() - - # Start with hby1 who will initiate the rotation with a POST - counselor = grouping.Counselor(hby=hby1) - notifier = notifying.Notifier(hby=hby1) - evtEnd = MultisigEventEnd(hby=hby1, counselor=counselor, notifier=notifier) - app.add_route("/multisig/{alias}/ixn", evtEnd, suffix="ixn") - - client = testing.TestClient(app) - - # aids is required - result = client.simulate_post(path="/multisig/test/ixn", body=b'{}') - assert result.status == falcon.HTTP_400 - assert result.text == "Invalid multisig group rotation request, 'aids' is required" - - # aids must include a local identifier - body = dict(group="test", aids=[ghab2.pre, ghab3.pre]) - b = json.dumps(body).encode("utf-8") - - result = client.simulate_post(path="/multisig/test/ixn", body=b) - assert result.status == falcon.HTTP_404 - assert result.text == "Invalid multisig group rotation request alias {alias} not found" - - body = dict( - aids=[ghab1.mhab.pre, ghab2.mhab.pre, ghab3.mhab.pre], - data=dict(i=ghab3.mhab.pre, s=0) - ) - b = json.dumps(body).encode("utf-8") - - # initiate a multisig rotation with a POST - client = testing.TestClient(app) - result = client.simulate_post(path=f"/multisig/{prefix}_group1/ixn", body=b) - assert result.status == falcon.HTTP_202 - - # escrow event for all signatures - assert hby1.db.gpse.get(keys=(ghab1.pre,)) is not None - - # sends local rotation event to other participants to start the rotation - assert len(evtEnd.postman.evts) == 2 - evt = evtEnd.postman.evts.popleft() - assert evt["src"] == ghab1.mhab.pre - assert evt["dest"] == ghab2.mhab.pre - assert evt["topic"] == "multisig" - evt = evtEnd.postman.evts.popleft() - assert evt["src"] == ghab1.mhab.pre - assert evt["dest"] == ghab3.mhab.pre - assert evt["topic"] == "multisig" - payload = evt["serder"].ked["a"] - assert payload == {'aids': ['EH__mobl7NDyyQCB1DoLK-OPSueraPtZAlWEjfOYkaba', - 'EJPlLivjjHWkkSpvUTT7iewTlG_TolGIpUbAxsK8Dslu', - 'ECKuCwnnPA3z212QjiWewHv2jQwArMu7HPRBUSXOSqKv'], - 'data': {'i': 'ECKuCwnnPA3z212QjiWewHv2jQwArMu7HPRBUSXOSqKv', 's': 0}, - 'gid': 'EERn_laF0qwP8zTBGL86LbF84J0Yh2IvQSRskH3BZZiy', - 'sn': 1} - - app = falcon.App() - # Now join rotation with hby2 who will initiate the rotation with a POST - counselor = grouping.Counselor(hby=hby1) - notifier = notifying.Notifier(hby=hby1) - evtEnd = MultisigEventEnd(hby=hby2, counselor=counselor, notifier=notifier) - app.add_route("/multisig/{alias}/ixn", evtEnd, suffix="ixn") - client = testing.TestClient(app) - result = client.simulate_put(path=f"/multisig/{prefix}_group2/ixn", body=b) - assert result.status == falcon.HTTP_202 - - # escrow event for all signatures - assert hby2.db.gpse.get(keys=(ghab2.pre,)) is not None - # no notifications set if joining - assert len(evtEnd.postman.evts) == 0 - - -def test_identifier_ends(): - with habbing.openHab(name="test", transferable=True, temp=True) as (hby, hab): - assert hab.pre == 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3' - - app = falcon.App() - - regery = credentialing.Regery(hby=hby, name=hab.name, temp=True) - verifier = verifying.Verifier(hby=hby, reger=regery.reger) - - notifier = notifying.Notifier(hby=hby) - counselor = grouping.Counselor(hby=hby) - registrar = credentialing.Registrar(hby=hby, rgy=regery, counselor=counselor) - credentialer = credentialing.Credentialer(hby=hby, rgy=regery, registrar=registrar, verifier=verifier) - - doers = kiwiing.loadEnds(hby=hby, - rgy=regery, - verifier=verifier, - notifier=notifier, - signaler=notifier.signaler, - app=app, path="/", - registrar=registrar, - credentialer=credentialer, - servery=booting.Servery(port=1234), - bootConfig=dict(), - counselor=counselor) - limit = 1.0 - tock = 0.03125 - doist = doing.Doist(tock=tock, limit=limit, doers=doers) - doist.enter() - - client = testing.TestClient(app) - - result = client.simulate_get(path="/ids") - assert result.status == falcon.HTTP_200 - - assert result.json == [{'DnD': False, - 'estOnly': False, - 'isith': '1', - 'metadata': {}, - 'name': 'test', - 'next_keys': ['EJhRr10e5p7LVB6JwLDIcgqsISktnfe5m60O_I2zZO6N'], - 'nsith': '1', - 'prefix': 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3', - 'public_keys': ['DGmIfLmgErg4zFHfPwaDckLNxsLqc5iS_P0QbLjbWR0I'], - 'receipts': 0, - 'seq_no': 0, - 'toad': 0, - 'witnesses': []}] - - req = dict(isith='1', count=1) - result = client.simulate_put(path="/ids/test/rot", body=json.dumps(req).encode("utf-8")) - assert result.status == falcon.HTTP_200 - - assert result.json == {'v': 'KERI10JSON000160_', - 't': 'rot', - 'd': 'EGnFNzw2UJKpQZYJj_xhcFYWE7prFWFBbghgcMuJ4VeM', - 'i': 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3', - 's': '1', - 'p': 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3', - 'kt': '1', - 'k': ['DGgN_X4ZJvgAMQpD3CqI5bidKkgkCLc_yk-Pk1culnXP'], - 'nt': '1', - 'n': ['EOh7LXjpAqsP6YNGOMVFjn02yCpXfGVsHbSYIQ5Ul7Ax'], - 'bt': '0', - 'br': [], - 'ba': [], - 'a': []} - - result = client.simulate_get(path="/ids") - assert result.status == falcon.HTTP_200 - - assert result.json == [{'DnD': False, - 'estOnly': False, - 'isith': '1', - 'metadata': {}, - 'name': 'test', - 'next_keys': ['EOh7LXjpAqsP6YNGOMVFjn02yCpXfGVsHbSYIQ5Ul7Ax'], - 'nsith': '1', - 'prefix': 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3', - 'public_keys': ['DGgN_X4ZJvgAMQpD3CqI5bidKkgkCLc_yk-Pk1culnXP'], - 'receipts': 0, - 'seq_no': 1, - 'toad': 0, - 'witnesses': []}] - - req = dict(transferable=True, wits=[], toad=0, isith='1', count=1, nsith='1', ncount=1, estOnly=False) - result = client.simulate_post(path="/ids/test2", body=json.dumps(req).encode("utf-8")) - assert result.status == falcon.HTTP_200 - assert result.json == {'v': 'KERI10JSON00012b_', - 't': 'icp', - 'd': 'EFreoTWR_zDOyPd3QeNvwDHYrgFYnurZST68-cMCoBMT', - 'i': 'EFreoTWR_zDOyPd3QeNvwDHYrgFYnurZST68-cMCoBMT', - 's': '0', - 'kt': '1', - 'k': ['DOUxFFi_t9quipRvAzIsoC_uoQXhpTIe62Y0fJffpEj1'], - 'nt': '1', - 'n': ['ENpmBFOoWlPjRBFtN4aq7tZ0cdKWSOPJLoa-w3-90JEk'], - 'bt': '0', - 'b': [], - 'c': [], - 'a': []} - - # Try to reuse the alias - req = dict(transferable=True, wits=[], toad=0, isith='1', count=1, nsith='1', ncount=1, estOnly=False) - result = client.simulate_post(path="/ids/test2", body=json.dumps(req).encode("utf-8")) - assert result.status == falcon.HTTP_400 - - # Create a delegated identifier - req = dict(transferable=True, wits=[], toad=0, isith='1', count=1, nsith='1', ncount=1, estOnly=False, - delpre="ECtWlHS2Wbx5M2Rg6nm69PCtzwb1veiRNvDpBGF9Z1Pc") - result = client.simulate_post(path="/ids/test3", body=json.dumps(req).encode("utf-8")) - assert result.status == falcon.HTTP_200 - assert result.json == {'v': 'KERI10JSON00015f_', - 't': 'dip', - 'd': 'EOhHlK7KtTcSH16YPwTq34Y4FaV7fyHmbybdc8aMgA98', - 'i': 'EOhHlK7KtTcSH16YPwTq34Y4FaV7fyHmbybdc8aMgA98', - 's': '0', - 'kt': '1', - 'k': ['DMIk0jr4_B7cnWUNuB7lWLlMQvNJM6uPQ2pxEq1N4OMI'], - 'nt': '1', - 'n': ['EDtSbRLbBc-NEn-sCqTNBCUJXZq6HT6zQPTtmL0DkENV'], - 'bt': '0', - 'b': [], - 'c': [], - 'a': [], - 'di': 'ECtWlHS2Wbx5M2Rg6nm69PCtzwb1veiRNvDpBGF9Z1Pc'} - - result = client.simulate_get(path="/ids") - assert result.status == falcon.HTTP_200 - assert len(result.json) == 3 - assert result.json[2] == {'DnD': False, - 'anchored': False, - 'delegated': True, - 'delegator': 'ECtWlHS2Wbx5M2Rg6nm69PCtzwb1veiRNvDpBGF9Z1Pc', - 'estOnly': False, - 'isith': '1', - 'metadata': {}, - 'name': 'test3', - 'next_keys': ['EDtSbRLbBc-NEn-sCqTNBCUJXZq6HT6zQPTtmL0DkENV'], - 'nsith': '1', - 'prefix': 'EOhHlK7KtTcSH16YPwTq34Y4FaV7fyHmbybdc8aMgA98', - 'public_keys': ['DMIk0jr4_B7cnWUNuB7lWLlMQvNJM6uPQ2pxEq1N4OMI'], - 'receipts': 0, - 'seq_no': 0, - 'toad': 0, - 'witnesses': []} - - req = dict(data=[{"i": 1, "s": 0, "d": 2}]) - result = client.simulate_put(path="/ids/test/ixn", body=json.dumps(req).encode("utf-8")) - assert result.status == falcon.HTTP_200 - - assert result.json == {'v': 'KERI10JSON0000de_', - 't': 'ixn', - 'd': 'EK6W1L2q1iHn9HcyfmMvXRbMQHK_ZNnT9HGiR09OZkbP', - 'i': 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3', - 's': '2', - 'p': 'EGnFNzw2UJKpQZYJj_xhcFYWE7prFWFBbghgcMuJ4VeM', - 'a': [{'i': 1, 's': 0, 'd': 2}]} - - req = dict(id="ignored", name="Wile", company="ACME", email="wile-coyote@acme.com") - result = client.simulate_put("/ids/bad/metadata", body=json.dumps(req).encode("utf-8")) - assert result.status == falcon.HTTP_404 # Unknown alias - result = client.simulate_post("/ids/bad/metadata", body=json.dumps(req).encode("utf-8")) - assert result.status == falcon.HTTP_404 # Unknown alias - - # Update contact data for identifier - result = client.simulate_put("/ids/test/metadata", body=json.dumps(req).encode("utf-8")) - assert result.status == falcon.HTTP_200 - res = dict(req) - res["id"] = 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3' - assert result.json == res - - # Test single GET with metadata - result = client.simulate_get("/ids/test") - assert result.status == falcon.HTTP_200 - assert result.json == {'DnD': False, - 'estOnly': False, - 'isith': '1', - 'metadata': {'company': 'ACME', - 'email': 'wile-coyote@acme.com', - 'name': 'Wile'}, - 'name': 'test', - 'next_keys': ['EOh7LXjpAqsP6YNGOMVFjn02yCpXfGVsHbSYIQ5Ul7Ax'], - 'nsith': '1', - 'prefix': 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3', - 'public_keys': ['DGgN_X4ZJvgAMQpD3CqI5bidKkgkCLc_yk-Pk1culnXP'], - 'receipts': 0, - 'seq_no': 2, - 'toad': 0, - 'witnesses': []} - - # Test list GET method with metadata - result = client.simulate_get("/ids") - assert result.status == falcon.HTTP_200 - assert result.json[0] == {'DnD': False, - 'estOnly': False, - 'isith': '1', - 'metadata': {'company': 'ACME', - 'email': 'wile-coyote@acme.com', - 'name': 'Wile'}, - 'name': 'test', - 'next_keys': ['EOh7LXjpAqsP6YNGOMVFjn02yCpXfGVsHbSYIQ5Ul7Ax'], - 'nsith': '1', - 'prefix': 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3', - 'public_keys': ['DGgN_X4ZJvgAMQpD3CqI5bidKkgkCLc_yk-Pk1culnXP'], - 'receipts': 0, - 'seq_no': 2, - 'toad': 0, - 'witnesses': []} - - # Change the alias for the identifier - req = dict(alias="another_test") - result = client.simulate_put("/ids/test/metadata", body=json.dumps(req).encode("utf-8")) - assert result.status == falcon.HTTP_200 - res["id"] = "ECtWlHS2Wbx5M2Rg6nm69PCtzwb1veiRNvDpBGF9Z1Pc" - - result = client.simulate_get("/ids") - assert result.status == falcon.HTTP_200 - assert result.json[0] == {'DnD': False, - 'estOnly': False, - 'isith': '1', - 'metadata': {'company': 'ACME', - 'email': 'wile-coyote@acme.com', - 'name': 'Wile'}, - 'name': 'another_test', - 'next_keys': ['EOh7LXjpAqsP6YNGOMVFjn02yCpXfGVsHbSYIQ5Ul7Ax'], - 'nsith': '1', - 'prefix': 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3', - 'public_keys': ['DGgN_X4ZJvgAMQpD3CqI5bidKkgkCLc_yk-Pk1culnXP'], - 'receipts': 0, - 'seq_no': 2, - 'toad': 0, - 'witnesses': []} - - # Verify the old ID is no longer valid and the new one now works - result = client.simulate_get("/ids/test") - assert result.status == falcon.HTTP_404 - result = client.simulate_get("/ids/another_test") - assert result.status == falcon.HTTP_200 - - # Replace all metadata with a post - req = dict(id="ignored", name="Alfred Lanning", company="USR Corp") - result = client.simulate_post("/ids/another_test/metadata", body=json.dumps(req).encode("utf-8")) - assert result.status == falcon.HTTP_200 - res = dict(req) - res["id"] = "EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3" - assert result.json == res - - # Alias can be changed with POST too - req = dict(alias="final_test") - result = client.simulate_post("/ids/another_test/metadata", body=json.dumps(req).encode("utf-8")) - assert result.status == falcon.HTTP_200 - res["id"] = "ECtWlHS2Wbx5M2Rg6nm69PCtzwb1veiRNvDpBGF9Z1Pc" - - # Bad post doesn't work either - result = client.simulate_post("/ids/test/metadata", body=json.dumps(req).encode("utf-8")) - assert result.status == falcon.HTTP_404 - - # Verify the old ID is no longer valid and the new one now works - result = client.simulate_get("/ids/another_test") - assert result.status == falcon.HTTP_404 - result = client.simulate_get("/ids/final_test") - assert result.status == falcon.HTTP_200 - - -def test_oobi_ends(seeder): - with habbing.openHby(name="wes", salt=coring.Salter(raw=b'wess-the-witness').qb64) as wesHby, \ - habbing.openHby(name="pal", salt=coring.Salter(raw=b'0123456789abcdef').qb64) as palHby: - wesHab = wesHby.makeHab(name="wes", transferable=False) - - palHab = palHby.makeHab(name="pal", icount=1, ncount=1, wits=[wesHab.pre]) - - assert palHab.pre == "EEWz3RVIvbGWw4VJC7JEZnGCLPYx4-QgWOwAzGnw-g8y" - - notifier = notifying.Notifier(hby=palHby) - oobiery = keri.app.oobiing.Oobiery(hby=palHby) - app = falcon.App() - regery = credentialing.Regery(hby=palHby, name=palHab.name, temp=True) - _ = kiwiing.loadEnds(hby=palHby, - rgy=regery, - verifier=None, - notifier=notifier, - signaler=notifier.signaler, - app=app, path="/", - counselor=None, - registrar=None, - credentialer=None, - servery=booting.Servery(port=1234), - bootConfig=dict()) - client = testing.TestClient(app) - - result = client.simulate_get(path="/oobi/test?role=witness") - assert result.status == falcon.HTTP_400 # Bad alias, does not exist - - result = client.simulate_get(path="/oobi/pal?role=watcher") - assert result.status == falcon.HTTP_404 # Bad role, watcher not supported yet - - result = client.simulate_get(path="/oobi/pal?role=witness") - assert result.status == falcon.HTTP_404 # Missing OOBI endpoints for witness - - result = client.simulate_get(path="/oobi/pal?role=controller") - assert result.status == falcon.HTTP_404 # Missing OOBI controller endpoints - - # Add controller endpoints - url = "http://127.0.0.1:9999/oobi/E6Dqo6tHmYTuQ3Lope4mZF_4hBoGJl93cBHRekr_iD_A/controller" - palHab.db.locs.put(keys=(palHab.pre, kering.Schemes.http), val=basing.LocationRecord(url=url)) - result = client.simulate_get(path="/oobi/pal?role=controller") - assert result.status == falcon.HTTP_200 # Missing OOBI controller endpoints - assert result.json == { - 'oobis': ['http://127.0.0.1:9999/oobi/EEWz3RVIvbGWw4VJC7JEZnGCLPYx4-QgWOwAzGnw-g8y/controller'], - 'role': 'controller'} - - # Seed with witness endpoints - seeder.seedWitEnds(palHby.db, witHabs=[wesHab], protocols=[kering.Schemes.http, kering.Schemes.tcp]) - - result = client.simulate_get(path="/oobi/pal?role=witness") - assert result.status == falcon.HTTP_200 - assert result.json == {'oobis': [ - 'http://127.0.0.1:5644/oobi/EEWz3RVIvbGWw4VJC7JEZnGCLPYx4-QgWOwAzGnw-g8y/witness' - '/BN8t3n1lxcV0SWGJIIF46fpSUqA7Mqre5KJNN3nbx3mr'], - 'role': 'witness'} - - # Post without a URL or RPY - data = dict() - b = json.dumps(data).encode("utf-8") - result = client.simulate_post(path="/oobi", body=b) - assert result.status == falcon.HTTP_400 - - # Post an RPY - data = dict(rpy={}) - b = json.dumps(data).encode("utf-8") - result = client.simulate_post(path="/oobi", body=b) - assert result.status == falcon.HTTP_501 - - data = dict(url="http://127.0.0.1:5644/oobi/E6Dqo6tHmYTuQ3Lope4mZF_4hBoGJl93cBHRekr_iD_A/witness/") - b = json.dumps(data).encode("utf-8") - result = client.simulate_post(path="/oobi", body=b) - assert result.status == falcon.HTTP_202 - assert oobiery.hby.db.oobis.cntAll() == 1 - (url,), item = next(oobiery.hby.db.oobis.getItemIter()) - assert item is not None - assert url == 'http://127.0.0.1:5644/oobi/E6Dqo6tHmYTuQ3Lope4mZF_4hBoGJl93cBHRekr_iD_A/witness/' - oobiery.hby.db.oobis.rem(keys=(url,)) - - # Post an RPY - data = dict(oobialias="sal", rpy={}) - b = json.dumps(data).encode("utf-8") - result = client.simulate_post(path="/oobi", body=b) - assert result.status == falcon.HTTP_501 - - # POST without an oobialias - data = dict(url="http://127.0.0.1:5644/oobi/E6Dqo6tHmYTuQ3Lope4mZF_4hBoGJl93cBHRekr_iD_A/witness/") - b = json.dumps(data).encode("utf-8") - result = client.simulate_post(path="/oobi", body=b) - assert result.status == falcon.HTTP_202 - assert oobiery.hby.db.oobis.cntAll() == 1 - (url,), item = next(oobiery.hby.db.oobis.getItemIter()) - assert item is not None - assert url == 'http://127.0.0.1:5644/oobi/E6Dqo6tHmYTuQ3Lope4mZF_4hBoGJl93cBHRekr_iD_A/witness/' - assert item.oobialias is None - oobiery.hby.db.oobis.rem(keys=(url,)) - - data = dict(oobialias="sal", url="http://127.0.0.1:5644/oobi/E6Dqo6tHmYTuQ3Lope4mZF_4hBoGJl93cBHRekr_iD_A" - "/witness/") - b = json.dumps(data).encode("utf-8") - result = client.simulate_post(path="/oobi", body=b) - assert result.status == falcon.HTTP_202 - assert oobiery.hby.db.oobis.cntAll() == 1 - (url,), item = next(oobiery.hby.db.oobis.getItemIter()) - assert item is not None - assert url == 'http://127.0.0.1:5644/oobi/E6Dqo6tHmYTuQ3Lope4mZF_4hBoGJl93cBHRekr_iD_A/witness/' - assert item.oobialias == 'sal' - - -def test_challenge_ends(seeder): - with habbing.openHby(name="pal", salt=coring.Salter(raw=b'0123456789abcdef').qb64) as palHby: - palHab = palHby.makeHab(name="pal", icount=1, ncount=1, wits=[]) - - assert palHab.pre == "EDtH1M06Na4Yf2_AoF-R8aY2izx3aVWsmmRNoLrWA-Gh" - - app = falcon.App() - notifier = notifying.Notifier(hby=palHby) - regery = credentialing.Regery(hby=palHby, name=palHab.name, temp=True) - _ = kiwiing.loadEnds(hby=palHby, - rgy=regery, - verifier=None, - notifier=notifier, - signaler=notifier.signaler, - app=app, path="/", - registrar=None, - credentialer=None, - servery=booting.Servery(port=1234), - bootConfig=dict(), - counselor=None) - client = testing.TestClient(app) - - result = client.simulate_get(path="/challenge?strength=256") - assert result.status == falcon.HTTP_200 - assert "words" in result.json - words = result.json["words"] - assert len(words) == 24 - - result = client.simulate_get(path="/challenge") - assert result.status == falcon.HTTP_200 - assert "words" in result.json - words = result.json["words"] - assert len(words) == 12 - - data = dict( - ) - b = json.dumps(data).encode("utf-8") - result = client.simulate_post(path="/challenge/joe", body=b) - assert result.status == falcon.HTTP_400 # Bad allias - result = client.simulate_post(path="/challenge/pal", body=b) - assert result.status == falcon.HTTP_400 # Missing words - - data["words"] = words - b = json.dumps(data).encode("utf-8") - result = client.simulate_post(path="/challenge/pal", body=b) - assert result.status == falcon.HTTP_400 # Missing recipient - - data["recipient"] = "Eo6MekLECO_ZprzHwfi7wG2ubOt2DWKZQcMZvTbenBNU" - b = json.dumps(data).encode("utf-8") - result = client.simulate_post(path="/challenge/pal", body=b) - assert result.status == falcon.HTTP_202 - - # assert len(.reps) == 1 - # rep = repd.reps.popleft() - # assert rep["topic"] == "challenge" - # assert rep["dest"] == "Eo6MekLECO_ZprzHwfi7wG2ubOt2DWKZQcMZvTbenBNU" - # assert rep["rep"].ked['r'] == '/challenge/response' - - -def test_contact_ends(seeder): - with habbing.openHby(name="pal", salt=coring.Salter(raw=b'0123456789abcdef').qb64) as palHby, \ - habbing.openHby(name="ken", salt=coring.Salter(raw=b'0123456789ghijkl').qb64) as kenHby: - - palHab = palHby.makeHab(name="pal", icount=1, ncount=1, wits=[]) - kvy = eventing.Kevery(db=palHab.db, local=False, lax=True) - assert palHab.pre == "EDtH1M06Na4Yf2_AoF-R8aY2izx3aVWsmmRNoLrWA-Gh" - - msgs = bytearray() - aids = [] - for i in range(5): - hab = kenHby.makeHab(name=f"ken{i}", icount=1, ncount=1, wits=[]) - aids.append(hab.pre) - msgs.extend(hab.makeOwnInception()) - - hab = kenHby.makeHab(name="bad", icount=1, ncount=1, wits=[]) - msgs.extend(hab.makeOwnInception()) - parsing.Parser().parse(ims=msgs, kvy=kvy) - - for aid in aids: - assert aid in palHab.kevers - - regery = credentialing.Regery(hby=kenHby, name=hab.name, temp=True) - notifier = notifying.Notifier(hby=palHby) - app = falcon.App() - _ = kiwiing.loadEnds(hby=palHby, - rgy=regery, - verifier=None, - notifier=notifier, - signaler=notifier.signaler, - app=app, path="/", - registrar=None, - credentialer=None, - servery=booting.Servery(port=1234), - bootConfig=dict(), - counselor=None) - client = testing.TestClient(app) - - response = client.simulate_get("/contacts") - assert response.status == falcon.HTTP_200 - assert response.json == [] - - data = dict( - name="test" - ) - b = json.dumps(data).encode("utf-8") - # POST to an identifier that is not in the Kever - response = client.simulate_post(f"/contacts/E8AKUcbZyik8EdkOwXgnyAxO5mSIPJWGZ_o7zMhnNnjo/{palHab.name}", body=b) - assert response.status == falcon.HTTP_404 - - # POST to a local identifier - response = client.simulate_post(f"/contacts/{palHab.pre}", body=b) - assert response.status == falcon.HTTP_400 - - for i in range(5): - data = dict( - id=aid[i], - first=f"Ken{i}", - last=f"Burns{i}", - company="GLEIF" - ) - b = json.dumps(data).encode("utf-8") - # POST to an identifier that is not in the Kever - response = client.simulate_post(f"/contacts/{aids[i]}", body=b) - assert response.status == falcon.HTTP_200 - - response = client.simulate_get(f"/contacts/E8AKUcbZyik8EdkOwXgnyAxO5mSIPJWGZ_o7zMhnNnjo") - assert response.status == falcon.HTTP_404 - - response = client.simulate_get(f"/contacts/{hab.pre}") - assert response.status == falcon.HTTP_404 - - response = client.simulate_get(f"/contacts/{aids[3]}") - assert response.status == falcon.HTTP_200 - assert response.json == {'company': 'GLEIF', - 'first': 'Ken3', - 'id': 'EAjKmvW6flpWJfdYYZ2Lu4pllPWKFjCBz0dcX-S86Nvg', - 'last': 'Burns3'} - - response = client.simulate_get(f"/contacts") - assert response.status == falcon.HTTP_200 - assert len(response.json) == 5 - data = {d["id"]: d for d in response.json} - for aid in aids: - assert aid in data - - data = dict(id=hab.pre, company="ProSapien") - b = json.dumps(data).encode("utf-8") - - response = client.simulate_put(f"/contacts/E8AKUcbZyik8EdkOwXgnyAxO5mSIPJWGZ_o7zMhnNnjo", body=b) - assert response.status == falcon.HTTP_404 - - response = client.simulate_put(f"/contacts/{palHab.pre}", body=b) - assert response.status == falcon.HTTP_400 - - response = client.simulate_put(f"/contacts/{aids[2]}", body=b) - assert response.status == falcon.HTTP_200 - assert response.json == {'company': 'ProSapien', - 'first': 'Ken2', - 'id': 'ELTQ3tF3n7QS8LDpKMdJyCMhVyMdvNPTiisnqW5ZQP3C', - 'last': 'Burns2'} - response = client.simulate_put(f"/contacts/{aids[4]}", body=b) - assert response.status == falcon.HTTP_200 - assert response.json == {'company': 'ProSapien', - 'first': 'Ken4', - 'id': 'EGwcSt3uvK5-oHI7hVU7dKMvWt0vRfMW2demzBBMDnBG', - 'last': 'Burns4'} - - response = client.simulate_get("/contacts", query_string="group=company") - assert response.status == falcon.HTTP_200 - assert len(response.json) == 2 - - gleif = response.json["GLEIF"] - data = {d["id"]: d for d in gleif} - assert aids[0] in data - assert aids[1] in data - assert aids[3] in data - - pros = response.json["ProSapien"] - data = {d["id"]: d for d in pros} - assert aids[2] in data - assert aids[4] in data - - # Begins with search on company name - response = client.simulate_get("/contacts", query_string="group=company&filter_value=Pro") - assert response.status == falcon.HTTP_200 - assert len(response.json) == 1 - - pros = response.json["ProSapien"] - data = {d["id"]: d for d in pros} - assert aids[2] in data - assert aids[4] in data - - response = client.simulate_get("/contacts", query_string="filter_field=last") - assert response.status == falcon.HTTP_400 - - response = client.simulate_get("/contacts", query_string="filter_field=last&filter_value=Burns3") - assert response.status == falcon.HTTP_200 - assert response.json == [{'challenges': [], - 'company': 'GLEIF', - 'first': 'Ken3', - 'id': 'EAjKmvW6flpWJfdYYZ2Lu4pllPWKFjCBz0dcX-S86Nvg', - 'last': 'Burns3', - 'wellKnowns': []}] - - # Begins with search on last name - response = client.simulate_get("/contacts", - query_string="filter_field=last&filter_value=Burns") - assert response.status == falcon.HTTP_200 - assert response.json == [{'challenges': [], - 'company': 'GLEIF', - 'first': 'Ken3', - 'id': 'EAjKmvW6flpWJfdYYZ2Lu4pllPWKFjCBz0dcX-S86Nvg', - 'last': 'Burns3', - 'wellKnowns': []}, - {'challenges': [], - 'company': 'GLEIF', - 'first': 'Ken1', - 'id': 'EER-n23rDM2RQB8Kw4KRrm8SFpoid4Jnelhauo6KxQpz', - 'last': 'Burns1', - 'wellKnowns': []}, - {'challenges': [], - 'company': 'ProSapien', - 'first': 'Ken4', - 'id': 'EGwcSt3uvK5-oHI7hVU7dKMvWt0vRfMW2demzBBMDnBG', - 'last': 'Burns4', - 'wellKnowns': []}, - {'challenges': [], - 'company': 'ProSapien', - 'first': 'Ken2', - 'id': 'ELTQ3tF3n7QS8LDpKMdJyCMhVyMdvNPTiisnqW5ZQP3C', - 'last': 'Burns2', - 'wellKnowns': []}, - {'challenges': [], - 'company': 'GLEIF', - 'first': 'Ken0', - 'id': 'EPo8Wy1xpTa6ri25M4IlmWBBzs5y8v4Qn3Z8xP4kEjcK', - 'last': 'Burns0', - 'wellKnowns': []}] - - response = client.simulate_delete(f"/contacts/E8AKUcbZyik8EdkOwXgnyAxO5mSIPJWGZ_o7zMhnNnjo") - assert response.status == falcon.HTTP_404 - - response = client.simulate_delete(f"/contacts/{aids[3]}") - assert response.status == falcon.HTTP_202 - - response = client.simulate_get("/contacts", query_string="filter_field=last&filter_value=Burns3") - assert response.status == falcon.HTTP_200 - assert response.json == [] - - data = bytearray(os.urandom(50)) - headers = {"Content-Type": "image/png", "Content-Length": "50"} - response = client.simulate_post(f"/contacts/E8AKUcbZyik8EdkOwXgnyAxO5mSIPJWGZ_o7zMhnNnjo/img", body=data, - headers=headers) - assert response.status == falcon.HTTP_404 - - data = bytearray(os.urandom(1000001)) - headers = {"Content-Type": "image/png", "Content-Length": "1000001"} - response = client.simulate_post(f"/contacts/{aids[0]}/img", body=data, headers=headers) - assert response.status == falcon.HTTP_400 - - data = bytearray(os.urandom(10000)) - headers = {"Content-Type": "image/png", "Content-Length": "10000"} - response = client.simulate_post(f"/contacts/{aids[0]}/img", body=data, headers=headers) - assert response.status == falcon.HTTP_202 - - response = client.simulate_get(f"/contacts/E8AKUcbZyik8EdkOwXgnyAxO5mSIPJWGZ_o7zMhnNnjo/img") - assert response.status == falcon.HTTP_404 - - response = client.simulate_get(f"/contacts/{aids[2]}/img") - assert response.status == falcon.HTTP_404 - - response = client.simulate_get(f"/contacts/{aids[0]}/img") - assert response.status == falcon.HTTP_200 - assert response.content == data - headers = response.headers - assert headers["Content-Type"] == "image/png" - assert headers["Content-Length"] == "10000" - - -def test_keystate_end(): - with habbing.openHab(name="test", transferable=True, temp=True) as (hby, hab): - assert hab.pre == 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3' - - app = falcon.App() - - regery = credentialing.Regery(hby=hby, name=hab.name, temp=True) - notifier = notifying.Notifier(hby=hby) - counselor = grouping.Counselor(hby=hby) - - _ = kiwiing.loadEnds(hby=hby, - rgy=regery, - verifier=None, - notifier=notifier, - signaler=notifier.signaler, - registrar=None, - credentialer=None, - servery=booting.Servery(port=1234), - bootConfig=dict(), - app=app, path="/", - counselor=counselor) - client = testing.TestClient(app) - - result = client.simulate_get(path="/keystate/E8AKUcbZyik8EdkOwXgnyAxO5mSIPJWGZ_o7zMhnNnjo") - assert result.status == falcon.HTTP_404 - - result = client.simulate_get(path=f"/keystate/{hab.pre}") - assert result.status == falcon.HTTP_200 - state = result.json["state"] - assert state["i"] == hab.pre - assert state["et"] == "icp" - assert state["k"] == ['DGmIfLmgErg4zFHfPwaDckLNxsLqc5iS_P0QbLjbWR0I'] - assert state["n"] == ['EJhRr10e5p7LVB6JwLDIcgqsISktnfe5m60O_I2zZO6N'] - - kel = result.json["kel"] - assert len(kel) == 1 - - # Ask for event with a bad public key - result = client.simulate_get(path=f"/keystate/pubkey/{state['n'][0]}") - assert result.status == falcon.HTTP_404 - - # Ask for event with a known public key - result = client.simulate_get(path=f"/keystate/pubkey/{state['k'][0]}") - assert result.status == falcon.HTTP_200 - assert result.json == {'a': [], - 'b': [], - 'bt': '0', - 'c': [], - 'd': 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3', - 'i': 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3', - 'k': ['DGmIfLmgErg4zFHfPwaDckLNxsLqc5iS_P0QbLjbWR0I'], - 'kt': '1', - 'n': ['EJhRr10e5p7LVB6JwLDIcgqsISktnfe5m60O_I2zZO6N'], - 'nt': '1', - 's': '0', - 't': 'icp', - 'v': 'KERI10JSON00012b_'} - - -def test_schema_ends(): - with habbing.openHby(name="test", salt=coring.Salter(raw=b'0123456789abcdef').qb64) as hby: - app = falcon.App() - notifier = notifying.Notifier(hby=hby) - regery = credentialing.Regery(hby=hby, name="test", temp=True) - _ = kiwiing.loadEnds(hby=hby, - rgy=regery, - verifier=None, - notifier=notifier, - signaler=notifier.signaler, - app=app, path="/", - registrar=None, - credentialer=None, - servery=booting.Servery(port=1234), - bootConfig=dict(), - counselor=None) - client = testing.TestClient(app) - - sed = dict() - sed["$id"] = "" - sed["$schema"] = "http://json-schema.org/draft-07/schema#" - sed.update(dict(type="object", properties=dict(a=dict(type="string")))) - sce = scheming.Schemer(sed=sed, typ=scheming.JSONSchema(), code=coring.MtrDex.Blake3_256) - hby.db.schema.pin(sce.said, sce) - - sed = dict() - sed["$id"] = "" - sed["$schema"] = "http://json-schema.org/draft-07/schema#" - sed.update(dict(type="object", properties=dict(b=dict(type="number"), ))) - sce = scheming.Schemer(sed=sed, typ=scheming.JSONSchema(), code=coring.MtrDex.Blake3_256) - hby.db.schema.pin(sce.said, sce) - - sed = dict() - sed["$id"] = "" - sed["$schema"] = "http://json-schema.org/draft-07/schema#" - sed.update(dict(type="object", properties=dict(c=dict(type="string", format="date-time")))) - sce = scheming.Schemer(sed=sed, typ=scheming.JSONSchema(), code=coring.MtrDex.Blake3_256) - hby.db.schema.pin(sce.said, sce) - - response = client.simulate_get("/schema") - assert response.status == falcon.HTTP_200 - assert len(response.json) == 3 - assert response.json[0]["$id"] == 'EHoMjhY-5V5jdSXr0yHEYWxSH8MeFfNEqnmhXbClTepe' - schema0id = 'EHoMjhY-5V5jdSXr0yHEYWxSH8MeFfNEqnmhXbClTepe' - assert response.json[1]["$id"] == 'ELrCCNUmu7t9OS5XX6MYwuyLHY13IWuJoFVPfBkjkGAd' - assert response.json[2]["$id"] == 'ENW0ZoANRhLAHczo7BwgzBlkDMZWFU2QilCCIbg98PK6' - - assert response.json[2]["properties"] == {'b': {'type': 'number'}} - assert response.json[0]["properties"] == {'c': {'format': 'date-time', 'type': 'string'}} - assert response.json[1]["properties"] == {'a': {'type': 'string'}} - - badschemaid = 'EH1MjhY-5V5jdSXr0yHEYWxSH8MeFfNEqnmhXbClTepe' - response = client.simulate_get(f"/schema/{badschemaid}") - assert response.status == falcon.HTTP_404 - - response = client.simulate_get(f"/schema/{schema0id}") - assert response.status == falcon.HTTP_200 - assert response.json["$id"] == schema0id - assert response.json["properties"] == {'c': {'format': 'date-time', 'type': 'string'}} - - -def test_escrow_end(mockHelpingNowUTC): - with habbing.openHby(name="bob", temp=True) as hby: - rgy = credentialing.Regery(hby=hby, name="bob", temp=True) - - notifier = notifying.Notifier(hby=hby) - app = falcon.App() - _ = kiwiing.loadEnds(hby=hby, - rgy=rgy, - verifier=None, - notifier=notifier, - signaler=notifier.signaler, - app=app, path="/", - registrar=None, - credentialer=None, - servery=booting.Servery(port=1234), - bootConfig=dict(), - counselor=None) - client = testing.TestClient(app) - - response = client.simulate_get("/escrows") - assert response.status == falcon.HTTP_200 - assert response.json == {'likely-duplicitous-events': [], - 'out-of-order-events': [], - 'partially-signed-events': [], - 'partially-witnessed-events': []} - - response = client.simulate_get("/escrows?escrow=partially-signed-events") - assert response.status == falcon.HTTP_200 - assert response.json == {'partially-signed-events': []} - - response = client.simulate_get("/escrows?escrow=unknown-escrow") - assert response.status == falcon.HTTP_200 - assert response.json == {} - - response = client.simulate_get( - "/escrows?escrow=partially-witnessed-events&pre=ECgrcJTdVr1TNnmmDrT8Pol9w_0BhsTxlQkWtjyrT060") - assert response.status == falcon.HTTP_200 - assert response.json == {'partially-witnessed-events': []} - - bob = hby.makeHab(name="bob") - icp = bob.kever.serder - sigs = [] - - key = dbing.dgKey(bob.pre, icp.said) # digest key - for sig in hby.db.getSigsIter(key): - sigs.append(coring.Siger(qb64b=bytes(sig))) - bob.kever.escrowPSEvent(serder=icp, sigers=sigs) - # regenerated down below - escrowedEvt = {'ked': {'a': [], - 'b': [], - 'bt': '0', - 'c': [], - 'd': 'EA_SbBUZYwqLVlAAn14d6QUBQCSReJlZ755JqTgmRhXH', - 'i': 'EA_SbBUZYwqLVlAAn14d6QUBQCSReJlZ755JqTgmRhXH', - 'k': ['DKiNnDmdOkcBjcAqL2FFhMZnSlPfNyGrJlCjJmX5b1nU'], - 'kt': '1', - 'n': ['EMP7Lg6BtehOYZt2RwOqXLNfMUiUllejAp8G_5EiANXR'], - 'nt': '1', - 's': '0', - 't': 'icp', - 'v': 'KERI10JSON00012b_'}, - 'receipts': {}, - 'signatures': [{'index': 0, - 'signature': - 'AAArkDBeflIAo4kBsKnc754XHJvdLnf04iq-noTFEJkbv2MeI' - 'GZtx6lIfJPmRSEmFMUkFW4otRrMeBGQ0-nlhHEE'}], - 'stored': True, - 'timestamp': '2021-01-01T00:00:00.000000+00:00', - 'witness_signatures': [], - 'witnesses': []} - - response = client.simulate_get("/escrows?pre=ECgrcJTdVr1TNnmmDrT8Pol9w_0BhsTxlQkWtjyrT060") - assert response.status == falcon.HTTP_200 - assert response.json == {'likely-duplicitous-events': [], - 'out-of-order-events': [], - 'partially-signed-events': [], - 'partially-witnessed-events': []} - - response = client.simulate_get("/escrows") - assert response.status == falcon.HTTP_200 - data = dict(response.json) - assert "partially-signed-events" in data - evt = data["partially-signed-events"] - del data["partially-signed-events"] - assert data == {'likely-duplicitous-events': [], - 'out-of-order-events': [], - 'partially-witnessed-events': []} - assert evt == [escrowedEvt] - - response = client.simulate_get(f"/escrows?escrow=partially-signed-events&pre={bob.pre}") - assert response.status == falcon.HTTP_200 - assert len(response.json) == 1 - assert len(response.json['partially-signed-events']) == 1 - - response = client.simulate_get(f"/escrows?escrow=partially-signed-events" - f"&pre=ECgrcJTdVr1TNnmmDrT8Pol9w_0BhsTxlQkWtjyrT060") - assert response.status == falcon.HTTP_200 - assert len(response.json) == 1 - assert len(response.json['partially-signed-events']) == 0 - - snkey = dbing.snKey(bob.pre, bob.kever.sn) - hby.db.delPses(snkey) - bob.kever.escrowPWEvent(serder=icp, sigers=sigs, wigers=None) - - response = client.simulate_get("/escrows?escrow=partially-witnessed-events") - assert response.status == falcon.HTTP_200 - assert len(response.json) == 1 - evt = response.json['partially-witnessed-events'] - assert evt == [escrowedEvt] - - response = client.simulate_get(f"/escrows?escrow=partially-witnessed-events&pre={bob.pre}") - assert response.status == falcon.HTTP_200 - assert len(response.json) == 1 - assert len(response.json['partially-witnessed-events']) == 1 - - response = client.simulate_get(f"/escrows?escrow=partially-witnessed-events" - f"&pre=ECgrcJTdVr1TNnmmDrT8Pol9w_0BhsTxlQkWtjyrT060") - assert response.status == falcon.HTTP_200 - assert len(response.json) == 1 - assert len(response.json['partially-witnessed-events']) == 0 - - hby.db.delPwes(snkey) - - kvy = eventing.Kevery(db=bob.db) - kvy.escrowOOEvent(serder=icp, sigers=sigs) - response = client.simulate_get("/escrows?escrow=out-of-order-events") - assert response.status == falcon.HTTP_200 - assert len(response.json) == 1 - evt = response.json['out-of-order-events'] - assert evt == [escrowedEvt] - - response = client.simulate_get(f"/escrows?escrow=out-of-order-events&pre={bob.pre}") - assert response.status == falcon.HTTP_200 - assert len(response.json) == 1 - assert len(response.json['out-of-order-events']) == 1 - - response = client.simulate_get(f"/escrows?escrow=out-of-order-events" - f"&pre=ECgrcJTdVr1TNnmmDrT8Pol9w_0BhsTxlQkWtjyrT060") - assert response.status == falcon.HTTP_200 - assert len(response.json) == 1 - assert len(response.json['out-of-order-events']) == 0 - - hby.db.delPde(key) - - kvy.escrowLDEvent(serder=icp, sigers=sigs) - response = client.simulate_get("/escrows?escrow=likely-duplicitous-events") - assert response.status == falcon.HTTP_200 - assert len(response.json) == 1 - evt = response.json['likely-duplicitous-events'] - assert evt == [escrowedEvt] - - response = client.simulate_get(f"/escrows?escrow=likely-duplicitous-events&pre={bob.pre}") - assert response.status == falcon.HTTP_200 - assert len(response.json) == 1 - assert len(response.json['likely-duplicitous-events']) == 1 - - response = client.simulate_get(f"/escrows?escrow=likely-duplicitous-events" - f"&pre=ECgrcJTdVr1TNnmmDrT8Pol9w_0BhsTxlQkWtjyrT060") - assert response.status == falcon.HTTP_200 - assert len(response.json) == 1 - assert len(response.json['likely-duplicitous-events']) == 0 - - -def test_presentation_ends(seeder, mockCoringRandomNonce, mockHelpingNowIso8601): - with habbing.openHby(name="pal", salt=coring.Salter(raw=b'0123456789abcdef').qb64) as palHby, \ - habbing.openHby(name="ken", salt=coring.Salter(raw=b'0123456789ghijkl').qb64) as kenHby: - seeder.seedSchema(palHby.db) - seeder.seedSchema(kenHby.db) - palHab = palHby.makeHab(name="pal", icount=1, ncount=1, wits=[]) - kvy = eventing.Kevery(db=palHab.db, local=False, lax=True) - assert palHab.pre == "EDtH1M06Na4Yf2_AoF-R8aY2izx3aVWsmmRNoLrWA-Gh" - - msgs = bytearray() - aids = [] - hab = kenHby.makeHab(name=f"ken", icount=1, ncount=1, wits=[]) - aids.append(hab.pre) - msgs.extend(hab.makeOwnInception()) - parsing.Parser().parse(ims=msgs, kvy=kvy) - - for aid in aids: - assert aid in palHab.kevers - - org = connecting.Organizer(hby=palHby) - org.set(hab.pre, field="alias", val="ken") - - palReg = credentialing.Regery(hby=palHby, name="han", temp=True) - notifier = notifying.Notifier(hby=palHby) - app = falcon.App() - ends = kiwiing.loadEnds(hby=palHby, - rgy=palReg, - verifier=None, - notifier=notifier, - signaler=notifier.signaler, - app=app, path="/", - registrar=None, - credentialer=None, - servery=booting.Servery(port=1234), - bootConfig=dict(), - counselor=None) - presentEnd = None - for end in ends: - if isinstance(end, kiwiing.PresentationEnd): - presentEnd = end - assert presentEnd is not None - - client = testing.TestClient(app) - - # Create a credential that we will present - schema = "EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC" - credSubject = dict( - LEI="254900OPPU84GM83MG36", - ) - - issuer = palReg.makeRegistry(prefix=palHab.pre, name="han") - rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() - palHab.interact(data=[rseal]) - seqner = coring.Seqner(sn=palHab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=palHab.kever.serder.saider) - palReg.processEscrows() - - verifier = verifying.Verifier(hby=palHby, reger=palReg.reger) - - creder = proving.credential(issuer=palHab.pre, - schema=schema, - recipient=palHab.pre, - data=credSubject, - status=issuer.regk, - ) - assert creder.said == "ENF8t9hfbZtM86yxqQLuipzJTTWmUl4tm2jSTDu9-egd" - - msg = signing.ratify(palHab, serder=creder) - - iss = issuer.issue(said=creder.said) - rseal = SealEvent(iss.pre, "0", iss.said)._asdict() - palHab.interact(data=[rseal]) - seqner = coring.Seqner(sn=palHab.kever.sn) - issuer.anchorMsg(pre=iss.pre, regd=iss.said, seqner=seqner, saider=palHab.kever.serder.saider) - palReg.processEscrows() - - parsing.Parser().parse(ims=msg, vry=verifier) - - # verify we can load serialized VC by SAID - key = creder.said.encode("utf-8") - assert palReg.reger.creds.get(key) is not None - - # Valid request asking for just the exn - body = dict( - said=creder.said, - recipient=hab.pre, - ) - raw = json.dumps(body).encode("utf-8") - response = client.simulate_post("/credentials/pal/presentations", body=raw) - assert response.status == falcon.HTTP_202 - assert len(presentEnd.postman.evts) == 1 - presentEnd.postman.evts.popleft() - - # Valid request using alias for recipient - body = dict( - said=creder.said, - recipient="ken", - ) - raw = json.dumps(body).encode("utf-8") - response = client.simulate_post("/credentials/pal/presentations", body=raw) - assert response.status == falcon.HTTP_202 - assert len(presentEnd.postman.evts) == 1 - presentEnd.postman.evts.popleft() - - # now ask to include the credential and associated data - body = dict( - said=creder.said, - recipient=hab.pre, - include=True - ) - raw = json.dumps(body).encode("utf-8") - response = client.simulate_post("/credentials/pal/presentations", body=raw) - assert response.status == falcon.HTTP_202 - assert len(presentEnd.postman.evts) == 10 - - # Bad alias - body = dict( - said=creder.said, - recipient=hab.pre, - include=True - ) - raw = json.dumps(body).encode("utf-8") - response = client.simulate_post("/credentials/jim/presentations", body=raw) - assert response.status == falcon.HTTP_400 - assert response.text == "Invalid alias jim for credential presentation" - - # No SAID in body - body = dict( - ) - raw = json.dumps(body).encode("utf-8") - response = client.simulate_post("/credentials/pal/presentations", body=raw) - assert response.status == falcon.HTTP_400 - assert response.text == "said is required, none provided" - - # No recipient in body - body = dict( - said=creder.said, - ) - raw = json.dumps(body).encode("utf-8") - response = client.simulate_post("/credentials/pal/presentations", body=raw) - assert response.status == falcon.HTTP_400 - assert response.text == "recipient is required, none provided" - - # SAID for a non-existant credential - body = dict( - said="ABC", - recipient=hab.pre, - include=True - ) - raw = json.dumps(body).encode("utf-8") - response = client.simulate_post("/credentials/pal/presentations", body=raw) - assert response.status == falcon.HTTP_404 - assert response.text == "credential ABC not found" - - presentEnd.postman.evts.clear() - - # Valid request using alias for recipient - body = dict( - schema=creder.said, - recipient="ken", - issuer=palHab.pre, - ) - raw = json.dumps(body).encode("utf-8") - response = client.simulate_post("/credentials/pal/requests", body=raw) - assert response.status == falcon.HTTP_202 - assert len(presentEnd.postman.evts) == 1 - presentEnd.postman.evts.popleft() - - # Valid request using alias for recipient - body = dict( - schema=creder.said, - recipient="ken", - issuer=palHab.pre, - ) - raw = json.dumps(body).encode("utf-8") - response = client.simulate_post("/credentials/jim/requests", body=raw) - assert response.status == falcon.HTTP_400 - assert response.text == "Invalid alias jim for credential request" - - # Valid request using alias for recipient - body = dict( - schema=creder.said, - issuer=palHab.pre, - ) - raw = json.dumps(body).encode("utf-8") - response = client.simulate_post("/credentials/pal/requests", body=raw) - assert response.status == falcon.HTTP_400 - assert response.text == "recp is required, none provided" - - # Valid request using alias for recipient - body = dict( - issuer=palHab.pre, - recipient="ken", - ) - raw = json.dumps(body).encode("utf-8") - response = client.simulate_post("/credentials/pal/requests", body=raw) - assert response.status == falcon.HTTP_400 - assert response.text == "schema is required, none provided" - - -def test_aied_ends(): - bran = "1B88Kq7afAZHlxsNIBE5y" - with habbing.openHby(name="test", salt=coring.Salter(raw=b'0123456789abcdef').qb64, bran=bran) as hby: - app = falcon.App() - notifier = notifying.Notifier(hby=hby) - regery = credentialing.Regery(hby=hby, name="test", temp=True) - _ = kiwiing.loadEnds(hby=hby, - rgy=regery, - verifier=None, - notifier=notifier, - signaler=notifier.signaler, - app=app, path="/", - registrar=None, - credentialer=None, - servery=booting.Servery(port=1234), - bootConfig=dict(), - counselor=None) - client = testing.TestClient(app) - - response = client.simulate_get("/codes") - assert response.status == falcon.HTTP_200 - assert "passcode" in response.json - aeid = response.json["passcode"] - assert len(aeid) == booting.DEFAULT_PASSCODE_SIZE - - # Change passcode - nbran = "pouh228IgK9RhloUnkydZ" - body = dict(current=bran, passcode=nbran) - response = client.simulate_post("/codes", body=json.dumps(body).encode("utf-8")) - assert response.status == falcon.HTTP_202 - - # Try to use the old passcode again - body = dict(current=bran, passcode=nbran) - response = client.simulate_post("/codes", body=json.dumps(body).encode("utf-8")) - assert response.status == falcon.HTTP_401 - - # Change back to the original passcode - body = dict(current=nbran, passcode=bran) - response = client.simulate_post("/codes", body=json.dumps(body).encode("utf-8")) - assert response.status == falcon.HTTP_202 - - # Try to use an invalid passcode - body = dict(current=bran, passcode="ABCDEF") - response = client.simulate_post("/codes", body=json.dumps(body).encode("utf-8")) - assert response.status == falcon.HTTP_400 - - -if __name__ == "__main__": - test_multisig_incept() diff --git a/tests/app/test_multisig.py b/tests/app/test_multisig.py deleted file mode 100644 index 3cb39d53f..000000000 --- a/tests/app/test_multisig.py +++ /dev/null @@ -1,209 +0,0 @@ -# -*- encoding: utf-8 -*- -""" -tests.app.test_multisig module - -""" -import json -import os - -import falcon -from falcon import testing -from hio.base import doing -from keri import kering -from keri.app import (habbing, storing, kiwiing, grouping, indirecting, - directing, agenting, booting, notifying) -from keri.core import coring, eventing, parsing -from keri.vdr import credentialing - -TEST_DIR = os.path.dirname(os.path.abspath(__file__)) - - -class TestDoer(doing.DoDoer): - - def __init__(self, wanHby, hby1, hab1, hby2, hab2, seeder): - self.hby1 = hby1 - self.hby2 = hby2 - self.hab1 = hab1 - self.hab2 = hab2 - - wanDoers = indirecting.setupWitness(alias="wan", hby=wanHby, tcpPort=5632, httpPort=5642) - wanHab = wanHby.habByName("wan") - seeder.seedWitEnds(self.hby1.db, witHabs=[wanHab], protocols=[kering.Schemes.http]) - seeder.seedWitEnds(self.hby2.db, witHabs=[wanHab], protocols=[kering.Schemes.http]) - # Verify the group identifier was incepted properly and matches the identifiers - assert wanHab.pre == "BOigXdxpp1r43JhO--czUTwrCXzoWrIwW8i41KWDlr8s" - assert hab1.pre == "EEJGgqemdGdA1w6rcY5rfCdbdlqVMwUU2wQUMOVNnM8Q" - assert hab2.pre == "EDFrdg8Se2rTQrNiA04zP5sIywZiriJQDGBv8UjIOdCw" - - self.notifier1 = notifying.Notifier(hby=hby1) - self.notifier2 = notifying.Notifier(hby=hby2) - - self.app1, doers1 = loadApp(hby1, self.notifier1) - self.app2, doers2 = loadApp(hby2, self.notifier2) - - doers = wanDoers + doers1 + doers2 - - self.toRemove = list(doers) - doers.extend([doing.doify(self.testDo)]) - - super(TestDoer, self).__init__(doers=doers) - - def testDo(self, tymth, tock=0.0): - self.wind(tymth) - self.tock = tock - yield self.tock - - witDoer = agenting.WitnessReceiptor(hby=self.hby1) - self.extend([witDoer]) - witDoer.msgs.append(dict(pre=self.hab1.pre)) - while not witDoer.cues: - yield self.tock - - cue = witDoer.cues.popleft() - print(cue) - - self.remove([witDoer]) - - witDoer = agenting.WitnessReceiptor(hby=self.hby2) - self.extend([witDoer]) - witDoer.msgs.append(dict(pre=self.hab2.pre)) - while not witDoer.cues: - yield self.tock - - cue = witDoer.cues.popleft() - print(cue) - - self.remove([witDoer]) - - kev1 = eventing.Kevery(db=self.hab1.db, lax=True, local=False) - kev2 = eventing.Kevery(db=self.hab2.db, lax=True, local=False) - - icp1 = self.hab1.db.cloneEvtMsg(pre=self.hab1.pre, fn=0, dig=self.hab1.kever.serder.said) - icp2 = self.hab2.db.cloneEvtMsg(pre=self.hab2.pre, fn=0, dig=self.hab2.kever.serder.said) - parsing.Parser().parse(ims=bytearray(icp1), kvy=kev2) - parsing.Parser().parse(ims=bytearray(icp2), kvy=kev1) - - client1 = testing.TestClient(self.app1) - client2 = testing.TestClient(self.app2) - - icpd = dict(aids=[self.hab1.pre, self.hab2.pre], - transferable=True, - toad=0, - isith='2', - nsith='2' - ) - - b = json.dumps(icpd).encode("utf-8") - response = client1.simulate_post("/groups/group1/icp", body=b) - assert response.status == falcon.HTTP_200 - serder = coring.Serder(ked=response.json) - assert serder.pre == serder.said == "EDZc_n-rSd4uhiZJGozouT45PxSr2NTYo3JFdEWE4GIA" - b = json.dumps(icpd).encode("utf-8") - response = client2.simulate_put("/groups/group2/icp", body=b) - assert response.status == falcon.HTTP_200 - serder = coring.Serder(ked=response.json) - assert serder.pre == serder.said == "EDZc_n-rSd4uhiZJGozouT45PxSr2NTYo3JFdEWE4GIA" - - while not (ghab1 := self.hby1.habByName("group1")): - yield self.tock - - assert ghab1.pre == "EDZc_n-rSd4uhiZJGozouT45PxSr2NTYo3JFdEWE4GIA" - - while not (ghab2 := self.hby2.habByName("group2")): - yield self.tock - - assert ghab2.pre == "EDZc_n-rSd4uhiZJGozouT45PxSr2NTYo3JFdEWE4GIA" - - while len(self.notifier1.getNotes()) != 1 or len(self.notifier2.getNotes()) != 1: - yield self.tock - - note = self.notifier1.getNotes()[0] - assert note.pad['a']['r'] == "/multisig/icp/complete" - assert note.pad['a']['a'] == {'i': 'EDZc_n-rSd4uhiZJGozouT45PxSr2NTYo3JFdEWE4GIA', 's': 0} - note = self.notifier2.getNotes()[0] - assert note.pad['a']['r'] == "/multisig/icp/complete" - assert note.pad['a']['a'] == {'i': 'EDZc_n-rSd4uhiZJGozouT45PxSr2NTYo3JFdEWE4GIA', 's': 0} - - rotd = dict(aids=[self.hab1.pre, self.hab2.pre], - toad=0, - isith='2', - nsith='2' - ) - - b = json.dumps(rotd).encode("utf-8") - response = client1.simulate_post("/groups/group1/rot", body=b) - assert response.status == falcon.HTTP_202 - - b = json.dumps(rotd).encode("utf-8") - response = client2.simulate_put("/groups/group2/rot", body=b) - assert response.status == falcon.HTTP_202 - - while not ghab1.kever.sn == 1: - yield self.tock - - assert ghab1.kever.serder.ked["t"] == coring.Ilks.rot - assert ghab1.kever.serder.said == "EBz-CqguoY0_yfiygx0Due-5z2xp027eoRdVNKxRvs_O" - - while not ghab2.kever.sn == 1: - yield self.tock - - assert ghab2.kever.serder.ked["t"] == coring.Ilks.rot - assert ghab2.kever.serder.said == "EBz-CqguoY0_yfiygx0Due-5z2xp027eoRdVNKxRvs_O" - - while len(self.notifier1.getNotes()) != 2 or len(self.notifier2.getNotes()) != 2: - yield self.tock - - notes = self.notifier1.getNotes() - note = notes[1] - assert note.pad['a']['r'] == "/multisig/rot/complete" - assert note.pad['a']['a'] == {'i': 'EDZc_n-rSd4uhiZJGozouT45PxSr2NTYo3JFdEWE4GIA', 's': 0} - notes = self.notifier2.getNotes() - note = notes[1] - assert note.pad['a']['r'] == "/multisig/rot/complete" - assert note.pad['a']['a'] == {'i': 'EDZc_n-rSd4uhiZJGozouT45PxSr2NTYo3JFdEWE4GIA', 's': 0} - - self.remove(self.toRemove) - return True - - -wanPre = "BOigXdxpp1r43JhO--czUTwrCXzoWrIwW8i41KWDlr8s" - - -def loadApp(hby, notifier): - app = falcon.App() - - counselor = grouping.Counselor(hby=hby) - mbx = indirecting.MailboxDirector(hby=hby, topics=["/receipt", "/replay", "/credential", "/multisig"]) - regery = credentialing.Regery(hby=hby, name="test", temp=True) - - doers = kiwiing.loadEnds(hby=hby, - rgy=regery, - verifier=None, - notifier=notifier, - signaler=notifier.signaler, - app=app, path="/", - registrar=None, - credentialer=None, - servery=booting.Servery(port=1234), - bootConfig=dict(), - counselor=counselor) - doers.extend([counselor, mbx]) - return app, doers - - -def test_multisig_identifier_ends(seeder): - salt = coring.Salter(raw=b'wann-the-witness').qb64 - with habbing.openHab(name="multisig1", temp=True, wits=[wanPre]) as (hby1, hab1), \ - habbing.openHab(name="multisig2", temp=True, wits=[wanPre]) as (hby2, hab2), \ - habbing.openHby(name="wan", salt=salt, temp=True) as wanHby: - testDoer = TestDoer(wanHby, hby1, hab1, hby2, hab2, seeder) - - # Run all participants - directing.runController(doers=[testDoer], expire=30.0) - - assert testDoer.done is True - - -if __name__ == "__main__": - pass - #test_multisig_identifier_ends(seeder) diff --git a/tests/app/test_notifying.py b/tests/app/test_notifying.py index a3996b68c..b02bd91c2 100644 --- a/tests/app/test_notifying.py +++ b/tests/app/test_notifying.py @@ -16,12 +16,12 @@ def test_notice(): payload = dict(name="John", email="john@example.com", msg="test") - note = notifying.notice(payload) + note = notifying.notice(attrs=payload) assert note.attrs == payload assert note.datetime is not None assert note.rid is not None - note = notifying.notice(payload, dt="2022-07-08T15:01:05.453632") + note = notifying.notice(attrs=payload, dt="2022-07-08T15:01:05.453632") assert note.rid is not None assert note.datetime == "2022-07-08T15:01:05.453632" assert note.attrs == payload @@ -58,7 +58,7 @@ def test_notice(): now = helping.nowUTC() payload = dict(name="John", email="john@example.com", msg="test") - note = notifying.notice(payload, dt=now) + note = notifying.notice(attrs=payload, dt=now) assert note.datetime == now.isoformat() @@ -67,7 +67,7 @@ def test_dictersuber(): payload = dict(name="John", email="john@example.com", msg="test") dsub = notifying.DicterSuber(db=db, subkey='nots.', sep='/', klas=notifying.Notice) - note = notifying.notice(payload, dt="2022-07-08T15:01:05.453632") + note = notifying.notice(attrs=payload, dt="2022-07-08T15:01:05.453632") dt = note.datetime said = note.rid assert dsub.put(keys=(dt, said), val=note) is True @@ -117,16 +117,16 @@ def test_noter(): payload = dict(name="John", email="john@example.com", msg="test") dt = helping.fromIso8601("2022-07-08T15:01:05.453632") cig = coring.Cigar(qb64="AABr1EJXI1sTuI51TXo4F1JjxIJzwPeCxa-Cfbboi7F4Y4GatPEvK629M7G_5c86_Ssvwg8POZWNMV-WreVqBECw") - note = notifying.notice(payload, dt=dt) + note = notifying.notice(attrs=payload, dt=dt) assert noter.add(note, cig) is True - notes = noter.getNotes(start="") + notes = noter.getNotes(start=0) assert len(notes) == 1 note1, cig1 = notes[0] assert note1.rid == note.rid assert cig1.qb64 == cig.qb64 - notes = noter.getNotes(start=dt) + notes = noter.getNotes(start=0) assert len(notes) == 1 note2, cig2 = notes[0] assert note2.rid == note.rid @@ -134,7 +134,7 @@ def test_noter(): assert noter.rem("ABC") is False assert noter.rem(note.rid) is True - notes = noter.getNotes(start="") + notes = noter.getNotes(start=0) assert len(notes) == 0 dt = datetime.datetime.now() @@ -146,7 +146,7 @@ def test_noter(): assert noter.add(note, cig) is True res = [] - for note in noter.getNoteIter(start=dt): + for note in noter.getNotes(start=0): res.append(note) assert len(res) == 3 @@ -160,11 +160,14 @@ def test_noter(): assert noter.add(note, cig) is True res = [] - for note in noter.getNoteIter(limit=5): + for note in noter.getNotes(end=4): res.append(note) assert len(res) == 5 + cnt = noter.getNoteCnt() + assert cnt == 13 + def test_notifier(): with habbing.openHby(name="test") as hby: @@ -205,16 +208,10 @@ def test_notifier(): notes = notifier.getNotes() assert len(notes) == 3 - res = [] - for note in notifier.getNoteIter(start=dt): - res.append(note) - - assert len(res) == 3 - payload = dict(a=1, b=2, c=3) dt = helping.fromIso8601("2022-07-08T15:01:05.453632") cig = coring.Cigar(qb64="AABr1EJXI1sTuI51TXo4F1JjxIJzwPeCxa-Cfbboi7F4Y4GatPEvK629M7G_5c86_Ssvwg8POZWNMV-WreVqBECw") - note = notifying.notice(payload, dt=dt) + note = notifying.notice(attrs=payload, dt=dt) assert notifier.noter.add(note, cig) is True assert notifier.mar(note.rid) is False diff --git a/tests/app/test_oobiing.py b/tests/app/test_oobiing.py index 6a888d133..fbd92f2f4 100644 --- a/tests/app/test_oobiing.py +++ b/tests/app/test_oobiing.py @@ -12,7 +12,7 @@ import keri from hio.core import http from keri.app import habbing, oobiing, notifying -from keri.core import coring +from keri.core import coring, parsing, serdering from keri.db import basing from keri.end import ending from keri.help import helping @@ -26,26 +26,17 @@ def test_oobi_share(mockHelpingNowUTC): oobi = "http://127.0.0.1:5642/oobi/Egw3N07Ajdkjvv4LB2Mhx2qxl6TOCFdWNJU6cYR_ImFg/witness" \ "/BGKVzj4ve0VSd8z_AmvhLg4lqcC_9WYX90k03q-R_Ydo?name=Phil" with habbing.openHab(name="test", temp=True) as (hby, hab): - exc = exchanging.Exchanger(db=hby.db, handlers=[]) + exc = exchanging.Exchanger(hby=hby, handlers=[]) notifier = notifying.Notifier(hby=hby) oobiing.loadHandlers(hby=hby, exc=exc, notifier=notifier) assert "/oobis" in exc.routes - handler = exc.routes["/oobis"] - msg = dict( - pre=hab.kever.prefixer, - payload=dict( - oobi=oobi - )) - handler.msgs.append(msg) - - limit = 1.0 - tock = 0.25 - doist = doing.Doist(limit=limit, tock=tock) - doist.do(doers=[handler]) - assert doist.tyme == limit + + exn, _ = oobiing.oobiRequestExn(hab, hab.pre, oobi) + + handler.handle(serder=exn) obr = hby.db.oobis.get(keys=(oobi,)) assert obr is not None @@ -62,57 +53,23 @@ def test_oobi_share(mockHelpingNowUTC): 'r': '/oobi', 'src': 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3'} - msg = dict( - pre=hab.kever.prefixer, - payload=dict( - )) - handler.msgs.append(msg) - - limit = 1.0 - tock = 0.25 - doist = doing.Doist(limit=limit, tock=tock) - doist.do(doers=[handler]) - assert doist.tyme == limit - assert len(notifier.signaler.signals) == 0 - exn, atc = oobiing.oobiRequestExn(hab=hab, dest="EO2kxXW0jifQmuPevqg6Zpi3vE-WYoj65i_XhpruWtOg", oobi="http://127.0.0.1/oobi") assert exn.ked == {'a': {'dest': 'EO2kxXW0jifQmuPevqg6Zpi3vE-WYoj65i_XhpruWtOg', 'oobi': 'http://127.0.0.1/oobi'}, - 'd': 'EHuKAScxCSm3v8ooWR2wIilGul_ZUwHAXt63Y5bhfvmt', + 'd': 'EMAhEMPbBU2B-Ha-yLxMEZk49KHYkzZgMv9aZS8gDl1m', 'dt': '2021-01-01T00:00:00.000000+00:00', + 'e': {}, + 'i': 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3', + 'p': '', 'q': {}, 'r': '/oobis', 't': 'exn', - 'v': 'KERI10JSON0000ed_'} - assert atc == (b'-HABEIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3-AABAACS1e3y_nIO' - b'l5UQAtrq2O9w-CaYTNTSDNjBK5k01nUFkV4yiHo-HE40nVsjrb9uKQYAHTaRVTUo' - b'nj3KashCBTMP') - - -def test_oobi_share_endpoint(): - with openMultiSig(prefix="test") as ((hby1, hab1), (hby2, hab2), (hby3, hab3)): - app = falcon.App() - oobiEnd = oobiing.OobiResource(hby=hby1) - app.add_route("/oobi/groups/{alias}/share", oobiEnd, suffix="share") - client = testing.TestClient(app) - - body = dict(oobis=[ - "http://127.0.0.1:3333/oobi", - "http://127.0.0.1:5555/oobi", - "http://127.0.0.1:7777/oobi" - ]) - raw = json.dumps(body).encode("utf-8") - - result = client.simulate_post(path="/oobi/groups/test_1/share", body=raw) - assert result.status == falcon.HTTP_400 - result = client.simulate_post(path="/oobi/groups/fake/share", body=raw) - assert result.status == falcon.HTTP_404 - result = client.simulate_post(path="/oobi/groups/test_group1/share", body=raw) - assert result.status == falcon.HTTP_200 - - # Assert that a message has been send to each participant for each OOBI - assert len(oobiEnd.postman.evts) == 6 + 'v': 'KERI10JSON00012e_'} + assert atc == (b'-FABEIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI30AAAAAAAAAAAAAAA' + b'AAAAAAAAEIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3-AABAACsgmsu' + b'VJoY5a7vicZQ7pT_MZqCe-0psgReRxyoBfFaAPxZ7Vss2eteFuvwDWBeyKc1B-yc' + b'p-2QZzIZJ94_9hIP') def test_oobiery(): @@ -198,7 +155,7 @@ def on_get(self, req, rep): } rpy = (self.hab.reply(route="/oobi/controller", data=a)) - ser = coring.Serder(raw=rpy) + ser = serdering.SerderKERI(raw=rpy) rep.status = falcon.HTTP_200 rep.content_type = "application/json" rep.data = ser.raw @@ -216,11 +173,11 @@ def test_authenticator(mockHelpingNowUTC): hby.db.woobi.pin(keys=(url,), val=obr) app = falcon.App() # falcon.App instances are callable WSGI apps - endDoers = oobiing.loadEnds(app, hby=hby) + oobiing.loadEnds(app, hby=hby) limit = 2.0 tock = 0.03125 - doers = endDoers + authn.doers + doers = authn.doers doist = doing.Doist(limit=limit, tock=tock) doist.do(doers=doers) diff --git a/tests/app/test_querying.py b/tests/app/test_querying.py new file mode 100644 index 000000000..d723eaa94 --- /dev/null +++ b/tests/app/test_querying.py @@ -0,0 +1,143 @@ +# -*- encoding: utf-8 -*- +""" +keri.app.querying module + +""" +from hio.base import doing + +from keri.app import habbing +from keri.app.querying import QueryDoer, KeyStateNoticer, LogQuerier, SeqNoQuerier, AnchorQuerier +from keri.core import parsing, eventing + + +def test_querying(): + with habbing.openHby() as hby, \ + habbing.openHby() as hby1: + inqHab = hby.makeHab(name="inquisitor") + subHab = hby1.makeHab(name="subject") + qdoer = QueryDoer(hby=hby, hab=inqHab, kvy=hby.kvy, pre=subHab.pre) + + icp = subHab.makeOwnInception() + parsing.Parser().parseOne(ims=bytearray(icp), kvy=inqHab.kvy) + + assert qdoer is not None + + tock = 0.03125 + limit = 1.0 + doist = doing.Doist(limit=limit, tock=tock, real=True) + + # doist.do(doers=doers) + deeds = doist.enter(doers=[qdoer]) + + assert len(qdoer.doers) == 1 + ksnDoer = qdoer.doers[0] + assert isinstance(ksnDoer, KeyStateNoticer) + assert len(ksnDoer.witq.msgs) == 1 + msg = ksnDoer.witq.msgs.popleft() + assert msg["src"] == inqHab.pre + assert msg["pre"] == subHab.pre + assert msg["r"] == "ksn" + assert msg["q"] == {'s': '0'} + assert msg["wits"] is None + + doist.recur(deeds=deeds) + + # Cue up a saved key state equal to the one we have + hby.kvy.cues.clear() + ksr = subHab.kever.state() + rpy = eventing.reply(route="/ksn", data=ksr._asdict()) + cue = dict(kin="keyStateSaved", ksn=ksr._asdict()) + hby.kvy.cues.append(cue) + + doist.recur(deeds=deeds) + + # We already have up to date key state so loaded will be true + assert qdoer.done is True + assert len(hby.kvy.cues) == 0 + + # create a new query doer + qdoer = QueryDoer(hby=hby, hab=inqHab, kvy=hby.kvy, pre=subHab.pre) + tock = 0.03125 + limit = 1.0 + doist = doing.Doist(limit=limit, tock=tock, real=True) + + # rotate AID and submit as a new keyStateSave + rot = subHab.rotate() + ksr = subHab.kever.state() + rpy = eventing.reply(route="/ksn", data=ksr._asdict()) + cue = dict(kin="keyStateSaved", ksn=ksr._asdict()) + hby.kvy.cues.append(cue) + deeds = doist.enter(doers=[qdoer]) + doist.recur(deeds=deeds) + + # We are behind in key state, so we aren't done and have queried for the logs + assert qdoer.done is False + assert len(qdoer.doers) == 1 + ksnDoer = qdoer.doers[0] + assert isinstance(ksnDoer, KeyStateNoticer) + assert len(ksnDoer.witq.msgs) == 1 + + assert len(ksnDoer.doers) == 1 + logDoer = ksnDoer.doers[0] + assert isinstance(logDoer, LogQuerier) + assert len(hby.kvy.cues) == 0 + + parsing.Parser().parseOne(ims=bytearray(rot), kvy=inqHab.kvy) + doist.recur(deeds=deeds) + + assert qdoer.done is True + + # Test sequence querier + sdoer = SeqNoQuerier(hby=hby, hab=inqHab, pre=subHab.pre, sn=5) + assert len(sdoer.witq.msgs) == 1 + + tock = 0.03125 + limit = 1.0 + doist = doing.Doist(limit=limit, tock=tock, real=True) + deeds = doist.enter(doers=[sdoer]) + doist.recur(deeds=deeds) + assert len(sdoer.witq.msgs) == 0 + + sdoer = SeqNoQuerier(hby=hby, hab=inqHab, pre=subHab.pre, sn=1) + assert len(sdoer.witq.msgs) == 1 + + tock = 0.03125 + limit = 1.0 + doist = doing.Doist(limit=limit, tock=tock, real=True) + deeds = doist.enter(doers=[sdoer]) + doist.recur(deeds=deeds) + assert len(sdoer.witq.msgs) == 1 + + # Test with originally unknown AID + sdoer = SeqNoQuerier(hby=hby, hab=inqHab, pre="ExxCHAI9bkl50F5SCKl2AWQbFGKeJtz0uxM2diTMxMQA", sn=1) + assert len(sdoer.witq.msgs) == 1 + + tock = 0.03125 + limit = 1.0 + doist = doing.Doist(limit=limit, tock=tock, real=True) + deeds = doist.enter(doers=[sdoer]) + doist.recur(deeds=deeds) + assert len(sdoer.witq.msgs) == 1 + + # Test anchor querier + adoer = AnchorQuerier(hby=hby, hab=inqHab, pre=subHab.pre, anchor={'s': '5'}) + assert len(adoer.witq.msgs) == 1 + + tock = 0.03125 + limit = 1.0 + doist = doing.Doist(limit=limit, tock=tock, real=True) + deeds = doist.enter(doers=[adoer]) + doist.recur(deeds=deeds) + assert len(sdoer.witq.msgs) == 1 + + # Test with originally unknown AID + adoer = AnchorQuerier(hby=hby, hab=inqHab, pre="ExxCHAI9bkl50F5SCKl2AWQbFGKeJtz0uxM2diTMxMQA", anchor={'s': '5'}) + assert len(adoer.witq.msgs) == 1 + + tock = 0.03125 + limit = 1.0 + doist = doing.Doist(limit=limit, tock=tock, real=True) + deeds = doist.enter(doers=[adoer]) + doist.recur(deeds=deeds) + assert len(adoer.witq.msgs) == 1 + diff --git a/tests/app/test_signaling.py b/tests/app/test_signaling.py index 40cfff921..9010d9e89 100644 --- a/tests/app/test_signaling.py +++ b/tests/app/test_signaling.py @@ -7,13 +7,10 @@ import time import falcon -import pytest from falcon import testing from hio.base import doing, tyming from keri.app import signaling -from keri.core import coring -from keri.db import dbing from keri.help import helping diff --git a/tests/app/test_signify.py b/tests/app/test_signify.py new file mode 100644 index 000000000..4afaa4647 --- /dev/null +++ b/tests/app/test_signify.py @@ -0,0 +1,134 @@ +# -*- encoding: utf-8 -*- +""" +tests.app.habbing remote module + +""" +import pytest + +from keri import kering +from keri.app import habbing +from keri.app.keeping import SaltyCreator +from keri.core import coring, eventing + + +def test_remote_salty_hab(): + name = "test" + tier = coring.Tiers.low + raw = b'\x05\xaa\x8f-S\x9a\xe9\xfaU\x9c\x02\x9c\x9b\x08Hu' + salter = coring.Salter(raw=raw, tier=tier) + + with habbing.openHby(name="remoteSalty") as remote, \ + habbing.openHby(name="local", salt=salter.qb64, temp=True, tier=tier) as local: + # create a single Local Hab and compare the results with the Signify Hab + + creator = SaltyCreator(salt=salter.qb64, stem="test", tier=tier) + pidx = 1 + ridx = 0 + kidx = 0 + + lhab = local.makeHab(name=name) + assert lhab.pre == "EHeU-ldGfJhxceV9BTq38HdFUoasoWEcYATiyZCcDH7N" + + # create current key + sith = 1 # one signer + + # original signing keypair transferable default + skp0 = creator.create(pidx=pidx, ridx=ridx, kidx=kidx, temp=True).pop() + # skp0 = salter.signer(path=path, temp=True, tier=tier) + assert skp0.code == coring.MtrDex.Ed25519_Seed + assert skp0.verfer.code == coring.MtrDex.Ed25519 + keys = [skp0.verfer.qb64] + assert keys == ['DPNKzAuOw9utnR6L1_bS0spnsPFbc609WdzUvJrfUh-h'] + + # create next key + # next signing keypair transferable is default + skp1 = creator.create(pidx=pidx, ridx=ridx+1, kidx=kidx+1, temp=True).pop() + assert skp1.code == coring.MtrDex.Ed25519_Seed + assert skp1.verfer.code == coring.MtrDex.Ed25519 + + # compute nxt digest + # transferable so nxt is not empty + ndiger = coring.Diger(ser=skp1.verfer.qb64b) + nxt = [ndiger.qb64] + assert nxt == ['EAbq5OnIog2j1Rm5dtFuFuSIBbKKxlV1ILwrRI5yPgtX'] + + toad = 0 # no witnesses + + icp = eventing.incept(keys, isith=sith, ndigs=nxt, toad=toad, code=coring.MtrDex.Blake3_256) + assert icp.raw == lhab.kever.serder.raw + tsig0 = skp0.sign(icp.raw, index=0) + assert tsig0.qb64b == (b'AAB0ewd_rP91-GX9d943r48qWXThuHpHbqMwJT92jFJWbbynC-QGXVRPaSX5DGAI4Bqyviw4zsz-' + b'uEAxo9HwEucF') + + hab = remote.makeSignifyHab(name, serder=icp, sigers=[tsig0], stem="test", pidx=pidx, tier=tier, temp=True) + assert hab.pre == lhab.pre # we have recreated the local hab with the remote hab + + kever = hab.kever + assert kever.prefixer.qb64 == lhab.pre # we have recreated the local hab with the remote hab + assert kever.sn == 0 + assert kever.serder.said == lhab.kever.serder.said + assert kever.ilk == coring.Ilks.icp + assert [verfer.qb64 for verfer in kever.verfers] == keys + assert [diger.qb64 for diger in kever.ndigers] == nxt + + habord = remote.db.habs.get(name) + assert habord.hid == "EHeU-ldGfJhxceV9BTq38HdFUoasoWEcYATiyZCcDH7N" + assert habord.sid == "EHeU-ldGfJhxceV9BTq38HdFUoasoWEcYATiyZCcDH7N" + + lhab.rotate() + + ridx = ridx + 1 + kidx = kidx + 1 + # Regenerate skp1 signer from data in Habord as we will on Signify client + skp1 = creator.create(pidx=pidx, ridx=ridx, kidx=kidx, temp=True).pop() + keys1 = [skp1.verfer.qb64] + skp2 = creator.create(pidx=pidx, ridx=ridx+1, kidx=kidx+1, temp=True).pop() + assert skp2.code == coring.MtrDex.Ed25519_Seed + assert skp2.verfer.code == coring.MtrDex.Ed25519 + ndiger1 = coring.Diger(ser=skp2.verfer.qb64b) + nxt1 = [ndiger1.qb64] + assert nxt1 == ['EKNg5bhKpDTv_DixBKYfOHHl1omtvQ06UD3Nf40JUsQ-'] + + rot = eventing.rotate(pre=hab.pre, keys=keys1, dig=icp.said, sn=1, isith=sith, ndigs=nxt1, toad=toad) + assert rot.raw == lhab.kever.serder.raw + + tsig1 = skp1.sign(rot.raw, index=0) + assert tsig1.qb64b == (b'AAAGWYaw6N_4Wk2IBVOaPGb-rnuj1ys5xSHjfYnAzTRdBN8VzT9GVkBE8CLxLp0iSQ_SCRNpKQEV' + b'6BIwPVyJS0cA') + + msg = hab.rotate(serder=rot, sigers=[tsig1]) + assert msg == (b'{"v":"KERI10JSON000160_","t":"rot","d":"EEZTwrSQdE6QXDNHGMVDf8Zc' + b'fA-us9tavFORrBaorrtf","i":"EHeU-ldGfJhxceV9BTq38HdFUoasoWEcYATiy' + b'ZCcDH7N","s":"1","p":"EHeU-ldGfJhxceV9BTq38HdFUoasoWEcYATiyZCcDH' + b'7N","kt":"1","k":["DN8nxDNnlY-qCNdb294nZQs29PXDsmbphujYJGQCLL0Y"' + b'],"nt":"1","n":["EKNg5bhKpDTv_DixBKYfOHHl1omtvQ06UD3Nf40JUsQ-"],' + b'"bt":"0","br":[],"ba":[],"a":[]}-AABAAAGWYaw6N_4Wk2IBVOaPGb-rnuj' + b'1ys5xSHjfYnAzTRdBN8VzT9GVkBE8CLxLp0iSQ_SCRNpKQEV6BIwPVyJS0cA') + + kever = hab.kever + assert kever.prefixer.qb64 == lhab.pre + assert kever.sn == 1 + assert kever.serder.said == lhab.kever.serder.said + assert kever.ilk == coring.Ilks.rot + assert [verfer.qb64 for verfer in kever.verfers] == keys1 + assert [diger.qb64 for diger in kever.ndigers] == nxt1 + + habord = remote.db.habs.get(name) + assert habord.hid == "EHeU-ldGfJhxceV9BTq38HdFUoasoWEcYATiyZCcDH7N" + assert habord.sid == "EHeU-ldGfJhxceV9BTq38HdFUoasoWEcYATiyZCcDH7N" + + with pytest.raises(kering.KeriError): + hab.sign(ser=rot.raw) + + # create something to sign + ser = b'abcdefghijklmnopqrstuvwxyz0123456789' + + lsigs = lhab.sign(ser=ser, indices=[0]) + assert lsigs[0].qb64b == (b'AABaTxcQvCatFXQJK2uYuss7JC2SLgisX70Tm0DyWAOxRPC1nYuMrbV2UWCa5zYQTIzu4I7SqfbD' + b'XKgvxjjpJfkP') + + # Regenerate signer from data in Habord as we will on Signify client + rskp = creator.create(pidx=pidx, ridx=ridx, kidx=kidx, temp=True).pop() + # Sign with regenerated signer + rsig = rskp.sign(ser=ser, index=0) + assert rsig.qb64b == lsigs[0].qb64b diff --git a/tests/app/test_signing.py b/tests/app/test_signing.py index f9338e555..3561d2201 100644 --- a/tests/app/test_signing.py +++ b/tests/app/test_signing.py @@ -9,7 +9,6 @@ from keri.core import coring, parsing, eventing from keri.core.eventing import SealEvent from keri.db import basing -from keri.peer import exchanging from keri.vc import proving from keri.vdr import verifying, credentialing @@ -24,7 +23,7 @@ def test_sad_signature(seeder, mockCoringRandomNonce, mockHelpingNowIso8601): engagementContextRole="Project Manager", ) - _, sad = coring.Saider.saidify(sad=personal, label=coring.Ids.d) + _, sad = coring.Saider.saidify(sad=personal, label=coring.Saids.d) d = dict( ri="EBmRy7xMwsxUelUauaXtMxTfPAMPAI6FkekwlOjkggt", @@ -60,7 +59,7 @@ def test_sad_signature(seeder, mockCoringRandomNonce, mockHelpingNowIso8601): b'bAqteP0BApbos85syKE_VgfuNTtVRYkAlw5fwb_4ZWN-V-FFO_MrSGt71luX0rt-9e' b'hNZFPHV1EuPc1YDQvZJ1XqPumewN') - # sign with non-trans identifer with a specific set of paths + # sign with non-trans identifier with a specific set of paths sig1 = signing.ratify(wanHab, cred, paths=paths) assert sig1 == (b'{"v":"ACDC10JSON0002e2_","d":"EIEbtplUZZrV3-jVAnOUcH-xcxOCuiJqtSK' b'lDJSxUJLW","i":"EBNHFK056fqNSG_MDE7d_Eqk0bazefvd4eeQLMPPNBnM","ri' @@ -144,7 +143,10 @@ def test_sad_signature(seeder, mockCoringRandomNonce, mockHelpingNowIso8601): rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() cred = proving.credential(schema="EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC", @@ -165,18 +167,6 @@ def test_sad_signature(seeder, mockCoringRandomNonce, mockHelpingNowIso8601): b'la0mm_DeMwS1KXomLb_j1zCmgZ3RJPAPACA539yer3U8JQlcgXrdbPlR-1kADcFA4bsN_' b'klRSu7p61y-Z2CS5d7Aitrc7yq00YIG_u-v7OToChDC3TsVCR4D') - iss = issuer.issue(said=cred.said) - rseal = SealEvent(iss.pre, "0", iss.said)._asdict() - hab.interact(data=[rseal]) - seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=iss.pre, regd=iss.said, seqner=seqner, saider=hab.kever.serder.saider) - regery.processEscrows() - - parsing.Parser().parse(ims=sig1, vry=verifier) - - saider = verifier.reger.saved.get(keys=cred.said) - assert saider is not None - """End Test""" @@ -207,30 +197,40 @@ def test_signature_transposition(seeder, mockCoringRandomNonce, mockHelpingNowIs rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() cred = proving.credential(schema="EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC", issuer=hab.pre, data=d, source={}, status=issuer.regk) # Sign with non-transferable identifier, defaults to single signature on entire SAD - sig0 = signing.ratify(hab=hab, serder=cred) - assert sig0 == (b'{"v":"ACDC10JSON00019e_","d":"EK88fyN65bfA63o1jgeOGKeIxw6sTJEwwU3y' - b'cpjdtCUD","i":"EKC8085pwSwzLwUGzh-HrEoFDwZnCJq27bVp5atdMT9o","ri":' - b'"ENzh5cyGjFhQYuIXuheXV2wkKp23rkxYI7wbEBQIyqhP","s":"EMQWEcCnVRk1ha' - b'tTNyK3sIykYSrrFvafX3bHQ9Gkk1kC","a":{"d":"EFyxk35e1r5G9pcuvv8j5F4F' - b'WRHD8xlZ_E4rWPdlVASI","dt":"2021-06-09T17:35:54.169967+00:00","i":' - b'"EIflL4H4134zYoRM6ls6Q086RLC_BhfNFh5uk-WxvhsL","LEI":"254900OPPU84' - b'GM83MG36"},"e":{}}-JAB6AABAAA--FABEKC8085pwSwzLwUGzh-HrEoFDwZnCJq2' - b'7bVp5atdMT9o0AAAAAAAAAAAAAAAAAAAAAAAEKC8085pwSwzLwUGzh-HrEoFDwZnCJ' - b'q27bVp5atdMT9o-AABAACIRDrYzCyMB5jBHY9jwfT4KEb7kx_vYgHJ7LDsiQRD-Roj' - b'5bGfJXj6PAo5TS36t4kWmiBhpvqLgb2l9vUhpiUK') + sig0 = bytearray(cred.raw) + sig0.extend(coring.Counter(coring.CtrDex.SealSourceTriples, count=1).qb64b) + sig0.extend(coring.Prefixer(qb64=issuer.regk).qb64b) + sig0.extend(seqner.qb64b) + sig0.extend(coring.Saider(qb64=issuer.regd).qb64b) + + assert sig0 == (b'{"v":"ACDC10JSON00019e_","d":"EK88fyN65bfA63o1jgeOGKeIxw6sTJEwwU' + b'3ycpjdtCUD","i":"EKC8085pwSwzLwUGzh-HrEoFDwZnCJq27bVp5atdMT9o","' + b'ri":"ENzh5cyGjFhQYuIXuheXV2wkKp23rkxYI7wbEBQIyqhP","s":"EMQWEcCn' + b'VRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC","a":{"d":"EFyxk35e1r5G9pcu' + b'vv8j5F4FWRHD8xlZ_E4rWPdlVASI","dt":"2021-06-09T17:35:54.169967+0' + b'0:00","i":"EIflL4H4134zYoRM6ls6Q086RLC_BhfNFh5uk-WxvhsL","LEI":"' + b'254900OPPU84GM83MG36"},"e":{}}-IABENzh5cyGjFhQYuIXuheXV2wkKp23rk' + b'xYI7wbEBQIyqhP0AAAAAAAAAAAAAAAAAAAAAABENzh5cyGjFhQYuIXuheXV2wkKp' + b'23rkxYI7wbEBQIyqhP') iss = issuer.issue(said=cred.said) rseal = SealEvent(iss.pre, "0", iss.said)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=iss.pre, regd=iss.said, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=iss.pre, + regd=iss.said, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() parsing.Parser().parse(ims=sig0, vry=verifier) @@ -238,77 +238,26 @@ def test_signature_transposition(seeder, mockCoringRandomNonce, mockHelpingNowIs saider = verifier.reger.saved.get(keys=cred.said) assert saider is not None - scre, sadsigers, sadcigars = verifier.reger.cloneCred(said=cred.said) - assert scre.raw == cred.raw - assert len(sadcigars) == 0 - assert len(sadsigers) == 1 - - (pather, prefixer, seqner, saider, sigers) = sadsigers[0] - assert pather.bext == "-" - assert prefixer.qb64 == hab.pre - assert seqner.sn == 0 - assert saider.qb64 == hab.kever.lastEst.d - assert len(sigers) == 1 - assert sigers[0].qb64b == (b'AACIRDrYzCyMB5jBHY9jwfT4KEb7kx_vYgHJ7LDsiQRD-Roj5bGfJXj6' - b'PAo5TS36t4kWmiBhpvqLgb2l9vUhpiUK') - - # Transpose the signature to a new root - scre, sadsigers, sadcigars = verifier.reger.cloneCred(said=cred.said, root=coring.Pather(path=["a", "b", "c"])) + scre, *_ = verifier.reger.cloneCred(said=cred.said) assert scre.raw == cred.raw - assert len(sadcigars) == 0 - assert len(sadsigers) == 1 - - (pather, prefixer, seqner, saider, sigers) = sadsigers[0] - assert pather.bext == "-a-b-c" # new emdded location - assert prefixer.qb64 == hab.pre - assert seqner.sn == 0 - assert saider.qb64 == hab.kever.lastEst.d - assert len(sigers) == 1 - assert sigers[0].qb64b == (b'AACIRDrYzCyMB5jBHY9jwfT4KEb7kx_vYgHJ7LDsiQRD-Roj5bGfJXj6PAo5TS36t4kWmiBhpvqL' - b'gb2l9vUhpiUK') - - # embed the credential in an exn and transpose the signature - scre, sadsigers, sadcigars = verifier.reger.cloneCred(said=cred.said, root=coring.Pather(path=["a"])) - exn = exchanging.exchange(route="/credential/issue", payload=scre.crd, date="2022-01-04T11:58:55.154502+00:00") - msg = hab.endorse(serder=exn) - msg.extend(eventing.proofize(sadtsgs=sadsigers, sadcigars=sadcigars)) - assert msg == (b'{"v":"KERI10JSON000239_","t":"exn","d":"EFRfdY3Gp3wq4CAgcvkVETlk' - b'9xkK7Yumwf8zBBVHDHSw","dt":"2022-01-04T11:58:55.154502+00:00","r' - b'":"/credential/issue","a":{"v":"ACDC10JSON00019e_","d":"EK88fyN6' - b'5bfA63o1jgeOGKeIxw6sTJEwwU3ycpjdtCUD","i":"EKC8085pwSwzLwUGzh-Hr' - b'EoFDwZnCJq27bVp5atdMT9o","ri":"ENzh5cyGjFhQYuIXuheXV2wkKp23rkxYI' - b'7wbEBQIyqhP","s":"EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC",' - b'"a":{"d":"EFyxk35e1r5G9pcuvv8j5F4FWRHD8xlZ_E4rWPdlVASI","dt":"20' - b'21-06-09T17:35:54.169967+00:00","i":"EIflL4H4134zYoRM6ls6Q086RLC' - b'_BhfNFh5uk-WxvhsL","LEI":"254900OPPU84GM83MG36"},"e":{}}}-VA0-FA' - b'BEKC8085pwSwzLwUGzh-HrEoFDwZnCJq27bVp5atdMT9o0AAAAAAAAAAAAAAAAAA' - b'AAAAAEKC8085pwSwzLwUGzh-HrEoFDwZnCJq27bVp5atdMT9o-AABAACBII0LRim' - b'847YJVAhsXeIUtxdvdh2AKnIVy-TBxchMMEQZ9qZgIh8eh-nYv1dE0lKomt7eXsa' - b't6WkAVoCzzfgB-JAB5AABAA-a-FABEKC8085pwSwzLwUGzh-HrEoFDwZnCJq27bV' - b'p5atdMT9o0AAAAAAAAAAAAAAAAAAAAAAAEKC8085pwSwzLwUGzh-HrEoFDwZnCJq' - b'27bVp5atdMT9o-AABAACIRDrYzCyMB5jBHY9jwfT4KEb7kx_vYgHJ7LDsiQRD-Ro' - b'j5bGfJXj6PAo5TS36t4kWmiBhpvqLgb2l9vUhpiUK') - - saider = verifier.reger.saved.get(keys=cred.said) - assert saider is not None # multiple path sigs with habbing.openHab(name="sid", temp=True, salt=b'0123456789abcdef') as (hby, hab): seeder.seedSchema(db=hby.db) regery = credentialing.Regery(hby=hby, name=hab.name, temp=True) - verifier = verifying.Verifier(hby=hby, reger=regery.reger) issuer = regery.makeRegistry(prefix=hab.pre, name=hab.name) rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=hab.kever.serder.saider) + saider = coring.Saider(qb64=hab.kever.serder.said) + issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=saider) regery.processEscrows() # where is this schema to be found? cred = proving.credential(schema="EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC", issuer=hab.pre, data=d, source={}, status=issuer.regk) - # sign with single sig transferable identfier with multiple specified paths + # sign with single sig transferable identifier with multiple specified paths # Bad magic values here but can't figure out where looks like Sadder Said seeder # is using a bad magic value sig1 = signing.ratify(hab=hab, serder=cred, paths=[[], ["a"], ["a", "i"]]) @@ -330,56 +279,6 @@ def test_signature_transposition(seeder, mockCoringRandomNonce, mockHelpingNowIs b'MT9o-AABAABsIw-EgCMnex1m7Qm8RkU4jMGAV3wNGyD_CxfetmMp-iGBLhZ5wArAw6' b'_Qdg75K_NMTKVV4hv7bWw3OvJnNY8A') - # Issue the credential and parse into credential store - iss = issuer.issue(said=cred.said) - rseal = SealEvent(iss.pre, "0", iss.said)._asdict() - hab.interact(data=[rseal]) - seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=iss.pre, regd=iss.said, seqner=seqner, saider=hab.kever.serder.saider) - regery.processEscrows() - - parsing.Parser().parse(ims=sig1, vry=verifier) - - # verify the credential is saved - saider = verifier.reger.saved.get(keys=cred.said) - assert saider is not None - - # cloneCred tales a root parameter for transposing the signatures to a base path - scre, sadsigers, sadcigars = verifier.reger.cloneCred(said=cred.said, root=coring.Pather(path=["a"])) - assert len(sadsigers) == 3 - - # create a new exn message with the credential as the payload - exn = exchanging.exchange(route="/credential/issue", payload=scre.crd, date="2022-01-04T11:58:55.154502+00:00") - - # sign the exn message - msg = hab.endorse(serder=exn) - - # attach the transposed signatures for the embedded credential - msg.extend(eventing.proofize(sadtsgs=sadsigers, sadcigars=sadcigars)) - assert msg == (b'{"v":"KERI10JSON000239_","t":"exn","d":"EFRfdY3Gp3wq4CAgcvkVETlk' - b'9xkK7Yumwf8zBBVHDHSw","dt":"2022-01-04T11:58:55.154502+00:00","r' - b'":"/credential/issue","a":{"v":"ACDC10JSON00019e_","d":"EK88fyN6' - b'5bfA63o1jgeOGKeIxw6sTJEwwU3ycpjdtCUD","i":"EKC8085pwSwzLwUGzh-Hr' - b'EoFDwZnCJq27bVp5atdMT9o","ri":"ENzh5cyGjFhQYuIXuheXV2wkKp23rkxYI' - b'7wbEBQIyqhP","s":"EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC",' - b'"a":{"d":"EFyxk35e1r5G9pcuvv8j5F4FWRHD8xlZ_E4rWPdlVASI","dt":"20' - b'21-06-09T17:35:54.169967+00:00","i":"EIflL4H4134zYoRM6ls6Q086RLC' - b'_BhfNFh5uk-WxvhsL","LEI":"254900OPPU84GM83MG36"},"e":{}}}-VA0-FA' - b'BEKC8085pwSwzLwUGzh-HrEoFDwZnCJq27bVp5atdMT9o0AAAAAAAAAAAAAAAAAA' - b'AAAAAEKC8085pwSwzLwUGzh-HrEoFDwZnCJq27bVp5atdMT9o-AABAACBII0LRim' - b'847YJVAhsXeIUtxdvdh2AKnIVy-TBxchMMEQZ9qZgIh8eh-nYv1dE0lKomt7eXsa' - b't6WkAVoCzzfgB-KAD6AABAAA--JAB5AACAA-a-a-i-FABEKC8085pwSwzLwUGzh-' - b'HrEoFDwZnCJq27bVp5atdMT9o0AAAAAAAAAAAAAAAAAAAAAAAEKC8085pwSwzLwU' - b'Gzh-HrEoFDwZnCJq27bVp5atdMT9o-AABAABsIw-EgCMnex1m7Qm8RkU4jMGAV3w' - b'NGyD_CxfetmMp-iGBLhZ5wArAw6_Qdg75K_NMTKVV4hv7bWw3OvJnNY8A-JAB4AA' - b'B-a-a-FABEKC8085pwSwzLwUGzh-HrEoFDwZnCJq27bVp5atdMT9o0AAAAAAAAAA' - b'AAAAAAAAAAAAAEKC8085pwSwzLwUGzh-HrEoFDwZnCJq27bVp5atdMT9o-AABAAB' - b'80PrmAUGj_iATyLY-kzdpI6omm5X05EsdkRZGymwVn62-1nijoSh0dlUo6rGOoyw' - b'UQWu-eZ0i5PuHskgV9nwP-JAB5AABAA-a-FABEKC8085pwSwzLwUGzh-HrEoFDwZ' - b'nCJq27bVp5atdMT9o0AAAAAAAAAAAAAAAAAAAAAAAEKC8085pwSwzLwUGzh-HrEo' - b'FDwZnCJq27bVp5atdMT9o-AABAACIRDrYzCyMB5jBHY9jwfT4KEb7kx_vYgHJ7LD' - b'siQRD-Roj5bGfJXj6PAo5TS36t4kWmiBhpvqLgb2l9vUhpiUK') - # signing SAD with non-transferable identifier with habbing.openHab(name="wan", temp=True, salt=b'0123456789abcdef', transferable=False) as (hby, hab): seeder.seedSchema(db=hby.db) diff --git a/tests/app/test_specing.py b/tests/app/test_specing.py deleted file mode 100644 index 10e1aee61..000000000 --- a/tests/app/test_specing.py +++ /dev/null @@ -1,193 +0,0 @@ -# -*- encoding: utf-8 -*- -""" -tests.app.agent_kiwiserver module - -""" -import json - -import falcon - -from keri.app import booting, specing, kiwiing, habbing, grouping, notifying - - -def test_spec_resource(): - with habbing.openHby(name="eve", base="test") as hby: - app = falcon.App() - servery = booting.Servery(port=1234) - - # Add a simple endpoint - passcodeEnd = booting.PasscodeEnd() - app.add_route("/codes", passcodeEnd) - - # Add a resource with multiple endpoints for different methods - bootEnd = booting.BootEnd(servery=servery) - app.add_route("/boot", bootEnd) - app.add_route("/boot/{name}", bootEnd, suffix="name") - - notifier = notifying.Notifier(hby=hby) - # Add a few with no resolutions at the root (resource=None for /group) - counselor = grouping.Counselor(hby=hby) - multiIcpEnd = kiwiing.MultisigInceptEnd(hby=hby, counselor=counselor, notifier=notifier) - app.add_route("/groups/{alias}/icp", multiIcpEnd) - multiRotEnd = kiwiing.MultisigEventEnd(hby=hby, counselor=counselor, notifier=notifier) - app.add_route("/groups/{alias}/rot", multiRotEnd, suffix="rot") - - lockEnd = kiwiing.LockEnd(servery=booting.Servery(port=1234), bootConfig=dict()) - app.add_route("/lock", lockEnd) - - resources = [passcodeEnd, bootEnd, multiIcpEnd, multiRotEnd, lockEnd] - specRes = specing.SpecResource(app=app, title='KERI Interactive Web Interface API', resources=resources) - - sd = specRes.spec.to_dict() - assert "paths" in sd - paths = sd["paths"] - print() - print(paths) - assert "/codes" in paths - codes = paths["/codes"] - assert len(codes) == 1 - assert "get" in codes - - assert "/boot" in paths - boot = paths["/boot"] - assert len(boot) == 2 - assert "post" in boot - assert "put" in boot - - assert "/boot/{name}" in paths - boot = paths["/boot/{name}"] - assert len(boot) == 1 - assert "get" in boot - - assert "/lock" in paths - lock = paths["/lock"] - assert len(lock) == 1 - assert "post" in lock - - assert "/groups/{alias}/icp" in paths - icp = paths["/groups/{alias}/icp"] - assert len(icp) == 2 - assert "post" in icp - assert "put" in icp - - assert "/groups/{alias}/rot" in paths - rot = paths["/groups/{alias}/rot"] - assert len(rot) == 2 - assert "post" in rot - assert "put" in rot - - # Assert on the entire JSON to ensure we are getting all the docs - js = json.dumps(sd) - - print(js) - - assert js == ('{"paths": {"/codes": {"get": {"summary": "Generate random 22 digit passcode ' - 'for use in securing and encrypting keystore", "description": "Generate ' - 'random 22 digit passcode for use in securing and encrypting keystore", ' - '"tags": ["Passcode"], "responses": {"200": {"description": "Randomly ' - 'generated 22 character passcode formatted as ' - 'xxxx-xxxxx-xxxx-xxxxx-xxxx"}}}}, "/boot": {"post": {"summary": "Create KERI ' - 'environment (database and keystore)", "description": "Creates the ' - 'directories for database and keystore for vacuous KERI instance using name ' - 'and aeid key or passcode to encrypt datastore. Fails if directory already ' - 'exists.", "tags": ["Boot"], "requestBody": {"required": true, "content": ' - '{"application/json": {"schema": {"type": "object", "properties": {"name": ' - '{"type": "string", "description": "human readable nickname for this agent", ' - '"example": "alice"}, "passcode": {"type": "string", "description": "passcode ' - 'for encrypting and securing this agent", "example": ' - '"RwyY-KleGM-jbe1-cUiSz-p3Ce"}}}}}}, "responses": {"200": {"description": ' - '"JSON object containing status message"}}}, "put": {"summary": "Unlock ' - 'keystore with aeid encryption key generated from passcode.", "description": ' - '"Unlock keystore with aeid encryption key generated from passcode..", ' - '"tags": ["Boot"], "requestBody": {"required": true, "content": ' - '{"application/json": {"schema": {"type": "object", "properties": {"name": ' - '{"type": "string", "description": "human readable nickname for this agent", ' - '"example": "alice"}, "passcode": {"type": "string", "description": "passcode ' - 'for unlocking the agent and decrypting the keystore", "example": ' - '"RwyY-KleGM-jbe1-cUiSz-p3Ce"}}}}}}, "responses": {"200": {"description": ' - '"JSON object containing status message"}}}}, "/lock": {"post": {"summary": ' - '"Lock", "description": "Reloads the API to the boot version", "tags": ' - '["Lock"], "responses": {"200": {"description": "locked"}}}}, "/boot/{name}": ' - '{"get": {"summary": "Query KERI environment for keystore name", "tags": ' - '["Boot"], "parameters": [{"in": "path", "name": "name", "schema": {"type": ' - '"string"}, "required": true, "description": "predetermined name of keep ' - 'keystore", "example": "alice"}], "responses": {"202": {"description": ' - '"Keystore exists"}, "404": {"description": "No keystore exists"}}}}, ' - '"/groups/{alias}/icp": {"post": {"summary": "Initiate a multisig group ' - 'inception", "description": "Initiate a multisig group inception with the ' - 'participants identified by the provided AIDs", "tags": ["Groups"], ' - '"parameters": [{"in": "path", "name": "alias", "schema": {"type": "string"}, ' - '"required": true, "description": "Human readable alias for the identifier to ' - 'create"}], "requestBody": {"required": true, "content": {"application/json": ' - '{"schema": {"type": "object", "properties": {"aids": {"type": "array", ' - '"items": {"type": "string"}, "description": "List of qb64 AIDs of ' - 'participants in multisig group"}, "notify": {"type": "boolean", "required": ' - 'false, "description": "True means to send mutlsig incept exn message to ' - 'other participants"}, "toad": {"type": "integer", "description": "Witness ' - 'receipt threshold"}, "wits": {"type": "array", "items": {"type": "string"}, ' - '"description": "List of qb64 AIDs of witnesses to be used for the new group ' - 'identfier"}, "isith": {"type": "string", "description": "Signing threshold ' - 'for the new group identifier"}, "nsith": {"type": "string", "description": ' - '"Next signing threshold for the new group identifier"}, "estOnly": {"type": ' - '"boolean", "required": false, "description": "True means this identifier ' - 'will not allow interaction events."}}}}}}, "responses": {"200": ' - '{"description": "Multisig group AID inception initiated."}}}, "put": ' - '{"summary": "Participate in a multisig group inception", "description": ' - '"Participate in a multisig group rotation", "tags": ["Groups"], ' - '"parameters": [{"in": "path", "name": "alias", "schema": {"type": "string"}, ' - '"required": true, "description": "Human readable alias for the identifier to ' - 'create"}], "requestBody": {"required": true, "content": {"application/json": ' - '{"schema": {"type": "object", "properties": {"aids": {"type": "array", ' - '"items": {"type": "string"}, "description": "List of qb64 AIDs of ' - 'participants in multisig group"}, "notify": {"type": "boolean", "required": ' - 'false, "description": "True means to send mutlsig incept exn message to ' - 'other participants"}, "toad": {"type": "integer", "description": "Witness ' - 'receipt threshold"}, "wits": {"type": "array", "items": {"type": "string"}, ' - '"description": "List of qb64 AIDs of witnesses to be used for the new group ' - 'identfier"}, "isith": {"type": "string", "description": "Signing threshold ' - 'for the new group identifier"}, "nsith": {"type": "string", "description": ' - '"Next signing threshold for the new group identifier"}, "estOnly": {"type": ' - '"boolean", "required": false, "description": "True means this identifier ' - 'will not allow interaction events."}}}}}}, "responses": {"200": ' - '{"description": "Multisig group AID inception initiated."}}}}, ' - '"/groups/{alias}/rot": {"post": {"summary": "Initiate multisig group ' - 'rotatation", "description": "Initiate a multisig group rotation with the ' - 'participants identified by the provided AIDs", "tags": ["Groups"], ' - '"parameters": [{"in": "path", "name": "alias", "schema": {"type": "string"}, ' - '"required": true, "description": "Human readable alias for the identifier to ' - 'create"}], "requestBody": {"required": true, "content": {"application/json": ' - '{"schema": {"type": "object", "properties": {"aids": {"type": "array", ' - '"description": "list of particiant identifiers for this rotation", "items": ' - '{"type": "string"}}, "wits": {"type": "array", "description": "list of ' - 'witness identifiers", "items": {"type": "string"}}, "adds": {"type": ' - '"array", "description": "list of witness identifiers to add", "items": ' - '{"type": "string"}}, "cuts": {"type": "array", "description": "list of ' - 'witness identifiers to remove", "items": {"type": "string"}}, "toad": ' - '{"type": "integer", "description": "withness threshold", "default": 1}, ' - '"isith": {"type": "string", "description": "signing threshold"}, "count": ' - '{"type": "integer", "description": "count of next key commitment."}, "data": ' - '{"type": "array", "description": "list of data objects to anchor to this ' - 'rotation event", "items": {"type": "object"}}}}}}}, "responses": {"200": ' - '{"description": "Rotation successful with KEL event returned"}, "400": ' - '{"description": "Error creating rotation event"}}}, "put": {"summary": ' - '"Participate in multisig group rotatation", "description": "Participate in a ' - 'multisig group rotation with the participants identified by the provided ' - 'AIDs", "tags": ["Groups"], "parameters": [{"in": "path", "name": "alias", ' - '"schema": {"type": "string"}, "required": true, "description": "Human ' - 'readable alias for the identifier to create"}], "requestBody": {"required": ' - 'true, "content": {"application/json": {"schema": {"type": "object", ' - '"properties": {"aids": {"type": "array", "description": "list of particiant ' - 'identifiers for this rotation", "items": {"type": "string"}}, "wits": ' - '{"type": "array", "description": "list of witness identifiers", "items": ' - '{"type": "string"}}, "adds": {"type": "array", "description": "list of ' - 'witness identifiers to add", "items": {"type": "string"}}, "cuts": {"type": ' - '"array", "description": "list of witness identifiers to remove", "items": ' - '{"type": "string"}}, "toad": {"type": "integer", "description": "withness ' - 'threshold", "default": 1}, "isith": {"type": "string", "description": ' - '"signing threshold"}, "count": {"type": "integer", "description": "count of ' - 'next key commitment."}, "data": {"type": "array", "description": "list of ' - 'data objects to anchor to this rotation event", "items": {"type": ' - '"object"}}}}}}}, "responses": {"200": {"description": "Rotation successful ' - 'with KEL event returned"}, "400": {"description": "Error creating rotation ' - 'event"}}}}}, "info": {"title": "KERI Interactive Web Interface API", ' - '"version": "1.0.0"}, "openapi": "3.0.2"}') diff --git a/tests/app/test_storing.py b/tests/app/test_storing.py index 3907e4384..82bf39352 100644 --- a/tests/app/test_storing.py +++ b/tests/app/test_storing.py @@ -8,7 +8,7 @@ import lmdb from keri.app import keeping -from keri.core import coring +from keri.core import coring, serdering from keri.db import dbing, basing from keri.peer import exchanging from keri.app.storing import Mailboxer @@ -80,8 +80,8 @@ def test_mailboxing(): d = dict(a="b", b=idx) dest = coring.Prefixer(qb64="EAD919wF4oiG7ck6mnBWTRD_Z-Io0wZKCxL0zjx5je9I") - exn = exchanging.exchange("/credential/issue", payload=d, - date="2021-07-15T13:01:37.624492+00:00") + exn, _ = exchanging.exchange("/credential/issue", payload=d, + date="2021-07-15T13:01:37.624492+00:00", sender=dest.qb64) mber.storeMsg(topic=dest.qb64b, msg=exn.raw) msgs = [] @@ -91,7 +91,7 @@ def test_mailboxing(): assert(len(msgs)) == 10 for idx, msg in msgs: - exn = coring.Serder(raw=msg) + exn = serdering.SerderKERI(raw=msg) d = exn.ked["a"] assert d["b"] == idx diff --git a/tests/app/test_watching.py b/tests/app/test_watching.py deleted file mode 100644 index b228c762a..000000000 --- a/tests/app/test_watching.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- encoding: utf-8 -*- -""" -tests.app.watching module - -""" -import time - -from hio.base import doing, tyming -from hio.core import http -from hio.help import decking - -from keri.app import habbing, watching -from keri.core import eventing, parsing, coring - - -def test_watcher_rotate_handler(seeder): - with habbing.openHab(name="watcher", transferable=False, temp=True) as (watHby, wat), \ - habbing.openHab(name="ctrl", transferable=True, temp=True) as (ctrlHby, ctrl): - - seeder.seedWatcherEnds(ctrlHby.db) - kiwi = watching.KiwiServer(hab=wat, controller=ctrl.pre) - server = http.Server(port=5644, app=kiwi.app) - httpServerDoer = http.ServerDoer(server=server) - - watKvy = eventing.Kevery(db=wat.db) - ctrlIcp = ctrl.makeOwnEvent(sn=0) - parsing.Parser().parse(ims=bytearray(ctrlIcp), kvy=watKvy) - assert wat.pre == 'BGYNONqsgWKDQuKyCNanZ-7DyT0oeb6ectMZ1WGyT7o8' - assert ctrl.pre == 'ECr8Y5fKGP9-HdSuYvZ043gQYHZfFLGL7py1317GjSrl' - - habr = ctrl.db.habs.get(ctrl.name) - habr.watchers = list([wat.pre]) - ctrl.db.habs.pin(ctrl.name, habr) - - rotateDoer = watching.WatcherClientRotateDoer(hab=ctrl, msgs=decking.Deck([wat.pre])) - doers = [kiwi, httpServerDoer, rotateDoer] - - limit = 1.0 - tock = 0.03125 - doist = doing.Doist(tock=tock, limit=limit, doers=doers) - doist.enter() - - tymer = tyming.Tymer(tymth=doist.tymen(), duration=doist.limit) - - while not tymer.expired: - doist.recur() - time.sleep(doist.tock) - - assert doist.limit == limit - - doist.exit() - - assert len(rotateDoer.cues) == 1 - cue = rotateDoer.cues.popleft() - - assert wat.pre != "BZg042qyBYoNC4rII1qdn7sPJPSh5vp5y0xnVYbJPujw" - assert cue["old"] == "BGYNONqsgWKDQuKyCNanZ-7DyT0oeb6ectMZ1WGyT7o8" - assert cue["new"] == wat.pre - - habr = ctrl.db.habs.get(ctrl.name) - assert habr.watchers == [wat.pre] diff --git a/tests/comply/test_direct_mode.py b/tests/comply/test_direct_mode.py index 200a2b900..6ca1c4823 100644 --- a/tests/comply/test_direct_mode.py +++ b/tests/comply/test_direct_mode.py @@ -108,12 +108,12 @@ def test_direct_mode_with_manager(): # create trans receipt by attaching siger to recipt msg reserder = receipt(pre=coeK.prefixer.qb64, sn=coeK.sn, - said=coeK.serder.saider.qb64) + said=coeK.serder.said) # sign controller's event not receipt # look up event to sign from validator's kever for coe coeIcpDig = bytes(valKevery.db.getKeLast(key=snKey(pre=coepre, sn=csn))) - assert coeIcpDig == coeK.serder.saider.qb64b + assert coeIcpDig == coeK.serder.saidb coeIcpRaw = bytes(valKevery.db.getEvt(key=dgKey(pre=coepre, dig=coeIcpDig))) #counter = Counter(CtrDex.ControllerIdxSigs) @@ -142,10 +142,10 @@ def test_direct_mode_with_manager(): assert valpre in coeKevery.kevers # check if receipt quadruple from validator in receipt database result = coeKevery.db.getVrcs(key=dgKey(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64)) + dig=coeKever.serder.said)) assert bytes(result[0]) == (valKever.prefixer.qb64b + Seqner(sn=valKever.sn).qb64b + - valKever.serder.saider.qb64b + + valKever.serder.saidb + sigers[0].qb64b) @@ -167,7 +167,7 @@ def test_direct_mode_with_manager(): assert bytes(result[0]) == (fake.encode("utf-8") + valKever.prefixer.qb64b + Seqner(sn=valKever.sn).qb64b + - valKever.serder.saider.qb64b + + valKever.serder.saidb + sigers[0].qb64b) # Send receipt from controller to validator @@ -180,11 +180,11 @@ def test_direct_mode_with_manager(): # create trans receipt reserder = receipt(pre=valK.prefixer.qb64, sn=valK.sn, - said=valK.serder.saider.qb64, ) + said=valK.serder.said, ) # sign validator's event not receipt # look up event to sign from controller's kever for validator valIcpDig = bytes(coeKevery.db.getKeLast(key=snKey(pre=valpre, sn=vsn))) - assert valIcpDig == valK.serder.saider.qb64b + assert valIcpDig == valK.serder.saidb valIcpRaw = bytes(coeKevery.db.getEvt(key=dgKey(pre=valpre, dig=valIcpDig))) sigers = coeMgr.sign(ser=valIcpRaw, verfers=coeVerfers) # return Siger if index # create receipt message @@ -199,10 +199,10 @@ def test_direct_mode_with_manager(): # check if receipt quadruple from controller in validator's receipt database result = valKevery.db.getVrcs(key=dgKey(pre=valKever.prefixer.qb64, - dig=valKever.serder.saider.qb64)) + dig=valKever.serder.said)) assert bytes(result[0]) == (coeKever.prefixer.qb64b + Seqner(sn=coeKever.sn).qb64b + - coeKever.serder.saider.qb64b + + coeKever.serder.saidb + sigers[0].qb64b) # Controller Event 1 Rotation Transferable @@ -212,7 +212,7 @@ def test_direct_mode_with_manager(): coeVerfers, coeDigers = coeMgr.rotate(pre=coeVerfers[0].qb64) coeSerder = rotate(pre=coeKever.prefixer.qb64, keys=[coeVerfers[0].qb64], - dig=coeKever.serder.saider.qb64, + dig=coeKever.serder.said, ndigs=[coeDigers[0].qb64], sn=csn) coe_event_digs.append(coeSerder.said) @@ -228,14 +228,14 @@ def test_direct_mode_with_manager(): # coeKevery.processOne(ims=bytearray(cmsg)) # make copy # verify controller's copy of controller's event stream is updated assert coeKever.sn == csn - assert coeKever.serder.saider.qb64 == coeSerder.said + assert coeKever.serder.said == coeSerder.said # simulate send message from controller to validator parsing.Parser().parse(ims=cmsg, kvy=valKevery) # valKevery.process(ims=cmsg) # verify validator's copy of controller's event stream is updated assert coeK.sn == csn - assert coeK.serder.saider.qb64 == coeSerder.said + assert coeK.serder.said == coeSerder.said # create receipt of controller's rotation # create seal of validator's last establishment event @@ -245,11 +245,11 @@ def test_direct_mode_with_manager(): # create validator receipt reserder = receipt(pre=coeK.prefixer.qb64, sn=coeK.sn, - said=coeK.serder.saider.qb64) + said=coeK.serder.said) # sign controller's event not receipt # look up event to sign from validator's kever for controller coeRotDig = bytes(valKevery.db.getKeLast(key=snKey(pre=coepre, sn=csn))) - assert coeRotDig == coeK.serder.saider.qb64b + assert coeRotDig == coeK.serder.saidb coeRotRaw = bytes(valKevery.db.getEvt(key=dgKey(pre=coepre, dig=coeRotDig))) sigers = valMgr.sign(ser=coeRotRaw, verfers=valVerfers) # validator create receipt message @@ -265,10 +265,10 @@ def test_direct_mode_with_manager(): # check if receipt quadruple from validator in receipt database result = coeKevery.db.getVrcs(key=dgKey(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64)) + dig=coeKever.serder.said)) assert bytes(result[0]) == (valKever.prefixer.qb64b + Seqner(sn=valKever.sn).qb64b + - valKever.serder.saider.qb64b + + valKever.serder.saidb + sigers[0].qb64b) # Next Event 2 Controller Interaction @@ -276,7 +276,7 @@ def test_direct_mode_with_manager(): assert csn == 2 assert cesn == 1 coeSerder = interact(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64, + dig=coeKever.serder.said, sn=csn) coe_event_digs.append(coeSerder.said) @@ -291,14 +291,14 @@ def test_direct_mode_with_manager(): # coeKevery.processOne(ims=bytearray(cmsg)) # make copy # verify controller's copy of controller's event stream is updated assert coeKever.sn == csn - assert coeKever.serder.saider.qb64 == coeSerder.said + assert coeKever.serder.said == coeSerder.said # simulate send message from controller to validator parsing.Parser().parse(ims=cmsg, kvy=valKevery) # valKevery.process(ims=cmsg) # verify validator's copy of controller's event stream is updated assert coeK.sn == csn - assert coeK.serder.saider.qb64 == coeSerder.said + assert coeK.serder.said == coeSerder.said # create receipt of controller's interaction # create seal of validator's last est event @@ -308,11 +308,11 @@ def test_direct_mode_with_manager(): # create validator receipt reserder = receipt(pre=coeK.prefixer.qb64, sn=coeK.sn, - said=coeK.serder.saider.qb64) + said=coeK.serder.said) # sign controller's event not receipt # look up event to sign from validator's kever for controller coeIxnDig = bytes(valKevery.db.getKeLast(key=snKey(pre=coepre, sn=csn))) - assert coeIxnDig == coeK.serder.saider.qb64b + assert coeIxnDig == coeK.serder.saidb coeIxnRaw = bytes(valKevery.db.getEvt(key=dgKey(pre=coepre, dig=coeIxnDig))) sigers = valMgr.sign(ser=coeIxnRaw, verfers=valVerfers) # create receipt message @@ -328,10 +328,10 @@ def test_direct_mode_with_manager(): # check if receipt quadruple from validator in receipt database result = coeKevery.db.getVrcs(key=dgKey(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64)) + dig=coeKever.serder.said)) assert bytes(result[0]) == (valKever.prefixer.qb64b + Seqner(sn=valKever.sn).qb64b + - valKever.serder.saider.qb64b + + valKever.serder.saidb + sigers[0].qb64b) diff --git a/tests/conftest.py b/tests/conftest.py index ae4a27764..89dbaab37 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -141,6 +141,35 @@ def seedWatcherEnds(db, protocols=None): @staticmethod def seedSchema(db): + # EAv8omZ-o3Pk45h72_WnIpt6LTWNzc8hmLjeblpxB9vz + sad = {'$id': '', + '$schema': 'http://json-schema.org/draft-07/schema#', 'title': 'Optional Issuee', + 'description': 'A credential with an optional issuee', + 'credentialType': 'UntargetedAttestation', + 'properties': {'v': {'type': 'string'}, 'd': {'type': 'string'}, 'i': {'type': 'string'}, + 'ri': {'description': 'credential status registry', 'type': 'string'}, + 's': {'description': 'schema SAID', 'type': 'string'}, 'a': {'properties': { + 'd': {'type': 'string'}, + 'i': {'type': 'string'}, + 'dt': { + 'format': + 'date-time', + 'type': 'string'}, + 'claim': { + 'type': 'string'}}, + 'additionalProperties': + False, + 'required': ['dt', + 'claim'], + 'type': 'object'}, + 'e': {'description': 'edges block', 'type': 'object'}, + 'r': {'type': 'object', 'description': 'rules block'}}, 'additionalProperties': False, + 'required': ['i', 'ri', 's', 'd', 'e', 'r'], 'type': 'object'} + + _, sad = coring.Saider.saidify(sad, label=coring.Saids.dollar) + schemer = scheming.Schemer(sed=sad) + db.schema.pin(schemer.said, schemer) + # OLD: "E1MCiPag0EWlqeJGzDA9xxr1bUSUR4fZXtqHDrwdXgbk" sad = {'$id': '', '$schema': 'http://json-schema.org/draft-07/schema#', 'title': 'Legal Entity vLEI Credential', @@ -170,7 +199,7 @@ def seedSchema(db): 'r': {'type': 'object', 'description': 'rules block'}}, 'additionalProperties': False, 'required': ['i', 'ri', 's', 'd', 'e', 'r'], 'type': 'object'} - _, sad = coring.Saider.saidify(sad, label=coring.Ids.dollar) + _, sad = coring.Saider.saidify(sad, label=coring.Saids.dollar) schemer = scheming.Schemer(sed=sad) # NEW: "ENTAoj2oNBFpaniRswwPcca9W1ElEeH2V7ahw68HV4G5 db.schema.pin(schemer.said, schemer) @@ -201,7 +230,7 @@ def seedSchema(db): 'LEI'], 'type': 'object'}, 'e': {'type': 'object'}}, 'additionalProperties': False, 'required': ['d', 'i', 'ri']} - _, sad = coring.Saider.saidify(sad, label=coring.Ids.dollar) + _, sad = coring.Saider.saidify(sad, label=coring.Saids.dollar) schemer = scheming.Schemer(sed=sad) # NEW: EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC db.schema.pin(schemer.said, schemer) @@ -242,7 +271,7 @@ def seedSchema(db): 'r': {'type': 'array', 'items': {'type': 'object'}, 'description': 'rules block', 'minItems': 0}}, 'additionalProperties': False, 'required': ['i', 'ri', 's', 'd', 'e', 'r'], 'type': 'object'} - _, sad = coring.Saider.saidify(sad, label=coring.Ids.dollar) + _, sad = coring.Saider.saidify(sad, label=coring.Saids.dollar) schemer = scheming.Schemer(sed=sad) # NEW: ED892b40P_GcESs3wOcc2zFvL_GVi2Ybzp9isNTZKqP0 db.schema.pin(schemer.said, schemer) @@ -280,7 +309,7 @@ def seedSchema(db): 'e': {'type': 'object'}}, 'additionalProperties': False, 'required': ['i', 'ri', 's', 'd'], 'type': 'object'} - _, sad = coring.Saider.saidify(sad, label=coring.Ids.dollar) + _, sad = coring.Saider.saidify(sad, label=coring.Saids.dollar) schemer = scheming.Schemer(sed=sad) # NEW: EFgnk_c08WmZGgv9_mpldibRuqFMTQN-rAgtD-TCOwbs db.schema.pin(schemer.said, schemer) diff --git a/tests/core/test_bare.py b/tests/core/test_bare.py index defd89b03..5794d22ce 100644 --- a/tests/core/test_bare.py +++ b/tests/core/test_bare.py @@ -20,20 +20,25 @@ def test_bare(): """ - Test bare message 'bre' + Test bare message 'bar' { "v" : "KERI10JSON00011c_", "t" : "bar", "d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM", - "r" : "logs/processor", + "dt": "2020-08-22T17:50:12.988921+00:00", + "r" : "sealed/processor", "a" : - { - "cid": "D3pYGFaqnrALTyejaJaGAVhNpSCtqyerPqWVK9ZBNZk0", - "role": "watcher", - "eid": "EAoTNZH3ULvYAfSVPzhzS6baU6JR2nmwyZ-i0d8JZ5CM", - "name": "John Jones", - } + { + "EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM": + { + "d": "EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM", + "i": "EAoTNZH3ULvYAfSVPzhzS6baU6JR2nmwyZ-i0d8JZ5CM", + "dt": "2020-08-22T17:50:12.988921+00:00", + "name": "John Jones", + "role": "Founder", + } + } } """ @@ -86,17 +91,19 @@ def test_bare(): name="besty", ) - + stamp = "2023-06-26T22:22:13.416766+00:00" serderE = eventing.bare(route="/to/the/moon", data=data, + stamp=stamp, ) - assert serderE.raw == (b'{"v":"KERI10JSON0000f9_","t":"bar","d":"EOBOm9NDlTey2VyDGhMZ-wKqOoS5FnJEPwdp' - b'IMVH7Oll","r":"/to/the/moon","a":{"cid":"DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6l' - b'lSvWQTWZN","role":"watcher","eid":"EAoTNZH3ULvYAfSVPzhzS6baU6JR2nmwyZ-i0d8JZ' - b'5CM","name":"besty"}}') + assert serderE.raw == (b'{"v":"KERI10JSON000121_","t":"bar","d":"EGPY61eN5zhw7nnlra3bQL8xapaMhP4I_0yi' + b'hFOLXNgH","dt":"2023-06-26T22:22:13.416766+00:00","r":"/to/the/moon","a":{"c' + b'id":"DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN","role":"watcher","eid":"E' + b'AoTNZH3ULvYAfSVPzhzS6baU6JR2nmwyZ-i0d8JZ5CM","name":"besty"}}') - assert serderE.ked["d"] == 'EOBOm9NDlTey2VyDGhMZ-wKqOoS5FnJEPwdpIMVH7Oll' + assert serderE.said == 'EGPY61eN5zhw7nnlra3bQL8xapaMhP4I_0yihFOLXNgH' + assert serderE.stamp == stamp # create SealEvent for endorsers est evt whose keys use to sign @@ -110,14 +117,14 @@ def test_bare(): s='0', d='EAuNWHss_H_kH4cG7Li1jn2DXfrEaqN7zhqTEhkeDZ2z') msg = messagize(serderE, sigers=[sigerC], seal=seal) - assert msg == (b'{"v":"KERI10JSON0000f9_","t":"bar","d":"EOBOm9NDlTey2VyDGhMZ-wKq' - b'OoS5FnJEPwdpIMVH7Oll","r":"/to/the/moon","a":{"cid":"DN6WBhWqp6w' - b'C08no2iWhgFYTaUgrasnqz6llSvWQTWZN","role":"watcher","eid":"EAoTN' - b'ZH3ULvYAfSVPzhzS6baU6JR2nmwyZ-i0d8JZ5CM","name":"besty"}}-FABDN6' - b'WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN0AAAAAAAAAAAAAAAAAAAAAA' - b'AEAuNWHss_H_kH4cG7Li1jn2DXfrEaqN7zhqTEhkeDZ2z-AABAACKOcmfrZtRsW_' - b'PKmt_gDXFiAsepoKl85WFTr_XaVGh2qkh_JQ7eN-nEFFgyPv-8a51jrOGRX_tY2M' - b'6DPQqQHUJ') + assert msg == (b'{"v":"KERI10JSON000121_","t":"bar","d":"EGPY61eN5zhw7nnlra3bQL8x' + b'apaMhP4I_0yihFOLXNgH","dt":"2023-06-26T22:22:13.416766+00:00","r' + b'":"/to/the/moon","a":{"cid":"DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6l' + b'lSvWQTWZN","role":"watcher","eid":"EAoTNZH3ULvYAfSVPzhzS6baU6JR2' + b'nmwyZ-i0d8JZ5CM","name":"besty"}}-FABDN6WBhWqp6wC08no2iWhgFYTaUg' + b'rasnqz6llSvWQTWZN0AAAAAAAAAAAAAAAAAAAAAAAEAuNWHss_H_kH4cG7Li1jn2' + b'DXfrEaqN7zhqTEhkeDZ2z-AABAAACsAGVg747fc-61v64LuAa6WbfCKjKgH6Xo0t' + b'1wz2X7E51I_aWCTSU3KIhqkZirj7aYK__AIy_UvC8Tub7APwH') # create endorsed bar with trans endorser # create trans key pair for endorser @@ -134,14 +141,14 @@ def test_bare(): s='0', d='EAuNWHss_H_kH4cG7Li1jn2DXfrEaqN7zhqTEhkeDZ2z') msg = messagize(serderE, sigers=[sigerE], seal=seal) - assert msg == (b'{"v":"KERI10JSON0000f9_","t":"bar","d":"EOBOm9NDlTey2VyDGhMZ-wKq' - b'OoS5FnJEPwdpIMVH7Oll","r":"/to/the/moon","a":{"cid":"DN6WBhWqp6w' - b'C08no2iWhgFYTaUgrasnqz6llSvWQTWZN","role":"watcher","eid":"EAoTN' - b'ZH3ULvYAfSVPzhzS6baU6JR2nmwyZ-i0d8JZ5CM","name":"besty"}}-FABDMr' - b'wi0a-Zblpqe5Hg7w7iz9JCKnMgWKu_W9w4aNUL64y0AAAAAAAAAAAAAAAAAAAAAA' - b'AEAuNWHss_H_kH4cG7Li1jn2DXfrEaqN7zhqTEhkeDZ2z-AABAABABWeacQ_nHgu' - b'Ugw6scJCUIbs5_vczaXxtKTYaryN15e_9Y7GT-korkJc4sHGpkmekr7w2XFhr1Da' - b'OTfVsyNUI') + assert msg == (b'{"v":"KERI10JSON000121_","t":"bar","d":"EGPY61eN5zhw7nnlra3bQL8x' + b'apaMhP4I_0yihFOLXNgH","dt":"2023-06-26T22:22:13.416766+00:00","r' + b'":"/to/the/moon","a":{"cid":"DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6l' + b'lSvWQTWZN","role":"watcher","eid":"EAoTNZH3ULvYAfSVPzhzS6baU6JR2' + b'nmwyZ-i0d8JZ5CM","name":"besty"}}-FABDMrwi0a-Zblpqe5Hg7w7iz9JCKn' + b'MgWKu_W9w4aNUL64y0AAAAAAAAAAAAAAAAAAAAAAAEAuNWHss_H_kH4cG7Li1jn2' + b'DXfrEaqN7zhqTEhkeDZ2z-AABAAAqSbIUsv723owtCsHk4ltmzhf0leA4BXxJiC3' + b'ZBD3jZzbVPwxKTv8cY1z-RnpS6gW1xgeL__Lb0Cr4p8ZisvEI') # create endorsed bar with nontrans endorser @@ -154,12 +161,13 @@ def test_bare(): cigarE = signerE.sign(ser=serderE.raw) # no index so Cigar assert signerE.verfer.verify(sig=cigarE.raw, ser=serderE.raw) msg = messagize(serderE, cigars=[cigarE]) - assert msg == (b'{"v":"KERI10JSON0000f9_","t":"bar","d":"EOBOm9NDlTey2VyDGhMZ-wKq' - b'OoS5FnJEPwdpIMVH7Oll","r":"/to/the/moon","a":{"cid":"DN6WBhWqp6w' - b'C08no2iWhgFYTaUgrasnqz6llSvWQTWZN","role":"watcher","eid":"EAoTN' - b'ZH3ULvYAfSVPzhzS6baU6JR2nmwyZ-i0d8JZ5CM","name":"besty"}}-CABBMr' - b'wi0a-Zblpqe5Hg7w7iz9JCKnMgWKu_W9w4aNUL64y0BBABWeacQ_nHguUgw6scJC' - b'UIbs5_vczaXxtKTYaryN15e_9Y7GT-korkJc4sHGpkmekr7w2XFhr1DaOTfVsyNUI') + assert msg == (b'{"v":"KERI10JSON000121_","t":"bar","d":"EGPY61eN5zhw7nnlra3bQL8x' + b'apaMhP4I_0yihFOLXNgH","dt":"2023-06-26T22:22:13.416766+00:00","r' + b'":"/to/the/moon","a":{"cid":"DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6l' + b'lSvWQTWZN","role":"watcher","eid":"EAoTNZH3ULvYAfSVPzhzS6baU6JR2' + b'nmwyZ-i0d8JZ5CM","name":"besty"}}-CABBMrwi0a-Zblpqe5Hg7w7iz9JCKn' + b'MgWKu_W9w4aNUL64y0BAqSbIUsv723owtCsHk4ltmzhf0leA4BXxJiC3ZBD3jZzb' + b'VPwxKTv8cY1z-RnpS6gW1xgeL__Lb0Cr4p8ZisvEI') """Done Test""" diff --git a/tests/core/test_coring.py b/tests/core/test_coring.py index 81b7cc510..7ec33df4a 100644 --- a/tests/core/test_coring.py +++ b/tests/core/test_coring.py @@ -11,19 +11,24 @@ from fractions import Fraction from builtins import OverflowError from math import ceil +from collections import namedtuple import blake3 import cbor2 as cbor import msgpack import pysodium import pytest +from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat +from cryptography.hazmat.primitives.asymmetric import ec, utils +from cryptography.hazmat.primitives import hashes +from cryptography import exceptions from keri.core import coring from keri.core import eventing -from keri.core.coring import Ilkage, Ilks, Ids, Idents, Sadder -from keri.core.coring import Seqner, NumDex, Number, Siger, Dater, Bexter -from keri.core.coring import Serder, Tholder -from keri.core.coring import Serialage, Serials, Vstrings +from keri.core.coring import (Ilkage, Ilks, Labels, Saids, Protos, Protocolage, + Sadder, Tholder, Seqner, + NumDex, Number, Siger, Dater, Bexter) +from keri.core.coring import Serialage, Serials, Tiers, Vstrings from keri.core.coring import (Sizage, MtrDex, Matter, Xizage, IdrDex, IdxSigDex, IdxCrtSigDex, IdxBthSigDex, Indexer, CtrDex, Counter, sniff, ProDex) @@ -36,8 +41,49 @@ from keri.help import helping from keri.kering import (EmptyMaterialError, RawMaterialError, DerivationError, ShortageError, InvalidCodeSizeError, InvalidVarIndexError, - InvalidValueError) -from keri.kering import Version, Versionage + InvalidValueError, DeserializeError) +from keri.kering import Version, Versionage, VersionError +from keri.kering import (ICP_LABELS, DIP_LABELS, ROT_LABELS, DRT_LABELS, IXN_LABELS, + RPY_LABELS) +from keri.kering import (VCP_LABELS, VRT_LABELS, ISS_LABELS, BIS_LABELS, REV_LABELS, + BRV_LABELS, TSN_LABELS, CRED_TSN_LABELS) + + + +def test_protos(): + """ + Test protocols namedtuple instance Protos + """ + + assert isinstance(Protos, Protocolage) + + assert Protos.keri == 'KERI' + assert Protos.crel == 'CREL' + assert Protos.acdc == 'ACDC' + + assert 'KERI' in Protos + assert 'CREL' in Protos + assert 'ACDC' in Protos + + """End Test""" + +def test_prodex(): + """ + Test genera in ProDex as instance of ProtocolGenusCodex + + """ + + assert dataclasses.asdict(ProDex) == { + 'KERI': '--AAA', # KERI and ACDC Protocol Stacks share the same tables + 'ACDC': '--AAA', + } + + assert '--AAA' in ProDex + assert ProDex.KERI == "--AAA" + assert ProDex.ACDC == "--AAA" + assert ProDex.KERI == ProDex.ACDC + + """End Test""" def test_ilks(): @@ -45,7 +91,7 @@ def test_ilks(): Test Ilkage namedtuple instance Ilks """ assert Ilks == Ilkage(icp='icp', rot='rot', ixn='ixn', dip='dip', drt='drt', - rct='rct', ksn='ksn', qry='qry', rpy='rpy', + rct='rct', qry='qry', rpy='rpy', exn='exn', pro='pro', bar='bar', vcp='vcp', vrt='vrt', iss='iss', rev='rev', bis='bis', brv='brv', ) @@ -67,8 +113,6 @@ def test_ilks(): assert Ilks.drt == 'drt' assert 'rct' in Ilks assert Ilks.rct == 'rct' - assert 'ksn' in Ilks - assert Ilks.ksn == 'ksn' assert 'qry' in Ilks assert Ilks.qry == 'qry' assert 'rpy' in Ilks @@ -98,6 +142,45 @@ def test_ilks(): """End Test """ +def test_labels(): + """ + Test Ilkage namedtuple instance Labels + """ + assert Labels == Ilkage(icp=ICP_LABELS, rot=ROT_LABELS, ixn=IXN_LABELS, + dip=DIP_LABELS, drt=DRT_LABELS, + rct=[], qry=[], rpy=RPY_LABELS, + exn=[], pro=[], bar=[], + vcp=VCP_LABELS, vrt=VRT_LABELS, iss=ISS_LABELS, + rev=REV_LABELS, bis=BIS_LABELS, brv=BRV_LABELS) + + assert isinstance(Labels, Ilkage) + + for fld in Labels._fields: + assert isinstance(getattr(Labels, fld), list) + + assert Labels.icp == ICP_LABELS + assert Labels.rot == ROT_LABELS + assert Labels.ixn == IXN_LABELS + assert Labels.dip == DIP_LABELS + assert Labels.drt == DRT_LABELS + assert Labels.rct == [] + assert Labels.qry == [] + assert Labels.rpy == RPY_LABELS + assert Labels.exn == [] + assert Labels.pro == [] + assert Labels.bar == [] + + assert Labels.vcp == VCP_LABELS + assert Labels.vrt == VRT_LABELS + assert Labels.iss == ISS_LABELS + assert Labels.rev == REV_LABELS + assert Labels.bis == BIS_LABELS + assert Labels.brv == BRV_LABELS + + """End Test """ + + + def test_b64_conversions(): """ @@ -280,6 +363,15 @@ def test_matter(): 'Big': 'N', 'X25519_Private': 'O', 'X25519_Cipher_Seed': 'P', + 'ECDSA_256r1_Seed': 'Q', + 'Tall': 'R', + 'Large': 'S', + 'Great': 'T', + 'Vast': 'U', + 'Label1': 'V', + 'Label2': 'W', + 'Tag3': 'X', + 'Tag7': 'Y', 'Salt_128': '0A', 'Ed25519_Sig': '0B', 'ECDSA_256k1_Sig': '0C', @@ -288,14 +380,24 @@ def test_matter(): 'SHA3_512': '0F', 'SHA2_512': '0G', 'Long': '0H', + 'ECDSA_256r1_Sig': '0I', + 'Tag1': '0J', + 'Tag2': '0K', + 'Tag5': '0L', + 'Tag6': '0M', 'ECDSA_256k1N': '1AAA', 'ECDSA_256k1': '1AAB', 'Ed448N': '1AAC', 'Ed448': '1AAD', 'Ed448_Sig': '1AAE', - 'Tern': '1AAF', + 'Tag4': '1AAF', 'DateTime': '1AAG', 'X25519_Cipher_Salt': '1AAH', + 'ECDSA_256r1N': '1AAI', + 'ECDSA_256r1': '1AAJ', + 'Null': '1AAK', + 'Yes': '1AAL', + 'No': '1AAM', 'TBD1': '2AAA', 'TBD2': '3AAA', 'StrB64_L0': '4A', @@ -310,8 +412,27 @@ def test_matter(): 'Bytes_Big_L0': '7AAB', 'Bytes_Big_L1': '8AAB', 'Bytes_Big_L2': '9AAB', + 'X25519_Cipher_L0': '4C', + 'X25519_Cipher_L1': '5C', + 'X25519_Cipher_L2': '6C', + 'X25519_Cipher_Big_L0': '7AAC', + 'X25519_Cipher_Big_L1': '8AAC', + 'X25519_Cipher_Big_L2': '9AAC', + 'X25519_Cipher_QB64_L0': '4D', + 'X25519_Cipher_QB64_L1': '5D', + 'X25519_Cipher_QB64_L2': '6D', + 'X25519_Cipher_QB64_Big_L0': '7AAD', + 'X25519_Cipher_QB64_Big_L1': '8AAD', + 'X25519_Cipher_QB64_Big_L2': '9AAD', + 'X25519_Cipher_QB2_L0': '4D', + 'X25519_Cipher_QB2_L1': '5D', + 'X25519_Cipher_QB2_L2': '6D', + 'X25519_Cipher_QB2_Big_L0': '7AAD', + 'X25519_Cipher_QB2_Big_L1': '8AAD', + 'X25519_Cipher_QB2_Big_L2': '9AAD' } + assert Matter.Codex == MtrDex # first character of code with hard size of code @@ -344,6 +465,15 @@ def test_matter(): 'N': Sizage(hs=1, ss=0, fs=12, ls=0), 'O': Sizage(hs=1, ss=0, fs=44, ls=0), 'P': Sizage(hs=1, ss=0, fs=124, ls=0), + 'Q': Sizage(hs=1, ss=0, fs=44, ls=0), + 'R': Sizage(hs=1, ss=0, fs=8, ls=0), + 'S': Sizage(hs=1, ss=0, fs=16, ls=0), + 'T': Sizage(hs=1, ss=0, fs=20, ls=0), + 'U': Sizage(hs=1, ss=0, fs=24, ls=0), + 'V': Sizage(hs=1, ss=0, fs=4, ls=1), + 'W': Sizage(hs=1, ss=0, fs=4, ls=0), + 'X': Sizage(hs=1, ss=0, fs=4, ls=0), + 'Y': Sizage(hs=1, ss=0, fs=8, ls=0), '0A': Sizage(hs=2, ss=0, fs=24, ls=0), '0B': Sizage(hs=2, ss=0, fs=88, ls=0), '0C': Sizage(hs=2, ss=0, fs=88, ls=0), @@ -352,6 +482,11 @@ def test_matter(): '0F': Sizage(hs=2, ss=0, fs=88, ls=0), '0G': Sizage(hs=2, ss=0, fs=88, ls=0), '0H': Sizage(hs=2, ss=0, fs=8, ls=0), + '0I': Sizage(hs=2, ss=0, fs=88, ls=0), + '0J': Sizage(hs=2, ss=0, fs=4, ls=0), + '0K': Sizage(hs=2, ss=0, fs=4, ls=0), + '0L': Sizage(hs=2, ss=0, fs=8, ls=0), + '0M': Sizage(hs=2, ss=0, fs=8, ls=0), '1AAA': Sizage(hs=4, ss=0, fs=48, ls=0), '1AAB': Sizage(hs=4, ss=0, fs=48, ls=0), '1AAC': Sizage(hs=4, ss=0, fs=80, ls=0), @@ -360,6 +495,11 @@ def test_matter(): '1AAF': Sizage(hs=4, ss=0, fs=8, ls=0), '1AAG': Sizage(hs=4, ss=0, fs=36, ls=0), '1AAH': Sizage(hs=4, ss=0, fs=100, ls=0), + '1AAI': Sizage(hs=4, ss=0, fs=48, ls=0), + '1AAJ': Sizage(hs=4, ss=0, fs=48, ls=0), + '1AAK': Sizage(hs=4, ss=0, fs=4, ls=0), + '1AAL': Sizage(hs=4, ss=0, fs=4, ls=0), + '1AAM': Sizage(hs=4, ss=0, fs=4, ls=0), '2AAA': Sizage(hs=4, ss=0, fs=8, ls=1), '3AAA': Sizage(hs=4, ss=0, fs=8, ls=2), '4A': Sizage(hs=2, ss=2, fs=None, ls=0), @@ -374,6 +514,24 @@ def test_matter(): '7AAB': Sizage(hs=4, ss=4, fs=None, ls=0), '8AAB': Sizage(hs=4, ss=4, fs=None, ls=1), '9AAB': Sizage(hs=4, ss=4, fs=None, ls=2), + '4C': Sizage(hs=2, ss=2, fs=None, ls=0), + '5C': Sizage(hs=2, ss=2, fs=None, ls=1), + '6C': Sizage(hs=2, ss=2, fs=None, ls=2), + '7AAC': Sizage(hs=4, ss=4, fs=None, ls=0), + '8AAC': Sizage(hs=4, ss=4, fs=None, ls=1), + '9AAC': Sizage(hs=4, ss=4, fs=None, ls=2), + '4D': Sizage(hs=2, ss=2, fs=None, ls=0), + '5D': Sizage(hs=2, ss=2, fs=None, ls=1), + '6D': Sizage(hs=2, ss=2, fs=None, ls=2), + '7AAD': Sizage(hs=4, ss=4, fs=None, ls=0), + '8AAD': Sizage(hs=4, ss=4, fs=None, ls=1), + '9AAD': Sizage(hs=4, ss=4, fs=None, ls=2), + '4E': Sizage(hs=2, ss=2, fs=None, ls=0), + '5E': Sizage(hs=2, ss=2, fs=None, ls=1), + '6E': Sizage(hs=2, ss=2, fs=None, ls=2), + '7AAE': Sizage(hs=4, ss=4, fs=None, ls=0), + '8AAE': Sizage(hs=4, ss=4, fs=None, ls=1), + '9AAE': Sizage(hs=4, ss=4, fs=None, ls=2) } assert Matter.Sizes['A'].hs == 1 # hard size @@ -389,10 +547,11 @@ def test_matter(): # if fs is not None else not (hs + ss) % 4 for val in Matter.Sizes.values(): if val.fs is not None: - assert val.ss == 0 and not val.fs % 4 and val.hs > 0 and val.fs > val.hs + assert val.ss == 0 and not val.fs % 4 and val.hs > 0 and val.fs >= (val.hs + val.ss) else: assert not (val.hs + val.ss) % 4 + # Bizes maps bytes of sextet of decoded first character of code with hard size of code # verify equivalents of items for Sizes and Bizes for skey, sval in Matter.Hards.items(): @@ -436,6 +595,8 @@ def test_matter(): assert matter.raw == verkey assert matter.transferable == False assert matter.digestive == False + assert matter.prefixive == True + # test round trip assert matter.qb64 == encodeB64(matter.qb2).decode("utf-8") assert matter.qb2 == decodeB64(matter.qb64.encode("utf-8")) @@ -536,6 +697,7 @@ def test_matter(): assert matter.qb2 == prebin assert matter.transferable == False assert matter.digestive == False + assert matter.prefixive == True # test nongreedy prefixb on full identifier both = prefixb + b":mystuff/mypath/toresource?query=what#fragment" @@ -546,6 +708,7 @@ def test_matter(): assert matter.qb2 == prebin assert matter.transferable == False assert matter.digestive == False + assert matter.prefixive == True # Test ._bexfil matter = Matter(qb64=prefix) # @@ -579,6 +742,7 @@ def test_matter(): assert matter.qb2 == prebin assert matter.transferable == False assert matter.digestive == False + assert matter.prefixive == True ims = bytearray(prefixb) # strip from ims qb64b matter = Matter(qb64b=ims, strip=True) @@ -590,6 +754,7 @@ def test_matter(): assert matter.qb2 == prebin assert matter.transferable == False assert matter.digestive == False + assert matter.prefixive == True assert not ims # stripped ims = bytearray(prebin) @@ -602,6 +767,7 @@ def test_matter(): assert matter.qb2 == prebin assert matter.transferable == False assert matter.digestive == False + assert matter.prefixive == True assert not ims # stripped # test strip with extra q64b @@ -616,6 +782,7 @@ def test_matter(): assert matter.qb2 == prebin assert matter.transferable == False assert matter.digestive == False + assert matter.prefixive == True assert ims == extra # stripped not include extra # test strip with extra qb2 @@ -630,6 +797,7 @@ def test_matter(): assert matter.qb2 == prebin assert matter.transferable == False assert matter.digestive == False + assert matter.prefixive == True assert ims == extra # stripped not include extra # test fix sized with leader 1 @@ -651,6 +819,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False assert matter.qb64 == encodeB64(matter.qb2).decode("utf-8") assert matter.qb2 == decodeB64(matter.qb64.encode("utf-8")) @@ -699,6 +868,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False # test with bad pad or lead badqb64 = '2AAA_2Fi' # '2AAA' + encodeB64(b'\xffab').decode("utf-8") @@ -731,6 +901,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False assert matter.qb64 == encodeB64(matter.qb2).decode("utf-8") assert matter.qb2 == decodeB64(matter.qb64.encode("utf-8")) @@ -766,6 +937,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False # test with bad pad or lead badqb64 = '3AAA__96' # '3AAA' + encodeB64(b'\xff\xffz').decode("utf-8") @@ -798,6 +970,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable is True assert matter.digestive is False + assert matter.prefixive == False assert matter.qb64 == encodeB64(matter.qb2).decode("utf-8") assert matter.qb2 == decodeB64(matter.qb64.encode("utf-8")) @@ -844,6 +1017,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False # test strip matter = Matter(qb2=bytearray(qb2), strip=True) @@ -883,6 +1057,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False # test variable sized with leader 1 with code replacement code2 = MtrDex.Bytes_L2 # use leader 0 code but with lead size 1 raw @@ -904,6 +1079,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False # test rize parameter to extract portion of raw passed in raw = b'abcdefghijk' # extra bytes in raw @@ -920,6 +1096,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False # test variable sized with leader 2 code = MtrDex.Bytes_L2 @@ -940,6 +1117,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False assert matter.qb64 == encodeB64(matter.qb2).decode("utf-8") assert matter.qb2 == decodeB64(matter.qb64.encode("utf-8")) @@ -977,6 +1155,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False # test with bad lead 2 # 4 bytes with lead 2 = two triplets = b'\xff\xffabcd' @@ -1011,6 +1190,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False # test variable sized with leader 2 with code replacement code1 = MtrDex.Bytes_L1 # use leader 1 code but with lead size 2 raw @@ -1032,6 +1212,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False # test rize parameter to extract portion of raw passed in raw = b'abcdefghijk' # extra bytes in raw @@ -1048,6 +1229,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False # test variable sized with leader 0 code = MtrDex.Bytes_L0 @@ -1068,6 +1250,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False assert matter.qb64 == encodeB64(matter.qb2).decode("utf-8") assert matter.qb2 == decodeB64(matter.qb64.encode("utf-8")) @@ -1103,6 +1286,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False # test variable sized with leader 0 with code replacement code1 = MtrDex.Bytes_L1 # use leader 1 code but with lead size 0 raw @@ -1124,6 +1308,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False # test variable sized with leader 0 with code replacement code1 = MtrDex.Bytes_L2 # use leader 2 code but with lead size 0 raw @@ -1145,6 +1330,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False # test rize parameter to extract portion of raw passed in raw = b'abcdefghijk' # extra bytes in raw @@ -1161,6 +1347,7 @@ def test_matter(): assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False # text big code substitution for size bigger than 4095 4k code0 = MtrDex.Bytes_L0 @@ -1238,6 +1425,7 @@ def test_matter(): assert matter.qb2 == qsigB2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False matter = Matter(qb64b=qsig64b) assert matter.raw == sig @@ -1247,6 +1435,7 @@ def test_matter(): assert matter.qb2 == qsigB2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False matter = Matter(qb64=qsig64) assert matter.raw == sig @@ -1256,6 +1445,7 @@ def test_matter(): assert matter.qb2 == qsigB2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False matter = Matter(qb2=qsigB2) assert matter.raw == sig @@ -1265,6 +1455,7 @@ def test_matter(): assert matter.qb2 == qsigB2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False # test short val = int("F77F", 16) @@ -1296,6 +1487,7 @@ def test_matter(): assert matter.qb2[bs:] == matter.raw assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False matter = Matter(qb64b=qb64b) assert matter.raw == raw @@ -1307,6 +1499,7 @@ def test_matter(): assert matter.qb2[bs:] == matter.raw assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False matter = Matter(qb64=qb64) assert matter.raw == raw @@ -1318,6 +1511,7 @@ def test_matter(): assert matter.qb2[bs:] == matter.raw assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False matter = Matter(qb2=qb2) assert matter.raw == raw @@ -1329,6 +1523,7 @@ def test_matter(): assert matter.qb2[bs:] == matter.raw assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False # test long val = int("F7F33F7F", 16) @@ -1360,6 +1555,7 @@ def test_matter(): assert matter.qb2[bs:] == matter.raw assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False matter = Matter(qb64b=qb64b) assert matter.raw == raw @@ -1371,6 +1567,7 @@ def test_matter(): assert matter.qb2[bs:] == matter.raw assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False matter = Matter(qb64=qb64) assert matter.raw == raw @@ -1382,6 +1579,7 @@ def test_matter(): assert matter.qb2[bs:] == matter.raw assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False matter = Matter(qb2=qb2) assert matter.raw == raw @@ -1393,30 +1591,35 @@ def test_matter(): assert matter.qb2[bs:] == matter.raw assert matter.transferable == True assert matter.digestive == False - - # test Tern as number - val = int("F89CFF", 16) - assert val == 16293119 - raw = val.to_bytes(3, 'big') - assert raw == b'\xf8\x9c\xff' - cs = len(MtrDex.Tern) + assert matter.prefixive == False + + # test Tag4 + #val = int("F89CFF", 16) + #assert val == 16293119 + #raw = val.to_bytes(3, 'big') + #assert raw == b'\xf8\x9c\xff' + raw = b'hio' + cs = len(MtrDex.Tag4) assert cs == 4 ps = cs % 4 assert ps == 0 txt = encodeB64(bytes([0]*ps) + raw) - assert txt == b'-Jz_' - qb64b = MtrDex.Tern.encode("utf-8") + txt[ps:] - assert qb64b == b'1AAF-Jz_' + #assert txt == b'-Jz_' + assert txt == b'aGlv' + qb64b = MtrDex.Tag4.encode("utf-8") + txt[ps:] + #assert qb64b == b'1AAF-Jz_' + assert qb64b == b'1AAFaGlv' qb64 = qb64b.decode("utf-8") qb2 = decodeB64(qb64b) - assert qb2 == b'\xd4\x00\x05\xf8\x9c\xff' + assert qb2 == b'\xd4\x00\x05hio' + #assert qb2 == b'\xd4\x00\x05\xf8\x9c\xff' bs = ceil((cs * 3) / 4) assert qb2[bs:] == raw # stable value in qb2 assert encodeB64(qb2) == qb64b - matter = Matter(raw=raw, code=MtrDex.Tern) + matter = Matter(raw=raw, code=MtrDex.Tag4) assert matter.raw == raw - assert matter.code == MtrDex.Tern + assert matter.code == MtrDex.Tag4 assert matter.qb64 == qb64 assert matter.qb64b == qb64b assert matter.qb2 == qb2 @@ -1424,10 +1627,11 @@ def test_matter(): assert matter.qb2[bs:] == matter.raw assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False matter = Matter(qb64b=qb64b) assert matter.raw == raw - assert matter.code == MtrDex.Tern + assert matter.code == MtrDex.Tag4 assert matter.qb64 == qb64 assert matter.qb64b == qb64b assert matter.qb2 == qb2 @@ -1435,10 +1639,11 @@ def test_matter(): assert matter.qb2[bs:] == matter.raw assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False matter = Matter(qb64=qb64) assert matter.raw == raw - assert matter.code == MtrDex.Tern + assert matter.code == MtrDex.Tag4 assert matter.qb64 == qb64 assert matter.qb64b == qb64b assert matter.qb2 == qb2 @@ -1446,10 +1651,11 @@ def test_matter(): assert matter.qb2[bs:] == matter.raw assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False matter = Matter(qb2=qb2) assert matter.raw == raw - assert matter.code == MtrDex.Tern + assert matter.code == MtrDex.Tag4 assert matter.qb64 == qb64 assert matter.qb64b == qb64b assert matter.qb2 == qb2 @@ -1457,19 +1663,20 @@ def test_matter(): assert matter.qb2[bs:] == matter.raw assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False - # test Tern as chars + # test Tag4 as chars txt = b'icp_' raw = decodeB64(txt) assert raw == b'\x89\xca\x7f' val = int.from_bytes(raw, 'big') assert val == 9030271 - cs = len(MtrDex.Tern) + cs = len(MtrDex.Tag4) assert cs == 4 ps = cs % 4 assert ps == 0 txt = encodeB64(bytes([0]*ps) + raw) - qb64b = MtrDex.Tern.encode("utf-8") + txt + qb64b = MtrDex.Tag4.encode("utf-8") + txt assert qb64b == b'1AAFicp_' qb64 = qb64b.decode("utf-8") qb2 = decodeB64(qb64b) @@ -1478,18 +1685,19 @@ def test_matter(): assert qb2[bs:] == raw # stable value in qb2 assert encodeB64(qb2) == qb64b - matter = Matter(raw=raw, code=MtrDex.Tern) + matter = Matter(raw=raw, code=MtrDex.Tag4) assert matter.raw == raw - assert matter.code == MtrDex.Tern + assert matter.code == MtrDex.Tag4 assert matter.qb64 == qb64 assert matter.qb64b == qb64b assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False matter = Matter(qb64b=qb64b) assert matter.raw == raw - assert matter.code == MtrDex.Tern + assert matter.code == MtrDex.Tag4 assert matter.qb64 == qb64 assert matter.qb64b == qb64b assert matter.qb2 == qb2 @@ -1497,19 +1705,21 @@ def test_matter(): assert matter.qb2[bs:] == matter.raw assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False matter = Matter(qb64=qb64) assert matter.raw == raw - assert matter.code == MtrDex.Tern + assert matter.code == MtrDex.Tag4 assert matter.qb64 == qb64 assert matter.qb64b == qb64b assert matter.qb2 == qb2 assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False matter = Matter(qb2=qb2) assert matter.raw == raw - assert matter.code == MtrDex.Tern + assert matter.code == MtrDex.Tag4 assert matter.qb64 == qb64 assert matter.qb64b == qb64b assert matter.qb2 == qb2 @@ -1517,6 +1727,7 @@ def test_matter(): assert matter.qb2[bs:] == matter.raw assert matter.transferable == True assert matter.digestive == False + assert matter.prefixive == False """ Done Test """ @@ -1532,12 +1743,16 @@ def test_indexer(): 'Ed25519_Crt_Sig': 'B', 'ECDSA_256k1_Sig': 'C', 'ECDSA_256k1_Crt_Sig': 'D', + 'ECDSA_256r1_Sig': 'E', + 'ECDSA_256r1_Crt_Sig': 'F', 'Ed448_Sig': '0A', 'Ed448_Crt_Sig': '0B', 'Ed25519_Big_Sig': '2A', 'Ed25519_Big_Crt_Sig': '2B', 'ECDSA_256k1_Big_Sig': '2C', 'ECDSA_256k1_Big_Crt_Sig': '2D', + 'ECDSA_256r1_Big_Sig': '2E', + 'ECDSA_256r1_Big_Crt_Sig': '2F', 'Ed448_Big_Sig': '3A', 'Ed448_Big_Crt_Sig': '3B', 'TBD0': '0z', @@ -1549,12 +1764,16 @@ def test_indexer(): assert IdrDex.Ed25519_Crt_Sig == 'B' assert IdrDex.ECDSA_256k1_Sig == 'C' assert IdrDex.ECDSA_256k1_Crt_Sig == 'D' + assert IdrDex.ECDSA_256r1_Sig == 'E' + assert IdrDex.ECDSA_256r1_Crt_Sig == 'F' assert IdrDex.Ed448_Sig == '0A' assert IdrDex.Ed448_Crt_Sig == '0B' assert IdrDex.Ed25519_Big_Sig == '2A' assert IdrDex.Ed25519_Big_Crt_Sig == '2B' assert IdrDex.ECDSA_256k1_Big_Sig == '2C' assert IdrDex.ECDSA_256k1_Big_Crt_Sig == '2D' + assert IdrDex.ECDSA_256r1_Big_Sig == '2E' + assert IdrDex.ECDSA_256r1_Big_Crt_Sig == '2F' assert IdrDex.Ed448_Big_Sig == '3A' assert IdrDex.Ed448_Big_Crt_Sig == '3B' assert IdrDex.TBD0 == '0z' @@ -1566,12 +1785,16 @@ def test_indexer(): 'Ed25519_Crt_Sig': 'B', 'ECDSA_256k1_Sig': 'C', 'ECDSA_256k1_Crt_Sig': 'D', + 'ECDSA_256r1_Sig': 'E', + 'ECDSA_256r1_Crt_Sig': 'F', 'Ed448_Sig': '0A', 'Ed448_Crt_Sig': '0B', 'Ed25519_Big_Sig': '2A', 'Ed25519_Big_Crt_Sig': '2B', 'ECDSA_256k1_Big_Sig': '2C', 'ECDSA_256k1_Big_Crt_Sig': '2D', + 'ECDSA_256r1_Big_Sig': '2E', + 'ECDSA_256r1_Big_Crt_Sig': '2F', 'Ed448_Big_Sig': '3A', 'Ed448_Big_Crt_Sig': '3B', } @@ -1580,12 +1803,16 @@ def test_indexer(): assert IdxSigDex.Ed25519_Crt_Sig == 'B' assert IdxSigDex.ECDSA_256k1_Sig == 'C' assert IdxSigDex.ECDSA_256k1_Crt_Sig == 'D' + assert IdxSigDex.ECDSA_256r1_Sig == 'E' + assert IdxSigDex.ECDSA_256r1_Crt_Sig == 'F' assert IdxSigDex.Ed448_Sig == '0A' assert IdxSigDex.Ed448_Crt_Sig == '0B' assert IdxSigDex.Ed25519_Big_Sig == '2A' assert IdxSigDex.Ed25519_Big_Crt_Sig == '2B' assert IdxSigDex.ECDSA_256k1_Big_Sig == '2C' assert IdxSigDex.ECDSA_256k1_Big_Crt_Sig == '2D' + assert IdxSigDex.ECDSA_256r1_Big_Sig == '2E' + assert IdxSigDex.ECDSA_256r1_Big_Crt_Sig == '2F' assert IdxSigDex.Ed448_Big_Sig == '3A' assert IdxSigDex.Ed448_Big_Crt_Sig == '3B' @@ -1593,34 +1820,42 @@ def test_indexer(): assert dataclasses.asdict(IdxCrtSigDex) == { 'Ed25519_Crt_Sig': 'B', 'ECDSA_256k1_Crt_Sig': 'D', + 'ECDSA_256r1_Crt_Sig': 'F', 'Ed448_Crt_Sig': '0B', 'Ed25519_Big_Crt_Sig': '2B', 'ECDSA_256k1_Big_Crt_Sig': '2D', + 'ECDSA_256r1_Big_Crt_Sig': '2F', 'Ed448_Big_Crt_Sig': '3B', } assert IdxCrtSigDex.Ed25519_Crt_Sig == 'B' assert IdxCrtSigDex.ECDSA_256k1_Crt_Sig == 'D' + assert IdxCrtSigDex.ECDSA_256r1_Crt_Sig == 'F' assert IdxCrtSigDex.Ed448_Crt_Sig == '0B' assert IdxCrtSigDex.Ed25519_Big_Crt_Sig == '2B' assert IdxCrtSigDex.ECDSA_256k1_Big_Crt_Sig == '2D' + assert IdxCrtSigDex.ECDSA_256r1_Big_Crt_Sig == '2F' assert IdxCrtSigDex.Ed448_Big_Crt_Sig == '3B' assert dataclasses.asdict(IdxBthSigDex) == { 'Ed25519_Sig': 'A', 'ECDSA_256k1_Sig': 'C', + 'ECDSA_256r1_Sig': 'E', 'Ed448_Sig': '0A', 'Ed25519_Big_Sig': '2A', 'ECDSA_256k1_Big_Sig': '2C', + 'ECDSA_256r1_Big_Sig': '2E', 'Ed448_Big_Sig': '3A', } assert IdxBthSigDex.Ed25519_Sig == 'A' assert IdxBthSigDex.ECDSA_256k1_Sig == 'C' + assert IdxBthSigDex.ECDSA_256r1_Sig == 'E' assert IdxBthSigDex.Ed448_Sig == '0A' assert IdxBthSigDex.Ed25519_Big_Sig == '2A' assert IdxBthSigDex.ECDSA_256k1_Big_Sig == '2C' + assert IdxBthSigDex.ECDSA_256r1_Big_Sig == '2E' assert IdxBthSigDex.Ed448_Big_Sig == '3A' @@ -1641,12 +1876,16 @@ def test_indexer(): 'B': Xizage(hs=1, ss=1, os=0, fs=88, ls=0), 'C': Xizage(hs=1, ss=1, os=0, fs=88, ls=0), 'D': Xizage(hs=1, ss=1, os=0, fs=88, ls=0), + 'E': Xizage(hs=1, ss=1, os=0, fs=88, ls=0), + 'F': Xizage(hs=1, ss=1, os=0, fs=88, ls=0), '0A': Xizage(hs=2, ss=2, os=1, fs=156, ls=0), '0B': Xizage(hs=2, ss=2, os=1, fs=156, ls=0), '2A': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), '2B': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), '2C': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), '2D': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), + '2E': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), + '2F': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), '3A': Xizage(hs=2, ss=6, os=3, fs=160, ls=0), '3B': Xizage(hs=2, ss=6, os=3, fs=160, ls=0), '0z': Xizage(hs=2, ss=2, os=0, fs=None, ls=0), @@ -2572,15 +2811,7 @@ def test_counter(): """ Done Test """ -def test_prodex(): - """ - Test ProtocolGenusCodex - """ - assert dataclasses.asdict(ProDex) == { - 'KERI': '--AAA', - } - """ Done Test """ def test_seqner(): @@ -2757,7 +2988,10 @@ def test_number(): assert number.qb2 == b'0\x00\x00' assert number.num == 0 assert number.numh == '0' + assert number.sn == 0 + assert number.snh == '0' assert not number.positive + assert number.inceptive assert hex(int.from_bytes(number.qb2, 'big')) == '0x300000' # test num as empty string defaults to 0 @@ -2788,12 +3022,17 @@ def test_number(): with pytest.raises(InvalidValueError): number = Number(num=" :") - # test hex number string too long > 32 characters - #with pytest.raises(InvalidValueError): - #number = Number(numh="0"*33) + num = (256 ** 18 - 1) # too big to represent + assert num == 22300745198530623141535718272648361505980415 + numh = f"{num:x}" + assert numh == 'ffffffffffffffffffffffffffffffffffff' + assert len(numh) == 18 * 2 - #with pytest.raises(InvalidValueError): - #number = Number(num="0"*33) + with pytest.raises(InvalidValueError): + number = Number(num=num) + + with pytest.raises(InvalidValueError): + number = Number(numh=numh) num = (256 ** 2 - 1) @@ -3572,7 +3811,7 @@ def test_pather(): assert pather.bext == text assert pather.qb64 == "4AADA-0-field1-0" assert pather.raw == b"\x03\xed>~'\xa5w_\xb4" - with pytest.raises(ValueError): + with pytest.raises(KeyError): pather.resolve(sad) assert pather.path == ["0", "field1", "0"] @@ -3643,6 +3882,99 @@ def test_verfer(): with pytest.raises(ValueError): verfer = Verfer(raw=verkey, code=MtrDex.Blake3_256) + + # secp256r1 + seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + d = int.from_bytes(seed, byteorder="big") + sigkey = ec.derive_private_key(d, ec.SECP256R1()) + verkey = sigkey.public_key().public_bytes(encoding=Encoding.X962, format=PublicFormat.CompressedPoint) + + verfer = Verfer(raw=verkey, code=MtrDex.ECDSA_256r1) + assert verfer.raw == verkey + assert verfer.code == MtrDex.ECDSA_256r1 + + ser = b'abcdefghijklmnopqrstuvwxyz0123456789' + + der = sigkey.sign(ser, ec.ECDSA(hashes.SHA256())) + (r, s) = utils.decode_dss_signature(der) + sig = bytearray(r.to_bytes(32, "big")) + sig.extend(s.to_bytes(32, "big")) + + result = verfer.verify(sig, ser) + assert result == True + + result = verfer.verify(der, b'ABC') + assert result == False + + # secp256r1N + seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + d = int.from_bytes(seed, byteorder="big") + sigkey = ec.derive_private_key(d, ec.SECP256R1()) + verkey = sigkey.public_key().public_bytes(encoding=Encoding.X962, format=PublicFormat.CompressedPoint) + + verferN = Verfer(raw=verkey, code=MtrDex.ECDSA_256r1N) + assert verferN.raw == verkey + assert verferN.code == MtrDex.ECDSA_256r1N + + ser = b'abcdefghijklmnopqrstuvwxyz0123456789' + + der = sigkey.sign(ser, ec.ECDSA(hashes.SHA256())) + (r, s) = utils.decode_dss_signature(der) + sig = bytearray(r.to_bytes(32, "big")) + sig.extend(s.to_bytes(32, "big")) + + result = verferN.verify(sig, ser) + assert result == True + + result = verferN.verify(der, b'ABC') + assert result == False + + # secp256k1 + seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + d = int.from_bytes(seed, byteorder="big") + sigkey = ec.derive_private_key(d, ec.SECP256K1()) + verkey = sigkey.public_key().public_bytes(encoding=Encoding.X962, format=PublicFormat.CompressedPoint) + + verfer = Verfer(raw=verkey, code=MtrDex.ECDSA_256k1) + assert verfer.raw == verkey + assert verfer.code == MtrDex.ECDSA_256k1 + + ser = b'abcdefghijklmnopqrstuvwxyz0123456789' + + der = sigkey.sign(ser, ec.ECDSA(hashes.SHA256())) + (r, s) = utils.decode_dss_signature(der) + sig = bytearray(r.to_bytes(32, "big")) + sig.extend(s.to_bytes(32, "big")) + + result = verfer.verify(sig, ser) + assert result == True + + result = verfer.verify(der, b'ABC') + assert result == False + + # secp256k1N + seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + d = int.from_bytes(seed, byteorder="big") + sigkey = ec.derive_private_key(d, ec.SECP256K1()) + verkey = sigkey.public_key().public_bytes(encoding=Encoding.X962, format=PublicFormat.CompressedPoint) + + verfer = Verfer(raw=verkey, code=MtrDex.ECDSA_256k1N) + assert verfer.raw == verkey + assert verfer.code == MtrDex.ECDSA_256k1N + + ser = b'abcdefghijklmnopqrstuvwxyz0123456789' + + der = sigkey.sign(ser, ec.ECDSA(hashes.SHA256())) + (r, s) = utils.decode_dss_signature(der) + sig = bytearray(r.to_bytes(32, "big")) + sig.extend(s.to_bytes(32, "big")) + + result = verfer.verify(sig, ser) + assert result == True + + result = verfer.verify(der, b'ABC') + assert result == False + """ Done Test """ @@ -3827,14 +4159,208 @@ def test_signer(): with pytest.raises(ValueError): # use invalid code not SEED type code signer = Signer(raw=seed, code=MtrDex.Ed25519N) + # Test Secp256r1, default seed + signer = Signer(code=MtrDex.ECDSA_256r1_Seed) + assert signer.code == MtrDex.ECDSA_256r1_Seed + assert len(signer.raw) == Matter._rawSize(signer.code) + assert signer.verfer.code == MtrDex.ECDSA_256r1 + assert len(signer.verfer.raw) == Matter._rawSize(signer.verfer.code) + cigar = signer.sign(ser) + assert cigar.code == MtrDex.ECDSA_256r1_Sig + assert len(cigar.raw) == Matter._rawSize(cigar.code) + result = signer.verfer.verify(cigar.raw, ser) + assert result is True - # test with only and ondex parameters + # Test non-default seed + seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + signer = Signer(raw=seed, code=MtrDex.ECDSA_256r1_Seed) + assert signer.code == MtrDex.ECDSA_256r1_Seed + assert len(signer.raw) == Matter._rawSize(signer.code) + assert signer.raw == seed + assert signer.verfer.code == MtrDex.ECDSA_256r1 + assert len(signer.verfer.raw) == Matter._rawSize(signer.verfer.code) + # Test hardcoded seed + seed = (b'\x9f{\xa8\xa7\xa8C9\x96&\xfa\xb1\x99\xeb\xaa \xc4\x1bG\x11\xc4\xaeSAR\xc9\xbd\x04\x9d\x85)~\x93') + signer = Signer(raw=seed, code=MtrDex.ECDSA_256r1_Seed) + assert signer.code == MtrDex.ECDSA_256r1_Seed + assert len(signer.raw) == Matter._rawSize(signer.code) + assert signer.raw == seed + assert signer.verfer.code == MtrDex.ECDSA_256r1 + assert len(signer.verfer.raw) == Matter._rawSize(signer.verfer.code) + assert signer.qb64 == "QJ97qKeoQzmWJvqxmeuqIMQbRxHErlNBUsm9BJ2FKX6T" + assert signer.verfer.qb64 == "1AAJA3cK_P2CDlh-_EMFPvyqTPI1POkw-dr14DANx5JEXDCZ" + + # Test vectors from CERSide + seed = (b'\x35\x86\xc9\xa0\x4d\x33\x67\x85\xd5\xe4\x6a\xda\x62\xf0\x54\xc5\xa5\xf4\x32\x3f\x46\xcb\x92\x23\x07' + b'\xe0\xe2\x79\xb7\xe5\xf5\x0a') + verkey = (b"\x03\x16\x99\xbc\xa0\x51\x8f\xa6\x6c\xb3\x5d\x6b\x0a\x92\xf6\x84\x96\x28\x7b\xb6\x64\xe8\xe8\x57\x69" + b"\x15\xb8\xea\x9a\x02\x06\x2a\xff") + sig = (b'\x8c\xfa\xb4\x40\x01\xd2\xab\x4a\xbc\xc5\x96\x8b\xa2\x65\x76\xcd\x51\x9d\x3b\x40\xc3\x35\x21\x73\x9a\x1b' + b'\xe8\x2f\xe1\x30\x28\xe1\x07\x90\x08\xa6\x42\xd7\x3f\x36\x8c\x96\x32\xff\x01\x64\x03\x18\x08\x85\xb8\xa4' + b'\x97\x76\xbe\x9c\xe4\xd7\xc5\xe7\x05\xda\x51\x23') + + signerqb64 = "QDWGyaBNM2eF1eRq2mLwVMWl9DI_RsuSIwfg4nm35fUK" + verferqb64 = "1AAJAxaZvKBRj6Zss11rCpL2hJYoe7Zk6OhXaRW46poCBir_" + cigarqb64 = "0ICM-rRAAdKrSrzFlouiZXbNUZ07QMM1IXOaG-gv4TAo4QeQCKZC1z82jJYy_wFkAxgIhbikl3a-nOTXxecF2lEj" + + ser = b'abc' + signer = Signer(raw=seed, code=MtrDex.ECDSA_256r1_Seed) + cigar = signer.sign(ser) + assert signer.code == MtrDex.ECDSA_256r1_Seed + assert len(signer.raw) == Matter._rawSize(signer.code) + assert signer.raw == seed + assert signer.qb64 == signerqb64 - """ Done Test """ + assert signer.verfer.code == MtrDex.ECDSA_256r1 + assert len(signer.verfer.raw) == Matter._rawSize(signer.verfer.code) + assert signer.verfer.raw == verkey + assert signer.verfer.qb64 == verferqb64 + + assert cigar.code == MtrDex.ECDSA_256r1_Sig + assert len(cigar.raw) == Matter._rawSize(cigar.code) + assert signer.verfer.verify(cigar.raw, ser) + assert signer.verfer.verify(sig, ser) + + cigar = Cigar(raw=sig, code=MtrDex.ECDSA_256r1_Sig) + assert cigar.qb64 == cigarqb64 + + + # Test Secp256k1, default seed + signer = Signer(code=MtrDex.ECDSA_256k1_Seed) + assert signer.code == MtrDex.ECDSA_256k1_Seed + assert len(signer.raw) == Matter._rawSize(signer.code) + assert signer.verfer.code == MtrDex.ECDSA_256k1 + assert len(signer.verfer.raw) == Matter._rawSize(signer.verfer.code) + + # create something to sign and verify + ser = b'abcdefghijklmnopqrstuvwxyz0123456789' + + cigar = signer.sign(ser) + assert cigar.code == MtrDex.ECDSA_256k1_Sig + assert len(cigar.raw) == Matter._rawSize(cigar.code) + result = signer.verfer.verify(cigar.raw, ser) + assert result is True + + index = 0 + siger = signer.sign(ser, index=index) + assert siger.code == IdrDex.ECDSA_256k1_Sig + assert len(siger.raw) == Indexer._rawSize(siger.code) + assert siger.index == index + assert siger.ondex == index + result = signer.verfer.verify(siger.raw, ser) + assert result == True + result = signer.verfer.verify(siger.raw, ser + b'ABCDEFG') + assert result == False + + # Non transferable + signer = Signer(code=MtrDex.ECDSA_256k1_Seed, transferable=False) # ECDSA_256k1N verifier + assert signer.code == MtrDex.ECDSA_256k1_Seed + assert len(signer.raw) == Matter._rawSize(signer.code) + assert signer.verfer.code == MtrDex.ECDSA_256k1N + assert len(signer.verfer.raw) == Matter._rawSize(signer.verfer.code) + + cigar = signer.sign(ser) + assert cigar.code == MtrDex.ECDSA_256k1_Sig + assert len(cigar.raw) == Matter._rawSize(cigar.code) + result = signer.verfer.verify(cigar.raw, ser) + assert result == True + + siger = signer.sign(ser, index=0) + assert siger.code == IdrDex.ECDSA_256k1_Sig + assert len(siger.raw) == Indexer._rawSize(siger.code) + assert siger.index == index + assert siger.ondex == index + result = signer.verfer.verify(siger.raw, ser) + assert result == True + result = signer.verfer.verify(siger.raw, ser + b'ABCDEFG') + assert result == False + + # Test non-default seed + seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + signer = Signer(raw=seed, code=MtrDex.ECDSA_256k1_Seed) + assert signer.code == MtrDex.ECDSA_256k1_Seed + assert len(signer.raw) == Matter._rawSize(signer.code) + assert signer.raw == seed + assert signer.verfer.code == MtrDex.ECDSA_256k1 + assert len(signer.verfer.raw) == Matter._rawSize(signer.verfer.code) + + cigar = signer.sign(ser) + assert cigar.code == MtrDex.ECDSA_256k1_Sig + assert len(cigar.raw) == Matter._rawSize(cigar.code) + result = signer.verfer.verify(cigar.raw, ser) + assert result == True + + index = 1 + siger = signer.sign(ser, index=index) + assert siger.code == IdrDex.ECDSA_256k1_Sig + assert len(siger.raw) == Indexer._rawSize(siger.code) + assert siger.index == index + assert siger.ondex == index + result = signer.verfer.verify(siger.raw, ser) + assert result == True + result = signer.verfer.verify(siger.raw, ser + b'ABCDEFG') + assert result == False + + # different both so Big + ondex = 3 + siger = signer.sign(ser, index=index, ondex=ondex) + assert siger.code == IdrDex.ECDSA_256k1_Big_Sig + assert len(siger.raw) == Indexer._rawSize(siger.code) + assert siger.index == index + assert siger.ondex == ondex + result = signer.verfer.verify(siger.raw, ser) + assert result == True + + # Test hardcoded seed from CERSide + seed = (b'\x9f{\xa8\xa7\xa8C9\x96&\xfa\xb1\x99\xeb\xaa \xc4\x1bG\x11\xc4\xaeSAR\xc9\xbd\x04\x9d\x85)~\x93') + signer = Signer(raw=seed, code=MtrDex.ECDSA_256k1_Seed) + assert signer.code == MtrDex.ECDSA_256k1_Seed + assert len(signer.raw) == Matter._rawSize(signer.code) + assert signer.raw == seed + assert signer.verfer.code == MtrDex.ECDSA_256k1 + assert len(signer.verfer.raw) == Matter._rawSize(signer.verfer.code) + assert signer.qb64 == "JJ97qKeoQzmWJvqxmeuqIMQbRxHErlNBUsm9BJ2FKX6T" + assert signer.verfer.qb64 == "1AABAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk" + + # Test vectors from CERSide + seed = (b'\x7f\x98\x0a\x3b\xe4\x45\xd7\x8c\xc9\x79\xa1\xee\x26\x20\x9c\x17\x71\x16\xab\xa6\xd6\xf1\x6a\x01\xe7\xb3\xce\xfe\xe2\x6c\x06\x08') + verkey = (b"\x02\xdb\x98\x33\x85\xa8\x0e\xbb\x7c\x15\x5d\xdd\xc6\x47\x6a\x24\x07\x9a\x7c\x96\x5f\x05\x0f\x62\xde\x2d\x47\x56\x9b\x54\x29\x16\x79") + sig = (b'\x5f\x80\xc0\x5a\xe4\x71\x32\x5d\xf7\xcb\xdb\x1b\xc2\xf4\x11\xc3\x05\xaf\xf4\xbe\x3b\x7e\xac\x3e\x8c\x15' + b'\x3a\x9f\xa5\x0a\x3d\x69\x75\x45\x93\x34\xc8\x96\x2b\xfe\x79\x8d\xd1\x4e\x9c\x1f\x6c\xa7\xc8\x12\xd6' + b'\x7a\x6c\xc5\x74\x9f\xef\x8d\xa7\x25\xa2\x95\x47\xcc') + + signerqb64 = "JH-YCjvkRdeMyXmh7iYgnBdxFqum1vFqAeezzv7ibAYI" + verferqb64 = "1AABAtuYM4WoDrt8FV3dxkdqJAeafJZfBQ9i3i1HVptUKRZ5" + cigarqb64 = "0CBfgMBa5HEyXffL2xvC9BHDBa_0vjt-rD6MFTqfpQo9aXVFkzTIliv-eY3RTpwfbKfIEtZ6bMV0n--NpyWilUfM" + + ser = b'abc' + signer = Signer(raw=seed, code=MtrDex.ECDSA_256k1_Seed) + cigar = signer.sign(ser) + assert signer.code == MtrDex.ECDSA_256k1_Seed + assert len(signer.raw) == Matter._rawSize(signer.code) + assert signer.raw == seed + assert signer.qb64 == signerqb64 + + assert signer.verfer.code == MtrDex.ECDSA_256k1 + assert len(signer.verfer.raw) == Matter._rawSize(signer.verfer.code) + assert signer.verfer.raw == verkey + assert signer.verfer.qb64 == verferqb64 + + assert cigar.code == MtrDex.ECDSA_256k1_Sig + assert len(cigar.raw) == Matter._rawSize(cigar.code) + assert signer.verfer.verify(cigar.raw, ser) + assert signer.verfer.verify(sig, ser) + + cigar = Cigar(raw=sig, code=MtrDex.ECDSA_256k1_Sig) + assert cigar.qb64 == cigarqb64 + # test with only and ondex parameters + + """ Done Test """ + def test_cipher(): """ Test Cipher subclass of Matter @@ -4122,6 +4648,12 @@ def test_salter(): with pytest.raises(ShortageError): salter = Salter(qb64='') + salter = Salter(raw=raw) + assert salter.stretch(temp=True) == b'\xd4@\xeb\xa6x\x86\xdf\x93\xd6C\xdc\xb8\xa6\x9b\x02\xafh\xc1m(L\xd6\xf6\x86YU>$[\xf9\xef\xc0' + assert salter.stretch(tier=Tiers.low) == b'\xf8e\x80\xbaX\x08\xb9\xba\xc6\x1e\x84\r\x1d\xac\xa7\\\x82Wc@`\x13\xfd\x024t\x8ct\xd3\x01\x19\xe9' + assert salter.stretch(tier=Tiers.med) == b',\xf3\x8c\xbb\xe9)\nSQ\xec\xad\x8c9?\xaf\xb8\xb0\xb3\xcdB\xda\xd8\xb6\xf7\r\xf6D}Z\xb9Y\x16' + assert salter.stretch(tier=Tiers.high) == b'(\xcd\xc4\xb85\xcd\xe8:\xfc\x00\x8b\xfd\xa6\tj.y\x98\x0b\x04\x1c\xe3hBc!I\xe49K\x16-' + """ Done Test """ @@ -4319,11 +4851,37 @@ def test_prefixer(): assert len(prefixer.raw) == Matter._rawSize(prefixer.code) assert len(prefixer.qb64) == Matter.Sizes[prefixer.code].fs - ked = dict(k=[prefixer.qb64], n="", t="icp") + ked = dict(v="", # version string + t="icp", + d="", # qb64 SAID + i="", # qb64 prefix + s="0", # hex string no leading zeros lowercase + kt=1, + k=[prefixer.qb64], # list of qb64 + nt="", + n=[], # hash qual Base64 + bt=0, + b=[], # list of qb64 may be empty + c=[], # list of config ordered mappings may be empty + a=[], # list of seal dicts + ) assert prefixer.verify(ked=ked) == True assert prefixer.verify(ked=ked, prefixed=True) == False - ked = dict(k=[prefixer.qb64], n="ABC", t="icp") + ked = dict(v="", # version string + t="icp", + d="", # qb64 SAID + i="", # qb64 prefix + s="0", # hex string no leading zeros lowercase + kt=1, + k=[prefixer.qb64], # list of qb64 + nt="1", + n=["ABCD"], # hash qual Base64 + bt=0, + b=[], # list of qb64 may be empty + c=[], # list of config ordered mappings may be empty + a=[], # list of seal dicts + ) assert prefixer.verify(ked=ked) == False assert prefixer.verify(ked=ked, prefixed=True) == False @@ -4332,7 +4890,20 @@ def test_prefixer(): assert len(prefixer.raw) == Matter._rawSize(prefixer.code) assert len(prefixer.qb64) == Matter.Sizes[prefixer.code].fs - ked = dict(k=[prefixer.qb64], t="icp") + ked = dict(v="", # version string + t="icp", + d="", # qb64 SAID + i="", # qb64 prefix + s="0", # hex string no leading zeros lowercase + kt=1, + k=[prefixer.qb64], # list of qb64 + nt="1", + n=["ABCD"], # hash qual Base64 + bt=0, + b=[], # list of qb64 may be empty + c=[], # list of config ordered mappings may be empty + a=[], # list of seal dicts + ) assert prefixer.verify(ked=ked) == True assert prefixer.verify(ked=ked, prefixed=True) == False @@ -4343,55 +4914,89 @@ def test_prefixer(): assert prefixer.verify(ked=ked, prefixed=True) == False # Test basic derivation from ked - ked = dict(k=[verfer.qb64], n="", t="icp") + ked = dict(v="", # version string + t="icp", + d="", # qb64 SAID + i="", # qb64 prefix + s="0", # hex string no leading zeros lowercase + kt=1, + k=[verfer.qb64], # list of qb64 + nt="", + n=0, # hash qual Base64 + bt=0, + b=[], # list of qb64 may be empty + c=[], # list of config ordered mappings may be empty + a=[], # list of seal dicts + ) prefixer = Prefixer(ked=ked, code=MtrDex.Ed25519) assert prefixer.qb64 == verfer.qb64 assert prefixer.verify(ked=ked) == True assert prefixer.verify(ked=ked, prefixed=True) == False - ked = dict(k=[verfer.qb64], n="", t="icp") # ked without prefix - with pytest.raises(EmptyMaterialError): # no code and no pre in ked - prefixer = Prefixer(ked=ked) + badked = dict(ked) + del badked["i"] + with pytest.raises(EmptyMaterialError): # no pre + prefixer = Prefixer(ked=badked) - verfer = Verfer(raw=verkey, code=MtrDex.Ed25519) # verfer code not match pre code - ked = dict(k=[verfer.qb64], n="", t="icp", i=preN) - with pytest.raises(DerivationError): - prefixer = Prefixer(ked=ked) + verfer = Verfer(raw=verkey, code=MtrDex.Ed25519) + badked = dict(ked) + badked["k"]=[verfer.qb64] + badked["i"]=preN + with pytest.raises(DerivationError): # verfer code not match pre code + prefixer = Prefixer(ked=badked) verfer = Verfer(raw=verkey, code=MtrDex.Ed25519) - ked = dict(k=[verfer.qb64], n="", t="icp", i=pre) + badked = dict(ked) + badked["k"]=[verfer.qb64] + badked["i"]=pre with pytest.raises(DerivationError): - prefixer = Prefixer(ked=ked, code=MtrDex.Ed25519N) # verfer code not match code + prefixer = Prefixer(ked=badked, code=MtrDex.Ed25519N) # verfer code not match code verfer = Verfer(raw=verkey, code=MtrDex.Ed25519N) - ked = dict(k=[verfer.qb64], n="", t="icp", i=pre) - prefixer = Prefixer(ked=ked, code=MtrDex.Ed25519N) # verfer code match code but not pre code + badked = dict(ked) + badked["k"]=[verfer.qb64] + badked["i"]=pre + prefixer = Prefixer(ked=badked, code=MtrDex.Ed25519N) # verfer code match code but not pre code assert prefixer.qb64 == verfer.qb64 - assert prefixer.verify(ked=ked) == True - assert prefixer.verify(ked=ked, prefixed=True) == False + assert prefixer.verify(ked=badked) == True + assert prefixer.verify(ked=badked, prefixed=True) == False verfer = Verfer(raw=verkey, code=MtrDex.Ed25519N) - ked = dict(k=[verfer.qb64], n="", t="icp", i=preN) - prefixer = Prefixer(ked=ked, code=MtrDex.Ed25519N) # verfer code match code and pre code + badked = dict(ked) + badked["k"]=[verfer.qb64] + badked["i"]=preN + prefixer = Prefixer(ked=badked, code=MtrDex.Ed25519N) # verfer code match code and pre code assert prefixer.qb64 == verfer.qb64 - assert prefixer.verify(ked=ked) == True - assert prefixer.verify(ked=ked, prefixed=True) == True + assert prefixer.verify(ked=badked) == True + assert prefixer.verify(ked=badked, prefixed=True) == True verfer = Verfer(raw=verkey, code=MtrDex.Ed25519N) - ked = dict(k=[verfer.qb64], n="", t="icp", i=preN) - prefixer = Prefixer(ked=ked) # verfer code match pre code + badked = dict(ked) + badked["k"]=[verfer.qb64] + badked["i"]=preN + prefixer = Prefixer(ked=badked) # verfer code match pre code assert prefixer.qb64 == verfer.qb64 - assert prefixer.verify(ked=ked) == True - assert prefixer.verify(ked=ked, prefixed=True) == True + assert prefixer.verify(ked=badked) == True + assert prefixer.verify(ked=badked, prefixed=True) == True verfer = Verfer(raw=verkey, code=MtrDex.Ed25519N) - ked = dict(k=[verfer.qb64], n="", t="icp") - with pytest.raises(EmptyMaterialError): - prefixer = Prefixer(ked=ked) + badked = dict(ked) + badked["k"]=[verfer.qb64] + del badked["i"] + with pytest.raises(EmptyMaterialError): # missing pre + prefixer = Prefixer(ked=badked) - ked = dict(k=[verfer.qb64], n="ABCD", t="icp") - with pytest.raises(DerivationError): - prefixer = Prefixer(ked=ked, code=MtrDex.Ed25519) + verfer = Verfer(raw=verkey, code=MtrDex.Ed25519N) + badked = dict(ked) + badked["k"]=[verfer.qb64] + with pytest.raises(ShortageError): # empty pre + prefixer = Prefixer(ked=badked) + + badked = dict(ked) + badked["k"]=[verfer.qb64] + badked["n"] = "ABCD" + with pytest.raises(DerivationError): # wrong code for transferable + prefixer = Prefixer(ked=badked, code=MtrDex.Ed25519) # Test digest derivation from inception ked vs = versify(version=Version, kind=Serials.json, size=0) @@ -4405,40 +5010,44 @@ def test_prefixer(): cnfg = [] ked = dict(v=vs, # version string + t=ilk, + d="", # SAID i="", # qb64 prefix s="{:x}".format(sn), # hex string no leading zeros lowercase - t=ilk, kt=sith, # hex string no leading zeros lowercase k=keys, # list of qb64 - n=nxt, # hash qual Base64 - wt="{:x}".format(toad), # hex string no leading zeros lowercase - w=wits, # list of qb64 may be empty + nt=0, + n=[], + bt="{:x}".format(toad), # hex string no leading zeros lowercase + b=wits, # list of qb64 may be empty c=cnfg, # list of config ordered mappings may be empty + a=[], # list of seal dicts ) prefixer = Prefixer(ked=ked, code=MtrDex.Blake3_256) - assert prefixer.qb64 == 'ELEjyRTtmfyp4VpTBTkv_b6KONMS1V8-EW-aGJ5P_QMo' - #'EREh4RHCZHUy5nPrY131A4h4RuDAOynRQdQY0PLJybEQ' + assert prefixer.qb64 == 'EEZn82xRQYFjfkPJ5ECrDNHJ6xSt_hjxybbt_WMpinEF' assert prefixer.verify(ked=ked) == True assert prefixer.verify(ked=ked, prefixed=True) == False # test with next digs ndigs = [Diger(ser=nxtfer.qb64b).qb64] ked = dict(v=vs, # version string + t=ilk, + d="", # SAID i="", # qb64 prefix s="{:x}".format(sn), # hex string no leading zeros lowercase - t=ilk, kt=sith, # hex string no leading zeros lowercase k=keys, # list of qb64 + nt=1, n=ndigs, # hash qual Base64 - wt="{:x}".format(toad), # hex string no leading zeros lowercase - w=wits, # list of qb64 may be empty + bt="{:x}".format(toad), # hex string no leading zeros lowercase + b=wits, # list of qb64 may be empty c=cnfg, # list of config ordered mappings may be empty + a=[], # list of seal dicts ) prefixer = Prefixer(ked=ked, code=MtrDex.Blake3_256) - assert prefixer.qb64 == 'EHZUmVPq9cXFvGwWP4ohwA27XlsWHBxxu4xFiXp8UOol' - #'EDve7ZqtIsIMrx6UVZRTcnLgEnYGGV2is_JI_Ps3hEnE' + assert prefixer.qb64 == 'EHB9-i6jOH6DbK_40vlGF0X78Mg__c3MSzu9AE9ZRrsC' assert prefixer.verify(ked=ked) == True assert prefixer.verify(ked=ked, prefixed=True) == False @@ -4465,39 +5074,47 @@ def test_prefixer(): keys = [signers[0].verfer.qb64, signers[1].verfer.qb64, signers[2].verfer.qb64] sith = [["1/2", "1/2", "1"]] ndigs = [Diger(ser=signers[3].verfer.qb64b).qb64] # default limen/sith + ked = dict(v=vs, # version string + t=ilk, + d="", # SAID i="", # qb64 prefix s="{:x}".format(sn), # hex string no leading zeros lowercase - t=ilk, kt=sith, # hex string no leading zeros lowercase k=keys, # list of qb64 + nt=1, n=ndigs, # hash qual Base64 - wt="{:x}".format(toad), # hex string no leading zeros lowercase - w=wits, # list of qb64 may be empty + bt="{:x}".format(toad), # hex string no leading zeros lowercase + b=wits, # list of qb64 may be empty c=cnfg, # list of config ordered mappings may be empty + a=[], # list of seal dicts ) prefixer1 = Prefixer(ked=ked, code=MtrDex.Blake3_256) - assert prefixer1.qb64 == 'EBfPkd-A2CQfJmfpmtc1V-yuleSeCcyWBIrTAygUgQ_T' + assert prefixer1.qb64 == 'EOnpRzJpF1LNdCXl7aQ76BxF7qT94PChM7WGKARhZeKj' assert prefixer1.verify(ked=ked) == True assert prefixer.verify(ked=ked, prefixed=True) == False # now test with different sith but same weights in two clauses sith = [["1/2", "1/2"], ["1"]] + ked = dict(v=vs, # version string + t=ilk, + d="", # SAID i="", # qb64 prefix s="{:x}".format(sn), # hex string no leading zeros lowercase - t=ilk, kt=sith, # hex string no leading zeros lowercase k=keys, # list of qb64 + nt=1, n=ndigs, # hash qual Base64 - wt="{:x}".format(toad), # hex string no leading zeros lowercase - w=wits, # list of qb64 may be empty + bt="{:x}".format(toad), # hex string no leading zeros lowercase + b=wits, # list of qb64 may be empty c=cnfg, # list of config ordered mappings may be empty + a=[], # list of seal dicts ) prefixer2 = Prefixer(ked=ked, code=MtrDex.Blake3_256) - assert prefixer2.qb64 == 'EB0_D51cTh_q6uOQ-byFiv5oNXZ-cxdqCqBAa4JmBLtb' + assert prefixer2.qb64 == 'ECBv9o83MnNYRTdXhwTeR5zgwt8jTr5NIuJ8P00BKySW' assert prefixer2.verify(ked=ked) == True assert prefixer.verify(ked=ked, prefixed=True) == False assert prefixer2.qb64 != prefixer1.qb64 # semantic diff -> syntactic diff @@ -4509,20 +5126,23 @@ def test_prefixer(): d='EB0_D51cTh_q6uOQ-byFiv5oNXZ-cxdqCqBAa4JmBLtb') ked = dict(v=vs, # version string + t=Ilks.dip, + d="", # SAID i="", # qb64 prefix s="{:x}".format(sn), # hex string no leading zeros lowercase - t=Ilks.dip, kt=sith, # hex string no leading zeros lowercase k=keys, # list of qb64 + nt=1, n=ndigs, # hash qual Base64 - wt="{:x}".format(toad), # hex string no leading zeros lowercase - w=wits, # list of qb64 may be empty + bt="{:x}".format(toad), # hex string no leading zeros lowercase + b=wits, # list of qb64 may be empty c=cnfg, # list of config ordered mappings may be empty - da=seal + a=[seal], # list of seal dicts + di='EBfPkd-A2CQfJmfpmtc1V-yuleSeCcyWBIrTAygUgQ_T', ) prefixer = Prefixer(ked=ked, code=MtrDex.Blake3_256) - assert prefixer.qb64 == 'EBabiu_JCkE0GbiglDXNB5C4NQq-hiGgxhHKXBxkiojg' + assert prefixer.qb64 == 'EEGithHj9A85F9hz1fxlF80U7wvpFoAPj6U4q4YWMehp' assert prefixer.verify(ked=ked) == True assert prefixer.verify(ked=ked, prefixed=True) == False @@ -4533,10 +5153,120 @@ def test_prefixer(): prefixer = Prefixer(ked=ked, code=MtrDex.Blake3_256, allows=[MtrDex.Blake3_256, MtrDex.Ed25519]) - assert prefixer.qb64 == 'EBabiu_JCkE0GbiglDXNB5C4NQq-hiGgxhHKXBxkiojg' + assert prefixer.qb64 == 'EEGithHj9A85F9hz1fxlF80U7wvpFoAPj6U4q4YWMehp' + assert prefixer.verify(ked=ked) == True + assert prefixer.verify(ked=ked, prefixed=True) == False + + # Secp256r1 + + preN = '1AAIA-KzxCX8SZSl-fpU3vc3z_MBuH06YShJFuiMdAmo37TM' + # 'BrHLayDN-mXKv62DAjFLX1_Y5yEUe0vA9YPe_ihiKYHE' + pre = '1AAJA-KzxCX8SZSl-fpU3vc3z_MBuH06YShJFuiMdAmo37TM' + + # sigkey = ec.generate_private_key(ec.SECP256R1()) + # verkey = sigkey.public_key().public_bytes(encoding=Encoding.X962, format=PublicFormat.CompressedPoint) + verkey = b'\x03\xe2\xb3\xc4%\xfcI\x94\xa5\xf9\xfaT\xde\xf77\xcf\xf3\x01\xb8}:a(I\x16\xe8\x8ct\t\xa8\xdf\xb4\xcc' + + verfer = Verfer(raw=verkey, code=MtrDex.ECDSA_256r1) + assert verfer.qb64 == '1AAJA-KzxCX8SZSl-fpU3vc3z_MBuH06YShJFuiMdAmo37TM' + + nxtkeyqb64 = [coring.Diger(ser=verfer.qb64b).qb64] # dfault sith is 1 + assert nxtkeyqb64 == ['EPrVv1ppjxrtV48cS9Tm49n5xojMlZfhEzExg6Ye_ORN'] + + prefixer = Prefixer(raw=verkey, code=MtrDex.ECDSA_256r1) # default code is None + assert prefixer.code == MtrDex.ECDSA_256r1 + assert len(prefixer.raw) == Matter._rawSize(prefixer.code) + assert len(prefixer.qb64) == Matter.Sizes[prefixer.code].fs + + ked = dict(v="", # version string + t="icp", + d="", # qb64 SAID + i="", # qb64 prefix + s="0", # hex string no leading zeros lowercase + kt=1, + k=[prefixer.qb64], # list of qb64 + nt="1", + n=["ABCD"], # hash qual Base64 + bt=0, + b=[], # list of qb64 may be empty + c=[], # list of config ordered mappings may be empty + a=[], # list of seal dicts + ) assert prefixer.verify(ked=ked) == True assert prefixer.verify(ked=ked, prefixed=True) == False + verfer = Verfer(raw=verkey, code=MtrDex.ECDSA_256r1) + prefixer = Prefixer(raw=verfer.raw, code=MtrDex.ECDSA_256r1N) + assert prefixer.code == MtrDex.ECDSA_256r1N + assert prefixer.verify(ked=ked) == False + assert prefixer.verify(ked=ked, prefixed=True) == False + + # Test basic derivation from ked + ked = dict(v="", # version string + t="icp", + d="", # qb64 SAID + i="", # qb64 prefix + s="0", # hex string no leading zeros lowercase + kt=1, + k=[verfer.qb64], # list of qb64 + nt="", + n=0, # hash qual Base64 + bt=0, + b=[], # list of qb64 may be empty + c=[], # list of config ordered mappings may be empty + a=[], # list of seal dicts + ) + prefixer = Prefixer(ked=ked, code=MtrDex.ECDSA_256r1) + assert prefixer.qb64 == verfer.qb64 + assert prefixer.verify(ked=ked) == True + assert prefixer.verify(ked=ked, prefixed=True) == False + + badked = dict(ked) + del badked["i"] + with pytest.raises(EmptyMaterialError): # no pre + prefixer = Prefixer(ked=badked) + + verfer = Verfer(raw=verkey, code=MtrDex.ECDSA_256r1) + badked = dict(ked) + badked["k"]=[verfer.qb64] + badked["i"]=preN + with pytest.raises(DerivationError): # verfer code not match pre code + prefixer = Prefixer(ked=badked) + + verfer = Verfer(raw=verkey, code=MtrDex.ECDSA_256r1) + badked = dict(ked) + badked["k"]=[verfer.qb64] + badked["i"]=pre + with pytest.raises(DerivationError): + prefixer = Prefixer(ked=badked, code=MtrDex.ECDSA_256r1N) # verfer code not match code + + verfer = Verfer(raw=verkey, code=MtrDex.ECDSA_256r1N) + badked = dict(ked) + badked["k"]=[verfer.qb64] + badked["i"]=pre + prefixer = Prefixer(ked=badked, code=MtrDex.ECDSA_256r1N) # verfer code match code but not pre code + assert prefixer.qb64 == verfer.qb64 + assert prefixer.verify(ked=badked) == True + assert prefixer.verify(ked=badked, prefixed=True) == False + + verfer = Verfer(raw=verkey, code=MtrDex.ECDSA_256r1N) + badked = dict(ked) + badked["k"]=[verfer.qb64] + badked["i"]=preN + prefixer = Prefixer(ked=badked, code=MtrDex.ECDSA_256r1N) # verfer code match code and pre code + assert prefixer.qb64 == verfer.qb64 + assert prefixer.verify(ked=badked) == True + assert prefixer.verify(ked=badked, prefixed=True) == True + + verfer = Verfer(raw=verkey, code=MtrDex.ECDSA_256r1N) + badked = dict(ked) + badked["k"]=[verfer.qb64] + badked["i"]=preN + prefixer = Prefixer(ked=badked) # verfer code match pre code + assert prefixer.qb64 == verfer.qb64 + assert prefixer.verify(ked=badked) == True + assert prefixer.verify(ked=badked, prefixed=True) == True + """ Done Test """ @@ -4606,7 +5336,7 @@ def test_saider(): code = MtrDex.Blake3_256 kind = Serials.json - label = Ids.dollar + label = Saids.dollar # Test with valid said qb64 said0 = 'EBG9LuUbFzV4OV5cGS9IeQWzy9SuyVFyVrpRc4l1xzPA' @@ -4722,11 +5452,11 @@ def test_saider(): assert saider.verify(sad2, prefixed=True, label=label) # kind default # test with default id field label Ids.d == 'd' and contains 'v' field - label = Ids.d + label = Saids.d code = MtrDex.Blake3_256 # back to default code # Load from vaccuous dict - label = Ids.d + label = Saids.d vs = versify(version=Version, kind=kind, size=0) # vaccuous size == 0 assert vs == 'KERI10JSON000000_' sad4 = dict( @@ -4975,8 +5705,8 @@ def test_versify(): vs = versify(kind=Serials.json, size=0) assert vs == "KERI10JSON000000_" assert len(vs) == VERFULLSIZE - ident, kind, version, size = deversify(vs) - assert ident == Idents.keri + proto, version, kind, size = deversify(vs) + assert proto == Protos.keri assert kind == Serials.json assert version == Version assert size == 0 @@ -4984,17 +5714,17 @@ def test_versify(): vs = versify(kind=Serials.json, size=65) assert vs == "KERI10JSON000041_" assert len(vs) == VERFULLSIZE - ident, kind, version, size = deversify(vs) - assert ident == Idents.keri + proto, version, kind, size = deversify(vs) + assert proto == Protos.keri assert kind == Serials.json assert version == Version assert size == 65 - vs = versify(ident=Idents.acdc, kind=Serials.json, size=86) + vs = versify(proto=Protos.acdc, kind=Serials.json, size=86) assert vs == "ACDC10JSON000056_" assert len(vs) == VERFULLSIZE - ident, kind, version, size = deversify(vs) - assert ident == Idents.acdc + proto, version, kind, size = deversify(vs) + assert proto == Protos.acdc assert kind == Serials.json assert version == Version assert size == 86 @@ -5002,8 +5732,8 @@ def test_versify(): vs = versify(kind=Serials.mgpk, size=0) assert vs == "KERI10MGPK000000_" assert len(vs) == VERFULLSIZE - ident, kind, version, size = deversify(vs) - assert ident == Idents.keri + proto, version, kind, size = deversify(vs) + assert proto == Protos.keri assert kind == Serials.mgpk assert version == Version assert size == 0 @@ -5011,8 +5741,8 @@ def test_versify(): vs = versify(kind=Serials.mgpk, size=65) assert vs == "KERI10MGPK000041_" assert len(vs) == VERFULLSIZE - ident, kind, version, size = deversify(vs) - assert ident == Idents.keri + proto, version, kind, size = deversify(vs) + assert proto == Protos.keri assert kind == Serials.mgpk assert version == Version assert size == 65 @@ -5020,8 +5750,8 @@ def test_versify(): vs = versify(kind=Serials.cbor, size=0) assert vs == "KERI10CBOR000000_" assert len(vs) == VERFULLSIZE - ident, kind, version, size = deversify(vs) - assert ident == Idents.keri + proto, version, kind, size = deversify(vs) + assert proto == Protos.keri assert kind == Serials.cbor assert version == Version assert size == 0 @@ -5029,426 +5759,14 @@ def test_versify(): vs = versify(kind=Serials.cbor, size=65) assert vs == "KERI10CBOR000041_" assert len(vs) == VERFULLSIZE - ident, kind, version, size = deversify(vs) - assert ident == Idents.keri + proto, version, kind, size = deversify(vs) + assert proto == Protos.keri assert kind == Serials.cbor assert version == Version assert size == 65 """End Test""" -def test_serder(): - """ - Test the support functionality for Serder key event serialization deserialization - """ - with pytest.raises(ValueError): - serder = Serder() - - e1 = dict(v=Vstrings.json, - d="", - i="ABCDEFG", - s="0001", - t="rot") - _, e1 = coring.Saider.saidify(sad=e1) - - serder = Serder(ked=e1) - assert serder.ked == e1 - assert serder.kind == Serials.json - assert serder.version == Versionage(major=1, minor=0) - assert serder.said == 'EIM66TjBMfwPnbwK7oZqbZyGz9nOeVmQHeH3NZxrsk8F' - #'EgzrpOMEx_A-dvAruhmptnIbP2c55WZAd4fc1nGuyTwU' - assert serder.saidb == b'EIM66TjBMfwPnbwK7oZqbZyGz9nOeVmQHeH3NZxrsk8F' - assert serder.size == 111 - assert serder.verfers == [] - assert serder.raw == (b'{"v":"KERI10JSON00006f_","d":"EIM66TjBMfwPnbwK7oZqbZyGz9nOeVmQHeH3NZxrsk8F",' - b'"i":"ABCDEFG","s":"0001","t":"rot"}') - assert serder.sn == 1 - assert serder.pre == "ABCDEFG" - assert serder.preb == b"ABCDEFG" - - e1s = json.dumps(e1, separators=(",", ":"), ensure_ascii=False).encode("utf-8") - assert e1s == ((b'{"v":"KERI10JSON00006f_","d":"EIM66TjBMfwPnbwK7oZqbZyGz9nOeVmQHeH3NZxrsk8F",' - b'"i":"ABCDEFG","s":"0001","t":"rot"}')) - vs = versify(kind=Serials.json, size=len(e1s)) # use real length - assert vs == 'KERI10JSON00006f_' - e1["v"] = vs # has real length - pretty = serder.pretty() - assert pretty == ('{\n' - ' "v": "KERI10JSON00006f_",\n' - ' "d": "EIM66TjBMfwPnbwK7oZqbZyGz9nOeVmQHeH3NZxrsk8F",\n' - ' "i": "ABCDEFG",\n' - ' "s": "0001",\n' - ' "t": "rot"\n' - '}') - - e1s = json.dumps(e1, separators=(",", ":"), ensure_ascii=False).encode("utf-8") - with pytest.raises(ShortageError): # test too short - ident1, kind1, vers1, size1 = sniff(e1s[:VERFULLSIZE]) - - ident1, kind1, vers1, size1 = sniff(e1s[:MINSNIFFSIZE]) - assert ident1 == Idents.keri - assert kind1 == Serials.json - assert size1 == 111 - - ident1, kind1, vers1, size1 = sniff(e1s) - assert ident1 == Idents.keri - assert kind1 == Serials.json - assert size1 == 111 - e1ss = e1s + b'extra attached at the end.' - ked1, idnt1, knd1, vrs1, siz1 = serder._inhale(e1ss) - assert ked1 == e1 - assert idnt1 == Idents.keri - assert knd1 == kind1 - assert vrs1 == vers1 - assert siz1 == size1 - - with pytest.raises(ShortageError): # test too short - ked1, knd1, vrs1, siz1 = serder._inhale(e1ss[:size1 - 1]) - - raw1, idnt1, knd1, ked1, ver1 = serder._exhale(ked=e1) - assert raw1 == e1s - assert idnt1 == Idents.keri - assert knd1 == kind1 - assert ked1 == e1 - assert vrs1 == vers1 - - e2 = dict(e1) - e2["v"] = Vstrings.mgpk - e2s = msgpack.dumps(e2) - assert e2s == (b'\x85\xa1v\xb1KERI10MGPK000000_\xa1d\xd9,EIM66TjBMfwPnbwK7oZqbZyGz9nOeVmQHeH' - b'3NZxrsk8F\xa1i\xa7ABCDEFG\xa1s\xa40001\xa1t\xa3rot') - vs = versify(kind=Serials.mgpk, size=len(e2s)) # use real length - assert vs == 'KERI10MGPK00005c_' - e2["v"] = vs # has real length - _, e2 = coring.Saider.saidify(sad=e2) - e2s = msgpack.dumps(e2) - - with pytest.raises(ShortageError): # test too short - ident2, kind2, vers2, size2 = sniff(e2s[:VERFULLSIZE]) - - ident2, kind2, vers2, size2 = sniff(e2s[:MINSNIFFSIZE]) - assert ident2 == Idents.keri - assert kind2 == Serials.mgpk - assert size2 == 92 - - ident2, kind2, vers2, size2 = sniff(e2s) - assert ident1 == Idents.keri - assert kind2 == Serials.mgpk - assert size2 == 92 - e2ss = e2s + b'extra attached at the end.' - ked2, idnt2, knd2, vrs2, siz2 = serder._inhale(e2ss) - assert ked2 == e2 - assert idnt2 == Idents.keri - assert knd2 == kind2 - assert vrs2 == vers2 - assert siz2 == size2 - - with pytest.raises(ShortageError): # test too short - ked2, knd2, vrs2, siz2 = serder._inhale(e2ss[:size2 - 1]) - - raw2, idnt2, knd2, ked2, ver2 = serder._exhale(ked=e2) - assert raw2 == e2s - assert idnt2 == Idents.keri - assert knd2 == kind2 - assert ked2 == e2 - assert vrs2 == vers2 - - e3 = dict(e1) - e3["v"] = Vstrings.cbor - e3s = cbor.dumps(e3) - assert e3s == (b'\xa5avqKERI10CBOR000000_adx,EIM66TjBMfwPnbwK7oZqbZyGz9nOeVmQHeH3NZxrsk8Faig' - b'ABCDEFGasd0001atcrot') - vs = versify(kind=Serials.cbor, size=len(e3s)) # use real length - assert vs == 'KERI10CBOR00005c_' - e3["v"] = vs # has real length - _, e3 = coring.Saider.saidify(sad=e3) - e3s = cbor.dumps(e3) - - with pytest.raises(ShortageError): # test too short - ident3, kind3, vers3, size3 = sniff(e3s[:VERFULLSIZE]) - - ident3, kind3, vers3, size3 = sniff(e3s[:MINSNIFFSIZE]) - assert ident1 == Idents.keri - assert kind3 == Serials.cbor - assert size3 == 92 - - ident3, kind3, vers3, size3 = sniff(e3s) - assert ident3 == Idents.keri - assert kind3 == Serials.cbor - assert size3 == 92 - e3ss = e3s + b'extra attached at the end.' - ked3, idnt3, knd3, vrs3, siz3 = serder._inhale(e3ss) - assert ked3 == e3 - assert idnt3 == Idents.keri - assert knd3 == kind3 - assert vrs3 == vers3 - assert siz3 == size3 - - with pytest.raises(ShortageError): # test too short - ked3, knd3, vrs3, siz3 = serder._inhale(e3ss[:size3 - 1]) - - raw3, idnt3, knd3, ked3, ver3 = serder._exhale(ked=e3) - assert raw3 == e3s - assert idnt3 == Idents.keri - assert knd3 == kind3 - assert ked3 == e3 - assert vrs3 == vers3 - - e4 = dict(v=versify(ident=Idents.acdc, kind=Serials.json, size=0), - d="", - i="ABCDEFG", - s="0001", - t="rot") - _, e4 = coring.Saider.saidify(sad=e4) - print() - print(e4) - e4s = json.dumps(e4, separators=(",", ":"), ensure_ascii=False).encode("utf-8") - assert e4s == (b'{"v":"ACDC10JSON00006f_","d":"EMFw6MEBmwWU28-7wK4SJ2kasSzVgLKkAM7iwoqJJ07Z",' - b'"i":"ABCDEFG","s":"0001","t":"rot"}') - vs = versify(ident=Idents.acdc, kind=Serials.json, size=len(e4s)) # use real length - assert vs == 'ACDC10JSON00006f_' - e4["v"] = vs # has real length - serder = Sadder(ked=e4) - pretty = serder.pretty() - assert pretty == ('{\n' - ' "v": "ACDC10JSON00006f_",\n' - ' "d": "EMFw6MEBmwWU28-7wK4SJ2kasSzVgLKkAM7iwoqJJ07Z",\n' - ' "i": "ABCDEFG",\n' - ' "s": "0001",\n' - ' "t": "rot"\n' - '}') - - e4s = json.dumps(e4, separators=(",", ":"), ensure_ascii=False).encode("utf-8") - with pytest.raises(ShortageError): # test too short - ident4, kind4, vers4, size4 = sniff(e4s[:VERFULLSIZE]) - - ident4, kind4, vers4, size4 = sniff(e4s[:MINSNIFFSIZE]) - assert ident4 == Idents.acdc - assert kind4 == Serials.json - assert size4 == 111 - - ident4, kind4, vers4, size4 = sniff(e4s) - assert ident4 == Idents.acdc - assert kind4 == Serials.json - assert size4 == 111 - - evt1 = Serder(raw=e1ss) - assert evt1.kind == kind1 - assert evt1.raw == e1s - assert evt1.ked == ked1 - assert evt1.size == size1 - assert evt1.raw == e1ss[:size1] - assert evt1.version == vers1 - assert evt1.sn == 1 - - # test digest properties .diger and .dig - assert evt1.saider.qb64 == evt1.said - assert evt1.saider.code == MtrDex.Blake3_256 - assert len(evt1.saider.raw) == 32 - assert len(evt1.said) == 44 - assert len(evt1.said) == Matter.Sizes[MtrDex.Blake3_256].fs - assert evt1.said == 'EIM66TjBMfwPnbwK7oZqbZyGz9nOeVmQHeH3NZxrsk8F' - assert evt1.saider.verify(evt1.ked) - - evt1 = Serder(ked=ked1) - assert evt1.kind == kind1 - assert evt1.raw == e1s - assert evt1.ked == ked1 - assert evt1.size == size1 - assert evt1.raw == e1ss[:size1] - assert evt1.version == vers1 - assert evt1.saider.code == MtrDex.Blake3_256 - - evt2 = Serder(raw=e2ss) - assert evt2.kind == kind2 - assert evt2.raw == e2s - assert evt2.ked == ked2 - assert evt2.version == vers2 - - evt2 = Serder(ked=ked2) - assert evt2.kind == kind2 - assert evt2.raw == e2s - assert evt2.ked == ked2 - assert evt2.size == size2 - assert evt2.raw == e2ss[:size2] - assert evt2.version == vers2 - - evt3 = Serder(raw=e3ss) - assert evt3.kind == kind3 - assert evt3.raw == e3s - assert evt3.ked == ked3 - assert evt3.version == vers3 - - evt3 = Serder(ked=ked3) - assert evt3.kind == kind3 - assert evt3.raw == e3s - assert evt3.ked == ked3 - assert evt3.size == size3 - assert evt3.raw == e3ss[:size3] - assert evt3.version == vers3 - - # round trip - evt2 = Serder(ked=evt1.ked) - assert evt2.kind == evt1.kind - assert evt2.raw == evt1.raw - assert evt2.ked == evt1.ked - assert evt2.size == evt1.size - assert evt2.version == vers2 - - # Test change in kind by Serder - ked1["v"] = Vstrings.mgpk - _, ked1 = coring.Saider.saidify(sad=ked1) - evt1 = Serder(ked=ked1, kind=Serials.mgpk) # ked is json but kind mgpk - assert evt1.kind == kind2 - assert evt1.raw == e2s - assert evt1.ked == ked2 - assert evt1.size == size2 - assert evt1.raw == e2ss[:size2] - assert evt1.version == vers1 - assert evt1.said == 'EMvr339cs3EH-WcXfLDGOi-rRjNQxK46PqvBcgdAgg8p' - assert evt1.saider.verify(evt1.ked) - - # round trip - evt2 = Serder(raw=evt1.raw) - assert evt2.kind == evt1.kind - assert evt2.raw == evt1.raw - assert evt2.ked == evt1.ked - assert evt2.size == evt1.size - assert evt2.version == vers2 - - ked1["v"] = Vstrings.cbor - _, ked1 = coring.Saider.saidify(sad=ked1) - evt1 = Serder(ked=ked1, kind=Serials.cbor) # ked is json but kind mgpk - assert evt1.kind == kind3 - assert evt1.raw == e3s - assert evt1.ked == ked3 - assert evt1.size == size3 - assert evt1.raw == e3ss[:size3] - assert evt1.version == vers1 - - # round trip - evt2 = Serder(raw=evt1.raw) - assert evt2.kind == evt1.kind - assert evt2.raw == evt1.raw - assert evt2.ked == evt1.ked - assert evt2.size == evt1.size - assert evt2.version == vers2 - - # use kind setter property - assert evt2.kind == Serials.cbor - evt2.kind = Serials.json - assert evt2.kind == Serials.json - ident, knd, version, size = deversify(evt2.ked["v"]) - assert ident == Idents.keri - assert knd == Serials.json - - # Test diger code - ked = {'v': "KERI10JSON00006a_", - 'd': 'HAg9_-rPd8oga-oyPghCEIlJZHKbYXcP86LQl0Yg2AvA', - 'i': 'ABCDEFG', 's': 1, - 't': 'rot'} - raw = ( - b'{"v":"KERI10JSON00006a_","d":"HAg9_-rPd8oga-oyPghCEIlJZHKbYXcP86LQl0Yg2AvA","i":"ABCDEFG","s":1,"t":"rot"}') - srdr = Serder(raw=raw, code=MtrDex.SHA3_256) - assert srdr.kind == 'JSON' - assert srdr.raw == raw - assert srdr.ked == ked - assert srdr.saider.code == MtrDex.SHA3_256 - - # Test compare - ked = {'v': "KERI10JSON00006a_", - 'd': 'EADZ055vgh5utgSY3OOL1lW0m1pJ1W0Ia6-SVuGa0OqE', - 'i': 'ABCDEFG', 's': 1, - 't': 'rot'} - raw = ( - b'{"v":"KERI10JSON00006a_","d":"EADZ055vgh5utgSY3OOL1lW0m1pJ1W0Ia6-SVuGa0OqE","i":"ABCDEFG","s":1,"t":"rot"}') - srdr = Serder(raw=raw) - assert srdr.kind == 'JSON' - assert srdr.raw == raw - assert srdr.ked == ked - assert srdr.saider.code == MtrDex.Blake3_256 - - # need tests will fully populated serder for icp rot dip drt - #aids = generatePublics(salt=None, count=3, transferable=False) - aids = ['BEy_EvE8OUMqj0AgCJ3wOCOrIVHVtwubYAysPyaAv9VI', - 'BC9Df6ssUZQFQZJYVUyfudw4WTQsugGcvVD_Z4ChFGE4', - 'BEejlxZytU7gjUwtgkmNKmBWiFPKSsXjk_uxzoun8dtK'] - - - pre0 = aids[0] - wit0 = aids[1] - wit1 = aids[2] - srdr = eventing.incept(keys=[pre0], wits=[wit0, wit1]) - assert srdr.raw == (b'{"v":"KERI10JSON00015a_","t":"icp","d":"EBAjyPZ8Ed4XXl5cVZhqAy7SuaGivQp0WqQK' - b'VXvg7oqd","i":"BEy_EvE8OUMqj0AgCJ3wOCOrIVHVtwubYAysPyaAv9VI","s":"0","kt":"1' - b'","k":["BEy_EvE8OUMqj0AgCJ3wOCOrIVHVtwubYAysPyaAv9VI"],"nt":"0","n":[],"bt":' - b'"2","b":["BC9Df6ssUZQFQZJYVUyfudw4WTQsugGcvVD_Z4ChFGE4","BEejlxZytU7gjUwtgkm' - b'NKmBWiFPKSsXjk_uxzoun8dtK"],"c":[],"a":[]}') - # test for serder.verfers and serder.werfers - assert srdr.pre == pre0 - assert srdr.sn == 0 - assert [verfer.qb64 for verfer in srdr.verfers] == [pre0] - assert [werfer.qb64 for werfer in srdr.werfers] == [wit0, wit1] - - # test .said and .saidb properties - ked = { - "v": "KERI10JSON00011c_", - "t": "rep", - "d": "EBAjyPZ8Ed4XXl5cVZhqAy7SuaGivQp0WqQKVXvg7oqd", - "dt": "2020-08-22T17:50:12.988921+00:00", - "r": "logs/processor", - "a": - { - "d": "EBAjyPZ8Ed4XXl5cVZhqAy7SuaGivQp0WqQKVXvg7oqd", - "i": "BEy_EvE8OUMqj0AgCJ3wOCOrIVHVtwubYAysPyaAv9VI", - "name": "John Jones", - "role": "Founder", - } - } - srdr = Serder(ked=ked) - assert srdr.said == 'EBAjyPZ8Ed4XXl5cVZhqAy7SuaGivQp0WqQKVXvg7oqd' - assert srdr.saidb == b'EBAjyPZ8Ed4XXl5cVZhqAy7SuaGivQp0WqQKVXvg7oqd' - - # test tholder - ked = dict(v="KERI10JSON000000_", # version string - t="icp", - d="", - i="BEy_EvE8OUMqj0AgCJ3wOCOrIVHVtwubYAysPyaAv9VI", # qb64 prefix - s="0", # hex string no leading zeros lowercase - kt="1", # hex string no leading zeros lowercase - k=["BC9Df6ssUZQFQZJYVUyfudw4WTQsugGcvVD_Z4ChFGE4"], # list of qb64 - n="", # hash qual Base64 - bt="0", # hex string no leading zeros lowercase - b=[], # list of qb64 may be empty - c=[], # list of config ordered mappings may be empty - a=[], # list of seal dicts - ) - _, ked = coring.Saider.saidify(sad=ked) - - srdr = Serder(ked=ked) - assert srdr.tholder.sith == "1" - assert srdr.tholder.thold == 1 - assert srdr.sn == 0 - assert srdr.sner.num == srdr.sn - - # test validation in Serder.sn property - ked["s"] = "-1" - srdr = Serder(ked=ked) - with pytest.raises(InvalidValueError): - sn = srdr.sn - - #ked["s"] = "0" * 33 - #srdr = Serder(ked=ked) - #with pytest.raises(InvalidValueError): - #sn = srdr.sn - - ked["s"] = "15.34" - srdr = Serder(ked=ked) - with pytest.raises(InvalidValueError): - sn = srdr.sn - - """Done Test """ - def test_tholder(): """ @@ -5818,106 +6136,6 @@ def test_tholder(): assert not tholder.satisfy(indices=[]) - - #raw = b"raw salt to test" - - ## create signers with verfers for keys - #signers = coring.Salter(raw=raw).signers(count=3, path="next", temp=True) - - #keys = [signer.verfer.qb64 for signer in signers] - #assert keys == ['DKX2UxU85IcgiGdhfAQUfd2kYyVVf6CLUp7ejNBlCYyC', - #'DDo75eoTr0yuYsgEwf5PGAZ7z9dsDb7jjt0ymdNGMKIy', - #'DBnsqw0gaUXMBqFs_4A3wUjnOyiVEMCrY5tWwvRj-wwl'] - - #digers = [Diger(ser=signer.verfer.qb64b) for signer in signers] - #digs = [diger.qb64 for diger in digers] - #assert digs == ['EAfMsW8tCq-tdsBufV9kqgqvfuKVWNdf9mSpIXQ1Vjdf', - #'EA76Pjxa03Bm62TjwO07C3_EVViO4Bgn5SLSr7FedoEG', - #'EBnncARb7X0yWLOTBW9X387vakzaiAwF6DCFYdiIDob2'] - - #assert tholder.includes(keys, digs) - - #bdigs = list(digs) - #del bdigs[0] - #assert not tholder.includes(keys, bdigs) - - #bkeys = list(keys) - #del bkeys[1] - #assert tholder.includes(bkeys, digs) - - #bkeys.append(keys[1]) - #assert not tholder.includes(bkeys, digs) - - #signer = Signer() - ## create something to sign and verify - #ser = b'abcdefghijklmnopqrstuvwxyz0123456789' - #index = 0 - #siger = signer.sign(ser, index=index) - #digs = [Diger(ser=siger.verfer.qb64b).qb64] - #sigers = [siger] - - #assert tholder.matches(sigers, digs) == [0] - - #raw = b"raw salt to test" - ## create signers with verfers - #signers = coring.Salter(raw=raw).signers(count=3, path="next", temp=True) - - ## create something to sign - #ser = b'abcdefghijklmnopqrstuvwxyz0123456789' - - ## test different index and ondex - #sigers = [] - #digers = [] - #for i, signer in enumerate(signers): - #o = len(signers) - 1 - i - #siger = signer.sign(ser=ser, index=i, ondex=o) - #diger = Diger(ser=siger.verfer.qb64b) - #sigers.append(siger) - #digers.append(diger) - - #digers.reverse() - - #ondices = tholder.exposeds(digers=digers, sigers=sigers) - #assert ondices ==[2, 1, 0] - - ## test partial mix - #siger0 = signers[0].sign(ser=ser, index=0) # both same - #assert siger0.code == IdxSigDex.Ed25519_Sig # both same - #diger0 = Diger(ser=siger0.verfer.qb64b) - - #siger1 = signers[1].sign(ser=ser, index=1, only=True) # current only - #assert siger1.code == IdxSigDex.Ed25519_Crt_Sig # current only - - #siger2 = signers[2].sign(ser=ser, index=2, ondex=1) # both different - #assert siger2.code == IdxSigDex.Ed25519_Big_Sig # both different - #diger1 = Diger(ser=siger2.verfer.qb64b) - - #sigers = [siger0, siger1, siger2] - #digers = [diger0, diger1] - - #ondices = tholder.exposeds(digers=digers, sigers=sigers) - #assert ondices ==[0, 1] - - - ## test Bad digest - #siger0 = signers[0].sign(ser=ser, index=0) # both same - #assert siger0.code == IdxSigDex.Ed25519_Sig # both same - #diger0 = Diger(ser=b"Bad Digest") # bad digest - - #siger1 = signers[1].sign(ser=ser, index=1, only=True) # current only - #assert siger1.code == IdxSigDex.Ed25519_Crt_Sig # current only - - #siger2 = signers[2].sign(ser=ser, index=2, ondex=1) # both different - #assert siger2.code == IdxSigDex.Ed25519_Big_Sig # both different - #diger1 = Diger(ser=siger2.verfer.qb64b, code=DigDex.Blake2b_256) - - #sigers = [siger0, siger1, siger2] - #digers = [diger0, diger1] - - #ondices = tholder.exposeds(digers=digers, sigers=sigers) - #assert ondices ==[1] - - """ Done Test """ @@ -5930,4 +6148,10 @@ def test_tholder(): #test_siger() #test_signer() #test_nexter() - test_tholder() + #test_tholder() + #test_ilks() + #test_labels() + #test_prefixer() + #test_genera() + test_prodex() + diff --git a/tests/core/test_crypto.py b/tests/core/test_crypto.py index 1350e0ee7..65d92d05f 100644 --- a/tests/core/test_crypto.py +++ b/tests/core/test_crypto.py @@ -12,6 +12,11 @@ from base64 import urlsafe_b64encode as encodeB64 from base64 import urlsafe_b64decode as decodeB64 +from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat +from cryptography.hazmat.primitives.asymmetric import ec, utils +from cryptography.hazmat.primitives import hashes +from cryptography import utils as cryptographyUtils +from cryptography import exceptions def test_pysodium(): @@ -46,9 +51,9 @@ def test_pysodium(): sigseed = pysodium.crypto_pwhash(outlen=32, passwd="", salt=salt, - opslimit=pysodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, - memlimit=pysodium.crypto_pwhash_MEMLIMIT_INTERACTIVE, - alg=pysodium.crypto_pwhash_ALG_DEFAULT) + opslimit=2, # pysodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, + memlimit=67108864, # pysodium.crypto_pwhash_MEMLIMIT_INTERACTIVE, + alg=pysodium.crypto_pwhash_ALG_ARGON2ID13) assert len(sigseed) == 32 # seed = (b'\xa9p\x89\x7f+\x0e\xc4\x9c\xf2\x01r\xafTI\xc0\xfa\xac\xd5\x99\xf8O\x8f=\x843\xa2\xb6e\x9fO\xff\xd0') @@ -471,5 +476,124 @@ def test_sha3(): """ +def test_secp256r1(): + """ + test secp256r1 + + https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/ + """ + + # create keypair without seed + private_key = ec.generate_private_key(ec.SECP256R1()) + assert isinstance(private_key.curve, ec.SECP256R1) + assert private_key.key_size == 256 # for the secp256r1 curve, the private key is 256-bit integer (32 bytes) + + public_key = private_key.public_key() + assert isinstance(public_key.curve, ec.SECP256R1) + assert public_key.key_size == 256 + + ser = b'abcdefghijklmnopqrstuvwxyz0123456789' + signature = private_key.sign(ser, ec.ECDSA(hashes.SHA256())) + try: + public_key.verify(signature, ser, ec.ECDSA(hashes.SHA256())) + except Exception as exc: + assert False, f"signature verification, raised an exception {exc}" + + # compress public key to bytes + verkey = public_key.public_bytes(encoding=Encoding.X962, format=PublicFormat.CompressedPoint) # compressed public key is 257-bit integer (~ 33 bytes) + assert len(verkey) == 33 + + # convert back to public key and verify + public_key = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), verkey) + try: + public_key.verify(signature, ser, ec.ECDSA(hashes.SHA256())) + except Exception as exc: + assert False, f"signature verification, raised an exception {exc}" + + # decode Ecdsa-Sig-Value signature to tuple (r, s) + # https://cryptography.io/en/latest/hazmat/primitives/asymmetric/utils/#cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature + (r, s) = utils.decode_dss_signature(signature) + sig = bytearray(r.to_bytes(32, "big")) + sig.extend(s.to_bytes(32, "big")) + + # encode signature to encoded Ecdsa-Sig-Value from raw r and s values + # https://cryptography.io/en/latest/hazmat/primitives/asymmetric/utils/#cryptography.hazmat.primitives.asymmetric.utils.encode_dss_signature + r = int.from_bytes(sig[:32], "big") + s = int.from_bytes(sig[32:], "big") + der = utils.encode_dss_signature(r, s) + # verify der + try: + public_key.verify(der, ser, ec.ECDSA(hashes.SHA256())) + except Exception as exc: + assert False, f"signature verification, raised an exception {exc}" + + with pytest.raises(exceptions.InvalidSignature): + public_key.verify(b'XYZ', ser, ec.ECDSA(hashes.SHA256())) + + """ + Done Test + """ + + +def test_secp256k1(): + """ + test secp256k1 + + https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/ + """ + + # create keypair without seed + private_key = ec.generate_private_key(ec.SECP256K1()) + assert isinstance(private_key.curve, ec.SECP256K1) + assert private_key.key_size == 256 # for the secp256k1 curve, the private key is 256-bit integer (32 bytes) + + public_key = private_key.public_key() + assert isinstance(public_key.curve, ec.SECP256K1) + assert public_key.key_size == 256 + + ser = b'abcdefghijklmnopqrstuvwxyz0123456789' + signature = private_key.sign(ser, ec.ECDSA(hashes.SHA256())) + try: + public_key.verify(signature, ser, ec.ECDSA(hashes.SHA256())) + except Exception as exc: + assert False, f"signature verification, raised an exception {exc}" + + # compress public key to bytes + verkey = public_key.public_bytes(encoding=Encoding.X962, format=PublicFormat.CompressedPoint) # compressed public key is 257-bit integer (~ 33 bytes) + assert len(verkey) == 33 + + # convert back to public key and verify + public_key = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), verkey) + try: + public_key.verify(signature, ser, ec.ECDSA(hashes.SHA256())) + except Exception as exc: + assert False, f"signature verification, raised an exception {exc}" + + # decode Ecdsa-Sig-Value signature to tuple (r, s) + # https://cryptography.io/en/latest/hazmat/primitives/asymmetric/utils/#cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature + (r, s) = utils.decode_dss_signature(signature) + sig = bytearray(r.to_bytes(32, "big")) + sig.extend(s.to_bytes(32, "big")) + + # encode signature to encoded Ecdsa-Sig-Value from raw r and s values + # https://cryptography.io/en/latest/hazmat/primitives/asymmetric/utils/#cryptography.hazmat.primitives.asymmetric.utils.encode_dss_signature + r = int.from_bytes(sig[:32], "big") + s = int.from_bytes(sig[32:], "big") + der = utils.encode_dss_signature(r, s) + # verify der + try: + public_key.verify(der, ser, ec.ECDSA(hashes.SHA256())) + except Exception as exc: + assert False, f"signature verification, raised an exception {exc}" + + with pytest.raises(exceptions.InvalidSignature): + public_key.verify(b'XYZ', ser, ec.ECDSA(hashes.SHA256())) + + """ + Done Test + """ + + + if __name__ == "__main__": test_blake3() diff --git a/tests/core/test_delegating.py b/tests/core/test_delegating.py index e89d2fdfe..deff595ad 100644 --- a/tests/core/test_delegating.py +++ b/tests/core/test_delegating.py @@ -15,7 +15,7 @@ def test_delegation(): """ - Test creation and validation of delegated identifer prefixes and events + Test creation and validation of delegated identifier prefixes and events """ # bob is the delegator del is bob's delegate @@ -68,8 +68,8 @@ def test_delegation(): # bobKvy.process(ims=bytearray(msg)) # process local copy of msg bobK = bobKvy.kevers[bob] assert bobK.prefixer.qb64 == bob - assert bobK.serder.saider.qb64 == bobSrdr.said - assert bobK.serder.saider.qb64 == 'EA_SbBUZYwqLVlAAn14d6QUBQCSReJlZ755JqTgmRhXH' + assert bobK.serder.said == bobSrdr.said + assert bobK.serder.said == 'EA_SbBUZYwqLVlAAn14d6QUBQCSReJlZ755JqTgmRhXH' # apply msg to del's Kevery parsing.Parser().parse(ims=bytearray(msg), kvy=delKvy) @@ -94,7 +94,7 @@ def test_delegation(): s=delSrdr.ked["s"], d=delSrdr.said) bobSrdr = eventing.interact(pre=bobK.prefixer.qb64, - dig=bobK.serder.saider.qb64, + dig=bobK.serder.said, sn=bobK.sn + 1, data=[seal._asdict()]) @@ -119,12 +119,12 @@ def test_delegation(): # apply msg to bob's Kevery parsing.Parser().parse(ims=bytearray(msg), kvy=bobKvy) # bobKvy.process(ims=bytearray(msg)) # process local copy of msg - assert bobK.serder.saider.qb64 == bobSrdr.said # key state updated so event was validated + assert bobK.serder.said == bobSrdr.said # key state updated so event was validated # apply msg to del's Kevery parsing.Parser().parse(ims=bytearray(msg), kvy=delKvy) # delKvy.process(ims=bytearray(msg)) # process remote copy of msg - assert delKvy.kevers[bob].serder.saider.qb64 == bobSrdr.said + assert delKvy.kevers[bob].serder.said == bobSrdr.said # now create msg with Del's delegated inception event sigers = delMgr.sign(ser=delSrdr.raw, verfers=verfers) @@ -140,7 +140,7 @@ def test_delegation(): msg.extend(counter.qb64b) seqner = coring.Seqner(sn=bobK.sn) msg.extend(seqner.qb64b) - msg.extend(bobSrdr.saider.qb64b) + msg.extend(bobSrdr.saidb) assert msg == (b'{"v":"KERI10JSON00015f_","t":"dip","d":"EHng2fV42DdKb5TLMIs6bbjF' b'kPNmIdQ5mSFn6BTnySJj","i":"EHng2fV42DdKb5TLMIs6bbjFkPNmIdQ5mSFn6' @@ -159,9 +159,9 @@ def test_delegation(): assert delPre in delKvy.kevers delK = delKvy.kevers[delPre] assert delK.delegated - assert delK.serder.saider.qb64 == delSrdr.said + assert delK.serder.said == delSrdr.said couple = delKvy.db.getAes(dbing.dgKey(delPre, delSrdr.said)) - assert couple == seqner.qb64b + bobSrdr.saider.qb64b + assert couple == seqner.qb64b + bobSrdr.saidb # apply Del's delegated inception event message to bob's Kevery parsing.Parser().parse(ims=bytearray(msg), kvy=bobKvy) @@ -169,16 +169,16 @@ def test_delegation(): assert delPre in bobKvy.kevers # successfully validated bobDelK = bobKvy.kevers[delPre] assert bobDelK.delegated - assert bobDelK.serder.saider.qb64 == delSrdr.said # key state updated so event was validated + assert bobDelK.serder.said == delSrdr.said # key state updated so event was validated couple = bobKvy.db.getAes(dbing.dgKey(delPre, delSrdr.said)) - assert couple == seqner.qb64b + bobSrdr.saider.qb64b + assert couple == seqner.qb64b + bobSrdr.saidb # Setup Del rotation event assuming that Bob's next event will be an ixn delegating event verfers, digers = delMgr.rotate(pre=delPre, temp=True) delSrdr = eventing.deltate(pre=bobDelK.prefixer.qb64, keys=[verfer.qb64 for verfer in verfers], - dig=bobDelK.serder.saider.qb64, + dig=bobDelK.serder.said, sn=bobDelK.sn + 1, ndigs=[diger.qb64 for diger in digers]) @@ -189,7 +189,7 @@ def test_delegation(): s=delSrdr.ked["s"], d=delSrdr.said) bobSrdr = eventing.interact(pre=bobK.prefixer.qb64, - dig=bobK.serder.saider.qb64, + dig=bobK.serder.said, sn=bobK.sn + 1, data=[seal._asdict()]) @@ -213,12 +213,12 @@ def test_delegation(): # apply msg to bob's Kevery parsing.Parser().parse(ims=bytearray(msg), kvy=bobKvy) # bobKvy.process(ims=bytearray(msg)) # process local copy of msg - assert bobK.serder.saider.qb64 == bobSrdr.said # key state updated so event was validated + assert bobK.serder.said == bobSrdr.said # key state updated so event was validated # apply msg to del's Kevery parsing.Parser().parse(ims=bytearray(msg), kvy=delKvy) # delKvy.process(ims=bytearray(msg)) # process remote copy of msg - assert delKvy.kevers[bob].serder.saider.qb64 == bobSrdr.said + assert delKvy.kevers[bob].serder.said == bobSrdr.said # now create msg from Del's delegated rotation event sigers = delMgr.sign(ser=delSrdr.raw, verfers=verfers) @@ -234,7 +234,7 @@ def test_delegation(): msg.extend(counter.qb64b) seqner = coring.Seqner(sn=bobK.sn) msg.extend(seqner.qb64b) - msg.extend(bobSrdr.saider.qb64b) + msg.extend(bobSrdr.saidb) assert msg ==(b'{"v":"KERI10JSON000160_","t":"drt","d":"EM5fj7YtOQYH3iLyWJr6HZVV' b'xrY5t46LRL2vkNpdnPi0","i":"EHng2fV42DdKb5TLMIs6bbjFkPNmIdQ5mSFn6' @@ -250,17 +250,17 @@ def test_delegation(): parsing.Parser().parse(ims=bytearray(msg), kvy=delKvy) # delKvy.process(ims=bytearray(msg)) # process remote copy of msg assert bobDelK.delegated - assert delK.serder.saider.qb64 == delSrdr.said + assert delK.serder.said == delSrdr.said couple = delKvy.db.getAes(dbing.dgKey(delPre, delSrdr.said)) - assert couple == seqner.qb64b + bobSrdr.saider.qb64b + assert couple == seqner.qb64b + bobSrdr.saidb # apply Del's delegated inception event message to bob's Kevery parsing.Parser().parse(ims=bytearray(msg), kvy=bobKvy) # bobKvy.process(ims=bytearray(msg)) # process local copy of msg assert bobDelK.delegated - assert bobDelK.serder.saider.qb64 == delSrdr.said # key state updated so event was validated + assert bobDelK.serder.said == delSrdr.said # key state updated so event was validated couple = delKvy.db.getAes(dbing.dgKey(delPre, delSrdr.said)) - assert couple == seqner.qb64b + bobSrdr.saider.qb64b + assert couple == seqner.qb64b + bobSrdr.saidb # test replay msgs = bytearray() diff --git a/tests/core/test_escrow.py b/tests/core/test_escrow.py index fad432da3..cac27e311 100644 --- a/tests/core/test_escrow.py +++ b/tests/core/test_escrow.py @@ -137,7 +137,7 @@ def test_partial_signed_escrow(): # create interaction event for srdr = eventing.interact(pre=kvr.prefixer.qb64, - dig=kvr.serder.saider.qb64, + dig=kvr.serder.said, sn=kvr.sn+1, data=[]) @@ -257,7 +257,7 @@ def test_partial_signed_escrow(): srdr = eventing.rotate(pre=kvr.prefixer.qb64, keys=[verfer.qb64 for verfer in verfers], isith=sith, - dig=kvr.serder.saider.qb64, + dig=kvr.serder.said, nsith=nxtsith, ndigs=[diger.qb64 for diger in digers], sn=kvr.sn+1, @@ -287,7 +287,7 @@ def test_partial_signed_escrow(): srdr = eventing.rotate(pre=kvr.prefixer.qb64, keys=[verfer.qb64 for verfer in verfers], isith=sith, - dig=kvr.serder.saider.qb64, + dig=kvr.serder.said, nsith=nxtsith, ndigs=[diger.qb64 for diger in digers], sn=kvr.sn+1, @@ -304,11 +304,11 @@ def test_partial_signed_escrow(): # apply msg to Kevery psr.parse(ims=bytearray(msg), kvy=kvy) # kvy.process(ims=bytearray(msg)) # process local copy of msg - assert kvr.serder.saider.qb64 != srdr.said # key state not updated + assert kvr.serder.said != srdr.said # key state not updated # process escrow kvy.processEscrowPartialSigs() - assert kvr.serder.saider.qb64 != srdr.said # key state not updated + assert kvr.serder.said != srdr.said # key state not updated msg = bytearray(srdr.raw) counter = coring.Counter(code=coring.CtrDex.ControllerIdxSigs) @@ -318,14 +318,14 @@ def test_partial_signed_escrow(): # apply msg to Kevery psr.parse(ims=bytearray(msg), kvy=kvy) # kvy.process(ims=bytearray(msg)) # process local copy of msg - assert kvr.serder.saider.qb64 != srdr.said # key state not updated + assert kvr.serder.said != srdr.said # key state not updated # get DTS set by escrow date time stamp on event edtsb = bytes(kvy.db.getDts(dbing.dgKey(pre, srdr.saidb))) # process escrow kvy.processEscrowPartialSigs() - assert kvr.serder.saider.qb64 == srdr.said # key state updated + assert kvr.serder.said == srdr.said # key state updated # get DTS set by first seen event acceptance date time stamp adtsb = bytes(kvy.db.getDts(dbing.dgKey(pre, srdr.saidb))) @@ -392,7 +392,7 @@ def test_missing_delegator_escrow(): # bobKvy.process(ims=bytearray(msg)) # process local copy of msg bobK = bobKvy.kevers[bobPre] assert bobK.prefixer.qb64 == bobPre - assert bobK.serder.saider.qb64 == bobSrdr.said + assert bobK.serder.said == bobSrdr.said # Setup Del's inception event assuming that Bob's next event will be an ixn delegating event verfers, digers = delMgr.incept(stem='del', temp=True) # algo default salty and rooted @@ -410,7 +410,7 @@ def test_missing_delegator_escrow(): s=delSrdr.ked["s"], d=delSrdr.said) bobSrdr = eventing.interact(pre=bobK.prefixer.qb64, - dig=bobK.serder.saider.qb64, + dig=bobK.serder.said, sn=bobK.sn+1, data=[seal._asdict()]) @@ -451,7 +451,7 @@ def test_missing_delegator_escrow(): assert delPre in bobKvy.kevers # successfully validated bobDelK = bobKvy.kevers[delPre] # delK in bobs kevery assert bobDelK.delegated - assert bobDelK.serder.saider.qb64 == delSrdr.said # key state updated so event was validated + assert bobDelK.serder.said == delSrdr.said # key state updated so event was validated couple = bobKvy.db.getAes(dbing.dgKey(delPre, delSrdr.said)) assert couple == seqner.qb64b + bobSrdr.saidb @@ -497,21 +497,21 @@ def test_missing_delegator_escrow(): assert delPre in delKvy.kevers # event removed from escrow delK = delKvy.kevers[delPre] assert delK.delegated - assert delK.serder.saider.qb64 == delSrdr.said + assert delK.serder.said == delSrdr.said couple = delKvy.db.getAes(dbing.dgKey(delPre, delSrdr.said)) assert couple == seqner.qb64b + bobSrdr.saidb escrows = delKvy.db.getPses(dbing.snKey(delPre, int(delSrdr.ked["s"], 16))) assert len(escrows) == 0 escrow = delKvy.db.getPde(dbing.dgKey(delPre, delSrdr.said)) - assert escrow is None + assert escrow is None # delegated inception delegation couple # Setup Del rotation event verfers, digers = delMgr.rotate(pre=delPre, temp=True) delSrdr = eventing.deltate(pre=bobDelK.prefixer.qb64, keys=[verfer.qb64 for verfer in verfers], - dig=bobDelK.serder.saider.qb64, + dig=bobDelK.serder.said, sn=bobDelK.sn+1, ndigs=[diger.qb64 for diger in digers]) @@ -520,7 +520,7 @@ def test_missing_delegator_escrow(): s=delSrdr.ked["s"], d=delSrdr.said) bobSrdr = eventing.interact(pre=bobK.prefixer.qb64, - dig=bobK.serder.saider.qb64, + dig=bobK.serder.said, sn=bobK.sn+1, data=[seal._asdict()]) @@ -536,12 +536,12 @@ def test_missing_delegator_escrow(): # apply msg to bob's Kevery psr.parse(ims=bytearray(msg), kvy=bobKvy) # bobKvy.process(ims=bytearray(msg)) # process local copy of msg - assert bobK.serder.saider.qb64 == bobSrdr.said # key state updated so event was validated + assert bobK.serder.said == bobSrdr.said # key state updated so event was validated # apply msg to del's Kevery psr.parse(ims=bytearray(msg), kvy=delKvy) # delKvy.process(ims=bytearray(msg)) # process remote copy of msg - assert delKvy.kevers[bobPre].serder.saider.qb64 == bobSrdr.said + assert delKvy.kevers[bobPre].serder.said == bobSrdr.said # now create msg from Del's delegated rotation event sigers = delMgr.sign(ser=delSrdr.raw, verfers=verfers) @@ -563,7 +563,7 @@ def test_missing_delegator_escrow(): psr.parse(ims=bytearray(msg), kvy=delKvy) # delKvy.process(ims=bytearray(msg)) # process remote copy of msg assert delK.delegated - assert delK.serder.saider.qb64 == delSrdr.said + assert delK.serder.said == delSrdr.said couple = delKvy.db.getAes(dbing.dgKey(delPre, delSrdr.said)) assert couple == seqner.qb64b + bobSrdr.saidb @@ -571,7 +571,7 @@ def test_missing_delegator_escrow(): psr.parse(ims=bytearray(msg), kvy=bobKvy) # bobKvy.process(ims=bytearray(msg)) # process local copy of msg assert bobDelK.delegated - assert bobDelK.serder.saider.qb64 == delSrdr.said # key state updated so event was validated + assert bobDelK.serder.said == delSrdr.said # key state updated so event was validated couple = bobKvy.db.getAes(dbing.dgKey(delPre, delSrdr.said)) assert couple == seqner.qb64b + bobSrdr.saidb @@ -1261,7 +1261,7 @@ def test_unverified_trans_receipt_escrow(): # create receipt(s) of rotation message with rotation message of receipter # create chit receipt(s) of interaction message seal = eventing.SealEvent(i=rpre, - s= rsrdr.ked["s"], + s=rsrdr.ked["s"], d=rsrdr.said) reserder = eventing.receipt(pre=pre, sn=2, said=rotdig) # sign event not receipt diff --git a/tests/core/test_eventing.py b/tests/core/test_eventing.py index 3da7a2f98..c6532e63f 100644 --- a/tests/core/test_eventing.py +++ b/tests/core/test_eventing.py @@ -9,12 +9,12 @@ import pysodium import pytest -from keri import help +from keri import kering from keri.app import habbing, keeping from keri.app.keeping import openKS, Manager -from keri.core import coring, eventing, parsing -from keri.core.coring import (Ilks, Diger, MtrDex, Matter, IdrDex, Indexer, - CtrDex, Counter, Salter, Serder, Siger, Cigar, +from keri.core import coring, eventing, parsing, serdering +from keri.core.coring import (Diger, MtrDex, Matter, IdrDex, Indexer, + CtrDex, Counter, Salter, Siger, Cigar, Seqner, Verfer, Signer, Prefixer, generateSigners, IdxSigDex, DigDex) from keri.core.eventing import Kever, Kevery @@ -27,10 +27,15 @@ deTransReceiptQuadruple, deTransReceiptQuintuple) from keri.core.eventing import (incept, rotate, interact, receipt, query, delcept, deltate, state, messagize) +from keri.core import serdering + from keri.db import dbing, basing from keri.db.basing import openDB from keri.db.dbing import dgKey, snKey -from keri.kering import (ValidationError, DerivationError) +from keri.kering import (ValidationError, DerivationError, Ilks) + +from keri import help +from keri.help import helping logger = help.ogler.getLogger() @@ -693,19 +698,19 @@ def test_keyeventfuncs(mockHelpingNowUTC): b'eq9W8_As","i":"BFs8BBx86uytIM0D2BhsE5rrqVIT8ef8mflpNceHo4XH","s":"0","kt":"1' b'","k":["BFs8BBx86uytIM0D2BhsE5rrqVIT8ef8mflpNceHo4XH"],"nt":"0","n":[],"bt":' b'"0","b":[],"c":[],"a":[]}') - saider = coring.Saider(sad=serder.ked, code=MtrDex.Blake3_256) - assert saider.verify(serder.ked) is True + #saider = coring.Saider(sad=serder.ked, code=MtrDex.Blake3_256) + #assert saider.verify(serder.ked) is True - with pytest.raises(DerivationError): - # non-empty nxt with non-transferable code + with pytest.raises(ValidationError): + # non-empty ndigs with non-transferable code serder = incept(keys=keys0, code=MtrDex.Ed25519N, ndigs=["ABCDE"]) - with pytest.raises(DerivationError): - # non-empty witnesses with non-transferable code + with pytest.raises(ValidationError): + # non-empty backers with non-transferable code serder = incept(keys=keys0, code=MtrDex.Ed25519N, wits=["ABCDE"]) - with pytest.raises(DerivationError): - # non-empty witnesses with non-transferable code + with pytest.raises(ValidationError): + # non-empty seals with non-transferable code serder = incept(keys=keys0, code=MtrDex.Ed25519N, data=[{"i": "ABCDE"}]) # Inception: Transferable Case but abandoned in incept so equivalent @@ -721,8 +726,8 @@ def test_keyeventfuncs(mockHelpingNowUTC): b'","k":["DFs8BBx86uytIM0D2BhsE5rrqVIT8ef8mflpNceHo4XH"],"nt":"0","n":[],"bt":' b'"0","b":[],"c":[],"a":[]}') - saider = coring.Saider(sad=serder.ked, code=MtrDex.Blake3_256) - assert saider.verify(serder.ked) is True + #saider = coring.Saider(sad=serder.ked, code=MtrDex.Blake3_256) + #assert saider.verify(serder.ked) is True # Inception: Transferable not abandoned i.e. next not empty,Self-Addressing # seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) @@ -826,12 +831,8 @@ def test_keyeventfuncs(mockHelpingNowUTC): b'","k":["DFs8BBx86uytIM0D2BhsE5rrqVIT8ef8mflpNceHo4XH"],"nt":"1","n":["EIf-EN' b'w7PrM52w4H-S7NGU2qVIfraXVIlV9hEAaMHg7W"],"bt":"0","b":[],"c":[],"a":[]}') - saider = coring.Saider(sad=serder0.ked, code=MtrDex.Blake3_256) - assert saider.qb64 == serder0.said - - saider = coring.Saider(sad=serder0.ked, code=MtrDex.Blake3_256) - assert saider.qb64 == serder0.said - + #saider = coring.Saider(sad=serder0.ked, code=MtrDex.Blake3_256) + #assert saider.qb64 == serder0.said # Rotation: Transferable not abandoned i.e. next not empty # seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) @@ -857,8 +858,8 @@ def test_keyeventfuncs(mockHelpingNowUTC): b'MQp8ayDRin0NG0Ymn_RXQP_v-PQ"],"nt":"1","n":["EIsKL3B6Zz5ICGxCQp-SoLXjwOrdlSb' b'LJrEn21c2zVaU"],"bt":"0","br":[],"ba":[],"a":[]}') - saider = coring.Saider(sad=serder1.ked, code=MtrDex.Blake3_256) - assert serder1.said == saider.qb64 + #saider = coring.Saider(sad=serder1.ked, code=MtrDex.Blake3_256) + #assert serder1.said == saider.qb64 @@ -886,8 +887,8 @@ def test_keyeventfuncs(mockHelpingNowUTC): b'p8ayDRin0NG0Ymn_RXQP_v-PQ"],"nt":1,"n":["EIsKL3B6Zz5ICGxCQp-SoLXjwOrdlSbLJrE' b'n21c2zVaU"],"bt":0,"br":[],"ba":[],"a":[]}') - saider = coring.Saider(sad=serder1.ked, code=MtrDex.Blake3_256) - assert serder1.said == saider.qb64 + #saider = coring.Saider(sad=serder1.ked, code=MtrDex.Blake3_256) + #assert serder1.said == saider.qb64 # Interaction: serder2 = interact(pre=pre, dig=serder1.said, sn=2) @@ -996,6 +997,470 @@ def test_keyeventfuncs(mockHelpingNowUTC): b'LJrEn21c2zVaU"],"bt":"0","br":[],"ba":[],"a":[]}') assert serderR.said == 'EMBBBkaLV7i6wNgfz3giib2ItrHsr548mtIflW0Hrbuv' + + seed = (b'\x9f{\xa8\xa7\xa8C9\x96&\xfa\xb1\x99\xeb\xaa \xc4\x1bG\x11\xc4\xaeSAR' + b'\xc9\xbd\x04\x9d\x85)~\x93') + + # Secp256r1 Inception: Non-transferable (ephemeral) case + signer0 = Signer(raw=seed, transferable=False, code=MtrDex.ECDSA_256r1_Seed) # original signing keypair non transferable + assert signer0.code == MtrDex.ECDSA_256r1_Seed + assert signer0.verfer.code == MtrDex.ECDSA_256r1N + keys0 = [signer0.verfer.qb64] + serder = incept(keys=keys0) # default nxt is empty so abandoned + assert serder.ked["i"] == '1AAIA3cK_P2CDlh-_EMFPvyqTPI1POkw-dr14DANx5JEXDCZ' + assert serder.ked["n"] == [] + assert serder.raw == (b'{"v":"KERI10JSON000105_","t":"icp","d":"ELIz2CFNp4vCTJkCKYzqkv1tJeqaPiwhHkNuWA0tKfxo",' + b'"i":"1AAIA3cK_P2CDlh-_EMFPvyqTPI1POkw-dr14DANx5JEXDCZ","s":"0","kt":"1",' + b'"k":["1AAIA3cK_P2CDlh-_EMFPvyqTPI1POkw-dr14DANx5JEXDCZ"],"nt":"0","n":[],"bt":"0","b":[],"c":[],"a":[]}') + #saider = coring.Saider(sad=serder.ked, code=MtrDex.Blake3_256) + #assert saider.verify(serder.ked) is True + + with pytest.raises(ValidationError): + # non-empty nxt with non-transferable code + serder = incept(keys=keys0, code=MtrDex.ECDSA_256r1N, ndigs=["ABCDE"]) + + with pytest.raises(ValidationError): + # non-empty witnesses with non-transferable code + serder = incept(keys=keys0, code=MtrDex.ECDSA_256r1N, wits=["ABCDE"]) + + with pytest.raises(ValidationError): + # non-empty witnesses with non-transferable code + serder = incept(keys=keys0, code=MtrDex.ECDSA_256r1N, data=[{"i": "ABCDE"}]) + + # Inception: Transferable Case but abandoned in incept so equivalent + signer0 = Signer(raw=seed, code=MtrDex.ECDSA_256r1_Seed) # original signing keypair transferable default + assert signer0.code == MtrDex.ECDSA_256r1_Seed + assert signer0.verfer.code == MtrDex.ECDSA_256r1 + keys0 = [signer0.verfer.qb64] + serder = incept(keys=keys0) # default nxt is empty so abandoned + assert serder.ked["i"] == '1AAJA3cK_P2CDlh-_EMFPvyqTPI1POkw-dr14DANx5JEXDCZ' + assert serder.ked["n"] == [] + assert serder.raw == (b'{"v":"KERI10JSON000105_","t":"icp","d":"EPqQeDE6eoawHEwQjyB4kwLTwZ2VV6jDz_TXWFV7sE8T",' + b'"i":"1AAJA3cK_P2CDlh-_EMFPvyqTPI1POkw-dr14DANx5JEXDCZ","s":"0","kt":"1",' + b'"k":["1AAJA3cK_P2CDlh-_EMFPvyqTPI1POkw-dr14DANx5JEXDCZ"],"nt":"0","n":[],' + b'"bt":"0","b":[],"c":[],"a":[]}') + + #saider = coring.Saider(sad=serder.ked, code=MtrDex.Blake3_256) + #assert saider.verify(serder.ked) is True + + # Inception: Transferable not abandoned i.e. next not empty,Self-Addressing + # seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + seed1 = (b'\x83B~\x04\x94\xe3\xceUQy\x11f\x0c\x93]\x1e\xbf\xacQ\xb5\xd6Y^\xa2E\xfa\x015' + b'\x98Y\xdd\xe8') + signer1 = Signer(raw=seed1, code=MtrDex.ECDSA_256r1_Seed) # next signing keypair transferable is default + assert signer1.code == MtrDex.ECDSA_256r1_Seed + assert signer1.verfer.code == MtrDex.ECDSA_256r1 + keys1 = [signer1.verfer.qb64] + # compute nxt digest + nxt1 = [coring.Diger(ser=signer1.verfer.qb64b).qb64] # dfault sith is 1 + assert nxt1 == ['EDCWQzPSj3zZBKMZ-_FAckxIMFM25ITsEwD72psBYak4'] + serder0 = incept(keys=keys0, ndigs=nxt1, code=MtrDex.Blake3_256) # intive false + pre = serder0.ked["i"] + assert serder0.ked["t"] == Ilks.icp + assert serder0.ked['d'] == serder0.ked["i"] == 'EFEscYZrbSsAPJq_OhGt19qb-ci1AtZTqwGqZ5FypKVd' + assert serder0.ked["s"] == '0' + assert serder0.ked["kt"] == "1" + assert serder0.ked["nt"] == "1" + assert serder0.ked["n"] == nxt1 + assert serder0.ked["bt"] == '0' # hex str + + assert serder0.raw == (b'{"v":"KERI10JSON00012f_","t":"icp","d":"EFEscYZrbSsAPJq_OhGt19qb-ci1AtZTqwGqZ5FypKVd",' + b'"i":"EFEscYZrbSsAPJq_OhGt19qb-ci1AtZTqwGqZ5FypKVd","s":"0",' + b'"kt":"1","k":["1AAJA3cK_P2CDlh-_EMFPvyqTPI1POkw-dr14DANx5JEXDCZ"],' + b'"nt":"1","n":["EDCWQzPSj3zZBKMZ-_FAckxIMFM25ITsEwD72psBYak4"],' + b'"bt":"0","b":[],"c":[],"a":[]}') + + # Inception: Transferable not abandoned i.e. next not empty,Self-Addressing, intive + # seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + seed1 = (b'\x83B~\x04\x94\xe3\xceUQy\x11f\x0c\x93]\x1e\xbf\xacQ\xb5\xd6Y^\xa2E\xfa\x015' + b'\x98Y\xdd\xe8') + signer1 = Signer(raw=seed1, code=MtrDex.ECDSA_256r1_Seed) # next signing keypair transferable is default + assert signer1.code == MtrDex.ECDSA_256r1_Seed + assert signer1.verfer.code == MtrDex.ECDSA_256r1 + keys1 = [signer1.verfer.qb64] + # compute nxt digest + nxt1 = [coring.Diger(ser=signer1.verfer.qb64b).qb64] # dfault sith is 1 + assert nxt1 == ['EDCWQzPSj3zZBKMZ-_FAckxIMFM25ITsEwD72psBYak4'] + serder0 = incept(keys=keys0, ndigs=nxt1, code=MtrDex.Blake3_256, intive=True) # intive true + pre = serder0.ked["i"] + assert serder0.ked["t"] == Ilks.icp + assert serder0.ked['d'] == pre == 'EJcQxvzMr3xaxa6rlXvPguII45JMmoRENnKFAsUS9Mx0' + assert serder0.ked["s"] == '0' + assert serder0.ked["kt"] == 1 + assert serder0.ked["nt"] == 1 + assert serder0.ked["n"] == nxt1 + assert serder0.ked["bt"] == 0 + assert serder0.raw == (b'{"v":"KERI10JSON000129_","t":"icp","d":"EJcQxvzMr3xaxa6rlXvPguII45JMmoRENnKFAsUS9Mx0",' + b'"i":"EJcQxvzMr3xaxa6rlXvPguII45JMmoRENnKFAsUS9Mx0","s":"0",' + b'"kt":1,"k":["1AAJA3cK_P2CDlh-_EMFPvyqTPI1POkw-dr14DANx5JEXDCZ"],' + b'"nt":1,"n":["EDCWQzPSj3zZBKMZ-_FAckxIMFM25ITsEwD72psBYak4"],' + b'"bt":0,"b":[],"c":[],"a":[]}') + + # Inception: Transferable not abandoned i.e. next not empty, Intive True + # seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + seed1 = (b'\x83B~\x04\x94\xe3\xceUQy\x11f\x0c\x93]\x1e\xbf\xacQ\xb5\xd6Y^\xa2E\xfa\x015' + b'\x98Y\xdd\xe8') + signer1 = Signer(raw=seed1, code=MtrDex.ECDSA_256r1_Seed) # next signing keypair transferable is default + assert signer1.code == MtrDex.ECDSA_256r1_Seed + assert signer1.verfer.code == MtrDex.ECDSA_256r1 + keys1 = [signer1.verfer.qb64] + # compute nxt digest + nxt1 = [coring.Diger(ser=signer1.verfer.qb64b).qb64] # dfault sith is 1 + assert nxt1 == ['EDCWQzPSj3zZBKMZ-_FAckxIMFM25ITsEwD72psBYak4'] + serder0 = incept(keys=keys0, ndigs=nxt1, intive=True) # intive true + pre = serder0.ked["i"] + assert serder0.ked["t"] == Ilks.icp + assert serder0.ked["i"] == '1AAJA3cK_P2CDlh-_EMFPvyqTPI1POkw-dr14DANx5JEXDCZ' + assert serder0.ked["s"] == '0' + assert serder0.ked["kt"] == 1 + assert serder0.ked["nt"] == 1 + assert serder0.ked["n"] == nxt1 + assert serder0.ked["bt"] == 0 # int not hex str + assert serder0.raw == (b'{"v":"KERI10JSON00012d_","t":"icp","d":"ECMsDN8WUWcKGe7X0GNrecOgtrTkuHTfoo7YoQ4TIOLS",' + b'"i":"1AAJA3cK_P2CDlh-_EMFPvyqTPI1POkw-dr14DANx5JEXDCZ","s":"0",' + b'"kt":1,"k":["1AAJA3cK_P2CDlh-_EMFPvyqTPI1POkw-dr14DANx5JEXDCZ"],' + b'"nt":1,"n":["EDCWQzPSj3zZBKMZ-_FAckxIMFM25ITsEwD72psBYak4"],' + b'"bt":0,"b":[],"c":[],"a":[]}') + + # Inception: Transferable not abandoned i.e. next not empty + # seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + seed1 = (b'\x83B~\x04\x94\xe3\xceUQy\x11f\x0c\x93]\x1e\xbf\xacQ\xb5\xd6Y^\xa2E\xfa\x015' + b'\x98Y\xdd\xe8') + signer1 = Signer(raw=seed1, code=MtrDex.ECDSA_256r1_Seed) # next signing keypair transferable is default + assert signer1.code == MtrDex.ECDSA_256r1_Seed + assert signer1.verfer.code == MtrDex.ECDSA_256r1 + keys1 = [signer1.verfer.qb64] + # compute nxt digest + nxt1 = [coring.Diger(ser=signer1.verfer.qb64b).qb64] # dfault sith is 1 + assert nxt1 == ['EDCWQzPSj3zZBKMZ-_FAckxIMFM25ITsEwD72psBYak4'] + serder0 = incept(keys=keys0, ndigs=nxt1) + pre = serder0.ked["i"] + assert serder0.ked["t"] == Ilks.icp + assert serder0.ked["i"] == '1AAJA3cK_P2CDlh-_EMFPvyqTPI1POkw-dr14DANx5JEXDCZ' + assert serder0.ked["s"] == '0' + assert serder0.ked["kt"] == "1" + assert serder0.ked["nt"] == "1" + assert serder0.ked["n"] == nxt1 + assert serder0.ked["bt"] == "0" # hex str + assert serder0.raw == (b'{"v":"KERI10JSON000133_","t":"icp","d":"EF8tNYXNKPjByUNg2s7zEhEKKl39COxwOYA1CpqvxB_J",' + b'"i":"1AAJA3cK_P2CDlh-_EMFPvyqTPI1POkw-dr14DANx5JEXDCZ","s":"0",' + b'"kt":"1","k":["1AAJA3cK_P2CDlh-_EMFPvyqTPI1POkw-dr14DANx5JEXDCZ"],' + b'"nt":"1","n":["EDCWQzPSj3zZBKMZ-_FAckxIMFM25ITsEwD72psBYak4"],' + b'"bt":"0","b":[],"c":[],"a":[]}') + + #saider = coring.Saider(sad=serder0.ked, code=MtrDex.Blake3_256) + #assert saider.qb64 == serder0.said + + + # Rotation: Transferable not abandoned i.e. next not empty + # seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + seed2 = (b'\xbe\x96\x02\xa9\x88\xce\xf9O\x1e\x0fo\xc0\xff\x98\xb6\xfa\x1e\xa2y\xf2' + b'e\xf9AL\x1aeK\xafj\xa1pB') + signer2 = Signer(raw=seed2, code=MtrDex.ECDSA_256r1_Seed) # next signing keypair transferable is default + assert signer2.code == MtrDex.ECDSA_256r1_Seed + assert signer2.verfer.code == MtrDex.ECDSA_256r1 + keys2 = [coring.Diger(ser=signer2.verfer.qb64b).qb64] + # compute nxt digest + serder1 = rotate(pre=pre, keys=keys1, dig=serder0.said, ndigs=keys2, sn=1) + print(f'evnt {serder1.raw}') + assert serder1.ked["t"] == Ilks.rot + assert serder1.ked["i"] == pre + assert serder1.ked["s"] == '1' + assert serder1.ked["p"] == serder0.said + assert serder1.ked["kt"] == "1" + assert serder1.ked["nt"] == "1" + assert serder1.ked["n"] == keys2 + assert serder1.ked["bt"] == '0' # hex str + assert serder1.raw == (b'{"v":"KERI10JSON000168_","t":"rot","d":"ENsyn8FRcmd9N3Dhi_kRdWcnI_AIa7yhDNsHXycclSXQ",' + b'"i":"1AAJA3cK_P2CDlh-_EMFPvyqTPI1POkw-dr14DANx5JEXDCZ","s":"1","p":"EF8tNYXNKPjByUNg2s7zEhEKKl39COxwOYA1CpqvxB_J",' + b'"kt":"1","k":["1AAJAtrK9Q8IqgO3B4IKY4m8Dl7dp1fC77dNCsHP2aWctria"],' + b'"nt":"1","n":["EIkmr0Ne3wbNvTKRU-A9NLmCL-RYgu2SZuzIb3n-9xFH"],' + b'"bt":"0","br":[],"ba":[],"a":[]}') + + #saider = coring.Saider(sad=serder1.ked, code=MtrDex.Blake3_256) + #assert serder1.said == saider.qb64 + + + # Secp256k1 Inception: Non-transferable (ephemeral) case + signer0 = Signer(raw=seed, transferable=False, code=MtrDex.ECDSA_256k1_Seed) # original signing keypair non transferable + assert signer0.code == MtrDex.ECDSA_256k1_Seed + assert signer0.verfer.code == MtrDex.ECDSA_256k1N + keys0 = [signer0.verfer.qb64] + serder = incept(keys=keys0) # default nxt is empty so abandoned + assert serder.ked["i"] == '1AAAAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk' + assert serder.ked["n"] == [] + assert serder.raw == (b'{"v":"KERI10JSON000105_","t":"icp","d":"EGEP0h6tTUUOeIK4ApGlnLl2lwD0lbaQGBfL9' + b'pM2v0J0","i":"1AAAAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk","s":"0","kt":"1"' + b',"k":["1AAAAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk"],"nt":"0","n":[],"bt":"0","b":[],"c":[],"a":[]}') + #saider = coring.Saider(sad=serder.ked, code=MtrDex.Blake3_256) + #assert saider.verify(serder.ked) is True + + # Inception: Transferable Case but abandoned in incept so equivalent + signer0 = Signer(raw=seed, code=MtrDex.ECDSA_256k1_Seed) # original signing keypair transferable default + assert signer0.code == MtrDex.ECDSA_256k1_Seed + assert signer0.verfer.code == MtrDex.ECDSA_256k1 + keys0 = [signer0.verfer.qb64] + serder = incept(keys=keys0) # default nxt is empty so abandoned + assert serder.ked["i"] == '1AABAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk' + assert serder.ked["n"] == [] + assert serder.raw == (b'{"v":"KERI10JSON000105_","t":"icp","d":"EO3M4d4pvQu2SXFLaXEy05ey80d71gbEakA2TgCTnJtN",' + b'"i":"1AABAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk","s":"0","kt":"1",' + b'"k":["1AABAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk"],"nt":"0","n":[],' + b'"bt":"0","b":[],"c":[],"a":[]}') + + #saider = coring.Saider(sad=serder.ked, code=MtrDex.Blake3_256) + #assert saider.verify(serder.ked) is True + + # Inception: Transferable not abandoned i.e. next not empty,Self-Addressing + # seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + seed1 = (b'\x83B~\x04\x94\xe3\xceUQy\x11f\x0c\x93]\x1e\xbf\xacQ\xb5\xd6Y^\xa2E\xfa\x015' + b'\x98Y\xdd\xe8') + signer1 = Signer(raw=seed1, code=MtrDex.ECDSA_256k1_Seed) # next signing keypair transferable is default + assert signer1.code == MtrDex.ECDSA_256k1_Seed + assert signer1.verfer.code == MtrDex.ECDSA_256k1 + keys1 = [signer1.verfer.qb64] + # compute nxt digest + nxt1 = [coring.Diger(ser=signer1.verfer.qb64b).qb64] # dfault sith is 1 + assert nxt1 == ['EJ6Ycs7kho8XRxiq3DK37jiJ8mU9RP9HpSYnARm26EnO'] + serder0 = incept(keys=keys0, ndigs=nxt1, code=MtrDex.Blake3_256) # intive false + pre = serder0.ked["i"] + assert serder0.ked["t"] == Ilks.icp + assert serder0.ked['d'] == serder0.ked["i"] == 'EBTJs_972Mh0Q2raFOINbmgtdtT0Od4VJm6aNd4xVW9u' + assert serder0.ked["s"] == '0' + assert serder0.ked["kt"] == "1" + assert serder0.ked["nt"] == "1" + assert serder0.ked["n"] == nxt1 + assert serder0.ked["bt"] == '0' # hex str + assert serder0.raw == (b'{"v":"KERI10JSON00012f_","t":"icp","d":"EBTJs_972Mh0Q2raFOINbmgtdtT0Od4VJm6aNd4xVW9u",' + b'"i":"EBTJs_972Mh0Q2raFOINbmgtdtT0Od4VJm6aNd4xVW9u","s":"0","kt":"1",' + b'"k":["1AABAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk"],' + b'"nt":"1","n":["EJ6Ycs7kho8XRxiq3DK37jiJ8mU9RP9HpSYnARm26EnO"],' + b'"bt":"0","b":[],"c":[],"a":[]}') + + # Inception: Transferable not abandoned i.e. next not empty,Self-Addressing, intive + # seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + seed1 = (b'\x83B~\x04\x94\xe3\xceUQy\x11f\x0c\x93]\x1e\xbf\xacQ\xb5\xd6Y^\xa2E\xfa\x015' + b'\x98Y\xdd\xe8') + signer1 = Signer(raw=seed1, code=MtrDex.ECDSA_256k1_Seed) # next signing keypair transferable is default + assert signer1.code == MtrDex.ECDSA_256k1_Seed + assert signer1.verfer.code == MtrDex.ECDSA_256k1 + keys1 = [signer1.verfer.qb64] + # compute nxt digest + nxt1 = [coring.Diger(ser=signer1.verfer.qb64b).qb64] # dfault sith is 1 + assert nxt1 == ['EJ6Ycs7kho8XRxiq3DK37jiJ8mU9RP9HpSYnARm26EnO'] + serder0 = incept(keys=keys0, ndigs=nxt1, code=MtrDex.Blake3_256, intive=True) # intive true + pre = serder0.ked["i"] + assert serder0.ked["t"] == Ilks.icp + assert serder0.ked['d'] == pre == 'ECzQWBHMIRJpUhrIB2sn4YUsb0HL-wE1wErVcQnkme5z' + assert serder0.ked["s"] == '0' + assert serder0.ked["kt"] == 1 + assert serder0.ked["nt"] == 1 + assert serder0.ked["n"] == nxt1 + assert serder0.ked["bt"] == 0 + assert serder0.raw == (b'{"v":"KERI10JSON000129_","t":"icp","d":"ECzQWBHMIRJpUhrIB2sn4YUsb0HL-wE1wErVcQnkme5z",' + b'"i":"ECzQWBHMIRJpUhrIB2sn4YUsb0HL-wE1wErVcQnkme5z","s":"0","kt":1,' + b'"k":["1AABAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk"],' + b'"nt":1,"n":["EJ6Ycs7kho8XRxiq3DK37jiJ8mU9RP9HpSYnARm26EnO"],' + b'"bt":0,"b":[],"c":[],"a":[]}') + + # Inception: Transferable not abandoned i.e. next not empty, Intive True + # seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + seed1 = (b'\x83B~\x04\x94\xe3\xceUQy\x11f\x0c\x93]\x1e\xbf\xacQ\xb5\xd6Y^\xa2E\xfa\x015' + b'\x98Y\xdd\xe8') + signer1 = Signer(raw=seed1, code=MtrDex.ECDSA_256k1_Seed) # next signing keypair transferable is default + assert signer1.code == MtrDex.ECDSA_256k1_Seed + assert signer1.verfer.code == MtrDex.ECDSA_256k1 + keys1 = [signer1.verfer.qb64] + # compute nxt digest + nxt1 = [coring.Diger(ser=signer1.verfer.qb64b).qb64] # dfault sith is 1 + assert nxt1 == ['EJ6Ycs7kho8XRxiq3DK37jiJ8mU9RP9HpSYnARm26EnO'] + serder0 = incept(keys=keys0, ndigs=nxt1, intive=True) # intive true + pre = serder0.ked["i"] + assert serder0.ked["t"] == Ilks.icp + assert serder0.ked["i"] == '1AABAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk' + assert serder0.ked["s"] == '0' + assert serder0.ked["kt"] == 1 + assert serder0.ked["nt"] == 1 + assert serder0.ked["n"] == nxt1 + assert serder0.ked["bt"] == 0 # int not hex str + assert serder0.raw == (b'{"v":"KERI10JSON00012d_","t":"icp","d":"EJR4ywPEpo08A10s8Eq8MGIRtmQx_D6szqYKGOl7jSpY",' + b'"i":"1AABAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk","s":"0","kt":1,' + b'"k":["1AABAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk"],' + b'"nt":1,"n":["EJ6Ycs7kho8XRxiq3DK37jiJ8mU9RP9HpSYnARm26EnO"],' + b'"bt":0,"b":[],"c":[],"a":[]}') + + + # Inception: Transferable not abandoned i.e. next not empty + # seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + seed1 = (b'\x83B~\x04\x94\xe3\xceUQy\x11f\x0c\x93]\x1e\xbf\xacQ\xb5\xd6Y^\xa2E\xfa\x015' + b'\x98Y\xdd\xe8') + signer1 = Signer(raw=seed1, code=MtrDex.ECDSA_256k1_Seed) # next signing keypair transferable is default + assert signer1.code == MtrDex.ECDSA_256k1_Seed + assert signer1.verfer.code == MtrDex.ECDSA_256k1 + keys1 = [signer1.verfer.qb64] + # compute nxt digest + nxt1 = [coring.Diger(ser=signer1.verfer.qb64b).qb64] # dfault sith is 1 + assert nxt1 == ['EJ6Ycs7kho8XRxiq3DK37jiJ8mU9RP9HpSYnARm26EnO'] + serder0 = incept(keys=keys0, ndigs=nxt1) + pre = serder0.ked["i"] + assert serder0.ked["t"] == Ilks.icp + assert serder0.ked["i"] == '1AABAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk' + assert serder0.ked["s"] == '0' + assert serder0.ked["kt"] == "1" + assert serder0.ked["nt"] == "1" + assert serder0.ked["n"] == nxt1 + assert serder0.ked["bt"] == "0" # hex str + assert serder0.raw == (b'{"v":"KERI10JSON000133_","t":"icp","d":"EJZHJo2S1oESDhtadHN-RUV4J3DM9xlWHdsi2TI8ztZ6",' + b'"i":"1AABAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk","s":"0","kt":"1",' + b'"k":["1AABAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk"],' + b'"nt":"1","n":["EJ6Ycs7kho8XRxiq3DK37jiJ8mU9RP9HpSYnARm26EnO"],' + b'"bt":"0","b":[],"c":[],"a":[]}') + + #saider = coring.Saider(sad=serder0.ked, code=MtrDex.Blake3_256) + #assert saider.qb64 == serder0.said + + # Rotation: Transferable not abandoned i.e. next not empty + # seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + seed2 = (b'\xbe\x96\x02\xa9\x88\xce\xf9O\x1e\x0fo\xc0\xff\x98\xb6\xfa\x1e\xa2y\xf2' + b'e\xf9AL\x1aeK\xafj\xa1pB') + signer2 = Signer(raw=seed2, code=MtrDex.ECDSA_256k1_Seed) # next signing keypair transferable is default + assert signer2.code == MtrDex.ECDSA_256k1_Seed + assert signer2.verfer.code == MtrDex.ECDSA_256k1 + keys2 = [coring.Diger(ser=signer2.verfer.qb64b).qb64] + # compute nxt digest + serder1 = rotate(pre=pre, keys=keys1, dig=serder0.said, ndigs=keys2, sn=1) + assert serder1.ked["t"] == Ilks.rot + assert serder1.ked["i"] == pre + assert serder1.ked["s"] == '1' + assert serder1.ked["p"] == serder0.said + assert serder1.ked["kt"] == "1" + assert serder1.ked["nt"] == "1" + assert serder1.ked["n"] == keys2 + assert serder1.ked["bt"] == '0' # hex str + assert serder1.raw == (b'{"v":"KERI10JSON000168_","t":"rot","d":"EKpAPAgfGLxzXuusVQJ4uTuSOzt1mm3a8K1VPRnJOufJ",' + b'"i":"1AABAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk","s":"1","p":"EJZHJo2S1oESDhtadHN-RUV4J3DM9xlWHdsi2TI8ztZ6",' + b'"kt":"1","k":["1AABA7KZA_wxPCXJ5BgZ9jjdrMIy3OQKgHfa6eKyLcZpEn26"],' + b'"nt":"1","n":["EDn6z-KqmwcDVCql1CkMkvSNbNghhMF2TwsdllyP4a07"],' + b'"bt":"0","br":[],"ba":[],"a":[]}') + + #saider = coring.Saider(sad=serder1.ked, code=MtrDex.Blake3_256) + #assert serder1.said == saider.qb64 + + + # Rotation: Transferable not abandoned i.e. next not empty Intive + # seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + seed2 = (b'\xbe\x96\x02\xa9\x88\xce\xf9O\x1e\x0fo\xc0\xff\x98\xb6\xfa\x1e\xa2y\xf2' + b'e\xf9AL\x1aeK\xafj\xa1pB') + signer2 = Signer(raw=seed2, code=MtrDex.ECDSA_256k1_Seed) # next signing keypair transferable is default + assert signer2.code == MtrDex.ECDSA_256k1_Seed + assert signer2.verfer.code == MtrDex.ECDSA_256k1 + keys2 = [coring.Diger(ser=signer2.verfer.qb64b).qb64] + # compute nxt digest + serder1 = rotate(pre=pre, keys=keys1, dig=serder0.said, ndigs=keys2, sn=1, intive=True) # intive + assert serder1.ked["t"] == Ilks.rot + assert serder1.ked["i"] == pre + assert serder1.ked["s"] == '1' + assert serder1.ked["p"] == serder0.said + assert serder1.ked["kt"] == 1 + assert serder1.ked["nt"] == 1 + assert serder1.ked["n"] == keys2 + assert serder1.ked["bt"] == 0 + assert serder1.raw == (b'{"v":"KERI10JSON000162_","t":"rot","d":"EHc84kDs5EsLQYVLkP7fe-7DUfCQ7jFY69Zqq2UfmvTe",' + b'"i":"1AABAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk","s":"1","p":"EJZHJo2S1oESDhtadHN-RUV4J3DM9xlWHdsi2TI8ztZ6",' + b'"kt":1,"k":["1AABA7KZA_wxPCXJ5BgZ9jjdrMIy3OQKgHfa6eKyLcZpEn26"],' + b'"nt":1,"n":["EDn6z-KqmwcDVCql1CkMkvSNbNghhMF2TwsdllyP4a07"],' + b'"bt":0,"br":[],"ba":[],"a":[]}') + + #saider = coring.Saider(sad=serder1.ked, code=MtrDex.Blake3_256) + #assert serder1.said == saider.qb64 + + # Interaction: + serder2 = interact(pre=pre, dig=serder1.said, sn=2) + assert serder2.ked["t"] == Ilks.ixn + assert serder2.ked["i"] == pre + assert serder2.ked["s"] == '2' + assert serder2.ked["p"] == serder1.said + assert serder2.raw == (b'{"v":"KERI10JSON0000cf_","t":"ixn","d":"EGa_yUkSwFvJTbFnEBosvpIkJ_AkyY5E3XdU7fqhtErf",' + b'"i":"1AABAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk","s":"2","p":"EHc84kDs5EsLQYVLkP7fe-7DUfCQ7jFY69Zqq2UfmvTe","a":[]}') + + # Receipt + serder3 = receipt(pre=pre, sn=0, said=serder2.said) + assert serder3.ked["i"] == pre + assert serder3.ked["s"] == "0" + assert serder3.ked["t"] == Ilks.rct + assert serder3.ked["d"] == serder2.said + assert serder3.raw == (b'{"v":"KERI10JSON000095_","t":"rct","d":"EGa_yUkSwFvJTbFnEBosvpIkJ_AkyY5E3XdU7fqhtErf",' + b'"i":"1AABAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk","s":"0"}') + + + serder4 = receipt(pre=pre, sn=2, said=serder2.said) + assert serder4.ked["i"] == pre + assert serder4.ked["s"] == "2" + assert serder4.ked["t"] == Ilks.rct + assert serder4.ked["d"] == serder2.said + assert serder4.raw == (b'{"v":"KERI10JSON000095_","t":"rct","d":"EGa_yUkSwFvJTbFnEBosvpIkJ_AkyY5E3XdU7fqhtErf",' + b'"i":"1AABAg299p5IMvuw71HW_TlbzGq5cVOQ7bRbeDuhheF-DPYk","s":"2"}') + + + # Delegated Inception: + # Transferable not abandoned i.e. next not empty + # seed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) + seedD = (b'\x83B~\x04\x94\xe3\xceUQy\x11f\x0c\x93]\x1e\xbf\xacQ\xb5\xd6Y^\xa2E\xfa\x015' + b'\x98Y\xdd\xe8') + signerD = Signer(raw=seedD, code=MtrDex.ECDSA_256k1_Seed) # next signing keypair transferable is default + assert signerD.code == MtrDex.ECDSA_256k1_Seed + assert signerD.verfer.code == MtrDex.ECDSA_256k1 + keysD = [signerD.verfer.qb64] + # compute nxt digest + nxtD = [Diger(ser=key.encode("utf-8")).qb64 for key in keysD] # default sith is 1 + # transferable so nxt is not empty + + delpre = 'EAdHxtdjCQUM-TVO8CgJAKb8ykXsFe4u9epTUQFCL7Yd' + serderD = delcept(keys=keysD, delpre=delpre, ndigs=nxtD) + pre = serderD.ked["i"] + assert serderD.ked["i"] == 'EFVACrfsy2Ke_tjqq-wroc-TE0IFZ-QNwQwuMVzl0rgj' + assert serderD.ked["s"] == '0' + assert serderD.ked["t"] == Ilks.dip + assert serderD.ked["n"] == nxtD + assert serderD.raw == (b'{"v":"KERI10JSON000163_","t":"dip","d":"EFVACrfsy2Ke_tjqq-wroc-TE0IFZ-QNwQwuMVzl0rgj",' + b'"i":"EFVACrfsy2Ke_tjqq-wroc-TE0IFZ-QNwQwuMVzl0rgj","s":"0",' + b'"kt":"1","k":["1AABA7KZA_wxPCXJ5BgZ9jjdrMIy3OQKgHfa6eKyLcZpEn26"],' + b'"nt":"1","n":["EJ6Ycs7kho8XRxiq3DK37jiJ8mU9RP9HpSYnARm26EnO"],' + b'"bt":"0","b":[],"c":[],"a":[],"di":"EAdHxtdjCQUM-TVO8CgJAKb8ykXsFe4u9epTUQFCL7Yd"}') + + assert serderD.said == 'EFVACrfsy2Ke_tjqq-wroc-TE0IFZ-QNwQwuMVzl0rgj' + + # Delegated Rotation: + # Transferable not abandoned i.e. next not empty + seedR = (b'\xbe\x96\x02\xa9\x88\xce\xf9O\x1e\x0fo\xc0\xff\x98\xb6\xfa\x1e\xa2y\xf2' + b'e\xf9AL\x1aeK\xafj\xa1pB') + signerR = Signer(raw=seedR, code=MtrDex.ECDSA_256k1_Seed) # next signing keypair transferable is default + assert signerR.code == MtrDex.ECDSA_256k1_Seed + assert signerR.verfer.code == MtrDex.ECDSA_256k1 + keysR = [signerR.verfer.qb64] + # compute nxt digest + # default sith is 1 + nxtR = [Diger(ser=signerR.verfer.qb64b).qb64] # transferable so nxt is not empty + + delpre = 'EAdHxtdjCQUM-TVO8CgJAKb8ykXsFe4u9epTUQFCL7Yd' + serderR = deltate(pre=pre, + keys=keysR, + dig='EANkcl_QewzrRSKH2p9zUskHI462CuIMS_HQIO132Z30', + sn=4, + ndigs=nxtR) + + assert serderR.ked["i"] == pre + assert serderR.ked["s"] == '4' + assert serderR.ked["t"] == Ilks.drt + assert serderR.ked["n"] == nxtR + assert serderR.raw == (b'{"v":"KERI10JSON000164_","t":"drt","d":"EMN4ZdZEZzB0FyHzAOKehHTa6WvvBfK3xwylPuxoJ4sO",' + b'"i":"EFVACrfsy2Ke_tjqq-wroc-TE0IFZ-QNwQwuMVzl0rgj","s":"4","p":"EANkcl_QewzrRSKH2p9zUskHI462CuIMS_HQIO132Z30",' + b'"kt":"1","k":["1AABAh-zxZOUdAZwXBhbtZQgzD3LLPMYxF7HgsPbd2mILaPc"],' + b'"nt":"1","n":["EDn6z-KqmwcDVCql1CkMkvSNbNghhMF2TwsdllyP4a07"],' + b'"bt":"0","br":[],"ba":[],"a":[]}') + + assert serderR.said == 'EMN4ZdZEZzB0FyHzAOKehHTa6WvvBfK3xwylPuxoJ4sO' + """ Done Test """ @@ -1037,7 +1502,7 @@ def test_state(mockHelpingNowUTC): "n": "EZ-i0d8JZAoTNZH3ULvaU6JR2nmwyYAfSVPzhzS6b5CM", "wt": "1", "w": ["DnmwyYAfSVPzhzS6b5CMZ-i0d8JZAoTNZH3ULvaU6JR2"], - "c": ["eo"], + "c": ["EO"], "ee": { "s": "1", @@ -1100,7 +1565,7 @@ def test_state(mockHelpingNowUTC): br=[preW0], ba=[preW3]) - serderK = state(pre=preC, + ksr = state(pre=preC, sn=4, pig='EAskHI462CuIMS_gNkcl_QewzrRSKH2p9zHQIO132Z30', dig='EANkcl_QewzrRSKH2p9zUskHI462CuIMS_HQIO132Z30', @@ -1114,20 +1579,21 @@ def test_state(mockHelpingNowUTC): wits=wits, ) - assert serderK.raw == (b'{"v":"KERI10JSON0002ca_","i":"DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN",' - b'"s":"4","p":"EAskHI462CuIMS_gNkcl_QewzrRSKH2p9zHQIO132Z30","d":"EANkcl_Qewzr' - b'RSKH2p9zUskHI462CuIMS_HQIO132Z30","f":"4","dt":"2021-01-01T00:00:00.000000+0' - b'0:00","et":"ixn","kt":"1","k":["DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN' - b'"],"nt":"1","n":["EDDOarj1lzr8pqG5a-SSnM2cc_3JgstRRjmzrrA_Bibg"],"bt":"2","b' - b'":["BGhCNcrRBR6mlBduhbuCYL7Bwc3gbuyaGo9opZsd0D8F","BO7x6ctSA7FllJx39hlObneti' - b'zGFjuZT1jq0geno0NRK","BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oCFASqwRc_9W"],"c":[],' - b'"ee":{"s":"3","d":"EUskHI462CuIMS_gNkcl_QewzrRSKH2p9zHQIO132Z30","br":["BDU5' - b'LLVHxQSb9EdSKDTYyqViusxGT8Y4DHOyktkOv5Rt"],"ba":["BK7isi_2-A-RE6Pbtdg7S1NSei' - b'nNQkJ4oCFASqwRc_9W"]},"di":""}') - - assert serderK.said == 'EANkcl_QewzrRSKH2p9zUskHI462CuIMS_HQIO132Z30' - assert serderK.pre == preC == 'DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN' - assert serderK.sn == 4 + raw = ksr._asjson() + assert raw == (b'{"vn":[1,0],"i":"DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN","s":"4","p":"' + b'EAskHI462CuIMS_gNkcl_QewzrRSKH2p9zHQIO132Z30","d":"EANkcl_QewzrRSKH2p9zUskHI' + b'462CuIMS_HQIO132Z30","f":"4","dt":"2021-01-01T00:00:00.000000+00:00","et":"i' + b'xn","kt":"1","k":["DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN"],"nt":"1","' + b'n":["EDDOarj1lzr8pqG5a-SSnM2cc_3JgstRRjmzrrA_Bibg"],"bt":"2","b":["BGhCNcrRB' + b'R6mlBduhbuCYL7Bwc3gbuyaGo9opZsd0D8F","BO7x6ctSA7FllJx39hlObnetizGFjuZT1jq0ge' + b'no0NRK","BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oCFASqwRc_9W"],"c":[],"ee":{"s":"3"' + b',"d":"EUskHI462CuIMS_gNkcl_QewzrRSKH2p9zHQIO132Z30","br":["BDU5LLVHxQSb9EdSK' + b'DTYyqViusxGT8Y4DHOyktkOv5Rt"],"ba":["BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oCFASqw' + b'Rc_9W"]},"di":""}') + + assert ksr.d == 'EANkcl_QewzrRSKH2p9zUskHI462CuIMS_HQIO132Z30' + assert ksr.i == preC == 'DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN' + assert ksr.s == '4' # create endorsed ksn with nontrans endorser # create nontrans key pair for endorder of KSN @@ -1136,23 +1602,23 @@ def test_state(mockHelpingNowUTC): preE = signerE.verfer.qb64 # use public key verfer.qb64 as pre assert preE == 'BMrwi0a-Zblpqe5Hg7w7iz9JCKnMgWKu_W9w4aNUL64y' - cigarE = signerE.sign(ser=serderK.raw) - assert signerE.verfer.verify(sig=cigarE.raw, ser=serderK.raw) - msg = messagize(serderK, cigars=[cigarE]) - assert msg == (b'{"v":"KERI10JSON0002ca_","i":"DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6' - b'llSvWQTWZN","s":"4","p":"EAskHI462CuIMS_gNkcl_QewzrRSKH2p9zHQIO1' - b'32Z30","d":"EANkcl_QewzrRSKH2p9zUskHI462CuIMS_HQIO132Z30","f":"4' - b'","dt":"2021-01-01T00:00:00.000000+00:00","et":"ixn","kt":"1","k' - b'":["DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN"],"nt":"1","n":' - b'["EDDOarj1lzr8pqG5a-SSnM2cc_3JgstRRjmzrrA_Bibg"],"bt":"2","b":["' - b'BGhCNcrRBR6mlBduhbuCYL7Bwc3gbuyaGo9opZsd0D8F","BO7x6ctSA7FllJx39' - b'hlObnetizGFjuZT1jq0geno0NRK","BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oC' - b'FASqwRc_9W"],"c":[],"ee":{"s":"3","d":"EUskHI462CuIMS_gNkcl_Qewz' - b'rRSKH2p9zHQIO132Z30","br":["BDU5LLVHxQSb9EdSKDTYyqViusxGT8Y4DHOy' - b'ktkOv5Rt"],"ba":["BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oCFASqwRc_9W"]' - b'},"di":""}-CABBMrwi0a-Zblpqe5Hg7w7iz9JCKnMgWKu_W9w4aNUL64y0BB6cL' - b'0DtDVDW26lgjbQu0_D_Pd_6ovBZj6fU-Qjmm7epVs51jEOOwXKbmG4yUvCSN-DQS' - b'YSc7HXZRp8CfAw9DQL') + cigarE = signerE.sign(ser=raw) + assert signerE.verfer.verify(sig=cigarE.raw, ser=raw) + #msg = messagize(serderK, cigars=[cigarE]) + #assert msg == (b'{"v":"KERI10JSON0002d5_","vn":[1,0],"i":"DN6WBhWqp6wC08no2iWhgFY' + #b'TaUgrasnqz6llSvWQTWZN","s":"4","p":"EAskHI462CuIMS_gNkcl_QewzrRS' + #b'KH2p9zHQIO132Z30","d":"EANkcl_QewzrRSKH2p9zUskHI462CuIMS_HQIO132' + #b'Z30","f":"4","dt":"2021-01-01T00:00:00.000000+00:00","et":"ixn",' + #b'"kt":"1","k":["DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN"],"n' + #b't":"1","n":["EDDOarj1lzr8pqG5a-SSnM2cc_3JgstRRjmzrrA_Bibg"],"bt"' + #b':"2","b":["BGhCNcrRBR6mlBduhbuCYL7Bwc3gbuyaGo9opZsd0D8F","BO7x6c' + #b'tSA7FllJx39hlObnetizGFjuZT1jq0geno0NRK","BK7isi_2-A-RE6Pbtdg7S1N' + #b'SeinNQkJ4oCFASqwRc_9W"],"c":[],"ee":{"s":"3","d":"EUskHI462CuIMS' + #b'_gNkcl_QewzrRSKH2p9zHQIO132Z30","br":["BDU5LLVHxQSb9EdSKDTYyqViu' + #b'sxGT8Y4DHOyktkOv5Rt"],"ba":["BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oCF' + #b'ASqwRc_9W"]},"di":""}-CABBMrwi0a-Zblpqe5Hg7w7iz9JCKnMgWKu_W9w4aN' + #b'UL64y0BAxhIlDV7ZlsV2OoDi1ltOQOFA7Z7NnjdoEL_rKdO2mn2q3LEtbPPYgJ9t' + #b'UOIE3F0BABStJ5RMOrqnrwntbZesG') # create endorsed ksn with trans endorser # create trans key pair for endorder of KSN @@ -1167,24 +1633,24 @@ def test_state(mockHelpingNowUTC): d='EAuNWHss_H_kH4cG7Li1jn2DXfrEaqN7zhqTEhkeDZ2z') # create endorsed ksn - sigerE = signerE.sign(ser=serderK.raw, index=0) - assert signerE.verfer.verify(sig=sigerE.raw, ser=serderK.raw) - msg = messagize(serderK, sigers=[sigerE], seal=seal) - assert msg == (b'{"v":"KERI10JSON0002ca_","i":"DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6' - b'llSvWQTWZN","s":"4","p":"EAskHI462CuIMS_gNkcl_QewzrRSKH2p9zHQIO1' - b'32Z30","d":"EANkcl_QewzrRSKH2p9zUskHI462CuIMS_HQIO132Z30","f":"4' - b'","dt":"2021-01-01T00:00:00.000000+00:00","et":"ixn","kt":"1","k' - b'":["DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN"],"nt":"1","n":' - b'["EDDOarj1lzr8pqG5a-SSnM2cc_3JgstRRjmzrrA_Bibg"],"bt":"2","b":["' - b'BGhCNcrRBR6mlBduhbuCYL7Bwc3gbuyaGo9opZsd0D8F","BO7x6ctSA7FllJx39' - b'hlObnetizGFjuZT1jq0geno0NRK","BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oC' - b'FASqwRc_9W"],"c":[],"ee":{"s":"3","d":"EUskHI462CuIMS_gNkcl_Qewz' - b'rRSKH2p9zHQIO132Z30","br":["BDU5LLVHxQSb9EdSKDTYyqViusxGT8Y4DHOy' - b'ktkOv5Rt"],"ba":["BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oCFASqwRc_9W"]' - b'},"di":""}-FABDMrwi0a-Zblpqe5Hg7w7iz9JCKnMgWKu_W9w4aNUL64y0AAAAA' - b'AAAAAAAAAAAAAAAAAAEAuNWHss_H_kH4cG7Li1jn2DXfrEaqN7zhqTEhkeDZ2z-A' - b'ABAAB6cL0DtDVDW26lgjbQu0_D_Pd_6ovBZj6fU-Qjmm7epVs51jEOOwXKbmG4yU' - b'vCSN-DQSYSc7HXZRp8CfAw9DQL') + sigerE = signerE.sign(ser=raw, index=0) + assert signerE.verfer.verify(sig=sigerE.raw, ser=raw) + #msg = messagize(serderK, sigers=[sigerE], seal=seal) + #assert msg == (b'{"v":"KERI10JSON0002d5_","vn":[1,0],"i":"DN6WBhWqp6wC08no2iWhgFY' + #b'TaUgrasnqz6llSvWQTWZN","s":"4","p":"EAskHI462CuIMS_gNkcl_QewzrRS' + #b'KH2p9zHQIO132Z30","d":"EANkcl_QewzrRSKH2p9zUskHI462CuIMS_HQIO132' + #b'Z30","f":"4","dt":"2021-01-01T00:00:00.000000+00:00","et":"ixn",' + #b'"kt":"1","k":["DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN"],"n' + #b't":"1","n":["EDDOarj1lzr8pqG5a-SSnM2cc_3JgstRRjmzrrA_Bibg"],"bt"' + #b':"2","b":["BGhCNcrRBR6mlBduhbuCYL7Bwc3gbuyaGo9opZsd0D8F","BO7x6c' + #b'tSA7FllJx39hlObnetizGFjuZT1jq0geno0NRK","BK7isi_2-A-RE6Pbtdg7S1N' + #b'SeinNQkJ4oCFASqwRc_9W"],"c":[],"ee":{"s":"3","d":"EUskHI462CuIMS' + #b'_gNkcl_QewzrRSKH2p9zHQIO132Z30","br":["BDU5LLVHxQSb9EdSKDTYyqViu' + #b'sxGT8Y4DHOyktkOv5Rt"],"ba":["BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oCF' + #b'ASqwRc_9W"]},"di":""}-FABDMrwi0a-Zblpqe5Hg7w7iz9JCKnMgWKu_W9w4aN' + #b'UL64y0AAAAAAAAAAAAAAAAAAAAAAAEAuNWHss_H_kH4cG7Li1jn2DXfrEaqN7zhq' + #b'TEhkeDZ2z-AABAAAxhIlDV7ZlsV2OoDi1ltOQOFA7Z7NnjdoEL_rKdO2mn2q3LEt' + #b'bPPYgJ9tUOIE3F0BABStJ5RMOrqnrwntbZesG') # State Delegated (key state notification) @@ -1237,7 +1703,7 @@ def test_state(mockHelpingNowUTC): preD = signerD.verfer.qb64 # use public key verfer.qb64 as trans pre assert preD == 'DBs-gd3nJGtF0Ch2jn7NLaUKsCKB7l3nLs-993_s5Ie1' - serderK = state(pre=preC, + ksr = state(pre=preC, sn=4, pig='EAskHI462CuIMS_gNkcl_QewzrRSKH2p9zHQIO132Z30', dig='EANkcl_QewzrRSKH2p9zUskHI462CuIMS_HQIO132Z30', @@ -1252,20 +1718,21 @@ def test_state(mockHelpingNowUTC): dpre=preD ) - assert serderK.raw == (b'{"v":"KERI10JSON0002f6_","i":"DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN",' - b'"s":"4","p":"EAskHI462CuIMS_gNkcl_QewzrRSKH2p9zHQIO132Z30","d":"EANkcl_Qewzr' - b'RSKH2p9zUskHI462CuIMS_HQIO132Z30","f":"4","dt":"2021-01-01T00:00:00.000000+0' - b'0:00","et":"ixn","kt":"1","k":["DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN' - b'"],"nt":"1","n":["EDDOarj1lzr8pqG5a-SSnM2cc_3JgstRRjmzrrA_Bibg"],"bt":"2","b' - b'":["BGhCNcrRBR6mlBduhbuCYL7Bwc3gbuyaGo9opZsd0D8F","BO7x6ctSA7FllJx39hlObneti' - b'zGFjuZT1jq0geno0NRK","BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oCFASqwRc_9W"],"c":[],' - b'"ee":{"s":"3","d":"EAskHI462CuIMS_gNkcl_QewzrRSKH2p9zHQIO132Z30","br":["BDU5' - b'LLVHxQSb9EdSKDTYyqViusxGT8Y4DHOyktkOv5Rt"],"ba":["BK7isi_2-A-RE6Pbtdg7S1NSei' - b'nNQkJ4oCFASqwRc_9W"]},"di":"DBs-gd3nJGtF0Ch2jn7NLaUKsCKB7l3nLs-993_s5Ie1"}') - - assert serderK.said == 'EANkcl_QewzrRSKH2p9zUskHI462CuIMS_HQIO132Z30' - assert serderK.pre == preC == 'DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN' - assert serderK.sn == 4 + raw = ksr._asjson() + assert raw == (b'{"vn":[1,0],"i":"DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN","s":"4","p":"' + b'EAskHI462CuIMS_gNkcl_QewzrRSKH2p9zHQIO132Z30","d":"EANkcl_QewzrRSKH2p9zUskHI' + b'462CuIMS_HQIO132Z30","f":"4","dt":"2021-01-01T00:00:00.000000+00:00","et":"i' + b'xn","kt":"1","k":["DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN"],"nt":"1","' + b'n":["EDDOarj1lzr8pqG5a-SSnM2cc_3JgstRRjmzrrA_Bibg"],"bt":"2","b":["BGhCNcrRB' + b'R6mlBduhbuCYL7Bwc3gbuyaGo9opZsd0D8F","BO7x6ctSA7FllJx39hlObnetizGFjuZT1jq0ge' + b'no0NRK","BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oCFASqwRc_9W"],"c":[],"ee":{"s":"3"' + b',"d":"EAskHI462CuIMS_gNkcl_QewzrRSKH2p9zHQIO132Z30","br":["BDU5LLVHxQSb9EdSK' + b'DTYyqViusxGT8Y4DHOyktkOv5Rt"],"ba":["BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oCFASqw' + b'Rc_9W"]},"di":"DBs-gd3nJGtF0Ch2jn7NLaUKsCKB7l3nLs-993_s5Ie1"}') + + assert ksr.d == 'EANkcl_QewzrRSKH2p9zUskHI462CuIMS_HQIO132Z30' + assert ksr.i == preC == 'DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN' + assert ksr.s == '4' # create endorsed ksn with nontrans endorser # create nontrans key pair for endorder of KSN @@ -1275,23 +1742,24 @@ def test_state(mockHelpingNowUTC): assert preE == 'BMrwi0a-Zblpqe5Hg7w7iz9JCKnMgWKu_W9w4aNUL64y' # create endorsed ksn - cigarE = signerE.sign(ser=serderK.raw) - assert signerE.verfer.verify(sig=cigarE.raw, ser=serderK.raw) - msg = messagize(serderK, cigars=[cigarE]) - assert msg == (b'{"v":"KERI10JSON0002f6_","i":"DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6' - b'llSvWQTWZN","s":"4","p":"EAskHI462CuIMS_gNkcl_QewzrRSKH2p9zHQIO1' - b'32Z30","d":"EANkcl_QewzrRSKH2p9zUskHI462CuIMS_HQIO132Z30","f":"4' - b'","dt":"2021-01-01T00:00:00.000000+00:00","et":"ixn","kt":"1","k' - b'":["DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN"],"nt":"1","n":' - b'["EDDOarj1lzr8pqG5a-SSnM2cc_3JgstRRjmzrrA_Bibg"],"bt":"2","b":["' - b'BGhCNcrRBR6mlBduhbuCYL7Bwc3gbuyaGo9opZsd0D8F","BO7x6ctSA7FllJx39' - b'hlObnetizGFjuZT1jq0geno0NRK","BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oC' - b'FASqwRc_9W"],"c":[],"ee":{"s":"3","d":"EAskHI462CuIMS_gNkcl_Qewz' - b'rRSKH2p9zHQIO132Z30","br":["BDU5LLVHxQSb9EdSKDTYyqViusxGT8Y4DHOy' - b'ktkOv5Rt"],"ba":["BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oCFASqwRc_9W"]' - b'},"di":"DBs-gd3nJGtF0Ch2jn7NLaUKsCKB7l3nLs-993_s5Ie1"}-CABBMrwi0' - b'a-Zblpqe5Hg7w7iz9JCKnMgWKu_W9w4aNUL64y0BDxwyDfiEThnNS8d928EUfIDm' - b'YDfoWUp0wdwIPaeanzIYOjAFtwxJcS7wiH9ICW7LJy7drrlOd4-uXqkV-YsIwK') + cigarE = signerE.sign(ser=raw) + assert signerE.verfer.verify(sig=cigarE.raw, ser=raw) + #msg = messagize(serderK, cigars=[cigarE]) + #assert msg == (b'{"v":"KERI10JSON000301_","vn":[1,0],"i":"DN6WBhWqp6wC08no2iWhgFY' + #b'TaUgrasnqz6llSvWQTWZN","s":"4","p":"EAskHI462CuIMS_gNkcl_QewzrRS' + #b'KH2p9zHQIO132Z30","d":"EANkcl_QewzrRSKH2p9zUskHI462CuIMS_HQIO132' + #b'Z30","f":"4","dt":"2021-01-01T00:00:00.000000+00:00","et":"ixn",' + #b'"kt":"1","k":["DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN"],"n' + #b't":"1","n":["EDDOarj1lzr8pqG5a-SSnM2cc_3JgstRRjmzrrA_Bibg"],"bt"' + #b':"2","b":["BGhCNcrRBR6mlBduhbuCYL7Bwc3gbuyaGo9opZsd0D8F","BO7x6c' + #b'tSA7FllJx39hlObnetizGFjuZT1jq0geno0NRK","BK7isi_2-A-RE6Pbtdg7S1N' + #b'SeinNQkJ4oCFASqwRc_9W"],"c":[],"ee":{"s":"3","d":"EAskHI462CuIMS' + #b'_gNkcl_QewzrRSKH2p9zHQIO132Z30","br":["BDU5LLVHxQSb9EdSKDTYyqViu' + #b'sxGT8Y4DHOyktkOv5Rt"],"ba":["BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oCF' + #b'ASqwRc_9W"]},"di":"DBs-gd3nJGtF0Ch2jn7NLaUKsCKB7l3nLs-993_s5Ie1"' + #b'}-CABBMrwi0a-Zblpqe5Hg7w7iz9JCKnMgWKu_W9w4aNUL64y0BBTNCmYMYJNNQd' + #b'QN9irkUaL_RBdztUlykS6P-fxDUofmfWuTL7Hb0XHjtcT-wJMCYO7G1i5IbuhSHf' + #b'o-maDh4EG') # create endorsed ksn with trans endorser # create trans key pair for endorder of KSN @@ -1306,25 +1774,25 @@ def test_state(mockHelpingNowUTC): d='EAuNWHss_H_kH4cG7Li1jn2DXfrEaqN7zhqTEhkeDZ2z') # create endorsed ksn - sigerE = signerE.sign(ser=serderK.raw, index=0) - assert signerE.verfer.verify(sig=sigerE.raw, ser=serderK.raw) - msg = messagize(serderK, sigers=[sigerE], seal=seal) - assert msg == (b'{"v":"KERI10JSON0002f6_","i":"DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6' - b'llSvWQTWZN","s":"4","p":"EAskHI462CuIMS_gNkcl_QewzrRSKH2p9zHQIO1' - b'32Z30","d":"EANkcl_QewzrRSKH2p9zUskHI462CuIMS_HQIO132Z30","f":"4' - b'","dt":"2021-01-01T00:00:00.000000+00:00","et":"ixn","kt":"1","k' - b'":["DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN"],"nt":"1","n":' - b'["EDDOarj1lzr8pqG5a-SSnM2cc_3JgstRRjmzrrA_Bibg"],"bt":"2","b":["' - b'BGhCNcrRBR6mlBduhbuCYL7Bwc3gbuyaGo9opZsd0D8F","BO7x6ctSA7FllJx39' - b'hlObnetizGFjuZT1jq0geno0NRK","BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oC' - b'FASqwRc_9W"],"c":[],"ee":{"s":"3","d":"EAskHI462CuIMS_gNkcl_Qewz' - b'rRSKH2p9zHQIO132Z30","br":["BDU5LLVHxQSb9EdSKDTYyqViusxGT8Y4DHOy' - b'ktkOv5Rt"],"ba":["BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oCFASqwRc_9W"]' - b'},"di":"DBs-gd3nJGtF0Ch2jn7NLaUKsCKB7l3nLs-993_s5Ie1"}-FABDMrwi0' - b'a-Zblpqe5Hg7w7iz9JCKnMgWKu_W9w4aNUL64y0AAAAAAAAAAAAAAAAAAAAAAAEA' - b'uNWHss_H_kH4cG7Li1jn2DXfrEaqN7zhqTEhkeDZ2z-AABAADxwyDfiEThnNS8d9' - b'28EUfIDmYDfoWUp0wdwIPaeanzIYOjAFtwxJcS7wiH9ICW7LJy7drrlOd4-uXqkV' - b'-YsIwK') + sigerE = signerE.sign(ser=raw, index=0) + assert signerE.verfer.verify(sig=sigerE.raw, ser=raw) + #msg = messagize(serderK, sigers=[sigerE], seal=seal) + #assert msg == (b'{"v":"KERI10JSON000301_","vn":[1,0],"i":"DN6WBhWqp6wC08no2iWhgFY' + #b'TaUgrasnqz6llSvWQTWZN","s":"4","p":"EAskHI462CuIMS_gNkcl_QewzrRS' + #b'KH2p9zHQIO132Z30","d":"EANkcl_QewzrRSKH2p9zUskHI462CuIMS_HQIO132' + #b'Z30","f":"4","dt":"2021-01-01T00:00:00.000000+00:00","et":"ixn",' + #b'"kt":"1","k":["DN6WBhWqp6wC08no2iWhgFYTaUgrasnqz6llSvWQTWZN"],"n' + #b't":"1","n":["EDDOarj1lzr8pqG5a-SSnM2cc_3JgstRRjmzrrA_Bibg"],"bt"' + #b':"2","b":["BGhCNcrRBR6mlBduhbuCYL7Bwc3gbuyaGo9opZsd0D8F","BO7x6c' + #b'tSA7FllJx39hlObnetizGFjuZT1jq0geno0NRK","BK7isi_2-A-RE6Pbtdg7S1N' + #b'SeinNQkJ4oCFASqwRc_9W"],"c":[],"ee":{"s":"3","d":"EAskHI462CuIMS' + #b'_gNkcl_QewzrRSKH2p9zHQIO132Z30","br":["BDU5LLVHxQSb9EdSKDTYyqViu' + #b'sxGT8Y4DHOyktkOv5Rt"],"ba":["BK7isi_2-A-RE6Pbtdg7S1NSeinNQkJ4oCF' + #b'ASqwRc_9W"]},"di":"DBs-gd3nJGtF0Ch2jn7NLaUKsCKB7l3nLs-993_s5Ie1"' + #b'}-FABDMrwi0a-Zblpqe5Hg7w7iz9JCKnMgWKu_W9w4aNUL64y0AAAAAAAAAAAAAA' + #b'AAAAAAAAAEAuNWHss_H_kH4cG7Li1jn2DXfrEaqN7zhqTEhkeDZ2z-AABAABTNCm' + #b'YMYJNNQdQN9irkUaL_RBdztUlykS6P-fxDUofmfWuTL7Hb0XHjtcT-wJMCYO7G1i' + #b'5IbuhSHfo-maDh4EG') """Done Test""" @@ -1598,14 +2066,15 @@ def test_kever(mockHelpingNowUTC): # create current key sith = 1 # one signer # original signing keypair transferable default - skp0 = salter.signer(path="A", temp=True) + skp0 = salter.signer(path="A", temp=True, transferable=True) assert skp0.code == MtrDex.Ed25519_Seed assert skp0.verfer.code == MtrDex.Ed25519 + assert skp0.verfer.qb64 == 'DAUDqkmn-hqlQKD8W-FAEa5JUvJC2I9yarEem-AAEg3e' keys = [skp0.verfer.qb64] # create next key # next signing keypair transferable is default - skp1 = salter.signer(path="N", temp=True) + skp1 = salter.signer(path="N", temp=True, transferable=True) assert skp1.code == MtrDex.Ed25519_Seed assert skp1.verfer.code == MtrDex.Ed25519 # compute nxt digest @@ -1618,32 +2087,27 @@ def test_kever(mockHelpingNowUTC): toad = 0 # no witnesses nsigs = 1 # one attached signature unspecified index - ked0 = dict(v=versify(kind=Serials.json, size=0), - t=Ilks.icp, - d="", - i="", # qual base 64 prefix - s="{:x}".format(sn), # hex string no leading zeros lowercase - kt="{:x}".format(sith), # hex string no leading zeros lowercase - k=keys, # list of signing keys each qual Base64 - n=nxt, # hash qual Base64 - bt="{:x}".format(toad), # hex string no leading zeros lowercase - b=[], # list of qual Base64 may be empty - c=[], # list of config ordered mappings may be empty - a=[], # list of seals - ) + # make with defaults with non-digestive prefix + serder = serdering.SerderKERI(makify=True, + ilk=kering.Ilks.icp, + saids = {'i': coring.PreDex.Ed25519}) - # Derive AID from ked - aid0 = Prefixer(ked=ked0, code=MtrDex.Ed25519) - assert aid0.code == MtrDex.Ed25519 - assert aid0.qb64 == skp0.verfer.qb64 == 'DAUDqkmn-hqlQKD8W-FAEa5JUvJC2I9yarEem-AAEg3e' - _, ked0 = coring.Saider.saidify(sad=ked0) - assert ked0['d'] == 'EOm-FWMD-CxK0-FC6NUj35kptATRCztnY7Q0B3En7B8g' + sad = serder.sad + sad['i'] = skp0.verfer.qb64 # non-digestive aid + sad['s'] = "{:x}".format(sn) # hex string + sad['kt'] = "{:x}".format(sith) # hex string + sad['k'] = keys + sad['nt'] = 1 + sad['n'] = nxt + sad['bt'] = "{:x}".format(toad) - # update ked with pre - ked0["i"] = aid0.qb64 + serder = serdering.SerderKERI(makify=True, verify=True, sad=sad) + assert serder.said == 'EBTCANzIfUThxmM1z1SFxQuwooGdF4QwtotRS01vZGqi' + assert serder.pre == 'DAUDqkmn-hqlQKD8W-FAEa5JUvJC2I9yarEem-AAEg3e' + aid0 = serder.pre - # Serialize ked0 - tser0 = Serder(ked=ked0) + # Assign first serialization + tser0 = serder # sign serialization tsig0 = skp0.sign(tser0.raw, index=0) @@ -1654,28 +2118,28 @@ def test_kever(mockHelpingNowUTC): kever = Kever(serder=tser0, sigers=[tsig0], db=db) # no error assert kever.db == db assert kever.cues == None - assert kever.prefixer.qb64 == aid0.qb64 + assert kever.prefixer.qb64 == aid0 assert kever.sner.num == 0 assert kever.sn == kever.sner.num # sn property assert [verfer.qb64 for verfer in kever.verfers] == [skp0.verfer.qb64] - assert kever.digs == nxt + assert kever.ndigs == nxt state = kever.db.states.get(keys=kever.prefixer.qb64) - assert state.sn == kever.sner.num == 0 + assert state.s == kever.sner.numh == '0' feqner = kever.db.fons.get(keys=(kever.prefixer.qb64, kever.serder.said)) assert feqner.sn == kever.sn - serderK = kever.state() - assert serderK.ked == state.ked - assert serderK.pre == kever.prefixer.qb64 - assert serderK.sn == kever.sn - assert ([verfer.qb64 for verfer in serderK.verfers] == + ksr = kever.state() # key state record + assert ksr == state + assert ksr.i == kever.prefixer.qb64 + assert ksr.s == kever.sner.numh + assert ([key for key in ksr.k] == [verfer.qb64 for verfer in kever.verfers]) - assert serderK.raw == (b'{"v":"KERI10JSON0001b6_","i":"DAUDqkmn-hqlQKD8W-FAEa5JUvJC2I9yarEem-AAEg3e",' - b'"s":"0","p":"","d":"EOm-FWMD-CxK0-FC6NUj35kptATRCztnY7Q0B3En7B8g","f":"0","d' - b't":"2021-01-01T00:00:00.000000+00:00","et":"icp","kt":"1","k":["DAUDqkmn-hql' - b'QKD8W-FAEa5JUvJC2I9yarEem-AAEg3e"],"nt":"0","n":["EAKUR-LmLHWMwXTLWQ1QjxHrih' - b'BmwwrV2tYaSG7hOrWj"],"bt":"0","b":[],"c":[],"ee":{"s":"0","d":"EOm-FWMD-CxK0' - b'-FC6NUj35kptATRCztnY7Q0B3En7B8g","br":[],"ba":[]},"di":""}') + assert ksr._asjson() == (b'{"vn":[1,0],"i":"DAUDqkmn-hqlQKD8W-FAEa5JUvJC2I9yarEem-AAEg3e","s":"0","p":"' + b'","d":"EBTCANzIfUThxmM1z1SFxQuwooGdF4QwtotRS01vZGqi","f":"0","dt":"2021-01-0' + b'1T00:00:00.000000+00:00","et":"icp","kt":"1","k":["DAUDqkmn-hqlQKD8W-FAEa5JU' + b'vJC2I9yarEem-AAEg3e"],"nt":"1","n":["EAKUR-LmLHWMwXTLWQ1QjxHrihBmwwrV2tYaSG7' + b'hOrWj"],"bt":"0","b":[],"c":[],"ee":{"s":"0","d":"EBTCANzIfUThxmM1z1SFxQuwoo' + b'GdF4QwtotRS01vZGqi","br":[],"ba":[]},"di":""}') # test exposeds raw = b"raw salt to test" @@ -1697,7 +2161,7 @@ def test_kever(mockHelpingNowUTC): digers.reverse() - kever.digers = digers # Monkey patch for test + kever.ndigers = digers # Monkey patch for test ondices = kever.exposeds(sigers=sigers) assert ondices ==[2, 1, 0] @@ -1716,7 +2180,7 @@ def test_kever(mockHelpingNowUTC): sigers = [siger0, siger1, siger2] digers = [diger0, diger1] - kever.digers = digers # Monkey patch for test + kever.ndigers = digers # Monkey patch for test ondices = kever.exposeds(sigers=sigers) assert ondices ==[0, 1] @@ -1736,62 +2200,57 @@ def test_kever(mockHelpingNowUTC): sigers = [siger0, siger1, siger2] digers = [diger0, diger1] - kever.digers = digers # Monkey patch for test + kever.ndigers = digers # Monkey patch for test ondices = kever.exposeds(sigers=sigers) assert ondices ==[1] - - - with openDB() as db: # Non-Transferable case + with openDB() as db: # Non-Transferable case Error nxt not empty + # test Error case Transferable incept event but with nontrans aid # Setup inception key event dict # create current key sith = 1 # one signer - skp0 = Signer(transferable=False) # original signing keypair non-transferable + # original signing keypair non-transferable + skp0 = salter.signer(path="A", temp=True, transferable=False) assert skp0.code == MtrDex.Ed25519_Seed assert skp0.verfer.code == MtrDex.Ed25519N + assert skp0.verfer.qb64 == 'BAUDqkmn-hqlQKD8W-FAEa5JUvJC2I9yarEem-AAEg3e' keys = [skp0.verfer.qb64] - # create next key Error case - skp1 = Signer() # next signing keypair transferable is default + # create next key + # next signing keypair transferable is default + skp1 = salter.signer(path="N", temp=True, transferable=True) assert skp1.code == MtrDex.Ed25519_Seed assert skp1.verfer.code == MtrDex.Ed25519 nxtkeys = [skp1.verfer.qb64] # compute nxt digest - nxt = [Diger(ser=skp1.verfer.qb64b).qb64] # nxt is not empty so error + nxt = [Diger(ser=skp1.verfer.qb64b).qb64] # nxt is not empty so will error sn = 0 # inception event so 0 toad = 0 # no witnesses nsigs = 1 # one attached signature unspecified index - ked0 = dict(v=versify(kind=Serials.json, size=0), - t=Ilks.icp, - d="", - i="", # qual base 64 prefix - s="{:x}".format(sn), # hex string no leading zeros lowercase - kt="{:x}".format(sith), # hex string no leading zeros lowercase - k=keys, # list of signing keys each qual Base64 - n=nxt, # hash qual Base64 - bt="{:x}".format(toad), # hex string no leading zeros lowercase - b=[], # list of qual Base64 may be empty - c=[], # list of config ordered mappings may be empty - a={}, # list of seals - ) + # make with defaults with non-transferable prefix + serder = serdering.SerderKERI(makify=True, + ilk=kering.Ilks.icp, + saids = {'i': coring.PreDex.Ed25519N}) - # Derive AID from ked - with pytest.raises(DerivationError): - aid0 = Prefixer(ked=ked0, code=MtrDex.Ed25519N) + sad = serder.sad + sad['i'] = skp0.verfer.qb64 # non-digestive aid + sad['s'] = "{:x}".format(sn) # hex string + sad['kt'] = "{:x}".format(sith) # hex string + sad['k'] = keys + sad['nt'] = 1 + sad['n'] = nxt + sad['bt'] = "{:x}".format(toad) - _, ked0 = coring.Saider.saidify(sad=ked0) + serder = serdering.SerderKERI(makify=True, verify=True, sad=sad) + assert serder.said == 'EFsuiA86Q5gGuVOO3tou8KSU6LORSExIUxzWNrlnW7WP' + assert serder.pre == skp0.verfer.qb64 + aid0 = serder.pre - # assert aid0.code == MtrDex.Ed25519N - # assert aid0.qb64 == skp0.verfer.qb64 - - # update ked with pre - ked0["i"] = skp0.verfer.qb64 - - # Serialize ked0 - tser0 = Serder(ked=ked0) + # assign serialization + tser0 = serder # sign serialization tsig0 = skp0.sign(tser0.raw, index=0) @@ -1806,34 +2265,29 @@ def test_kever(mockHelpingNowUTC): nxt = "" # nxt is empty so no error sn = 0 # inception event so 0 toad = 0 # no witnesses - nsigs = 1 # one attached signature unspecified index - ked0 = dict(v=versify(kind=Serials.json, size=0), - t=Ilks.icp, - d="", - i="", # qual base 64 prefix - s="{:x}".format(sn), # hex string no leading zeros lowercase - kt="{:x}".format(sith), # hex string no leading zeros lowercase - k=keys, # list of signing keys each qual Base64 - n=nxt, # hash qual Base64 - bt="{:x}".format(toad), # hex string no leading zeros lowercase - b=[], # list of qual Base64 may be empty - c=[], # list of config ordered mappings may be empty - a=[], # list of seals - ) - # Derive AID from ked - aid0 = Prefixer(ked=ked0, code=MtrDex.Ed25519N) + # make with defaults with non-transferable prefix + serder = serdering.SerderKERI(makify=True, + ilk=kering.Ilks.icp, + saids = {'i': coring.PreDex.Ed25519N}) - assert aid0.code == MtrDex.Ed25519N - assert aid0.qb64 == skp0.verfer.qb64 + sad = serder.sad + sad['i'] = skp0.verfer.qb64 # non-digestive aid + sad['s'] = "{:x}".format(sn) # hex string + sad['kt'] = "{:x}".format(sith) # hex string + sad['k'] = keys + sad['nt'] = 0 + sad['n'] = nxt + sad['bt'] = "{:x}".format(toad) - # update ked with pre - ked0["i"] = aid0.qb64 - _, ked0 = coring.Saider.saidify(sad=ked0) + serder = serdering.SerderKERI(makify=True, verify=True, sad=sad) + assert serder.said == 'EHXNdcXZzJnRIdaNk30W5h4yD5sZ2Y_n3u_ReE65X9w-' + assert serder.pre == skp0.verfer.qb64 + aid0 = serder.pre - # Serialize ked0 - tser0 = Serder(ked=ked0) + # assign serialization + tser0 = serder # sign serialization tsig0 = skp0.sign(tser0.raw, index=0) @@ -1843,53 +2297,77 @@ def test_kever(mockHelpingNowUTC): kever = Kever(serder=tser0, sigers=[tsig0], db=db) # valid so no error - with openDB() as db: # Non-Transferable case + with openDB() as db: # Non-Transferable case baks not empty # Setup inception key event dict # create current key - sith = 1 # one signer - skp0 = Signer(transferable=False) # original signing keypair non-transferable + + # original signing keypair non-transferable + skp0 = salter.signer(path="B", temp=True, transferable=False) assert skp0.code == MtrDex.Ed25519_Seed assert skp0.verfer.code == MtrDex.Ed25519N + assert skp0.verfer.qb64 == 'BEe36N1fb59sXaHIUBOlfSCf4J_H5xajMuMr5u_isjs4' + sith = 1 # one signer keys = [skp0.verfer.qb64] - # create next key Error case - skp1 = Signer() # next signing keypair transferable is default + # create next key + # next signing keypair transferable is default + skp1 = salter.signer(path="O", temp=True, transferable=True) assert skp1.code == MtrDex.Ed25519_Seed assert skp1.verfer.code == MtrDex.Ed25519 nxtkeys = [skp1.verfer.qb64] - # compute nxt digest + # compute nxt digest must be empty nxt = "" sn = 0 # inception event so 0 toad = 0 # no witnesses - nsigs = 1 # one attached signature unspecified index + # error case if baks not empty baks = ["BAyRFMideczFZoapylLIyCjSdhtqVb31wZkRKvPfNqkw"] - ked0 = dict(v=versify(kind=Serials.json, size=0), - t=Ilks.icp, - d="", - i="", # qual base 64 prefix - s="{:x}".format(sn), # hex string no leading zeros lowercase - kt="{:x}".format(sith), # hex string no leading zeros lowercase - k=keys, # list of signing keys each qual Base64 - n=nxt, # hash qual Base64 - bt="{:x}".format(toad), # hex string no leading zeros lowercase - b=baks, # list of qual Base64 may be empty - c=[], # list of config ordered mappings may be empty - a={}, # list of seals - ) + # make with defaults with non-transferable prefix + serder = serdering.SerderKERI(makify=True, + ilk=kering.Ilks.icp, + saids = {'i': coring.PreDex.Ed25519N}) + + sad = serder.sad + sad['i'] = skp0.verfer.qb64 # non-digestive aid + sad['s'] = "{:x}".format(sn) # hex string + sad['kt'] = "{:x}".format(sith) # hex string + sad['k'] = keys + sad['nt'] = 0 + sad['n'] = nxt + sad['bt'] = "{:x}".format(toad) + sad['b'] = baks + + serder = serdering.SerderKERI(makify=True, verify=True, sad=sad) + assert serder.said == 'EKcREpfNupJ8oOqdnqDIyJVr1-GgIMBrVOtBUR9Gm6lO' + assert serder.pre == skp0.verfer.qb64 + + + # assign serialization + tser0 = serder + + # sign serialization + tsig0 = skp0.sign(tser0.raw, index=0) + + # verify signature + assert skp0.verfer.verify(tsig0.raw, tser0.raw) + + with pytest.raises(ValidationError): + kever = Kever(serder=tser0, sigers=[tsig0], db=db) - # Derive AID from ked - with pytest.raises(DerivationError): - aid0 = Prefixer(ked=ked0, code=MtrDex.Ed25519N) - # update ked with pre - ked0["i"] = skp0.verfer.qb64 - _, ked0 = coring.Saider.saidify(sad=ked0) + # retry with toad =1 and baks not empty + toad = 1 + sad =serder.sad # makes copy + sad['bt'] = "{:x}".format(toad) - # Serialize ked0 - tser0 = Serder(ked=ked0) + serder = serdering.SerderKERI(makify=True, verify=True, sad=sad) + assert serder.said == 'EBKhptvqccp0KNBaS45bNPdTE4m19U1IvweHJW2PIEDI' + assert serder.pre == skp0.verfer.qb64 + + # assign serialization + tser0 = serder # sign serialization tsig0 = skp0.sign(tser0.raw, index=0) @@ -1900,38 +2378,25 @@ def test_kever(mockHelpingNowUTC): with pytest.raises(ValidationError): kever = Kever(serder=tser0, sigers=[tsig0], db=db) + # retry with valid empty baks baks = [] # use some data, also invalid a = [dict(i="EAz8Wqqom6eeIFsng3cGQiUJ1uiNelCrR9VgFlk_8QAM")] sn = 0 # inception event so 0 toad = 0 # no witnesses - nsigs = 1 # one attached signature unspecified index - ked0 = dict(v=versify(kind=Serials.json, size=0), - t=Ilks.icp, - d="", - i="", # qual base 64 prefix - s="{:x}".format(sn), # hex string no leading zeros lowercase - kt="{:x}".format(sith), # hex string no leading zeros lowercase - k=keys, # list of signing keys each qual Base64 - n=nxt, # hash qual Base64 - bt="{:x}".format(toad), # hex string no leading zeros lowercase - b=baks, # list of qual Base64 may be empty - c=[], # list of config ordered mappings may be empty - a=a, # list of seals - ) - - # Derive AID from ked - with pytest.raises(DerivationError): - aid0 = Prefixer(ked=ked0, code=MtrDex.Ed25519N) + sad =serder.sad # makes copy + sad['bt'] = "{:x}".format(toad) + sad['b'] = baks + sad['a'] = a - # update ked with pre - ked0["i"] = aid0.qb64 - _, ked0 = coring.Saider.saidify(sad=ked0) + serder = serdering.SerderKERI(makify=True, verify=True, sad=sad) + assert serder.said == 'EEu-cdj_9b_66XRJ5UuhgEvJxAPpn4RjyaHvRgDU3iyA' + assert serder.pre == skp0.verfer.qb64 - # Serialize ked0 - tser0 = Serder(ked=ked0) + # assign serialization + tser0 = serder # sign serialization tsig0 = skp0.sign(tser0.raw, index=0) @@ -1943,38 +2408,24 @@ def test_kever(mockHelpingNowUTC): kever = Kever(serder=tser0, sigers=[tsig0], db=db) # valid so no error # retry with valid empty baks and empty a - baks = [] + a = [] - sn = 0 # inception event so 0 toad = 0 # no witnesses - nsigs = 1 # one attached signature unspecified index + baks = [] - ked0 = dict(v=versify(kind=Serials.json, size=0), - t=Ilks.icp, - d="", - i="", # qual base 64 prefix - s="{:x}".format(sn), # hex string no leading zeros lowercase - kt="{:x}".format(sith), # hex string no leading zeros lowercase - k=keys, # list of signing keys each qual Base64 - n=nxt, # hash qual Base64 - bt="{:x}".format(toad), # hex string no leading zeros lowercase - b=baks, # list of qual Base64 may be empty - c=[], # list of config ordered mappings may be empty - a=a, # list of seals - ) + sad =serder.sad # makes copy + sad['bt'] = "{:x}".format(toad) + sad['b'] = baks + sad['a'] = a - # Derive AID from ked - aid0 = Prefixer(ked=ked0, code=MtrDex.Ed25519N) - assert aid0.code == MtrDex.Ed25519N - assert aid0.qb64 == skp0.verfer.qb64 + serder = serdering.SerderKERI(makify=True, verify=True, sad=sad) + assert serder.said == 'EOyd2ZALXBm5k9lEpmvakO6RYPDgX1zWSFNd3MfOXL-e' + assert serder.pre == skp0.verfer.qb64 - # update ked with pre - ked0["i"] = aid0.qb64 - _, ked0 = coring.Saider.saidify(sad=ked0) + # assign serialization + tser0 = serder - # Serialize ked0 - tser0 = Serder(ked=ked0) # sign serialization tsig0 = skp0.sign(tser0.raw, index=0) @@ -2035,11 +2486,11 @@ def test_keyeventsequence_0(): kever = Kever(serder=serder0, sigers=[sig0], db=conlgr) assert kever.prefixer.qb64 == pre assert kever.sn == 0 - assert kever.serder.saider.qb64 == serder0.said + assert kever.serder.said == serder0.said assert kever.ilk == Ilks.icp assert kever.tholder.thold == 1 assert [verfer.qb64 for verfer in kever.verfers] == keys0 - assert kever.digs == nxt1 + assert kever.ndigs == nxt1 assert kever.estOnly is False assert kever.transferable is True @@ -2068,10 +2519,10 @@ def test_keyeventsequence_0(): kever.update(serder=serder1, sigers=[sig1]) assert kever.prefixer.qb64 == pre assert kever.sn == 1 - assert kever.serder.saider.qb64 == serder1.said + assert kever.serder.said == serder1.said assert kever.ilk == Ilks.rot assert [verfer.qb64 for verfer in kever.verfers] == keys1 - assert kever.digs == nxt2 + assert kever.ndigs == nxt2 pigers = kever.fetchPriorDigers() # digs from inception before rotation assert pigers is not None @@ -2098,10 +2549,10 @@ def test_keyeventsequence_0(): kever.update(serder=serder2, sigers=[sig2]) assert kever.prefixer.qb64 == pre assert kever.sn == 2 - assert kever.serder.saider.qb64 == serder2.said + assert kever.serder.said == serder2.said assert kever.ilk == Ilks.rot assert [verfer.qb64 for verfer in kever.verfers] == keys2 - assert kever.digs == nxt3 + assert kever.ndigs == nxt3 pigers = kever.fetchPriorDigers() # digs from rotation before rotation assert pigers is not None @@ -2121,10 +2572,10 @@ def test_keyeventsequence_0(): kever.update(serder=serder3, sigers=[sig3]) assert kever.prefixer.qb64 == pre assert kever.sn == 3 - assert kever.serder.saider.qb64 == serder3.said + assert kever.serder.said == serder3.said assert kever.ilk == Ilks.ixn assert [verfer.qb64 for verfer in kever.verfers] == keys2 # no change - assert kever.digs == nxt3 # no change + assert kever.ndigs == nxt3 # no change pigers = kever.fetchPriorDigers() assert pigers is not None @@ -2144,10 +2595,10 @@ def test_keyeventsequence_0(): kever.update(serder=serder4, sigers=[sig4]) assert kever.prefixer.qb64 == pre assert kever.sn == 4 - assert kever.serder.saider.qb64 == serder4.said + assert kever.serder.said == serder4.said assert kever.ilk == Ilks.ixn assert [verfer.qb64 for verfer in kever.verfers] == keys2 # no change - assert kever.digs == nxt3 # no change + assert kever.ndigs == nxt3 # no change pigers = kever.fetchPriorDigers() # digs from rot before rot before ixn ixn assert pigers is not None @@ -2173,10 +2624,10 @@ def test_keyeventsequence_0(): kever.update(serder=serder5, sigers=[sig5]) assert kever.prefixer.qb64 == pre assert kever.sn == 5 - assert kever.serder.saider.qb64 == serder5.said + assert kever.serder.said == serder5.said assert kever.ilk == Ilks.rot assert [verfer.qb64 for verfer in kever.verfers] == keys3 - assert kever.digs == nxt4 + assert kever.ndigs == nxt4 pigers = kever.fetchPriorDigers() # digs from rot before ixn ixn before rot assert pigers is not None @@ -2196,10 +2647,10 @@ def test_keyeventsequence_0(): kever.update(serder=serder6, sigers=[sig6]) assert kever.prefixer.qb64 == pre assert kever.sn == 6 - assert kever.serder.saider.qb64 == serder6.said + assert kever.serder.said == serder6.said assert kever.ilk == Ilks.ixn assert [verfer.qb64 for verfer in kever.verfers] == keys3 # no change - assert kever.digs == nxt4 # no change + assert kever.ndigs == nxt4 # no change # Event 7 Rotation to null NonTransferable Abandon serder7 = rotate(pre=pre, keys=keys4, dig=serder6.said, sn=7) @@ -2217,10 +2668,10 @@ def test_keyeventsequence_0(): kever.update(serder=serder7, sigers=[sig7]) assert kever.prefixer.qb64 == pre assert kever.sn == 7 - assert kever.serder.saider.qb64 == serder7.said + assert kever.serder.said == serder7.said assert kever.ilk == Ilks.rot assert [verfer.qb64 for verfer in kever.verfers] == keys4 - assert kever.digs == [] + assert kever.ndigs == [] assert not kever.transferable # Event 8 Interaction @@ -2305,11 +2756,11 @@ def test_keyeventsequence_1(): kever = Kever(serder=serder0, sigers=[sig0], db=conlgr) assert kever.prefixer.qb64 == pre assert kever.sn == 0 - assert kever.serder.saider.qb64 == serder0.said + assert kever.serder.said == serder0.said assert kever.ilk == Ilks.icp assert kever.tholder.thold == 1 assert [verfer.qb64 for verfer in kever.verfers] == keys0 - assert kever.digs == nxt1 + assert kever.ndigs == nxt1 assert kever.estOnly is True assert kever.transferable is True @@ -2345,10 +2796,10 @@ def test_keyeventsequence_1(): kever.update(serder=serder2, sigers=[sig2]) assert kever.prefixer.qb64 == pre assert kever.sn == 1 - assert kever.serder.saider.qb64 == serder2.said + assert kever.serder.said == serder2.said assert kever.ilk == Ilks.rot assert [verfer.qb64 for verfer in kever.verfers] == keys1 - assert kever.digs == nxt2 + assert kever.ndigs == nxt2 db_digs = [bytes(val).decode("utf-8") for val in kever.db.getKelIter(pre)] assert db_digs == event_digs @@ -2425,7 +2876,7 @@ def test_multisig_digprefix(): serder = rotate(pre=kever.prefixer.qb64, keys=keys, isith=sith, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, ndigs=[coring.Diger(ser=sig).qb64 for sig in nxtkeys], sn=1) # create sig counter @@ -2443,7 +2894,7 @@ def test_multisig_digprefix(): # Event 2 Interaction serder = interact(pre=kever.prefixer.qb64, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=2) # create sig counter counter = Counter(CtrDex.ControllerIdxSigs, count=count) # default is count = 1 @@ -2459,7 +2910,7 @@ def test_multisig_digprefix(): # Event 4 Interaction serder = interact(pre=kever.prefixer.qb64, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=3) # create sig counter counter = Counter(CtrDex.ControllerIdxSigs, count=count) # default is count = 1 @@ -2479,7 +2930,7 @@ def test_multisig_digprefix(): serder = rotate(pre=kever.prefixer.qb64, keys=keys, isith="2", - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=4) # create sig counter counter = Counter(CtrDex.ControllerIdxSigs, count=count) # default is count = 1 @@ -2550,7 +3001,7 @@ def test_recovery(): assert sn == esn == 1 serder = rotate(pre=kever.prefixer.qb64, keys=[signers[esn].verfer.qb64], - dig=kever.serder.saider.qb64, + dig=kever.serder.said, ndigs=[coring.Diger(ser=signers[esn + 1].verfer.qb64b).qb64], sn=sn) @@ -2571,7 +3022,7 @@ def test_recovery(): assert sn == 2 assert esn == 1 serder = interact(pre=kever.prefixer.qb64, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=sn) event_digs.append(serder.said) # create sig counter @@ -2592,7 +3043,7 @@ def test_recovery(): assert esn == 2 serder = rotate(pre=kever.prefixer.qb64, keys=[signers[esn].verfer.qb64], - dig=kever.serder.saider.qb64, + dig=kever.serder.said, ndigs=[coring.Diger(ser=signers[esn + 1].verfer.qb64b).qb64], sn=sn) event_digs.append(serder.said) @@ -2612,7 +3063,7 @@ def test_recovery(): assert sn == 4 assert esn == 2 serder = interact(pre=kever.prefixer.qb64, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=sn) event_digs.append(serder.said) # create sig counter @@ -2631,7 +3082,7 @@ def test_recovery(): assert sn == 5 assert esn == 2 serder = interact(pre=kever.prefixer.qb64, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=sn) event_digs.append(serder.said) # create sig counter @@ -2650,7 +3101,7 @@ def test_recovery(): assert sn == 6 assert esn == 2 serder = interact(pre=kever.prefixer.qb64, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=sn) event_digs.append(serder.said) # create sig counter @@ -2692,7 +3143,7 @@ def test_recovery(): assert sn == 6 assert esn == 3 serder = interact(pre=kever.prefixer.qb64, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=sn) event_digs.append(serder.said) # create sig counter @@ -2717,7 +3168,7 @@ def test_recovery(): assert db_digs[7] == event_digs[6] assert db_digs[6] == event_digs[7] - db_est_digs = [bytes(val).decode("utf-8") for val in kever.db.getKelEstIter(pre)] + db_est_digs = [bytes(val).decode("utf-8") for val in kever.db.getKelLastIter(pre)] assert len(db_est_digs) == 7 assert db_est_digs[0:5] == event_digs[0:5] assert db_est_digs[5:7] == event_digs[7:9] @@ -2733,7 +3184,7 @@ def test_recovery(): y_db_digs = [bytes(val).decode("utf-8") for val in kevery.db.getKelIter(pre)] assert db_digs == y_db_digs - y_db_est_digs = [bytes(val).decode("utf-8") for val in kevery.db.getKelEstIter(pre)] + y_db_est_digs = [bytes(val).decode("utf-8") for val in kevery.db.getKelLastIter(pre)] assert db_est_digs == y_db_est_digs assert not os.path.exists(kevery.db.path) @@ -2811,7 +3262,7 @@ def test_receipt(): # create receipt from val to coe reserder = receipt(pre=coeKever.prefixer.qb64, sn=coeKever.sn, - said=coeKever.serder.saider.qb64) + said=coeKever.serder.said) # sign event not receipt valCigar = valSigner.sign(ser=serder.raw) # returns Cigar cause no index assert valCigar.qb64 == ('0BADE2aOlwLi6OCF-jzRWSPuaOo916ADjwhA92hBQ1km' @@ -2834,7 +3285,7 @@ def test_receipt(): # coeKevery.process(ims=res) # coe process the receipt from val # check if in receipt database result = coeKevery.db.getRcts(key=dgKey(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64)) + dig=coeKever.serder.said)) assert bytes(result[0]) == valPrefixer.qb64b + valCigar.qb64b assert len(result) == 1 @@ -2852,8 +3303,8 @@ def test_receipt(): res.extend(valPrefixer.qb64b) res.extend(valCigar.qb64b) + # coe process the escrow receipt from val parsing.Parser().parse(ims=res, kvy=coeKevery) - # coeKevery.process(ims=res) # coe process the escrow receipt from val # check if in escrow database result = coeKevery.db.getUres(key=snKey(pre=coeKever.prefixer.qb64, sn=2)) @@ -2878,7 +3329,7 @@ def test_receipt(): # coeKevery.processOne(ims=res) # coe process the escrow receipt from val # no new receipt at valid dig result = coeKevery.db.getRcts(key=dgKey(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64)) + dig=coeKever.serder.said)) assert len(result) == 1 # no new receipt at invalid dig result = coeKevery.db.getRcts(key=dgKey(pre=coeKever.prefixer.qb64, @@ -2891,7 +3342,7 @@ def test_receipt(): assert sn == esn == 1 serder = rotate(pre=coeKever.prefixer.qb64, keys=[coeSigners[esn].verfer.qb64], - dig=coeKever.serder.saider.qb64, + dig=coeKever.serder.said, ndigs=[coring.Diger(ser=coeSigners[esn + 1].verfer.qb64b).qb64], sn=sn) @@ -2914,7 +3365,7 @@ def test_receipt(): assert sn == 2 assert esn == 1 serder = interact(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64, + dig=coeKever.serder.said, sn=sn) event_digs.append(serder.said) # create sig counter @@ -2938,7 +3389,7 @@ def test_receipt(): assert esn == 2 serder = rotate(pre=coeKever.prefixer.qb64, keys=[coeSigners[esn].verfer.qb64], - dig=coeKever.serder.saider.qb64, + dig=coeKever.serder.said, ndigs=[coring.Diger(ser=coeSigners[esn + 1].verfer.qb64b).qb64], sn=sn) event_digs.append(serder.said) @@ -2961,7 +3412,7 @@ def test_receipt(): assert sn == 4 assert esn == 2 serder = interact(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64, + dig=coeKever.serder.said, sn=sn) event_digs.append(serder.said) # create sig counter @@ -2983,7 +3434,7 @@ def test_receipt(): assert sn == 5 assert esn == 2 serder = interact(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64, + dig=coeKever.serder.said, sn=sn) event_digs.append(serder.said) # create sig counter @@ -3005,7 +3456,7 @@ def test_receipt(): assert sn == 6 assert esn == 2 serder = interact(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64, + dig=coeKever.serder.said, sn=sn) event_digs.append(serder.said) # create sig counter @@ -3148,11 +3599,11 @@ def test_direct_mode(): # create validator receipt reserder = receipt(pre=coeK.prefixer.qb64, sn=coeK.sn, - said=coeK.serder.saider.qb64) + said=coeK.serder.said) # sign coe's event not receipt # look up event to sign from val's kever for coe coeIcpDig = bytes(valKevery.db.getKeLast(key=snKey(pre=coepre, sn=csn))) - assert coeIcpDig == coeK.serder.saider.qb64b == b'EJe_sKQb1otKrz6COIL8VFvBv3DEFvtKaVFGn1vm0IlL' + assert coeIcpDig == coeK.serder.saidb == b'EJe_sKQb1otKrz6COIL8VFvBv3DEFvtKaVFGn1vm0IlL' coeIcpRaw = bytes(valKevery.db.getEvt(key=dgKey(pre=coepre, dig=coeIcpDig))) assert coeIcpRaw == (b'{"v":"KERI10JSON00012b_","t":"icp","d":"EJe_sKQb1otKrz6COIL8VFvBv3DEFvtKaVFG' b'n1vm0IlL","i":"EJe_sKQb1otKrz6COIL8VFvBv3DEFvtKaVFGn1vm0IlL","s":"0","kt":"1' @@ -3183,10 +3634,10 @@ def test_direct_mode(): assert valpre in coeKevery.kevers # check if receipt quadruple from val in receipt database result = coeKevery.db.getVrcs(key=dgKey(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64)) + dig=coeKever.serder.said)) assert bytes(result[0]) == (valKever.prefixer.qb64b + Seqner(sn=valKever.sn).qb64b + - valKever.serder.saider.qb64b + + valKever.serder.saidb + siger.qb64b) assert bytes(result[0]) == (b'EAzjKx3hSVJArKpIOVt2KfTRjq8st22hL25Ho9vn' b'Nodz0AAAAAAAAAAAAAAAAAAAAAAAEAzjKx3h' @@ -3204,12 +3655,18 @@ def test_direct_mode(): # create message vmsg = messagize(serder=reserder, sigers=[siger], seal=seal) - assert vmsg == (b'{"v":"KERI10JSON000091_","t":"rct","d":"EJe_sKQb1otKrz6COIL8VFvB' - b'v3DEFvtKaVFGn1vm0IlL","i":"EJe_sKQb1otKrz6COIL8VFvBv3DEFvtKaVFGn' - b'1vm0IlL","s":"a"}-FABEAzjKx3hSVJArKpIOVt2KfTRjq8st22hL25Ho9vnNod' - b'z0AAAAAAAAAAAAAAAAAAAAAAAEAzjKx3hSVJArKpIOVt2KfTRjq8st22hL25Ho9v' - b'nNodz-AABAAD-iI61odpZQjzm0fN9ZATjHx-KjQ9W3-CIlvhowwUaPC5KnQAIGYF' - b'uWJyRgAQalYVSEWoyMK2id_ONTFUE-NcF') + assert vmsg == bytearray(b'{"v":"KERI10JSON000091_","t":"rct","d":"EJe_sKQb1otKrz6COIL8VFvB' + b'v3DEFvtKaVFGn1vm0IlL","i":"EJe_sKQb1otKrz6COIL8VFvBv3DEFvtKaVFGn' + b'1vm0IlL","s":"a"}-FABEAzjKx3hSVJArKpIOVt2KfTRjq8st22hL25Ho9vnNod' + b'z0AAAAAAAAAAAAAAAAAAAAAAAEAzjKx3hSVJArKpIOVt2KfTRjq8st22hL25Ho9v' + b'nNodz-AABAAD-iI61odpZQjzm0fN9ZATjHx-KjQ9W3-CIlvhowwUaPC5KnQAIGYF' + b'uWJyRgAQalYVSEWoyMK2id_ONTFUE-NcF') + + #bytearray(b'{"v":"KERI10JSON000067_","t":"rct","d":null,"i":"EJe_sKQb1otKrz6' + #b'COIL8VFvBv3DEFvtKaVFGn1vm0IlL","s":"a"}-FABEAzjKx3hSVJArKpIOVt2K' + #b'fTRjq8st22hL25Ho9vnNodz0AAAAAAAAAAAAAAAAAAAAAAAEAzjKx3hSVJArKpIO' + #b'Vt2KfTRjq8st22hL25Ho9vnNodz-AABAAD-iI61odpZQjzm0fN9ZATjHx-KjQ9W3' + #b'-CIlvhowwUaPC5KnQAIGYFuWJyRgAQalYVSEWoyMK2id_ONTFUE-NcF') parsing.Parser().parse(ims=vmsg, kvy=coeKevery) # coeKevery.process(ims=vmsg) # coe process the escrow receipt from val # check if receipt quadruple in escrow database @@ -3218,7 +3675,7 @@ def test_direct_mode(): assert bytes(result[0]) == (fake.encode("utf-8") + valKever.prefixer.qb64b + Seqner(sn=valKever.sn).qb64b + - valKever.serder.saider.qb64b + + valKever.serder.saidb + siger.qb64b) # Send receipt from coe to val @@ -3231,11 +3688,11 @@ def test_direct_mode(): # create validator receipt reserder = receipt(pre=valK.prefixer.qb64, sn=valK.sn, - said=valK.serder.saider.qb64) + said=valK.serder.said) # sign vals's event not receipt # look up event to sign from coe's kever for val valIcpDig = bytes(coeKevery.db.getKeLast(key=snKey(pre=valpre, sn=vsn))) - assert valIcpDig == valK.serder.saider.qb64b == b'EAzjKx3hSVJArKpIOVt2KfTRjq8st22hL25Ho9vnNodz' + assert valIcpDig == valK.serder.saidb == b'EAzjKx3hSVJArKpIOVt2KfTRjq8st22hL25Ho9vnNodz' valIcpRaw = bytes(coeKevery.db.getEvt(key=dgKey(pre=valpre, dig=valIcpDig))) assert valIcpRaw == (b'{"v":"KERI10JSON00012b_","t":"icp","d":"EAzjKx3hSVJArKpIOVt2KfTRjq8st22hL25H' b'o9vnNodz","i":"EAzjKx3hSVJArKpIOVt2KfTRjq8st22hL25Ho9vnNodz","s":"0","kt":"1' @@ -3265,10 +3722,10 @@ def test_direct_mode(): # check if receipt quadruple from coe in val's receipt database result = valKevery.db.getVrcs(key=dgKey(pre=valKever.prefixer.qb64, - dig=valKever.serder.saider.qb64)) + dig=valKever.serder.said)) assert bytes(result[0]) == (coeKever.prefixer.qb64b + Seqner(sn=coeKever.sn).qb64b + - coeKever.serder.saider.qb64b + + coeKever.serder.saidb + siger.qb64b) assert bytes(result[0]) == (b'EJe_sKQb1otKrz6COIL8VFvBv3DEFvtKaVFGn1vm0' b'IlL0AAAAAAAAAAAAAAAAAAAAAAAEJe_sKQb' @@ -3282,7 +3739,7 @@ def test_direct_mode(): assert csn == cesn == 1 coeSerder = rotate(pre=coeKever.prefixer.qb64, keys=[coeSigners[cesn].verfer.qb64], - dig=coeKever.serder.saider.qb64, + dig=coeKever.serder.said, ndigs=[coring.Diger(ser=coeSigners[cesn + 1].verfer.qb64b).qb64], sn=csn) coe_event_digs.append(coeSerder.said) @@ -3308,14 +3765,14 @@ def test_direct_mode(): # coeKevery.processOne(ims=bytearray(cmsg)) # make copy # verify coe's copy of coe's event stream is updated assert coeKever.sn == csn - assert coeKever.serder.saider.qb64 == coeSerder.said + assert coeKever.serder.said == coeSerder.said # simulate send message from coe to val parsing.Parser().parse(ims=cmsg, kvy=valKevery) # valKevery.process(ims=cmsg) # verify val's copy of coe's event stream is updated assert coeK.sn == csn - assert coeK.serder.saider.qb64 == coeSerder.said + assert coeK.serder.said == coeSerder.said # create receipt of coe's rotation # create seal of val's last est event @@ -3325,11 +3782,11 @@ def test_direct_mode(): # create validator receipt reserder = receipt(pre=coeK.prefixer.qb64, sn=coeK.sn, - said=coeK.serder.saider.qb64) + said=coeK.serder.said) # sign coe's event not receipt # look up event to sign from val's kever for coe coeRotDig = bytes(valKevery.db.getKeLast(key=snKey(pre=coepre, sn=csn))) - assert coeRotDig == coeK.serder.saider.qb64b == b'EKlC013XEpwYuCQ84aVnEAqzNurjAJDN6ayK-9NxggAr' + assert coeRotDig == coeK.serder.saidb == b'EKlC013XEpwYuCQ84aVnEAqzNurjAJDN6ayK-9NxggAr' coeRotRaw = bytes(valKevery.db.getEvt(key=dgKey(pre=coepre, dig=coeRotDig))) assert coeRotRaw == (b'{"v":"KERI10JSON000160_","t":"rot","d":"EKlC013XEpwYuCQ84aVnEAqzNurjAJDN6ayK' b'-9NxggAr","i":"EJe_sKQb1otKrz6COIL8VFvBv3DEFvtKaVFGn1vm0IlL","s":"1","p":"EJ' @@ -3359,10 +3816,10 @@ def test_direct_mode(): # check if receipt quadruple from val in receipt database result = coeKevery.db.getVrcs(key=dgKey(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64)) + dig=coeKever.serder.said)) assert bytes(result[0]) == (valKever.prefixer.qb64b + Seqner(sn=valKever.sn).qb64b + - valKever.serder.saider.qb64b + + valKever.serder.saidb + siger.qb64b) assert bytes(result[0]) == (b'EAzjKx3hSVJArKpIOVt2KfTRjq8st22hL25Ho9vnN' @@ -3377,7 +3834,7 @@ def test_direct_mode(): assert csn == 2 assert cesn == 1 coeSerder = interact(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64, + dig=coeKever.serder.said, sn=csn) coe_event_digs.append(coeSerder.said) # create sig counter @@ -3399,14 +3856,14 @@ def test_direct_mode(): # coeKevery.processOne(ims=bytearray(cmsg)) # make copy # verify coe's copy of coe's event stream is updated assert coeKever.sn == csn - assert coeKever.serder.saider.qb64 == coeSerder.said + assert coeKever.serder.said == coeSerder.said # simulate send message from coe to val parsing.Parser().parse(ims=cmsg, kvy=valKevery) # valKevery.process(ims=cmsg) # verify val's copy of coe's event stream is updated assert coeK.sn == csn - assert coeK.serder.saider.qb64 == coeSerder.said + assert coeK.serder.said == coeSerder.said # create receipt of coe's interaction # create seal of val's last est event @@ -3416,11 +3873,11 @@ def test_direct_mode(): # create validator receipt reserder = receipt(pre=coeK.prefixer.qb64, sn=coeK.sn, - said=coeK.serder.saider.qb64) + said=coeK.serder.said) # sign coe's event not receipt # look up event to sign from val's kever for coe coeIxnDig = bytes(valKevery.db.getKeLast(key=snKey(pre=coepre, sn=csn))) - assert coeIxnDig == coeK.serder.saider.qb64b == b'EG3O9AV3lhySOadwTn810vHOZDc6B8TZY_u_4_iy_ono' + assert coeIxnDig == coeK.serder.saidb == b'EG3O9AV3lhySOadwTn810vHOZDc6B8TZY_u_4_iy_ono' coeIxnRaw = bytes(valKevery.db.getEvt(key=dgKey(pre=coepre, dig=coeIxnDig))) assert coeIxnRaw == (b'{"v":"KERI10JSON0000cb_","t":"ixn","d":"EG3O9AV3lhySOadwTn810vHOZDc6B8TZY_u_' b'4_iy_ono","i":"EJe_sKQb1otKrz6COIL8VFvBv3DEFvtKaVFGn1vm0IlL","s":"2","p":"EK' @@ -3447,10 +3904,10 @@ def test_direct_mode(): # check if receipt quadruple from val in receipt database result = coeKevery.db.getVrcs(key=dgKey(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64)) + dig=coeKever.serder.said)) assert bytes(result[0]) == (valKever.prefixer.qb64b + Seqner(sn=valKever.sn).qb64b + - valKever.serder.saider.qb64b + + valKever.serder.saidb + siger.qb64b) assert bytes(result[0]) == (b'EAzjKx3hSVJArKpIOVt2KfTRjq8st22hL25Ho9vnNo' @@ -3607,12 +4064,12 @@ def test_direct_mode_cbor_mgpk(): # create validator receipt reserder = receipt(pre=coeK.prefixer.qb64, sn=coeK.sn, - said=coeK.serder.saider.qb64, + said=coeK.serder.said, kind=Serials.mgpk) # sign coe's event not receipt # look up event to sign from val's kever for coe coeIcpDig = bytes(valKevery.db.getKeLast(key=snKey(pre=coepre, sn=csn))) - assert coeIcpDig == coeK.serder.saider.qb64b + assert coeIcpDig == coeK.serder.saidb coeIcpRaw = bytes(valKevery.db.getEvt(key=dgKey(pre=coepre, dig=coeIcpDig))) assert coeIcpRaw == (b'\xadavqKERI10CBOR0000f9_atcicpadx,EDTOWE_oHAO7j6rhUMGfQ_kX8GJbpaAhO-luqqsp5' b'mK-aix,EDTOWE_oHAO7j6rhUMGfQ_kX8GJbpaAhO-luqqsp5mK-asa0bkta1ak\x81x,DC8kCMH' @@ -3643,10 +4100,10 @@ def test_direct_mode_cbor_mgpk(): assert valpre in coeKevery.kevers # check if receipt quadruple from val in receipt database result = coeKevery.db.getVrcs(key=dgKey(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64)) + dig=coeKever.serder.said)) assert bytes(result[0]) == (valKever.prefixer.qb64b + Seqner(sn=valKever.sn).qb64b + - valKever.serder.saider.qb64b + + valKever.serder.saidb + siger.qb64b) assert bytes(result[0]) == (b'EFBYcX4vOeL7Y5pz0iQ5yCfxd19R1dgA_r9i1nVdq' b'MZX0AAAAAAAAAAAAAAAAAAAAAAAEFBYcX4v' @@ -3680,7 +4137,7 @@ def test_direct_mode_cbor_mgpk(): assert bytes(result[0]) == (fake.encode("utf-8") + valKever.prefixer.qb64b + Seqner(sn=valKever.sn).qb64b + - valKever.serder.saider.qb64b + + valKever.serder.saidb + siger.qb64b) # Send receipt from coe to val @@ -3693,12 +4150,12 @@ def test_direct_mode_cbor_mgpk(): # create validator receipt reserder = receipt(pre=valK.prefixer.qb64, sn=valK.sn, - said=valK.serder.saider.qb64, + said=valK.serder.said, kind=Serials.cbor) # sign vals's event not receipt # look up event to sign from coe's kever for val valIcpDig = bytes(coeKevery.db.getKeLast(key=snKey(pre=valpre, sn=vsn))) - assert valIcpDig == valK.serder.saider.qb64b + assert valIcpDig == valK.serder.saidb valIcpRaw = bytes(coeKevery.db.getEvt(key=dgKey(pre=valpre, dig=valIcpDig))) assert valIcpRaw == (b'\x8d\xa1v\xb1KERI10MGPK0000f9_\xa1t\xa3icp\xa1d\xd9,EFBYcX4vOeL7Y5pz0iQ5y' b'Cfxd19R1dgA_r9i1nVdqMZX\xa1i\xd9,EFBYcX4vOeL7Y5pz0iQ5yCfxd19R1dgA_r9i1nVdq' @@ -3727,10 +4184,10 @@ def test_direct_mode_cbor_mgpk(): # check if receipt from coe in val's receipt database result = valKevery.db.getVrcs(key=dgKey(pre=valKever.prefixer.qb64, - dig=valKever.serder.saider.qb64)) + dig=valKever.serder.said)) assert bytes(result[0]) == (coeKever.prefixer.qb64b + Seqner(sn=coeKever.sn).qb64b + - coeKever.serder.saider.qb64b + + coeKever.serder.saidb + siger.qb64b) assert bytes(result[0]) == (b'EDTOWE_oHAO7j6rhUMGfQ_kX8GJbpaAhO-luqqsp5' b'mK-0AAAAAAAAAAAAAAAAAAAAAAAEDTOWE_o' @@ -3744,7 +4201,7 @@ def test_direct_mode_cbor_mgpk(): assert csn == cesn == 1 coeSerder = rotate(pre=coeKever.prefixer.qb64, keys=[coeSigners[cesn].verfer.qb64], - dig=coeKever.serder.saider.qb64, + dig=coeKever.serder.said, ndigs=[coring.Diger(ser=coeSigners[cesn + 1].verfer.qb64b).qb64], sn=csn, kind=Serials.cbor) @@ -3771,14 +4228,14 @@ def test_direct_mode_cbor_mgpk(): # coeKevery.processOne(ims=bytearray(cmsg)) # make copy # verify coe's copy of coe's event stream is updated assert coeKever.sn == csn - assert coeKever.serder.saider.qb64 == coeSerder.said + assert coeKever.serder.said == coeSerder.said # simulate send message from coe to val parsing.Parser().parse(ims=cmsg, kvy=valKevery) # valKevery.process(ims=cmsg) # verify val's copy of coe's event stream is updated assert coeK.sn == csn - assert coeK.serder.saider.qb64 == coeSerder.said + assert coeK.serder.said == coeSerder.said # create receipt of coe's rotation # create seal of val's last est event @@ -3788,12 +4245,12 @@ def test_direct_mode_cbor_mgpk(): # create validator receipt reserder = receipt(pre=coeK.prefixer.qb64, sn=coeK.sn, - said=coeK.serder.saider.qb64, + said=coeK.serder.said, kind=Serials.mgpk) # sign coe's event not receipt # look up event to sign from val's kever for coe coeRotDig = bytes(valKevery.db.getKeLast(key=snKey(pre=coepre, sn=csn))) - assert coeRotDig == coeK.serder.saider.qb64b + assert coeRotDig == coeK.serder.saidb coeRotRaw = bytes(valKevery.db.getEvt(key=dgKey(pre=coepre, dig=coeRotDig))) assert coeRotRaw == (b'\xaeavqKERI10CBOR00012b_atcrotadx,EN4m9YLkeBgWVIvwmj45_qdnBBBY61NVZbwOe__MA' b'sYMaix,EDTOWE_oHAO7j6rhUMGfQ_kX8GJbpaAhO-luqqsp5mK-asa1apx,EDTOWE_oHAO7j6rhU' @@ -3821,10 +4278,10 @@ def test_direct_mode_cbor_mgpk(): # check if receipt from val in receipt database result = coeKevery.db.getVrcs(key=dgKey(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64)) + dig=coeKever.serder.said)) assert bytes(result[0]) == (valKever.prefixer.qb64b + Seqner(sn=valKever.sn).qb64b + - valKever.serder.saider.qb64b + + valKever.serder.saidb + siger.qb64b) assert bytes(result[0]) == (b'EFBYcX4vOeL7Y5pz0iQ5yCfxd19R1dgA_r9i1nV' @@ -3839,7 +4296,7 @@ def test_direct_mode_cbor_mgpk(): assert csn == 2 assert cesn == 1 coeSerder = interact(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64, + dig=coeKever.serder.said, sn=csn, kind=Serials.cbor) coe_event_digs.append(coeSerder.said) @@ -3863,14 +4320,14 @@ def test_direct_mode_cbor_mgpk(): # coeKevery.processOne(ims=bytearray(cmsg)) # make copy # verify coe's copy of coe's event stream is updated assert coeKever.sn == csn - assert coeKever.serder.saider.qb64 == coeSerder.said + assert coeKever.serder.said == coeSerder.said # simulate send message from coe to val parsing.Parser().parse(ims=cmsg, kvy=valKevery) # valKevery.process(ims=cmsg) # verify val's copy of coe's event stream is updated assert coeK.sn == csn - assert coeK.serder.saider.qb64 == coeSerder.said + assert coeK.serder.said == coeSerder.said # create receipt of coe's interaction # create seal of val's last est event @@ -3880,12 +4337,12 @@ def test_direct_mode_cbor_mgpk(): # create validator receipt reserder = receipt(pre=coeK.prefixer.qb64, sn=coeK.sn, - said=coeK.serder.saider.qb64, + said=coeK.serder.said, kind=Serials.mgpk) # sign coe's event not receipt # look up event to sign from val's kever for coe coeIxnDig = bytes(valKevery.db.getKeLast(key=snKey(pre=coepre, sn=csn))) - assert coeIxnDig == coeK.serder.saider.qb64b + assert coeIxnDig == coeK.serder.saidb coeIxnRaw = bytes(valKevery.db.getEvt(key=dgKey(pre=coepre, dig=coeIxnDig))) assert coeIxnRaw == (b'\xa7avqKERI10CBOR0000b2_atcixnadx,EEobyRfni6TAn' b'EROE5yL9sC6lhKEbpbmXyeqSZ1Qj' @@ -3913,10 +4370,10 @@ def test_direct_mode_cbor_mgpk(): # check if receipt from val in receipt database result = coeKevery.db.getVrcs(key=dgKey(pre=coeKever.prefixer.qb64, - dig=coeKever.serder.saider.qb64)) + dig=coeKever.serder.said)) assert bytes(result[0]) == (valKever.prefixer.qb64b + Seqner(sn=valKever.sn).qb64b + - valKever.serder.saider.qb64b + + valKever.serder.saidb + siger.qb64b) assert bytes(result[0]) == (b'EFBYcX4vOeL7Y5pz0iQ5yCfxd19R1dgA_r9i1nV' @@ -3975,10 +4432,11 @@ def test_process_nontransferable(): # but when used with inception event must be compatible event sn = 0 # inception event so 0 sith = 1 # one signer - nxt = "" # non-transferable so nxt is empty + nxt = [] # non-transferable so nxt is empty toad = 0 # no witnesses nsigs = 1 # one attached signature unspecified index + #["v", "t", "d", "i", "s", "kt", "k", "nt", "n","bt", "b", "c", "a"] ked0 = dict(v=versify(kind=Serials.json, size=0), t=Ilks.icp, d="", @@ -3986,10 +4444,12 @@ def test_process_nontransferable(): s="{:x}".format(sn), # hex string no leading zeros lowercase kt="{:x}".format(sith), # hex string no leading zeros lowercase k=[aid0.qb64], # list of signing keys each qual Base64 + nt=0, n=nxt, # hash qual Base64 - wt="{:x}".format(toad), # hex string no leading zeros lowercase - w=[], # list of qual Base64 may be empty + bt="{:x}".format(toad), # hex string no leading zeros lowercase + b=[], # list of qual Base64 may be empty c=[], # list of config ordered mappings may be empty + a=[], ) _, ked0 = coring.Saider.saidify(sad=ked0) @@ -3997,7 +4457,7 @@ def test_process_nontransferable(): assert aid0.verify(ked=ked0) # Serialize ked0 - tser0 = Serder(ked=ked0) + tser0 = serdering.SerderKERI(sad=ked0) # sign serialization tsig0 = skp0.sign(tser0.raw, index=0) @@ -4012,7 +4472,7 @@ def test_process_nontransferable(): msgb0 = bytearray(tser0.raw + cnt0.qb64b + tsig0.qb64b) # deserialize packet - rser0 = Serder(raw=msgb0) + rser0 = serdering.SerderKERI(raw=msgb0) assert rser0.raw == tser0.raw del msgb0[:rser0.size] # strip off event from front @@ -4064,30 +4524,33 @@ def test_process_transferable(): toad = 0 # no witnesses nsigs = 1 # one attached signature unspecified index - ked0 = dict(v=versify(kind=Serials.json, size=0), - t=Ilks.icp, - d="", - i="", # qual base 64 prefix - s="{:x}".format(sn), # hex string no leading zeros lowercase - kt="{:x}".format(sith), # hex string no leading zeros lowercase - k=keys, # list of signing keys each qual Base64 - n=nxt, # hash qual Base64 - wt="{:x}".format(toad), # hex string no leading zeros lowercase - w=[], # list of qual Base64 may be empty - c=[], - ) - # Derive AID from ked + ked0 = dict(v=versify(kind=Serials.json, size=0), # version string + t=Ilks.icp, + d="", # SAID + i="", # qb64 prefix + s="{:x}".format(sn), # hex string no leading zeros lowercase + kt="{:x}".format(sith), # hex string no leading zeros lowercase + k=keys, # list of qb64 + nt=1, + n=nxt, # hash qual Base64 + bt=0, # hex string no leading zeros lowercase + b=[], # list of qb64 may be empty + c=[], # list of config ordered mappings may be empty + a=[], # list of seal dicts + ) + + + # Use non digestive AID aid0 = Prefixer(ked=ked0, code=MtrDex.Ed25519) assert aid0.code == MtrDex.Ed25519 assert aid0.qb64 == skp0.verfer.qb64 - _, ked0 = coring.Saider.saidify(sad=ked0) - # update ked with pre ked0["i"] = aid0.qb64 + _, ked0 = coring.Saider.saidify(sad=ked0) # Serialize ked0 - tser0 = Serder(ked=ked0) + tser0 = serdering.SerderKERI(sad=ked0) # sign serialization tsig0 = skp0.sign(tser0.raw, index=0) @@ -4102,7 +4565,7 @@ def test_process_transferable(): msgb0 = bytearray(tser0.raw + cnt0.qb64b + tsig0.qb64b) # deserialize packet - rser0 = Serder(raw=msgb0) + rser0 = serdering.SerderKERI(raw=msgb0) assert rser0.raw == tser0.raw del msgb0[:rser0.size] # strip off event from front @@ -4128,7 +4591,7 @@ def test_process_transferable(): assert raid0.verify(ked=rser0.ked) # verify nxt digest from event is still valid - digers=rser0.digers + digers=rser0.ndigers #assert rnxt1.includes(keys=nxtkeys) """ Done Test """ @@ -4198,23 +4661,24 @@ def test_process_manual(): k=[aidmat.qb64], # list of signing keys each qual Base64 nt=nxtsith, n=nxts, - wt="{:x}".format(toad), # hex string no leading zeros lowercase - w=[], # list of qual Base64 may be empty - c=[], # list of config ordered mappings may be empty + bt="{:x}".format(toad), # hex string no leading zeros lowercase + b=[], # list of qual Base64 may be empty + c=[], # list of config traits may be empty + a=[], # list of seals ordered mappings may be empty ) _, ked0 = coring.Saider.saidify(sad=ked0) - txsrdr = Serder(ked=ked0, kind=Serials.json) - assert txsrdr.raw == (b'{"v":"KERI10JSON000124_","t":"icp","d":"EKlLyOddVoxzsk8UaJFvYA2YDusEenTpaYXk' - b'MLtCpUbh","i":"DK-WsHD7MKfQpBjJ3B2GwjqY9z90G94uzMs7irCiT-dL","s":"0","kt":"1' - b'","k":["DK-WsHD7MKfQpBjJ3B2GwjqY9z90G94uzMs7irCiT-dL"],"nt":"1","n":["EDcWJG' - b'2wb0EXMZTzs4DgMwAWN_Qn2E6oMkTtX2C8E_4R"],"wt":"0","w":[],"c":[]}') + txsrdr = serdering.SerderKERI(sad=ked0, kind=Serials.json) + assert txsrdr.raw == (b'{"v":"KERI10JSON00012b_","t":"icp","d":"EKYHED-wvkYDZv4tNUF9qiC1kgnnGLS9YUU8' + b'PCWig_n4","i":"DK-WsHD7MKfQpBjJ3B2GwjqY9z90G94uzMs7irCiT-dL","s":"0","kt":"1' + b'","k":["DK-WsHD7MKfQpBjJ3B2GwjqY9z90G94uzMs7irCiT-dL"],"nt":"1","n":["EDcWJG' + b'2wb0EXMZTzs4DgMwAWN_Qn2E6oMkTtX2C8E_4R"],"bt":"0","b":[],"c":[],"a":[]}') - assert txsrdr.size == 292 + assert txsrdr.size == 299 txdig = blake3.blake3(txsrdr.raw).digest() txdigmat = coring.Saider(sad=ked0, code=MtrDex.Blake3_256) - assert txdigmat.qb64 == 'EKlLyOddVoxzsk8UaJFvYA2YDusEenTpaYXkMLtCpUbh' + assert txdigmat.qb64 == 'EKYHED-wvkYDZv4tNUF9qiC1kgnnGLS9YUU8PCWig_n4' assert txsrdr.said == txdigmat.qb64 @@ -4225,17 +4689,16 @@ def test_process_manual(): assert not result # None if verifies successfully else raises ValueError txsigmat = Siger(raw=sig0raw, code=IdrDex.Ed25519_Sig, index=index) - assert txsigmat.qb64 == ('AAClimpgQX2jFTbYlTebmxIVRpE1SzPCcHdyNm-EsBJAOUVXH' - 'bdRBd6wbpePWsuEcWIK-k9kbX-PagPVG6lsKhcP') + assert txsigmat.qb64 == 'AAAbwxAS2DuLS4HLaiv9hHd6YEolpPjMKYRdXY3VwmWKh5gW5J2QBsZ1cMmdrlyIlgvqTQEViicNv9mHHOvj9ZAB' assert len(txsigmat.qb64) == 88 assert txsigmat.index == index msgb = txsrdr.raw + txsigmat.qb64.encode("utf-8") - assert len(msgb) == 380 # 292 + 88 + assert len(msgb) == 387 # 299 + 88 # Recieve side - rxsrdr = Serder(raw=msgb) + rxsrdr = serdering.SerderKERI(raw=msgb) assert rxsrdr.size == txsrdr.size assert rxsrdr.ked == ked0 @@ -4289,22 +4752,22 @@ def test_reload_kever(mockHelpingNowUTC): assert natHab.kever.serder.said == 'EA3QbTpV15MvLSXHSedm4lRYdQhmYXqXafsD4i75B_yo' ldig = bytes(natHab.db.getKeLast(dbing.snKey(natHab.pre, natHab.kever.sn))) assert ldig == natHab.kever.serder.saidb - serder = coring.Serder(raw=bytes(natHab.db.getEvt(dbing.dgKey(natHab.pre, ldig)))) + serder = serdering.SerderKERI(raw=bytes(natHab.db.getEvt(dbing.dgKey(natHab.pre, ldig)))) assert serder.said == natHab.kever.serder.said nstate = natHab.kever.state() - state = natHab.db.states.get(keys=natHab.pre) # Serder instance - assert state.raw == (b'{"v":"KERI10JSON00029e_","i":"EBm9JqQKS4a3EYv5I7BmAPiwhdSQvFAOpqe0dgk3kgH_",' - b'"s":"6","p":"ED_HpKSCQJoeGxHYjPRD2tgUhbIrLf6fH3e3xJFSq2dL","d":"EA3QbTpV15Mv' - b'LSXHSedm4lRYdQhmYXqXafsD4i75B_yo","f":"6","dt":"2021-01-01T00:00:00.000000+0' - b'0:00","et":"ixn","kt":"2","k":["DCORPGaoMtI_RyJFUTIzk0xdza_z6sBQ2e2wzYtEAs3s' - b'","DNjSHBbYJaaUKJuPd34n7SRYiZHirwvW-QiHRtfRvBh4","DN-hL9CKn6WdsINEG207T4pSdj' - b'aMIxU9SKhfeeHCwfvT"],"nt":"2","n":["EGZ9WHJPgrvDpe08gJpEZ8Gz-rcy72ZG7Tey0PS2' - b'CrXY","EO_z0OFTUZ1pmfxj-VnQJcsYFdIVq2tWkN9nUWRxQab_","EMeWMAZpVy7IX6yl4F2t-W' - b'oUCaRFZ-0g5dx_LLoEywhx"],"bt":"0","b":[],"c":[],"ee":{"s":"2","d":"EJ7s1vk30' - b'hWK_l-exQtzj4P5u_wIzki1drVR4FAKDbEW","br":[],"ba":[]},"di":""}') - assert state.ked["f"] == '6' - assert state.ked == nstate.ked + state =natHab.db.states.get(keys=natHab.pre) # key state record + assert state._asjson() == (b'{"vn":[1,0],"i":"EBm9JqQKS4a3EYv5I7BmAPiwhdSQvFAOpqe0dgk3kgH_","s":"6","p":"' + b'ED_HpKSCQJoeGxHYjPRD2tgUhbIrLf6fH3e3xJFSq2dL","d":"EA3QbTpV15MvLSXHSedm4lRYd' + b'QhmYXqXafsD4i75B_yo","f":"6","dt":"2021-01-01T00:00:00.000000+00:00","et":"i' + b'xn","kt":"2","k":["DCORPGaoMtI_RyJFUTIzk0xdza_z6sBQ2e2wzYtEAs3s","DNjSHBbYJa' + b'aUKJuPd34n7SRYiZHirwvW-QiHRtfRvBh4","DN-hL9CKn6WdsINEG207T4pSdjaMIxU9SKhfeeH' + b'CwfvT"],"nt":"2","n":["EGZ9WHJPgrvDpe08gJpEZ8Gz-rcy72ZG7Tey0PS2CrXY","EO_z0O' + b'FTUZ1pmfxj-VnQJcsYFdIVq2tWkN9nUWRxQab_","EMeWMAZpVy7IX6yl4F2t-WoUCaRFZ-0g5dx' + b'_LLoEywhx"],"bt":"0","b":[],"c":[],"ee":{"s":"2","d":"EJ7s1vk30hWK_l-exQtzj4' + b'P5u_wIzki1drVR4FAKDbEW","br":[],"ba":[]},"di":""}') + assert state.f == '6' + assert state == nstate # now create new Kever with state kever = eventing.Kever(state=state, db=natHby.db) @@ -4314,17 +4777,16 @@ def test_reload_kever(mockHelpingNowUTC): assert kever.serder.said == natHab.kever.serder.said kstate = kever.state() - assert kstate.ked == state.ked - assert state.raw == (b'{"v":"KERI10JSON00029e_","i":"EBm9JqQKS4a3EYv5I7BmAPiwhdSQvFAOpqe0dgk3kgH_",' - b'"s":"6","p":"ED_HpKSCQJoeGxHYjPRD2tgUhbIrLf6fH3e3xJFSq2dL","d":"EA3QbTpV15Mv' - b'LSXHSedm4lRYdQhmYXqXafsD4i75B_yo","f":"6","dt":"2021-01-01T00:00:00.000000+0' - b'0:00","et":"ixn","kt":"2","k":["DCORPGaoMtI_RyJFUTIzk0xdza_z6sBQ2e2wzYtEAs3s' - b'","DNjSHBbYJaaUKJuPd34n7SRYiZHirwvW-QiHRtfRvBh4","DN-hL9CKn6WdsINEG207T4pSdj' - b'aMIxU9SKhfeeHCwfvT"],"nt":"2","n":["EGZ9WHJPgrvDpe08gJpEZ8Gz-rcy72ZG7Tey0PS2' - b'CrXY","EO_z0OFTUZ1pmfxj-VnQJcsYFdIVq2tWkN9nUWRxQab_","EMeWMAZpVy7IX6yl4F2t-W' - b'oUCaRFZ-0g5dx_LLoEywhx"],"bt":"0","b":[],"c":[],"ee":{"s":"2","d":"EJ7s1vk30' - b'hWK_l-exQtzj4P5u_wIzki1drVR4FAKDbEW","br":[],"ba":[]},"di":""}') - + assert kstate == state + assert state._asjson() == (b'{"vn":[1,0],"i":"EBm9JqQKS4a3EYv5I7BmAPiwhdSQvFAOpqe0dgk3kgH_","s":"6","p":"' + b'ED_HpKSCQJoeGxHYjPRD2tgUhbIrLf6fH3e3xJFSq2dL","d":"EA3QbTpV15MvLSXHSedm4lRYd' + b'QhmYXqXafsD4i75B_yo","f":"6","dt":"2021-01-01T00:00:00.000000+00:00","et":"i' + b'xn","kt":"2","k":["DCORPGaoMtI_RyJFUTIzk0xdza_z6sBQ2e2wzYtEAs3s","DNjSHBbYJa' + b'aUKJuPd34n7SRYiZHirwvW-QiHRtfRvBh4","DN-hL9CKn6WdsINEG207T4pSdjaMIxU9SKhfeeH' + b'CwfvT"],"nt":"2","n":["EGZ9WHJPgrvDpe08gJpEZ8Gz-rcy72ZG7Tey0PS2CrXY","EO_z0O' + b'FTUZ1pmfxj-VnQJcsYFdIVq2tWkN9nUWRxQab_","EMeWMAZpVy7IX6yl4F2t-WoUCaRFZ-0g5dx' + b'_LLoEywhx"],"bt":"0","b":[],"c":[],"ee":{"s":"2","d":"EJ7s1vk30hWK_l-exQtzj4' + b'P5u_wIzki1drVR4FAKDbEW","br":[],"ba":[]},"di":""}') assert not os.path.exists(natHby.ks.path) assert not os.path.exists(natHby.db.path) @@ -4425,7 +4887,7 @@ def test_load_event(mockHelpingNowUTC): teeIcp.extend(counter.qb64b) seqner = coring.Seqner(sn=torHab.kever.sn) teeIcp.extend(seqner.qb64b) - teeIcp.extend(torHab.kever.serder.saider.qb64b) + teeIcp.extend(torHab.kever.serder.saidb) # Endorse Tee's inception event with Tor's Hab just so we have trans receipts rct = torHab.receipt(serder=teeHab.kever.serder) @@ -4480,4 +4942,7 @@ def test_load_event(mockHelpingNowUTC): if __name__ == "__main__": # pytest.main(['-vv', 'test_eventing.py::test_keyeventfuncs']) #test_process_manual() - test_keyeventsequence_0() + #test_keyeventsequence_0() + #test_process_transferable() + #test_messagize() + test_direct_mode() diff --git a/tests/core/test_kevery.py b/tests/core/test_kevery.py index 842903d68..56dd97bff 100644 --- a/tests/core/test_kevery.py +++ b/tests/core/test_kevery.py @@ -4,7 +4,7 @@ from keri import help from keri.app import habbing -from keri.core import parsing, eventing, coring +from keri.core import parsing, eventing, coring, serdering from keri.core.coring import CtrDex, Counter from keri.core.coring import Salter from keri.core.eventing import Kever, Kevery @@ -59,7 +59,7 @@ def test_kevery(): # Event 1 Rotation Transferable serder = rotate(pre=kever.prefixer.qb64, keys=[signers[1].verfer.qb64], - dig=kever.serder.saider.qb64, + dig=kever.serder.said, ndigs=[coring.Diger(ser=signers[2].verfer.qb64b).qb64], sn=1) event_digs.append(serder.said) @@ -77,7 +77,7 @@ def test_kevery(): # Event 2 Rotation Transferable serder = rotate(pre=kever.prefixer.qb64, keys=[signers[2].verfer.qb64], - dig=kever.serder.saider.qb64, + dig=kever.serder.said, ndigs=[coring.Diger(ser=signers[3].verfer.qb64b).qb64], sn=2) event_digs.append(serder.said) @@ -94,7 +94,7 @@ def test_kevery(): # Event 3 Interaction serder = interact(pre=kever.prefixer.qb64, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=3) event_digs.append(serder.said) # create sig counter @@ -110,7 +110,7 @@ def test_kevery(): # Event 4 Interaction serder = interact(pre=kever.prefixer.qb64, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=4) event_digs.append(serder.said) # create sig counter @@ -127,7 +127,7 @@ def test_kevery(): # Event 5 Rotation Transferable serder = rotate(pre=kever.prefixer.qb64, keys=[signers[3].verfer.qb64], - dig=kever.serder.saider.qb64, + dig=kever.serder.said, ndigs=[coring.Diger(ser=signers[4].verfer.qb64b).qb64], sn=5) event_digs.append(serder.said) @@ -144,7 +144,7 @@ def test_kevery(): # Event 6 Interaction serder = interact(pre=kever.prefixer.qb64, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=6) event_digs.append(serder.said) # create sig counter @@ -162,7 +162,7 @@ def test_kevery(): # nxt digest is empty serder = rotate(pre=kever.prefixer.qb64, keys=[signers[4].verfer.qb64], - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=7) event_digs.append(serder.said) # create sig counter @@ -178,7 +178,7 @@ def test_kevery(): # Event 8 Interaction serder = interact(pre=kever.prefixer.qb64, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=8) # create sig counter counter = Counter(CtrDex.ControllerIdxSigs) # default is count = 1 @@ -195,7 +195,7 @@ def test_kevery(): # Event 8 Rotation override interaction serder = rotate(pre=kever.prefixer.qb64, keys=[signers[4].verfer.qb64], - dig=kever.serder.saider.qb64, + dig=kever.serder.said, ndigs=[coring.Diger(ser=signers[5].verfer.qb64b).qb64], sn=8) # create sig counter @@ -371,7 +371,7 @@ def test_stale_event_receipts(): kvy = eventing.Kevery(db=witHab.db, lax=False, local=False) parsing.Parser().parse(ims=bytearray(bobIcp), kvy=kvy) assert bobHab.pre in witHab.kevers - iserder = coring.Serder(raw=bytearray(bobIcp)) + iserder = serdering.SerderKERI(raw=bytearray(bobIcp)) msg = witHab.receipt(serder=iserder) parsing.Parser().parse(ims=bytearray(msg), kvy=bamKvy) @@ -385,7 +385,7 @@ def test_stale_event_receipts(): for witHab in [wesHab, wanHab]: kvy = eventing.Kevery(db=witHab.db, lax=False, local=False) parsing.Parser().parse(ims=bytearray(rot0), kvy=kvy) - iserder = coring.Serder(raw=bytearray(rot0)) + iserder = serdering.SerderKERI(raw=bytearray(rot0)) msg = witHab.receipt(serder=iserder) parsing.Parser().parse(ims=bytearray(msg), kvy=bamKvy) @@ -393,7 +393,7 @@ def test_stale_event_receipts(): assert bamKvy.kevers[bobHab.pre].sn == 1 # Validate that bam has 2 receipts in DB for event 1 - ser = coring.Serder(raw=rot0) + ser = serdering.SerderKERI(raw=rot0) dgkey = dbing.dgKey(ser.preb, ser.saidb) wigs = bamHby.db.getWigs(dgkey) assert len(wigs) == 2 @@ -405,7 +405,7 @@ def test_stale_event_receipts(): for witHab in [wesHab, wanHab]: kvy = eventing.Kevery(db=witHab.db, lax=False, local=False) parsing.Parser().parse(ims=bytearray(rot1), kvy=kvy) - iserder = coring.Serder(raw=bytearray(rot1)) + iserder = serdering.SerderKERI(raw=bytearray(rot1)) msg = witHab.receipt(serder=iserder) parsing.Parser().parse(ims=bytearray(msg), kvy=bamKvy) @@ -416,7 +416,7 @@ def test_stale_event_receipts(): # Pass receipts from Wil for event 1 to Bam kvy = eventing.Kevery(db=wilHab.db, lax=False, local=False) parsing.Parser().parse(ims=bytearray(rot0), kvy=kvy) - iserder = coring.Serder(raw=bytearray(rot0)) + iserder = serdering.SerderKERI(raw=bytearray(rot0)) msg = wilHab.receipt(serder=iserder) parsing.Parser().parse(ims=bytearray(msg), kvy=bamKvy) diff --git a/tests/core/test_keystate.py b/tests/core/test_keystate.py index 53949ffd9..b3cfb7e2d 100644 --- a/tests/core/test_keystate.py +++ b/tests/core/test_keystate.py @@ -7,13 +7,14 @@ """ from keri.app import habbing -from keri.core import coring, eventing, parsing, routing +from keri.core import coring, eventing, parsing, routing, serdering def test_keystate(mockHelpingNowUTC): """ { "v": "KERI10JSON000301_", + "vn": (1,0), "t": "rpy", "d": "E_9aLcmV9aEVEm7mXvEY3V_CmbyvG7Ahj6HCq-D48meM", "dt": "2021-11-04T12:57:59.823350+00:00", @@ -60,7 +61,7 @@ def test_keystate(mockHelpingNowUTC): # Wes is his witness # Bam is verifying the key state for Bob from Wes - # defualt for openHby temp = True + # default for openHby temp = True with (habbing.openHby(name="bob", base="test") as bobHby, habbing.openHby(name="bam", base="test") as bamHby, habbing.openHby(name="wes", base="test", salt=salt) as wesHby): @@ -78,24 +79,24 @@ def test_keystate(mockHelpingNowUTC): bobIcp = bobHab.makeOwnEvent(sn=0) parsing.Parser().parse(ims=bytearray(bobIcp), kvy=wesKvy) assert bobHab.pre in wesHab.kevers - iserder = coring.Serder(raw=bytearray(bobIcp)) + iserder = serdering.SerderKERI(raw=bytearray(bobIcp)) wesHab.receipt(serder=iserder) - # Get ksn from Bob and verify - ksn = bobHab.kever.state() - assert ksn.pre == bobHab.pre - assert ksn.sn == 0 - assert ksn.ked["d"] == bobHab.kever.serder.said + # Get key state record (ksr) from Bob and verify + ksr = bobHab.kever.state() + assert ksr.i == bobHab.pre + assert ksr.s == '0' + assert ksr.d == bobHab.kever.serder.said - # Get ksn from Wes and verify + # Get key state record (ksr) from Wes and verify bobKeverFromWes = wesHab.kevers[bobHab.pre] - ksn = bobKeverFromWes.state() - assert ksn.pre == bobHab.pre - assert ksn.sn == 0 - assert ksn.ked["d"] == bobHab.kever.serder.said + ksr = bobKeverFromWes.state() + assert ksr.i == bobHab.pre + assert ksr.s == '0' + assert ksr.d == bobHab.kever.serder.said - msg = wesHab.reply(route="/ksn/" + wesHab.pre, data=ksn.ked) + msg = wesHab.reply(route="/ksn/" + wesHab.pre, data=ksr._asdict()) bamRtr = routing.Router() bamRvy = routing.Revery(db=bamHby.db, rtr=bamRtr) @@ -106,7 +107,7 @@ def test_keystate(mockHelpingNowUTC): assert len(bamKvy.cues) == 1 cue = bamKvy.cues.popleft() assert cue["kin"] == "keyStateSaved" - assert cue["serder"].pre == bobHab.pre + assert cue["ksn"]["i"] == bobHab.pre msgs = bytearray() # outgoing messages for msg in wesHby.db.clonePreIter(pre=bobHab.pre, fn=0): @@ -142,21 +143,21 @@ def test_keystate(mockHelpingNowUTC): parsing.Parser().parse(ims=bytearray(bobIcp), kvy=wesKvy) assert bobHab.pre in wesHab.kevers - # Get ksn from Bob and verify - ksn = bobHab.kever.state() - assert ksn.pre == bobHab.pre - assert ksn.sn == 0 - assert ksn.ked["d"] == bobHab.kever.serder.said + # Get ksr key state record from Bob and verify + ksr = bobHab.kever.state() + assert ksr.i == bobHab.pre + assert ksr.s == '0' + assert ksr.d == bobHab.kever.serder.said - # Get ksn from Wes and verify + # Get ksr key state record from Wes and verify bobKeverFromWes = wesHab.kevers[bobHab.pre] - ksn = bobKeverFromWes.state() - assert ksn.pre == bobHab.pre - assert ksn.sn == 0 - assert ksn.ked["d"] == bobHab.kever.serder.said + ksr = bobKeverFromWes.state() + assert ksr.i == bobHab.pre + assert ksr.s == '0' + assert ksr.d == bobHab.kever.serder.said - msg = wesHab.reply(route="/ksn/" + wesHab.pre, data=ksn.ked) + msg = wesHab.reply(route="/ksn/" + wesHab.pre, data=ksr._asdict()) #bamHab = habbing.Habitat(name="bam", ks=bamKS, db=bamDB, isith='1', icount=1, #transferable=True, temp=True) @@ -176,7 +177,7 @@ def test_keystate(mockHelpingNowUTC): assert len(bamKvy.cues) == 1 cue = bamKvy.cues.popleft() assert cue["kin"] == "keyStateSaved" - assert cue["serder"].pre == bobHab.pre + assert cue["ksn"]["i"] == bobHab.pre msgs = bytearray() # outgoing messages for msg in wesHby.db.clonePreIter(pre=bobHab.pre, fn=0): @@ -211,20 +212,20 @@ def test_keystate(mockHelpingNowUTC): parsing.Parser().parse(ims=bytearray(bobIcp), kvy=wesKvy) assert bobHab.pre in wesHab.kevers - # Get ksn from Bob and verify - ksn = bobHab.kever.state() - assert ksn.pre == bobHab.pre - assert ksn.sn == 0 - assert ksn.ked["d"] == bobHab.kever.serder.said + # Get ksr key state record from Bob and verify + ksr = bobHab.kever.state() + assert ksr.i == bobHab.pre + assert ksr.s == '0' + assert ksr.d == bobHab.kever.serder.said - # Get ksn from Wes and verify + # Get ksr key state record from Wes and verify bobKeverFromWes = wesHab.kevers[bobHab.pre] - ksn = bobKeverFromWes.state() - assert ksn.pre == bobHab.pre - assert ksn.sn == 0 - assert ksn.ked["d"] == bobHab.kever.serder.said + ksr = bobKeverFromWes.state() + assert ksr.i == bobHab.pre + assert ksr.s == '0' + assert ksr.d == bobHab.kever.serder.said - msg = wesHab.reply(route="/ksn/" + wesHab.pre, data=ksn.ked) + msg = wesHab.reply(route="/ksn/" + wesHab.pre, data=ksr._asdict()) bamKvy = eventing.Kevery(db=bamHby.db, lax=False, local=False) parsing.Parser().parse(ims=bytearray(msg), kvy=bamKvy) @@ -242,22 +243,22 @@ def test_keystate(mockHelpingNowUTC): bobHab = bobHby.makeHab(name="bob", isith='1', icount=1, transferable=True) assert bobHab.pre == bobpre - # Get ksn from Bob and verify - ksn = bobHab.kever.state() - assert ksn.pre == bobHab.pre - assert ksn.sn == 0 - assert ksn.ked["d"] == bobHab.kever.serder.said + # Get ksr key state record from Bob and verify + ksr = bobHab.kever.state() + assert ksr.i == bobHab.pre + assert ksr.s == '0' + assert ksr.d == bobHab.kever.serder.said for _ in range(3): bobHab.rotate() - # Get ksn from Bob and verify - ksn = bobHab.kever.state() - assert ksn.pre == bobHab.pre - assert ksn.sn == 3 - assert ksn.ked["d"] == bobHab.kever.serder.said + # Get ksr key state record from Bob and verify + ksr = bobHab.kever.state() + assert ksr.i == bobHab.pre + assert ksr.s == '3' + assert ksr.d == bobHab.kever.serder.said - staleKsn = bobHab.reply(route="/ksn/" + bobHab.pre, data=ksn.ked) + staleKsn = bobHab.reply(route="/ksn/" + bobHab.pre, data=ksr._asdict()) bamRtr = routing.Router() bamRvy = routing.Revery(db=bamHby.db, rtr=bamRtr) @@ -269,12 +270,12 @@ def test_keystate(mockHelpingNowUTC): bobHab.rotate() # Get ksn from Bob and verify - ksn = bobHab.kever.state() - assert ksn.pre == bobHab.pre - assert ksn.sn == 8 - assert ksn.ked["d"] == bobHab.kever.serder.said + ksr = bobHab.kever.state() + assert ksr.i == bobHab.pre + assert ksr.s == '8' + assert ksr.d == bobHab.kever.serder.said - liveKsn = bobHab.reply(route="/ksn/" + bobHab.pre, data=ksn.ked) + liveKsn = bobHab.reply(route="/ksn/" + bobHab.pre, data=ksr._asdict()) parsing.Parser().parse(ims=bytearray(liveKsn), kvy=bamKvy, rvy=bamRvy) msgs = bytearray() # outgoing messages @@ -292,7 +293,7 @@ def test_keystate(mockHelpingNowUTC): saider = bamHby.db.knas.get(keys=keys) assert saider.qb64 == bobHab.kever.serder.said latest = bamHby.db.ksns.get(keys=(saider.qb64,)) - assert latest.sn == 8 + assert latest.s == '8' """End Test""" diff --git a/tests/core/test_parsing.py b/tests/core/test_parsing.py index 7db6e4c8e..2909effc5 100644 --- a/tests/core/test_parsing.py +++ b/tests/core/test_parsing.py @@ -63,7 +63,7 @@ def test_parser(): # Event 1 Rotation Transferable serder = rotate(pre=kever.prefixer.qb64, keys=[signers[1].verfer.qb64], - dig=kever.serder.saider.qb64, + dig=kever.serder.said, ndigs=[coring.Diger(ser=signers[2].verfer.qb64b).qb64], sn=1) event_digs.append(serder.said) @@ -81,7 +81,7 @@ def test_parser(): # Event 2 Rotation Transferable serder = rotate(pre=kever.prefixer.qb64, keys=[signers[2].verfer.qb64], - dig=kever.serder.saider.qb64, + dig=kever.serder.said, ndigs=[coring.Diger(ser=signers[3].verfer.qb64b).qb64], sn=2) event_digs.append(serder.said) @@ -98,7 +98,7 @@ def test_parser(): # Event 3 Interaction serder = interact(pre=kever.prefixer.qb64, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=3) event_digs.append(serder.said) # create sig counter @@ -114,7 +114,7 @@ def test_parser(): # Event 4 Interaction serder = interact(pre=kever.prefixer.qb64, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=4) event_digs.append(serder.said) # create sig counter @@ -131,7 +131,7 @@ def test_parser(): # Event 5 Rotation Transferable serder = rotate(pre=kever.prefixer.qb64, keys=[signers[3].verfer.qb64], - dig=kever.serder.saider.qb64, + dig=kever.serder.said, ndigs=[coring.Diger(ser=signers[4].verfer.qb64b).qb64], sn=5) event_digs.append(serder.said) @@ -148,7 +148,7 @@ def test_parser(): # Event 6 Interaction serder = interact(pre=kever.prefixer.qb64, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=6) event_digs.append(serder.said) # create sig counter @@ -166,7 +166,7 @@ def test_parser(): # nxt digest is empty serder = rotate(pre=kever.prefixer.qb64, keys=[signers[4].verfer.qb64], - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=7) event_digs.append(serder.said) # create sig counter @@ -180,9 +180,9 @@ def test_parser(): msgs.extend(counter.qb64b) msgs.extend(siger.qb64b) - # Event 8 Interaction + # Event 8 Interaction but already abandoned serder = interact(pre=kever.prefixer.qb64, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=8) # create sig counter counter = Counter(CtrDex.ControllerIdxSigs) # default is count = 1 @@ -196,10 +196,10 @@ def test_parser(): msgs.extend(counter.qb64b) msgs.extend(siger.qb64b) - # Event 8 Rotation + # Event 8 Rotation override interaction but already abandoned serder = rotate(pre=kever.prefixer.qb64, keys=[signers[4].verfer.qb64], - dig=kever.serder.saider.qb64, + dig=kever.serder.said, ndigs=[coring.Diger(ser=signers[5].verfer.qb64b).qb64], sn=8) # create sig counter diff --git a/tests/core/test_parsing_pathed.py b/tests/core/test_parsing_pathed.py index b853b05cc..d3b26a162 100644 --- a/tests/core/test_parsing_pathed.py +++ b/tests/core/test_parsing_pathed.py @@ -8,19 +8,24 @@ from keri import help from keri.app import habbing -from keri.core import parsing, coring +from keri.core import parsing, coring, serdering from keri.peer import exchanging logger = help.ogler.getLogger() def test_pathed_material(mockHelpingNowUTC): - class MockHandler: resource = "/fwd" + local = True def __init__(self): self.msgs = decking.Deck() + self.atcs = decking.Deck() + + def handle(self, serder, attachments=None): + self.msgs.append(serder) + self.atcs.append(attachments) with (habbing.openHby(name="pal", salt=coring.Salter(raw=b'0123456789abcdef').qb64) as hby, habbing.openHby(name="deb", base="test") as debHby): @@ -28,47 +33,39 @@ def __init__(self): palHab = hby.makeHab(name="pal") debHab = debHby.makeHab(name="deb", isith=sith, icount=3) # Create series of events - debMsgs = [debHab.makeOwnInception(), debHab.interact(), debHab.rotate(), debHab.interact()] - events = [] - atc = bytearray() - for i, msg in enumerate(debMsgs): - evt = coring.Serder(raw=msg) - events.append(evt.ked) - pather = coring.Pather(path=["a", i]) - btc = pather.qb64b + msg[evt.size:] - atc.extend(coring.Counter(code=coring.CtrDex.PathedMaterialQuadlets, - count=(len(btc) // 4)).qb64b) - atc.extend(btc) - - fwd = exchanging.exchange(route='/fwd', - modifiers=dict(pre=palHab.pre, topic="replay"), payload=events) - fwd = debHab.endorse(fwd, last=True, pipelined=False) - fwd.extend(atc) + debMsgs = dict(icp=debHab.makeOwnInception(), ixn0=debHab.interact(), rot=debHab.rotate(), + ixn1=debHab.interact()) + fwd, end = exchanging.exchange(route='/fwd', + modifiers=dict(pre=palHab.pre, topic="replay"), payload={}, embeds=debMsgs, + sender=debHab.pre) + fwd = debHab.endorse(fwd, last=False, pipelined=False) + fwd.extend(end) handler = MockHandler() - exc = exchanging.Exchanger(db=debHby.db, handlers=[handler]) + exc = exchanging.Exchanger(hby=debHby, handlers=[handler]) parser = parsing.Parser(exc=exc) parser.parseOne(ims=fwd) assert len(handler.msgs) == 1 - msg = handler.msgs.popleft() + serder = handler.msgs.popleft() - payload = msg["payload"] - assert len(payload) == 4 - assert payload[0]["t"] == coring.Ilks.icp - assert payload[1]["t"] == coring.Ilks.ixn - assert payload[2]["t"] == coring.Ilks.rot - assert payload[3]["t"] == coring.Ilks.ixn - attachments = msg["attachments"] + embeds = serder.ked['e'] + assert len(embeds) == 5 + assert embeds["icp"]["t"] == coring.Ilks.icp + assert embeds["ixn0"]["t"] == coring.Ilks.ixn + assert embeds["rot"]["t"] == coring.Ilks.rot + assert embeds["ixn1"]["t"] == coring.Ilks.ixn + assert len(handler.atcs) == 1 + attachments = handler.atcs.popleft() assert len(attachments) == 4 (path1, attachment1) = attachments[0] - assert path1.bext == "-0" + assert path1.bext == "-icp" assert attachment1 == (b'-AADAADqkN1IwOepXk5LYPaLBCoHWnZpdWZ2qmhLQKY9I-ape8cTqwHKPg5EP98y' b'bxgYDhAzpOkv9BzE2dhVeac0l7cKABBJhNtfZG642LFbrRurILy0iKMoT8bc1Olk' b'cFYDpmCUwIYlH_jNk-7WlxtgunEMMcBvvGl_E5xuZ_Il6YLSUY4JACAIrMoryRki' b'spZKXWabmx2aBrTgTaGBvysk7B3-mcF0Mg1riSikRar5d70gBZIQjAUuE6KYWLd1' b'Sa0CTMzaTZAO') (path2, attachment2) = attachments[1] - assert path2.bext == "-1" + assert path2.bext == "-ixn0" assert attachment2 == (b'-AADAAA9aT5vgzKjSVl_xcCXiLIUIqYl9___1Gll8Sj6dDIAygsBQ-lVATd1ifTe' b'_DcsKTwY6sCr1a29f1LNOY_tngoLABCUcENmDJH_Xeh7Pc5q8Nwww5FcTJtpHkBT' b'wdeJ-v6aSPUMaTdkXI7n_3r-8ogrDlKddjgYiOTt2V7f53g-JbYCACAVG_IWtYZp' diff --git a/tests/core/test_partial_rotation.py b/tests/core/test_partial_rotation.py index e8643d75a..b19c1b55c 100644 --- a/tests/core/test_partial_rotation.py +++ b/tests/core/test_partial_rotation.py @@ -67,7 +67,7 @@ def test_partial_rotation(): rotser = eventing.rotate(pre=kever.prefixer.qb64, isith='3', keys=keys, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, nsith='4', ndigs=ndigs, sn=1) @@ -102,7 +102,7 @@ def test_partial_rotation(): rotser = eventing.rotate(pre=kever.prefixer.qb64, isith='3', keys=keys, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, nsith='2', ndigs=ndigs, sn=2) @@ -156,7 +156,7 @@ def test_partial_rotation(): rotser = eventing.rotate(pre=kever.prefixer.qb64, isith=["1/2", "1/2", "1/3"], keys=keys, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, nsith=["1/2", "1/2", "1/3", "1/3", "1/3"], ndigs=ndigs, sn=1) @@ -184,7 +184,7 @@ def test_partial_rotation(): rotser = eventing.rotate(pre=kever.prefixer.qb64, isith='2', keys=keys, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, nsith='0', ndigs=ndigs, sn=2) diff --git a/tests/core/test_replay.py b/tests/core/test_replay.py index cbd6e6c6b..5ce494c9a 100644 --- a/tests/core/test_replay.py +++ b/tests/core/test_replay.py @@ -8,7 +8,7 @@ from keri import help from keri.app import habbing -from keri.core import coring, eventing, parsing +from keri.core import coring, eventing, parsing, serdering from keri.help import helping logger = help.ogler.getLogger() @@ -328,7 +328,7 @@ def test_replay(): debFelMsgs.extend(msg) # parse msg - serder = coring.Serder(raw=msg) + serder = serdering.SerderKERI(raw=msg) assert serder.raw == debHab.iserder.raw assert serder.sn == fn # no recovery forks so sn == fn assert serder.ked["t"] == coring.Ilks.icp @@ -402,14 +402,14 @@ def test_replay(): cloner = debHab.db.clonePreIter(pre=debHab.pre, fn=fn) # create iterator not at 0 msg = next(cloner) # next event with attachments assert len(msg) == 1279 - serder = coring.Serder(raw=msg) + serder = serdering.SerderKERI(raw=msg) assert serder.sn == fn # no recovery forks so sn == fn assert serder.ked["t"] == coring.Ilks.ixn debFelMsgs.extend(msg) fn += 1 msg = next(cloner) # get zeroth event with attachments - serder = coring.Serder(raw=msg) + serder = serdering.SerderKERI(raw=msg) assert serder.sn == fn # no recovery forks so sn == fn assert serder.ked["t"] == coring.Ilks.rot assert len(msg) == 1648 @@ -420,7 +420,7 @@ def test_replay(): fn += 1 while (fn <= 6): msg = next(cloner) # get zeroth event with attachments - serder = coring.Serder(raw=msg) + serder = serdering.SerderKERI(raw=msg) assert serder.sn == fn # no recovery forks so sn == fn assert serder.ked["t"] == coring.Ilks.ixn assert len(msg) == 1279 diff --git a/tests/core/test_reply.py b/tests/core/test_reply.py index 79b17cdf5..608ae48f4 100644 --- a/tests/core/test_reply.py +++ b/tests/core/test_reply.py @@ -110,8 +110,8 @@ def test_reply(mockHelpingNowUTC): #assert tamHab.ks == tamKS #assert tamHab.db == tamDB assert tamHab.kever.prefixer.transferable - assert len(tamHab.iserder.werfers) == len(wits) - for werfer in tamHab.iserder.werfers: + assert len(tamHab.iserder.berfers) == len(wits) + for werfer in tamHab.iserder.berfers: assert werfer.qb64 in wits assert tamHab.kever.wits == wits assert tamHab.kever.toader.num == 2 @@ -179,7 +179,7 @@ def test_reply(mockHelpingNowUTC): ) serderR = eventing.reply(route=route, data=data, ) - assert serderR.ked['dt'] == help.helping.DTS_BASE_0 + assert serderR.stamp == help.helping.DTS_BASE_0 assert serderR.raw == (b'{"v":"KERI10JSON000113_","t":"rpy","d":"EFlkeg-NociMRXHSGBSqARxV5y7zuT5z-ZpL' b'ZAkcoMkk","dt":"2021-01-01T00:00:00.000000+00:00","r":"/end/role/add","a":{"' @@ -245,7 +245,7 @@ def test_reply(mockHelpingNowUTC): # stale datetime serderR = eventing.reply(route=route, data=data, ) - assert serderR.ked['dt'] == help.helping.DTS_BASE_0 + assert serderR.stamp == help.helping.DTS_BASE_0 assert serderR.raw == (b'{"v":"KERI10JSON000113_","t":"rpy","d":"EM_AD-vVfhW-paUryMAZJKasyBuz_GoYIU_k' b'fp7hmqHY","dt":"2021-01-01T00:00:00.000000+00:00","r":"/end/role/cut","a":{"' diff --git a/tests/core/test_scheming.py b/tests/core/test_scheming.py index 7ed6724cb..a79bac996 100644 --- a/tests/core/test_scheming.py +++ b/tests/core/test_scheming.py @@ -7,7 +7,7 @@ import pytest -from keri.core.coring import MtrDex, dumps, Saider, Ids +from keri.core.coring import MtrDex, dumps, Saider, Saids from keri.core.scheming import Schemer, JSONSchema, CacheResolver from keri.db import basing from keri.kering import ValidationError @@ -40,7 +40,7 @@ def test_json_schema(): } # generate serialized saidified schema ssad - saider, ssad = Saider.saidify(ssad, label=Ids.dollar) + saider, ssad = Saider.saidify(ssad, label=Saids.dollar) assert saider.qb64 == 'EMRvS7lGxc1eDleXBkvSHkFs8vUrslRcla6UXOJdcczw' sser = dumps(ssad) assert sser == (b'{"$id":"EMRvS7lGxc1eDleXBkvSHkFs8vUrslRcla6UXOJdcczw","$schema":"http://json' @@ -211,7 +211,7 @@ def test_resolution(): } # generate serialized saidified schema refsad - saider, refsad = Saider.saidify(refsad, label=Ids.dollar) + saider, refsad = Saider.saidify(refsad, label=Saids.dollar) refsaid = saider.qb64 assert refsaid == 'EL3Luusa97P8dZOCI8KEN2ShG35HVS8S6-z1vuu52F-C' ref = dumps(refsad) @@ -246,7 +246,7 @@ def test_resolution(): ssad["properties"]["xy"]["$ref"] = f"did:keri:{refsaid}" # generate serialized saidified schema ssad - saider, ssad = Saider.saidify(ssad, label=Ids.dollar) + saider, ssad = Saider.saidify(ssad, label=Saids.dollar) said = saider.qb64 assert said == 'EKcRFuOiLUMEgTljL8FWPOpDosH2Cz38HhgdmRKpUHTe' sser = dumps(ssad) diff --git a/tests/core/test_serdering.py b/tests/core/test_serdering.py new file mode 100644 index 000000000..fde03b557 --- /dev/null +++ b/tests/core/test_serdering.py @@ -0,0 +1,2500 @@ +# -*- encoding: utf-8 -*- +""" +tests.core.test_serdering module + +""" +import dataclasses +import json +from collections import namedtuple + +import cbor2 as cbor +import msgpack + +import pytest + +from keri import kering +from keri.kering import Versionage, Version + +from keri.core import coring + +from keri.core.serdering import (Fieldage, Serdery, Serder, + SerderKERI, SerderACDC, ) + + + +def test_serder(): + """ + Test Serder + """ + + # Test Serder + + assert Serder.Fields == {'KERI': {Versionage(major=1, minor=0): {'icp': Fieldage(saids={'d': 'E', 'i': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0', 'kt': '0', 'k': [], 'nt': '0', 'n': [], 'bt': '0', 'b': [], 'c': [], 'a': []}), + 'rot': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0', 'p': '', 'kt': '0', 'k': [], 'nt': '0', 'n': [], 'bt': '0', 'br': [], 'ba': [], 'a': []}), + 'ixn': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0', 'p': '', 'a': []}), + 'dip': Fieldage(saids={'d': 'E', 'i': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0', 'kt': '0', 'k': [], 'nt': '0', 'n': [], 'bt': '0', 'b': [], 'c': [], 'a': [], 'di': ''}), + 'drt': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0', 'p': '', 'kt': '0', 'k': [], 'nt': '0', 'n': [], 'bt': '0', 'br': [], 'ba': [], 'a': []}), + 'rct': Fieldage(saids={}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0'}), + 'qry': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'dt': '', 'r': '', 'rr': '', 'q': {}}), + 'rpy': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'dt': '', 'r': '', 'a': []}), + 'pro': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'dt': '', 'r': '', 'rr': '', 'q': {}}), + 'bar': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'dt': '', 'r': '', 'a': []}), + 'exn': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 'p': '', 'dt': '', 'r': '', 'q': {}, 'a': [], 'e': {}}), + 'vcp': Fieldage(saids={'d': 'E', 'i': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 'ii': '', 's': '0', 'c': [], 'bt': '0', 'b': [], 'n': ''}), + 'vrt': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 'p': '', 's': '0', 'bt': '0', 'br': [], 'ba': []}), + 'iss': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0', 'ri': '', 'dt': ''}), + 'rev': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0', 'ri': '', 'p': '', 'dt': ''}), + 'bis': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 'ii': '', 's': '0', 'ra': {}, 'dt': ''}), + 'brv': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0', 'p': '', 'ra': {}, 'dt': ''})}, + Versionage(major=1, minor=1): {'icp': Fieldage(saids={'d': 'E', 'i': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0', 'kt': '0', 'k': [], 'nt': '0', 'n': [], 'bt': '0', 'b': [], 'c': [], 'a': []}), + 'rot': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0', 'p': '', 'kt': '0', 'k': [], 'nt': '0', 'n': [], 'bt': '0', 'br': [], 'ba': [], 'c': [], 'a': []}), + 'ixn': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0', 'p': '', 'a': []}), + 'dip': Fieldage(saids={'d': 'E', 'i': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0', 'kt': '0', 'k': [], 'nt': '0', 'n': [], 'bt': '0', 'b': [], 'c': [], 'a': [], 'di': ''}), + 'drt': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0', 'p': '', 'kt': '0', 'k': [], 'nt': '0', 'n': [], 'bt': '0', 'br': [], 'ba': [], 'c': [], 'a': []}), + 'rct': Fieldage(saids={}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0'}), + 'qry': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 'dt': '', 'r': '', 'rr': '', 'q': {}}), + 'rpy': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 'dt': '', 'r': '', 'a': []}), + 'pro': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 'dt': '', 'r': '', 'rr': '', 'q': {}}), + 'bar': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 'dt': '', 'r': '', 'a': []}), + 'exn': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 'p': '', 'dt': '', 'r': '', 'q': {}, 'a': [], 'e': {}})}}, + 'CREL': {Versionage(major=1, minor=1): {'vcp': Fieldage(saids={'d': 'E', 'i': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 'ii': '', 's': '0', 'c': [], 'bt': '0', 'b': [], 'u': ''}), + 'vrt': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 'p': '', 's': '0', 'bt': '0', 'br': [], 'ba': []}), + 'iss': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0', 'ri': '', 'dt': ''}), + 'rev': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0', 'ri': '', 'p': '', 'dt': ''}), + 'bis': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 'ii': '', 's': '0', 'ra': {}, 'dt': ''}), + 'brv': Fieldage(saids={'d': 'E'}, alls={'v': '', 't': '', 'd': '', 'i': '', 's': '0', 'p': '', 'ra': {}, 'dt': ''})}}, + 'ACDC': {Versionage(major=1, minor=0): {None: Fieldage(saids={'d': 'E'}, alls={'v': '', 'd': '', 'i': '', 's': ''})}}} + + + assert Serder.Ilks == {'KERI': 'icp', 'CREL': 'vcp', 'ACDC': None} + + assert Serder.Fields[kering.Protos.acdc][kering.Vrsn_1_0][None].saids == {'d': 'E'} + assert Serder.Fields[kering.Protos.acdc][kering.Vrsn_1_0][None].alls == {'v': '', 'd': '', 'i': '', 's': ''} + + # said field labels must be subset of all field labels + assert (set(Serder.Fields[kering.Protos.acdc][kering.Vrsn_1_0][None].saids.keys()) <= + set(Serder.Fields[kering.Protos.acdc][kering.Vrsn_1_0][None].alls.keys())) + + + for proto, vrsns in Serder.Fields.items(): + for vrsn, ilks in vrsns.items(): + for ilk, fields in ilks.items(): + assert set(fields.saids.keys()) <= set(fields.alls.keys()) + + + with pytest.raises(ValueError): + serder = Serder() + + #Test Serder bare makify bootstrap for ACDC JSON + serder = Serder(makify=True, proto=kering.Protos.acdc) # make defaults for ACDC + assert serder.sad == {'v': 'ACDC10JSON00005a_', + 'd': 'EMk7BvrqO_2sYjpI_-BmSELOFNie-muw4XTi3iYCz6pT', + 'i': '', + 's': ''} + assert serder.raw == (b'{"v":"ACDC10JSON00005a_","d":"EMk7BvrqO_2sYjpI_-' + b'BmSELOFNie-muw4XTi3iYCz6pT","i":"","s":""}') + assert serder.verify() + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + + serder = Serder(sad=sad) + assert serder.raw == raw + assert isinstance(serder.raw, bytes) + assert serder.sad == sad + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.saidb == said.encode("utf-8") + assert serder.ilk == None + assert serder.compare(said=said) + assert serder.compare(said=said.encode("utf-8")) + assert not serder.compare(said='EMk7BvrqO_2sYjpI_-BmSELOFNie-muw4XTi3iYCz6pE') + assert serder.pretty() == ('{\n' + ' "v": "ACDC10JSON00005a_",\n' + ' "d": "EMk7BvrqO_2sYjpI_-BmSELOFNie-muw4XTi3iYCz6pT",\n' + ' "i": "",\n' + ' "s": ""\n' + '}') + + serder = Serder(raw=raw) + assert serder.raw == raw + assert isinstance(serder.raw, bytes) + assert serder.sad == sad + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.saidb == said.encode("utf-8") + assert serder.ilk == None + assert serder.compare(said=said) + assert serder.compare(said=said.encode("utf-8")) + + serder = Serder(sad=sad, makify=True) # test makify with sad + assert serder.raw == raw + assert isinstance(serder.raw, bytes) + assert serder.sad == sad + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.saidb == said.encode("utf-8") + assert serder.ilk == None + assert serder.compare(said=said) + + + + serder = Serder(sad=sad, verify=False) # test not verify + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.saidb == said.encode("utf-8") + assert serder.ilk == None + assert serder.compare(said=said) + + serder = Serder(raw=raw, verify=False) # test not verify + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.saidb == said.encode("utf-8") + assert serder.ilk == None + assert serder.compare(said=said) + + + + # Test ignores strip if raw is bytes not bytearray + serder = Serder(raw=raw, strip=True) + assert serder.raw == raw + assert isinstance(serder.raw, bytes) + + # Test strip of bytearray + extra = bytearray(b'Not a serder.') + stream = bytearray(raw) + extra + assert stream == bytearray(b'{"v":"ACDC10JSON00005a_","d":"EMk7BvrqO_2sYjp' + b'I_-BmSELOFNie-muw4XTi3iYCz6pT","i":"","s":""}' + b'Not a serder.') + serder = Serder(raw=stream, strip=True) + assert serder.raw == raw + assert isinstance(serder.raw, bytes) + assert stream == extra + assert serder.sad == sad + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == None + + # test verify bad digest value + badraw = (b'{"v":"ACDC10JSON00005a_",' + b'"d":"EMk7BvrqO_2sYjpI_-BmSELOFNie-muw4XTi3iYCz6pE",' + b'"i":"","s":""}') + with pytest.raises(kering.ValidationError): + serder = Serder(raw=badraw, verify=True) + + + + #Test makify bootstrap for ACDC with CBOR + serder = Serder(makify=True, proto=kering.Protos.acdc, kind=kering.Serials.cbor) + assert serder.sad == {'v': 'ACDC10CBOR00004b_', + 'd': 'EGahYhEMb_Sz0L1UwhrUvbyxyzoi_G85-pD9jRjhnqgU', + 'i': '', + 's': ''} + assert serder.raw == (b'\xa4avqACDC10CBOR00004b_adx,EGahYhEMb_Sz0L1UwhrU' + b'vbyxyzoi_G85-pD9jRjhnqgUai`as`') + assert serder.verify() + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + + serder = Serder(sad=sad) + assert serder.raw == raw + assert isinstance(serder.raw, bytes) + assert serder.sad == sad + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.cbor + assert serder.said == said + assert serder.saidb == said.encode("utf-8") + assert serder.ilk == None + assert serder.compare(said=said) + assert serder.compare(said=said.encode("utf-8")) + assert not serder.compare(said='EMk7BvrqO_2sYjpI_-BmSELOFNie-muw4XTi3iYCz6pE') + assert serder.pretty() == ('{\n' + ' "v": "ACDC10CBOR00004b_",\n' + ' "d": "EGahYhEMb_Sz0L1UwhrUvbyxyzoi_G85-pD9jRjhnqgU",\n' + ' "i": "",\n' + ' "s": ""\n' + '}') + + serder = Serder(raw=raw) + assert serder.raw == raw + assert isinstance(serder.raw, bytes) + assert serder.sad == sad + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.cbor + assert serder.said == said + assert serder.saidb == said.encode("utf-8") + assert serder.ilk == None + assert serder.compare(said=said) + assert serder.compare(said=said.encode("utf-8")) + + serder = Serder(sad=sad, makify=True) # test makify with sad + assert serder.raw == raw + assert isinstance(serder.raw, bytes) + assert serder.sad == sad + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.cbor + assert serder.said == said + assert serder.saidb == said.encode("utf-8") + assert serder.ilk == None + assert serder.compare(said=said) + + + #Test makify bootstrap for ACDC with MGPK + serder = Serder(makify=True, proto=kering.Protos.acdc, kind=kering.Serials.mgpk) + assert serder.sad == {'v': 'ACDC10MGPK00004b_', + 'd': 'EGV5wdF1nRbSXatBgZDpAxlGL6BuATjpUYBuk0AQW7GC', + 'i': '', + 's': ''} + assert serder.raw == (b'\x84\xa1v\xb1ACDC10MGPK00004b_\xa1d\xd9,EGV5wdF1' + b'nRbSXatBgZDpAxlGL6BuATjpUYBuk0AQW7GC\xa1i\xa0\xa1s\xa0') + assert serder.verify() + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + + serder = Serder(sad=sad) + assert serder.raw == raw + assert isinstance(serder.raw, bytes) + assert serder.sad == sad + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.mgpk + assert serder.said == said + assert serder.saidb == said.encode("utf-8") + assert serder.ilk == None + assert serder.compare(said=said) + assert serder.compare(said=said.encode("utf-8")) + assert not serder.compare(said='EMk7BvrqO_2sYjpI_-BmSELOFNie-muw4XTi3iYCz6pE') + assert serder.pretty() == ('{\n' + ' "v": "ACDC10MGPK00004b_",\n' + ' "d": "EGV5wdF1nRbSXatBgZDpAxlGL6BuATjpUYBuk0AQW7GC",\n' + ' "i": "",\n' + ' "s": ""\n' + '}') + + serder = Serder(raw=raw) + assert serder.raw == raw + assert isinstance(serder.raw, bytes) + assert serder.sad == sad + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.mgpk + assert serder.said == said + assert serder.saidb == said.encode("utf-8") + assert serder.ilk == None + assert serder.compare(said=said) + assert serder.compare(said=said.encode("utf-8")) + + serder = Serder(sad=sad, makify=True) # test makify with sad + assert serder.raw == raw + assert isinstance(serder.raw, bytes) + assert serder.sad == sad + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.mgpk + assert serder.said == said + assert serder.saidb == said.encode("utf-8") + assert serder.ilk == None + assert serder.compare(said=said) + + + # Test KERI JSON with makify defaults for self bootstrap which is state msg + serder = Serder(makify=True) # make with all defaults is state message + assert serder.sad == {'v': 'KERI10JSON0000cf_', + 't': 'icp', + 'd': 'EF6LmlLkfoNVY25RcGTsqKLW5uHq36FbnNEdjON07Rwv', + 'i': 'EF6LmlLkfoNVY25RcGTsqKLW5uHq36FbnNEdjON07Rwv', + 's': '0', + 'kt': '0', + 'k': [], + 'nt': '0', + 'n': [], + 'bt': '0', + 'b': [], + 'c': [], + 'a': []} + assert serder.raw == (b'{"v":"KERI10JSON0000cf_","t":"icp","d":"EF6LmlLkfoNVY25RcGTsqKLW5uHq36FbnNEd' + b'jON07Rwv","i":"EF6LmlLkfoNVY25RcGTsqKLW5uHq36FbnNEdjON07Rwv","s":"0","kt":"0' + b'","k":[],"nt":"0","n":[],"bt":"0","b":[],"c":[],"a":[]}') + + assert serder.verify() + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + + serder = Serder(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk == kering.Ilks.icp + + serder = Serder(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk == kering.Ilks.icp + + + # Test KERI JSON with makify defaults for self bootstrap with ilk icp + serder = Serder(makify=True, ilk=kering.Ilks.icp) # make with defaults + assert serder.sad =={'v': 'KERI10JSON0000cf_', + 't': 'icp', + 'd': 'EF6LmlLkfoNVY25RcGTsqKLW5uHq36FbnNEdjON07Rwv', + 'i': 'EF6LmlLkfoNVY25RcGTsqKLW5uHq36FbnNEdjON07Rwv', + 's': '0', + 'kt': '0', + 'k': [], + 'nt': '0', + 'n': [], + 'bt': '0', + 'b': [], + 'c': [], + 'a': []} + + assert serder.raw == (b'{"v":"KERI10JSON0000cf_","t":"icp","d":"EF6LmlLkfoNVY25RcGTsqKLW5uHq36FbnNEd' + b'jON07Rwv","i":"EF6LmlLkfoNVY25RcGTsqKLW5uHq36FbnNEdjON07Rwv","s":"0","kt":"0' + b'","k":[],"nt":"0","n":[],"bt":"0","b":[],"c":[],"a":[]}') + + + assert serder.verify() + assert serder.ilk == kering.Ilks.icp + assert serder.sad['i'] == serder.said # default prefix is saidive + + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + + serder = Serder(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + serder = Serder(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + + # Test with non-digestive code for 'i' saidive field no sad + serder = Serder(makify=True, + ilk=kering.Ilks.icp, + saids = {'i': coring.PreDex.Ed25519}) + + assert serder.sad == {'v': 'KERI10JSON0000a3_', + 't': 'icp', + 'd': 'EEeXbwybn8tv2Wo_YNBpaqP3PobjvzUs6tH0XNRmfOTx', + 'i': '', + 's': '0', + 'kt': '0', + 'k': [], + 'nt': '0', + 'n': [], + 'bt': '0', + 'b': [], + 'c': [], + 'a': []} + + assert serder.raw == (b'{"v":"KERI10JSON0000a3_","t":"icp","d":"EEeXbwybn8tv2Wo_YNBpaqP3PobjvzUs6tH0' + b'XNRmfOTx","i":"","s":"0","kt":"0","k":[],"nt":"0","n":[],"bt":"0","b":[],"c"' + b':[],"a":[]}') + + assert not serder.verify() # because of empty 'i' field saidive + assert serder.ilk == kering.Ilks.icp + assert serder.sad['i'] == '' != serder.said # prefix is not saidive + + sad = serder.sad + + # test makify with preloaded non-digestive 'i' value in sad + pre = 'DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx' + sad['i'] = pre + + serder = Serder(sad=sad, makify=True) + assert serder.sad == {'v': 'KERI10JSON0000cf_', + 't': 'icp', + 'd': 'EIXK39EgyxshefoCdSpKCkG5FR9s405YI4FAHDvAqO_R', + 'i': 'DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx', + 's': '0', + 'kt': '0', + 'k': [], + 'nt': '0', + 'n': [], + 'bt': '0', + 'b': [], + 'c': [], + 'a': []} + + assert serder.raw ==(b'{"v":"KERI10JSON0000cf_","t":"icp","d":"EIXK39EgyxshefoCdSpKCkG5FR9s405YI4FA' + b'HDvAqO_R","i":"DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx","s":"0","kt":"0' + b'","k":[],"nt":"0","n":[],"bt":"0","b":[],"c":[],"a":[]}') + + assert serder.verify() + assert serder.ilk == kering.Ilks.icp + assert serder.sad['i'] == pre != said # prefix is not saidive + + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + + serder = Serder(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + serder = Serder(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + # Test KERI JSON with makify defaults for self bootstrap with ilk rot + serder = Serder(makify=True, ilk=kering.Ilks.rot) # make with defaults + assert serder.sad == {'v': 'KERI10JSON0000ac_', + 't': 'rot', + 'd': 'EMgauZPVfh6807jO9QO8A4Iauq1xhYTZnKX2doVd_UDl', + 'i': '', + 's': '0', + 'p': '', + 'kt': '0', + 'k': [], + 'nt': '0', + 'n': [], + 'bt': '0', + 'br': [], + 'ba': [], + 'a': []} + + assert serder.raw == (b'{"v":"KERI10JSON0000ac_","t":"rot","d":"EMgauZPVfh6807jO9QO8A4Iauq1xhYTZnKX2' + b'doVd_UDl","i":"","s":"0","p":"","kt":"0","k":[],"nt":"0","n":[],"bt":"0","br' + b'":[],"ba":[],"a":[]}') + + + assert serder.verify() + assert serder.ilk == kering.Ilks.rot + assert serder.sad['i'] == '' != serder.said # prefix is not saidive + + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + + serder = Serder(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + serder = Serder(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + + + # ToDo: create malicious raw values to test verify more thoroughly + # ToDo: create bad sad values to test makify more thoroughly + # unhappy paths + + + """End Test""" + +def test_serderkeri(): + """Test SerderKERI default""" + + # Test KERI JSON with makify defaults for bootstrap which is state (ksn) msg + # ksn msg has no ilk field for itself because is is embedded in exn or other + serder = SerderKERI(makify=True) # make with all defaults is state message + assert serder.sad == {'v': 'KERI10JSON0000cf_', + 't': 'icp', + 'd': 'EF6LmlLkfoNVY25RcGTsqKLW5uHq36FbnNEdjON07Rwv', + 'i': 'EF6LmlLkfoNVY25RcGTsqKLW5uHq36FbnNEdjON07Rwv', + 's': '0', + 'kt': '0', + 'k': [], + 'nt': '0', + 'n': [], + 'bt': '0', + 'b': [], + 'c': [], + 'a': []} + + assert serder.raw == (b'{"v":"KERI10JSON0000cf_","t":"icp","d":"EF6LmlLkfoNVY25RcGTsqKLW5uHq36FbnNEd' + b'jON07Rwv","i":"EF6LmlLkfoNVY25RcGTsqKLW5uHq36FbnNEdjON07Rwv","s":"0","kt":"0' + b'","k":[],"nt":"0","n":[],"bt":"0","b":[],"c":[],"a":[]}') + + assert serder.verify() # because empty prefix 'i' field + assert serder.ilk == kering.Ilks.icp + assert serder.said == 'EF6LmlLkfoNVY25RcGTsqKLW5uHq36FbnNEdjON07Rwv' + assert serder.pre == serder.said # prefix is not saidive + + sad = serder.sad + pre = 'DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx' + sad['i'] = pre + said = serder.said + + serder = SerderKERI(sad=sad, makify=True) + + assert serder.verify() + assert serder.ilk == kering.Ilks.icp + assert serder.said == 'EIXK39EgyxshefoCdSpKCkG5FR9s405YI4FAHDvAqO_R' + assert serder.pre == pre # prefix is not saidive + + sad = serder.sad + raw = serder.raw + size = serder.size + said = serder.said + pre = serder.pre + + serder = SerderKERI(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.pre == pre + assert serder.ilk == kering.Ilks.icp + + assert serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == [] + assert serder.traits == [] + assert serder.tholder.sith == '0' + assert serder.keys == [] + assert [verfer.qb64 for verfer in serder.verfers] == [] + assert serder.ntholder.sith == '0' + assert [diger.qb64 for diger in serder.ndigers] == [] + assert serder.bner.num == 0 + assert serder.bn == 0 + assert serder.backs == [] + assert [verfer.qb64 for verfer in serder.berfers] == [] + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + + serder = SerderKERI(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == kering.Ilks.icp + + assert serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == [] + assert serder.traits == [] + assert serder.tholder.sith == '0' + assert serder.keys == [] + assert [verfer.qb64 for verfer in serder.verfers] == [] + assert serder.ntholder.sith == '0' + assert [diger.qb64 for diger in serder.ndigers] == [] + assert serder.bner.num == 0 + assert serder.bn == 0 + assert serder.backs == [] + assert [verfer.qb64 for verfer in serder.berfers] == [] + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + +def test_serderkeri_icp(): + """Test SerderKERI icp msg""" + + # Test KERI JSON with makify defaults for self bootstrap with ilk icp + serder = SerderKERI(makify=True, ilk=kering.Ilks.icp) # make with defaults + assert serder.sad == {'v': 'KERI10JSON0000cf_', + 't': 'icp', + 'd': 'EF6LmlLkfoNVY25RcGTsqKLW5uHq36FbnNEdjON07Rwv', + 'i': 'EF6LmlLkfoNVY25RcGTsqKLW5uHq36FbnNEdjON07Rwv', + 's': '0', + 'kt': '0', + 'k': [], + 'nt': '0', + 'n': [], + 'bt': '0', + 'b': [], + 'c': [], + 'a': []} + + + assert serder.raw == (b'{"v":"KERI10JSON0000cf_","t":"icp","d":"EF6LmlLkfoNVY25RcGTsqKLW5uHq36FbnNEd' + b'jON07Rwv","i":"EF6LmlLkfoNVY25RcGTsqKLW5uHq36FbnNEdjON07Rwv","s":"0","kt":"0' + b'","k":[],"nt":"0","n":[],"bt":"0","b":[],"c":[],"a":[]}') + + + assert serder.verify() + assert serder.ilk == kering.Ilks.icp + assert serder.pre == serder.said # default prefix is saidive + + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + pre = serder.pre + + serder = SerderKERI(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == [] + assert serder.traits == [] + assert serder.tholder.sith == '0' + assert serder.keys == [] + assert [verfer.qb64 for verfer in serder.verfers] == [] + assert serder.ntholder.sith == '0' + assert [diger.qb64 for diger in serder.ndigers] == [] + assert serder.bner.num == 0 + assert serder.bn == 0 + assert serder.backs == [] + assert [verfer.qb64 for verfer in serder.berfers] == [] + assert serder.prior == None + assert serder.priorb == None + assert serder.cuts == None + assert serder.adds == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + serder = SerderKERI(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == [] + assert serder.traits == [] + assert serder.tholder.sith == '0' + assert serder.keys == [] + assert [verfer.qb64 for verfer in serder.verfers] == [] + assert serder.ntholder.sith == '0' + assert [diger.qb64 for diger in serder.ndigers] == [] + assert serder.bner.num == 0 + assert serder.bn == 0 + assert serder.backs == [] + assert [verfer.qb64 for verfer in serder.berfers] == [] + assert serder.prior == None + assert serder.priorb == None + assert serder.cuts == None + assert serder.adds == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + + # Test with non-digestive code for 'i' saidive field no sad + serder = SerderKERI(makify=True, + ilk=kering.Ilks.icp, + saids = {'i': coring.PreDex.Ed25519}) + + assert serder.sad == {'v': 'KERI10JSON0000a3_', + 't': 'icp', + 'd': 'EEeXbwybn8tv2Wo_YNBpaqP3PobjvzUs6tH0XNRmfOTx', + 'i': '', + 's': '0', + 'kt': '0', + 'k': [], + 'nt': '0', + 'n': [], + 'bt': '0', + 'b': [], + 'c': [], + 'a': []} + + assert serder.raw == (b'{"v":"KERI10JSON0000a3_","t":"icp","d":"EEeXbwybn8tv2Wo_YNBpaqP3PobjvzUs6tH0' + b'XNRmfOTx","i":"","s":"0","kt":"0","k":[],"nt":"0","n":[],"bt":"0","b":[],"c"' + b':[],"a":[]}') + + assert not serder.verify() # because of empty 'i' field saidive + assert serder.ilk == kering.Ilks.icp + assert serder.pre == '' != serder.said # prefix is not saidive + + sad = serder.sad + + # test makify with preloaded non-digestive 'i' value in sad + pre = 'DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx' + sad['i'] = pre + + serder = SerderKERI(sad=sad, makify=True) + assert serder.sad == {'v': 'KERI10JSON0000cf_', + 't': 'icp', + 'd': 'EIXK39EgyxshefoCdSpKCkG5FR9s405YI4FAHDvAqO_R', + 'i': 'DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx', + 's': '0', + 'kt': '0', + 'k': [], + 'nt': '0', + 'n': [], + 'bt': '0', + 'b': [], + 'c': [], + 'a': []} + + assert serder.raw ==(b'{"v":"KERI10JSON0000cf_","t":"icp","d":"EIXK39EgyxshefoCdSpKCkG5FR9s405YI4FA' + b'HDvAqO_R","i":"DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx","s":"0","kt":"0' + b'","k":[],"nt":"0","n":[],"bt":"0","b":[],"c":[],"a":[]}') + + assert serder.verify() + assert serder.ilk == kering.Ilks.icp + assert serder.pre == pre != said # prefix is not saidive + + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + + serder = SerderKERI(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == [] + assert serder.traits == [] + assert serder.tholder.sith == '0' + assert serder.keys == [] + assert [verfer.qb64 for verfer in serder.verfers] == [] + assert serder.ntholder.sith == '0' + assert [diger.qb64 for diger in serder.ndigers] == [] + assert serder.bner.num == 0 + assert serder.bn == 0 + assert serder.backs == [] + assert [verfer.qb64 for verfer in serder.berfers] == [] + assert serder.prior == None + assert serder.priorb == None + assert serder.cuts == None + assert serder.adds == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + serder = SerderKERI(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == [] + assert serder.traits == [] + assert serder.tholder.sith == '0' + assert serder.keys == [] + assert [verfer.qb64 for verfer in serder.verfers] == [] + assert serder.ntholder.sith == '0' + assert [diger.qb64 for diger in serder.ndigers] == [] + assert serder.bner.num == 0 + assert serder.bn == 0 + assert serder.backs == [] + assert [verfer.qb64 for verfer in serder.berfers] == [] + assert serder.prior == None + assert serder.priorb == None + assert serder.cuts == None + assert serder.adds == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + """End Test""" + +def test_serderkeri_rot(): + """Test SerderKERI rot msg""" + + # Test KERI JSON with makify defaults for self bootstrap with ilk rot + serder = SerderKERI(makify=True, ilk=kering.Ilks.rot) # make with defaults + assert serder.sad == {'v': 'KERI10JSON0000ac_', + 't': 'rot', + 'd': 'EMgauZPVfh6807jO9QO8A4Iauq1xhYTZnKX2doVd_UDl', + 'i': '', + 's': '0', + 'p': '', + 'kt': '0', + 'k': [], + 'nt': '0', + 'n': [], + 'bt': '0', + 'br': [], + 'ba': [], + 'a': []} + + + assert serder.raw == (b'{"v":"KERI10JSON0000ac_","t":"rot","d":"EMgauZPVfh6807jO9QO8A4Iauq1xhYTZnKX2' + b'doVd_UDl","i":"","s":"0","p":"","kt":"0","k":[],"nt":"0","n":[],"bt":"0","br' + b'":[],"ba":[],"a":[]}') + + assert not serder.verify() # because pre is empty + assert serder.ilk == kering.Ilks.rot + assert serder.pre == '' != serder.said # prefix is not saidive + + sad = serder.sad + + # test makify with preloaded non-digestive 'i' value in sad + pre = 'DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx' + sad['i'] = pre + + serder = SerderKERI(sad=sad, makify=True) + + assert serder.verify() # because pre is empty + assert serder.ilk == kering.Ilks.rot + assert serder.pre == pre != serder.said # prefix is not saidive + + + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + + serder = SerderKERI(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == [] + assert serder.traits == None + assert serder.tholder.sith == '0' + assert serder.keys == [] + assert [verfer.qb64 for verfer in serder.verfers] == [] + assert serder.ntholder.sith == '0' + assert [diger.qb64 for diger in serder.ndigers] == [] + assert serder.bner.num == 0 + assert serder.bn == 0 + assert serder.berfers == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + serder = SerderKERI(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == [] + assert serder.traits == None + assert serder.tholder.sith == '0' + assert serder.keys == [] + assert [verfer.qb64 for verfer in serder.verfers] == [] + assert serder.ntholder.sith == '0' + assert [diger.qb64 for diger in serder.ndigers] == [] + assert serder.bner.num == 0 + assert serder.bn == 0 + assert serder.backs == None + assert serder.berfers == None + assert serder.prior == "" + assert serder.priorb == b"" + assert serder.cuts == [] + assert serder.adds == [] + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + """End Test""" + +def test_serderkeri_ixn(): + """Test SerderKERI ixn msg""" + + # Test KERI JSON with makify defaults for self bootstrap with ilk ixn + serder = SerderKERI(makify=True, ilk=kering.Ilks.ixn) # make with defaults + assert serder.sad == {'v': 'KERI10JSON000073_', + 't': 'ixn', + 'd': 'ELI1jUxlJky6RvRieoO20H7_YikKnQMthnWM38etba3r', + 'i': '', + 's': '0', + 'p': '', + 'a': []} + + assert serder.raw == (b'{"v":"KERI10JSON000073_","t":"ixn","d":"ELI1jUxlJky6RvRieoO20H7_YikKnQMthnWM' + b'38etba3r","i":"","s":"0","p":"","a":[]}') + + assert not serder.verify() # because pre is empty + assert serder.ilk == kering.Ilks.ixn + assert serder.pre == '' != serder.said # prefix is not saidive + + sad = serder.sad + + # test makify with preloaded non-digestive 'i' value in sad + pre = 'DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx' + sad['i'] = pre + + serder = SerderKERI(sad=sad, makify=True) + + assert serder.verify() # because pre is empty + assert serder.ilk == kering.Ilks.ixn + assert serder.pre == pre != serder.said # prefix is not saidive + + + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + + serder = SerderKERI(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert not serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == [] + assert serder.traits == None + assert serder.tholder == None + assert serder.keys == None + assert serder.verfers == None + assert serder.ntholder == None + assert serder.ndigers == None + assert serder.bner == None + assert serder.bn == None + assert serder.backs == None + assert serder.berfers == None + assert serder.prior == "" + assert serder.priorb == b"" + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + serder = SerderKERI(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert not serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == [] + assert serder.traits == None + assert serder.tholder == None + assert serder.keys == None + assert serder.verfers == None + assert serder.ntholder == None + assert serder.ndigers == None + assert serder.bner == None + assert serder.bn == None + assert serder.backs == None + assert serder.berfers == None + assert serder.prior == "" + assert serder.priorb == b"" + assert serder.cuts == None + assert serder.adds == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + """End Test""" + +def test_serderkeri_dip(): + """Test SerderKERI dip msg""" + + # Test KERI JSON with makify defaults for self bootstrap with ilk dip + serder = SerderKERI(makify=True, ilk=kering.Ilks.dip) # make with defaults + assert serder.sad == {'v': 'KERI10JSON0000d7_', + 't': 'dip', + 'd': 'EPyzEgwg6ls8iY4jViniM15rAFWaaVbsZ4eP2a9ZcKfC', + 'i': 'EPyzEgwg6ls8iY4jViniM15rAFWaaVbsZ4eP2a9ZcKfC', + 's': '0', + 'kt': '0', + 'k': [], + 'nt': '0', + 'n': [], + 'bt': '0', + 'b': [], + 'c': [], + 'a': [], + 'di': ''} + + assert serder.raw == (b'{"v":"KERI10JSON0000d7_","t":"dip","d":"EPyzEgwg6ls8iY4jViniM15rAFWaaVbsZ4eP' + b'2a9ZcKfC","i":"EPyzEgwg6ls8iY4jViniM15rAFWaaVbsZ4eP2a9ZcKfC","s":"0","kt":"0' + b'","k":[],"nt":"0","n":[],"bt":"0","b":[],"c":[],"a":[],"di":""}') + + + assert not serder.verify() # serder.delpre empty so not valid PreDex code + assert serder.ilk == kering.Ilks.dip + assert serder.pre == serder.said # default prefix is saidive + + delpre = 'EPyz9ZcKfCEgwg6ls8iY4jViniM15rAFWaaVbsZ4eP2a' + sad = serder.sad + sad["di"] = delpre + + serder = SerderKERI(makify=True, sad=sad) + assert serder.sad == {'v': 'KERI10JSON000103_', + 't': 'dip', + 'd': 'EJrgptxlZU7ue_WQkZb5wwSyv-LE0B-eOhRZp_OZUUJa', + 'i': 'EJrgptxlZU7ue_WQkZb5wwSyv-LE0B-eOhRZp_OZUUJa', + 's': '0', + 'kt': '0', + 'k': [], + 'nt': '0', + 'n': [], + 'bt': '0', + 'b': [], + 'c': [], + 'a': [], + 'di': 'EPyz9ZcKfCEgwg6ls8iY4jViniM15rAFWaaVbsZ4eP2a'} + + assert serder.raw == (b'{"v":"KERI10JSON000103_","t":"dip","d":"EJrgptxlZU7ue_WQkZb5wwSyv-LE0B-eOhRZ' + b'p_OZUUJa","i":"EJrgptxlZU7ue_WQkZb5wwSyv-LE0B-eOhRZp_OZUUJa","s":"0","kt":"0' + b'","k":[],"nt":"0","n":[],"bt":"0","b":[],"c":[],"a":[],"di":"EPyz9ZcKfCEgwg6' + b'ls8iY4jViniM15rAFWaaVbsZ4eP2a"}') + + assert serder.verify() + + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + pre = serder.pre + + serder = SerderKERI(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == [] + assert serder.traits == [] + assert serder.tholder.sith == '0' + assert serder.keys == [] + assert [verfer.qb64 for verfer in serder.verfers] == [] + assert serder.ntholder.sith == '0' + assert [diger.qb64 for diger in serder.ndigers] == [] + assert serder.bner.num == 0 + assert serder.bn == 0 + assert [verfer.qb64 for verfer in serder.berfers] == [] + assert serder.prior == None + assert serder.priorb == None + assert serder.cuts == None + assert serder.adds == None + assert serder.delpre == delpre + assert serder.delpreb == delpre.encode("utf-8") + #assert serder.fner == None + #assert serder.fn == None + + serder = SerderKERI(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == [] + assert serder.traits == [] + assert serder.tholder.sith == '0' + assert serder.keys == [] + assert [verfer.qb64 for verfer in serder.verfers] == [] + assert serder.ntholder.sith == '0' + assert [diger.qb64 for diger in serder.ndigers] == [] + assert serder.bner.num == 0 + assert serder.bn == 0 + assert serder.backs == [] + assert [verfer.qb64 for verfer in serder.berfers] == [] + assert serder.prior == None + assert serder.priorb == None + assert serder.cuts == None + assert serder.adds == None + assert serder.delpre == delpre + assert serder.delpreb == delpre.encode("utf-8") + #assert serder.fner == None + #assert serder.fn == None + + + # Test with non-digestive code for 'i' saidive field no sad + serder = SerderKERI(makify=True, + ilk=kering.Ilks.dip, + saids = {'i': coring.PreDex.Ed25519}) + + assert serder.sad == {'v': 'KERI10JSON0000ab_', + 't': 'dip', + 'd': 'EEPX5NpQed1laFb8VZPES3zAoMcEuMq796KnN33GwWqF', + 'i': '', + 's': '0', + 'kt': '0', + 'k': [], + 'nt': '0', + 'n': [], + 'bt': '0', + 'b': [], + 'c': [], + 'a': [], + 'di': ''} + + + assert serder.raw == (b'{"v":"KERI10JSON0000ab_","t":"dip","d":"EEPX5NpQed1laFb8VZPES3zAoMcEuMq796Kn' + b'N33GwWqF","i":"","s":"0","kt":"0","k":[],"nt":"0","n":[],"bt":"0","b":[],"c"' + b':[],"a":[],"di":""}') + + + assert not serder.verify() # because of empty 'i' field and 'di' field + assert serder.ilk == kering.Ilks.dip + assert serder.pre == '' != serder.said # prefix is not saidive + + sad = serder.sad + + # test makify with preloaded non-digestive 'i' value in sad + pre = 'DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx' # non digest so raise error + sad['i'] = pre + sad['di'] = delpre + + serder = SerderKERI(sad=sad, makify=True) + + assert not serder.verify() + pre = 'EF78YGUYCWXptoVVel1TN1F9-KShPHAtEqvf-TEiGvv9' + sad['i'] = pre + + serder = SerderKERI(sad=sad, makify=True) + assert serder.verify() + + assert serder.ilk == kering.Ilks.dip + assert serder.pre == said != pre # prefix is computed + assert serder.delpre == delpre + + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + pre = serder.pre + + + serder = SerderKERI(sad=sad, + makify=True, + saids = {'i': coring.PreDex.Blake3_256}) + + assert serder.sad == {'v': 'KERI10JSON000103_', + 't': 'dip', + 'd': 'EJrgptxlZU7ue_WQkZb5wwSyv-LE0B-eOhRZp_OZUUJa', + 'i': 'EJrgptxlZU7ue_WQkZb5wwSyv-LE0B-eOhRZp_OZUUJa', + 's': '0', + 'kt': '0', + 'k': [], + 'nt': '0', + 'n': [], + 'bt': '0', + 'b': [], + 'c': [], + 'a': [], + 'di': 'EPyz9ZcKfCEgwg6ls8iY4jViniM15rAFWaaVbsZ4eP2a'} + + assert serder.raw == (b'{"v":"KERI10JSON000103_","t":"dip","d":"EJrgptxlZU7ue_WQkZb5wwSyv-LE0B-eOhRZ' + b'p_OZUUJa","i":"EJrgptxlZU7ue_WQkZb5wwSyv-LE0B-eOhRZp_OZUUJa","s":"0","kt":"0' + b'","k":[],"nt":"0","n":[],"bt":"0","b":[],"c":[],"a":[],"di":"EPyz9ZcKfCEgwg6' + b'ls8iY4jViniM15rAFWaaVbsZ4eP2a"}') + + assert serder.verify() + assert serder.ilk == kering.Ilks.dip + assert serder.pre == said == pre # prefix is computed same as before + assert serder.delpre == delpre + assert serder.said == said + + + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + pre = serder.pre + + serder = SerderKERI(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == [] + assert serder.traits == [] + assert serder.tholder.sith == '0' + assert serder.keys == [] + assert [verfer.qb64 for verfer in serder.verfers] == [] + assert serder.ntholder.sith == '0' + assert [diger.qb64 for diger in serder.ndigers] == [] + assert serder.bner.num == 0 + assert serder.bn == 0 + assert [verfer.qb64 for verfer in serder.berfers] == [] + assert serder.prior == None + assert serder.priorb == None + assert serder.cuts == None + assert serder.adds == None + assert serder.delpre == delpre + assert serder.delpreb == delpre.encode("utf-8") + #assert serder.fner == None + #assert serder.fn == None + + serder = SerderKERI(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == [] + assert serder.traits == [] + assert serder.tholder.sith == '0' + assert serder.keys == [] + assert [verfer.qb64 for verfer in serder.verfers] == [] + assert serder.ntholder.sith == '0' + assert [diger.qb64 for diger in serder.ndigers] == [] + assert serder.bner.num == 0 + assert serder.bn == 0 + assert serder.backs == [] + assert [verfer.qb64 for verfer in serder.berfers] == [] + assert serder.prior == None + assert serder.priorb == None + assert serder.cuts == None + assert serder.adds == None + assert serder.delpre == delpre + assert serder.delpreb == delpre.encode("utf-8") + #assert serder.fner == None + #assert serder.fn == None + + """End Test""" + +def test_serderkeri_drt(): + """Test SerderKERI drt msg""" + # Test KERI JSON with makify defaults for self bootstrap with ilk drt + serder = SerderKERI(makify=True, ilk=kering.Ilks.drt) # make with defaults + assert serder.sad == {'v': 'KERI10JSON0000ac_', + 't': 'drt', + 'd': 'EMiEhgKRsD559TX6b03AT5P2GfKPPqoNk5COHZxU2TkR', + 'i': '', + 's': '0', + 'p': '', + 'kt': '0', + 'k': [], + 'nt': '0', + 'n': [], + 'bt': '0', + 'br': [], + 'ba': [], + 'a': []} + + assert serder.raw == (b'{"v":"KERI10JSON0000ac_","t":"drt","d":"EMiEhgKRsD559TX6b03AT5P2GfKPPqoNk5CO' + b'HZxU2TkR","i":"","s":"0","p":"","kt":"0","k":[],"nt":"0","n":[],"bt":"0","br' + b'":[],"ba":[],"a":[]}') + + + assert not serder.verify() # because pre is empty + assert serder.ilk == kering.Ilks.drt + assert serder.pre == '' != serder.said # prefix is not saidive + + sad = serder.sad + + # test makify with preloaded non-digestive 'i' value in sad + pre = 'DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx' + sad['i'] = pre + + serder = SerderKERI(sad=sad, makify=True) + + assert not serder.verify() # because pre is not digest and delpre is empty + sad = serder.sad + pre = 'EF78YGUYCWXptoVVel1TN1F9-KShPHAtEqvf-TEiGvv9' + sad['i'] = pre + + serder = SerderKERI(sad=sad, makify=True) + + assert serder.verify() + assert serder.ilk == kering.Ilks.drt + assert serder.pre == pre != serder.said # prefix is not computed + assert serder.delpre == None + + + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + + serder = SerderKERI(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == [] + assert serder.traits == None + assert serder.tholder.sith == '0' + assert [verfer.qb64 for verfer in serder.verfers] == [] + assert serder.ntholder.sith == '0' + assert [diger.qb64 for diger in serder.ndigers] == [] + assert serder.bner.num == 0 + assert serder.bn == 0 + assert serder.backs == None + assert serder.berfers == None + assert serder.prior == "" + assert serder.priorb == b"" + assert serder.cuts == [] + assert serder.adds == [] + assert serder.delpre == None + assert serder.delpreb == None + + serder = SerderKERI(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == [] + assert serder.traits == None + assert serder.tholder.sith == '0' + assert [verfer.qb64 for verfer in serder.verfers] == [] + assert serder.ntholder.sith == '0' + assert [diger.qb64 for diger in serder.ndigers] == [] + assert serder.bner.num == 0 + assert serder.bn == 0 + assert serder.backs == None + assert serder.berfers == None + assert serder.prior == "" + assert serder.priorb == b"" + assert serder.cuts == [] + assert serder.adds == [] + assert serder.delpre == None + assert serder.delpreb == None + + """End Test""" + +def test_serderkeri_rct(): + """Test SerderKERI rct msg""" + + # Test KERI JSON with makify defaults for self bootstrap with ilk ixn + serder = SerderKERI(makify=True, ilk=kering.Ilks.rct) # make with defaults + assert serder.sad == {'v': 'KERI10JSON000039_', 't': 'rct', 'd': '', 'i': '', 's': '0'} + + assert serder.raw == b'{"v":"KERI10JSON000039_","t":"rct","d":"","i":"","s":"0"}' + + assert not serder.verify() # because pre is empty + assert serder.ilk == kering.Ilks.rct + assert serder._said == None # no saidive fields + assert serder.pre == '' # prefix is not saidive + assert serder.said == '' # d field is not saidive + + sad = serder.sad + + # test makify with preloaded non-digestive 'i' value in sad + pre = 'DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx' + sad['i'] = pre + + serder = SerderKERI(sad=sad, makify=True) + + assert serder.verify() # because pre is empty + assert serder.ilk == kering.Ilks.rct + assert serder.pre == pre != serder.said # prefix is not saidive + + + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + + serder = SerderKERI(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert not serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == None + assert serder.traits == None + assert serder.tholder == None + assert serder.verfers == None + assert serder.ntholder == None + assert serder.ndigers == None + assert serder.bner == None + assert serder.bn == None + assert serder.berfers == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + serder = SerderKERI(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert not serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == None + assert serder.traits == None + assert serder.tholder == None + assert serder.verfers == None + assert serder.ntholder == None + assert serder.ndigers == None + assert serder.bner == None + assert serder.bn == None + assert serder.backs == None + assert serder.berfers == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + """End Test""" + +def test_serderkeri_qry(): + """Test SerderKERI qry query msg""" + # Test KERI JSON with makify defaults for self bootstrap with ilk qry + serder = SerderKERI(makify=True, ilk=kering.Ilks.qry) # make with defaults + assert serder.sad =={'v': 'KERI10JSON000074_', + 't': 'qry', + 'd': 'EHVP7GS9B8PFKDogN3WD93NcSg6hShBXiolOqwnO3Vfm', + 'dt': '', + 'r': '', + 'rr': '', + 'q': {}} + + assert serder.raw == (b'{"v":"KERI10JSON000074_","t":"qry","d":"EHVP7GS9B8PFKDogN3WD93NcSg6hShBXiolO' + b'qwnO3Vfm","dt":"","r":"","rr":"","q":{}}') + + + assert serder.verify() + assert serder.ilk == kering.Ilks.qry + assert serder.pre == None != serder.said # prefix is not saidive + + #sad = serder.sad + # test makify with preloaded non-digestive 'i' value in sad + #pre = 'DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx' + #sad['i'] = pre + + #serder = SerderKERI(sad=sad, makify=True) + + #assert serder.verify() # because pre is empty + #assert serder.ilk == kering.Ilks.qry + #assert serder.pre == pre != serder.said # prefix is not saidive + + + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + + serder = SerderKERI(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert not serder.estive + assert serder.ked == serder.sad + assert serder.pre == None + assert serder.preb == None + assert serder.sner == None + assert serder.sn == None + assert serder.seals == None + assert serder.traits == None + assert serder.tholder == None + assert serder.verfers == None + assert serder.ntholder == None + assert serder.ndigers == None + assert serder.bner == None + assert serder.bn == None + assert serder.backs == None + assert serder.berfers == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + serder = SerderKERI(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert not serder.estive + assert serder.ked == serder.sad + assert serder.pre == None + assert serder.preb == None + assert serder.sner == None + assert serder.sn == None + assert serder.seals == None + assert serder.traits == None + assert serder.tholder == None + assert serder.verfers == None + assert serder.ntholder == None + assert serder.ndigers == None + assert serder.bner == None + assert serder.bn == None + assert serder.backs == None + assert serder.berfers == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + """End Test""" + + +def test_serderkeri_rpy(): + """Test SerderKERI rpy reply msg""" + + # Test KERI JSON with makify defaults for self bootstrap with ilk rpy + serder = SerderKERI(makify=True, ilk=kering.Ilks.rpy) # make with defaults + assert serder.sad =={'v': 'KERI10JSON00006c_', + 't': 'rpy', + 'd': 'EFnZ6ER7GXDjNpcn-QgXWqW4IZVAp73cCKC_zW_48Nu-', + 'dt': '', + 'r': '', + 'a': []} + + assert serder.raw == (b'{"v":"KERI10JSON00006c_","t":"rpy","d":"EFnZ6ER7GXDjNpcn-QgXWqW4IZVAp73cCKC_' + b'zW_48Nu-","dt":"","r":"","a":[]}') + + + assert serder.verify() + assert serder.ilk == kering.Ilks.rpy + assert serder.pre == None != serder.said # prefix is not saidive + + #sad = serder.sad + # test makify with preloaded non-digestive 'i' value in sad + #pre = 'DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx' + #sad['i'] = pre + + #serder = SerderKERI(sad=sad, makify=True) + + #assert serder.verify() # because pre is empty + #assert serder.ilk == kering.Ilks.qry + #assert serder.pre == pre != serder.said # prefix is not saidive + + + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + + serder = SerderKERI(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert not serder.estive + assert serder.ked == serder.sad + assert serder.pre == None + assert serder.preb == None + assert serder.sner == None + assert serder.sn == None + assert serder.seals == [] + assert serder.traits == None + assert serder.tholder == None + assert serder.verfers == None + assert serder.ntholder == None + assert serder.ndigers == None + assert serder.bner == None + assert serder.bn == None + assert serder.berfers == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + serder = SerderKERI(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert not serder.estive + assert serder.ked == serder.sad + assert serder.pre == None + assert serder.preb == None + assert serder.sner == None + assert serder.sn == None + assert serder.seals == [] + assert serder.traits == None + assert serder.tholder == None + assert serder.verfers == None + assert serder.ntholder == None + assert serder.ndigers == None + assert serder.bner == None + assert serder.bn == None + assert serder.backs == None + assert serder.berfers == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + """End Test""" + +def test_serderkeri_pro(): + """Test SerderKERI pro prod msg""" + # Test KERI JSON with makify defaults for self bootstrap with ilk qry + serder = SerderKERI(makify=True, ilk=kering.Ilks.pro) # make with defaults + assert serder.sad =={'v': 'KERI10JSON000074_', + 't': 'pro', + 'd': 'EP5pwF1ioQjnY1J0Gu12f_ZZEaoAntM3bng52tzZAvrM', + 'dt': '', + 'r': '', + 'rr': '', + 'q': {}} + + assert serder.raw == (b'{"v":"KERI10JSON000074_","t":"pro","d":"EP5pwF1ioQjnY1J0Gu12f_ZZEaoAntM3bng5' + b'2tzZAvrM","dt":"","r":"","rr":"","q":{}}') + + + assert serder.verify() + assert serder.ilk == kering.Ilks.pro + assert serder.pre == None != serder.said # prefix is not saidive + + #sad = serder.sad + # test makify with preloaded non-digestive 'i' value in sad + #pre = 'DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx' + #sad['i'] = pre + + #serder = SerderKERI(sad=sad, makify=True) + + #assert serder.verify() # because pre is empty + #assert serder.ilk == kering.Ilks.qry + #assert serder.pre == pre != serder.said # prefix is not saidive + + + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + + serder = SerderKERI(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert not serder.estive + assert serder.ked == serder.sad + assert serder.pre == None + assert serder.preb == None + assert serder.sner == None + assert serder.sn == None + assert serder.seals == None + assert serder.traits == None + assert serder.tholder == None + assert serder.verfers == None + assert serder.ntholder == None + assert serder.ndigers == None + assert serder.bner == None + assert serder.bn == None + assert serder.backs == None + assert serder.berfers == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + serder = SerderKERI(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert not serder.estive + assert serder.ked == serder.sad + assert serder.pre == None + assert serder.preb == None + assert serder.sner == None + assert serder.sn == None + assert serder.seals == None + assert serder.traits == None + assert serder.tholder == None + assert serder.verfers == None + assert serder.ntholder == None + assert serder.ndigers == None + assert serder.bner == None + assert serder.bn == None + assert serder.backs == None + assert serder.berfers == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + """End Test""" + +def test_serderkeri_bar(): + """Test SerderKERI bar alls msg""" + + # Test KERI JSON with makify defaults for self bootstrap with ilk bar + serder = SerderKERI(makify=True, ilk=kering.Ilks.bar) # make with defaults + assert serder.sad =={'v': 'KERI10JSON00006c_', + 't': 'bar', + 'd': 'EAGe-dBuaN1l1LFK8MrBS60BiFhSbxrf_l6dZBkd8JNR', + 'dt': '', + 'r': '', + 'a': []} + + assert serder.raw == (b'{"v":"KERI10JSON00006c_","t":"bar","d":"EAGe-dBuaN1l1LFK8MrBS60BiFhSbxrf_l6d' + b'ZBkd8JNR","dt":"","r":"","a":[]}') + + + assert serder.verify() + assert serder.ilk == kering.Ilks.bar + assert serder.pre == None != serder.said # prefix is not saidive + + #sad = serder.sad + # test makify with preloaded non-digestive 'i' value in sad + #pre = 'DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx' + #sad['i'] = pre + + #serder = SerderKERI(sad=sad, makify=True) + + #assert serder.verify() # because pre is empty + #assert serder.ilk == kering.Ilks.qry + #assert serder.pre == pre != serder.said # prefix is not saidive + + + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + + serder = SerderKERI(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert not serder.estive + assert serder.ked == serder.sad + assert serder.pre == None + assert serder.preb == None + assert serder.sner == None + assert serder.sn == None + assert serder.seals == [] + assert serder.traits == None + assert serder.tholder == None + assert serder.verfers == None + assert serder.ntholder == None + assert serder.ndigers == None + assert serder.bner == None + assert serder.bn == None + assert serder.backs == None + assert serder.berfers == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + serder = SerderKERI(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert not serder.estive + assert serder.ked == serder.sad + assert serder.pre == None + assert serder.preb == None + assert serder.sner == None + assert serder.sn == None + assert serder.seals == [] + assert serder.traits == None + assert serder.tholder == None + assert serder.verfers == None + assert serder.ntholder == None + assert serder.ndigers == None + assert serder.bner == None + assert serder.bn == None + assert serder.backs == None + assert serder.berfers == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + """End Test""" + +def test_serderkeri_exn(): + """Test SerderKERI exn msg""" + + # Test KERI JSON with makify defaults for self bootstrap with ilk ixn + serder = SerderKERI(makify=True, ilk=kering.Ilks.exn) # make with defaults + assert serder.sad == {'v': 'KERI10JSON000088_', + 't': 'exn', + 'd': 'EMuAoRSE4zREKKYyvuNeYCDM9_MwPQIh1WL0cFC4e-bU', + 'i': '', + 'p': '', + 'dt': '', + 'r': '', + 'q': {}, + 'a': [], + 'e': {}} + + assert serder.raw == (b'{"v":"KERI10JSON000088_","t":"exn",' + b'"d":"EMuAoRSE4zREKKYyvuNeYCDM9_MwPQIh1WL0' + b'cFC4e-bU","i":"","p":"","dt":"","r":"","q":{},"a":[],"e":{}}') + + + + assert serder.verify() # because pre is empty + assert serder.ilk == kering.Ilks.exn + assert serder.pre == '' + assert serder.prior == '' + + #sad = serder.sad + ## test makify with preloaded non-digestive 'i' value in sad + #pre = 'DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx' + #sad['i'] = pre + + #serder = SerderKERI(sad=sad, makify=True) + #assert serder.verify() # because pre is empty + #assert serder.ilk == kering.Ilks.exn + ## need to fix this, since exn does not include prefix field which should be + ## required + #assert serder.pre == pre != serder.said # prefix is not saidive + + + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + + serder = SerderKERI(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert not serder.estive + assert serder.ked == serder.sad + assert serder.pre == '' + assert serder.preb == b'' + assert serder.prior == '' + assert serder.priorb == b'' + assert serder.sner == None + assert serder.sn == None + assert serder.seals == [] + assert serder.traits == None + assert serder.tholder == None + assert serder.verfers == None + assert serder.ntholder == None + assert serder.ndigers == None + assert serder.bner == None + assert serder.bn == None + assert serder.backs == None + assert serder.berfers == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + serder = SerderKERI(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert not serder.estive + assert serder.ked == serder.sad + assert serder.pre == '' # serder.sad['i'] == pre + assert serder.preb == b'' # serder.pre.encode("utf-8") + assert serder.prior == '' + assert serder.priorb == b'' + assert serder.sner == None + assert serder.sn == None + assert serder.seals == [] + assert serder.traits == None + assert serder.tholder == None + assert serder.verfers == None + assert serder.ntholder == None + assert serder.ndigers == None + assert serder.bner == None + assert serder.bn == None + assert serder.backs == None + assert serder.berfers == None + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + + + """End Test""" + +def test_serderkeri_vcp(): + """Test SerderKERI vcp msg""" + + # Test KERI JSON with makify defaults for self bootstrap with ilk vcp + serder = SerderKERI(makify=True, ilk=kering.Ilks.vcp) # make with defaults + assert serder.sad == {'v': 'KERI10JSON0000b7_', + 't': 'vcp', + 'd': 'ELJmaZ1Cq3JoXDmNNtTpL-oNTpo4936wx4YAvXMK5tLU', + 'i': 'ELJmaZ1Cq3JoXDmNNtTpL-oNTpo4936wx4YAvXMK5tLU', + 'ii': '', + 's': '0', + 'c': [], + 'bt': '0', + 'b': [], + 'n': ''} + + + assert serder.raw == (b'{"v":"KERI10JSON0000b7_","t":"vcp","d":"ELJmaZ1Cq3JoXDmNNtTpL-oNTpo4936wx4YA' + b'vXMK5tLU","i":"ELJmaZ1Cq3JoXDmNNtTpL-oNTpo4936wx4YAvXMK5tLU","ii":"","s":"0"' + b',"c":[],"bt":"0","b":[],"n":""}') + + assert serder.verify() + assert serder.ilk == kering.Ilks.vcp + assert serder.pre == serder.said # default prefix is saidive + + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + ilk = serder.ilk + pre = serder.pre + + serder = SerderKERI(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert not serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == None + assert serder.traits == [] + assert serder.tholder == None + assert serder.verfers == None + assert serder.ntholder == None + assert serder.ndigers == None + assert serder.bner.num == 0 + assert serder.bn == 0 + assert serder.berfers == [] + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + assert serder.uuid == None + assert serder.nonce == '' + + serder = SerderKERI(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.keri + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == ilk + + assert not serder.estive + assert serder.ked == serder.sad + assert serder.pre == serder.sad['i'] == pre + assert serder.preb == serder.pre.encode("utf-8") + assert serder.sner.num == 0 + assert serder.sn == 0 + assert serder.seals == None + assert serder.traits == [] + assert serder.tholder == None + assert serder.verfers == None + assert serder.ntholder == None + assert serder.ndigers == None + assert serder.bner.num == 0 + assert serder.bn == 0 + assert serder.berfers == [] + assert serder.delpre == None + assert serder.delpreb == None + #assert serder.fner == None + #assert serder.fn == None + assert serder.uuid == None + assert serder.nonce == '' + + + """End Test""" + + +def test_serderacdc(): + """Test SerderACDC""" + + with pytest.raises(ValueError): + serder = SerderACDC() + + serder = SerderACDC(makify=True, proto=kering.Protos.acdc) # make defaults for ACDC + assert serder.sad == {'v': 'ACDC10JSON00005a_', + 'd': 'EMk7BvrqO_2sYjpI_-BmSELOFNie-muw4XTi3iYCz6pT', + 'i': '', + 's': ''} + assert serder.raw == (b'{"v":"ACDC10JSON00005a_","d":"EMk7BvrqO_2sYjpI_' + b'-BmSELOFNie-muw4XTi3iYCz6pT","i":"","s":""}') + + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == 90 + assert serder.kind == kering.Serials.json + assert serder.said == 'EMk7BvrqO_2sYjpI_-BmSELOFNie-muw4XTi3iYCz6pT' + assert serder.ilk == None + + assert serder.issuer == serder.sad['i'] == '' + assert serder.issuerb == serder.issuer.encode("utf-8") + + sad = serder.sad + raw = serder.raw + + with pytest.raises(kering.ValidationError): + serder = SerderACDC(sad=sad) + + with pytest.raises(kering.ValidationError): + serder = SerderACDC(sad=sad) + + isr = 'EO8CE5RH1X8QJwHHhPkj_S6LJQDRNOiGohW327FMA6D2' + sad['i'] = isr + + serder = SerderACDC(sad=sad, makify=True) + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + + + serder = SerderACDC(sad=sad) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == None + assert serder.issuer == isr + + + serder = SerderACDC(raw=raw) + assert serder.raw == raw + assert serder.sad == sad + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_1_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.ilk == None + assert serder.issuer == isr + + + + """End Test""" + + +def test_serdery(): + """Test Serdery""" + #Create incoming message stream for Serdery to reap + + serder = SerderKERI(makify=True, ilk=kering.Ilks.ixn) # make with defaults + sad = serder.sad + pre = "EDGnGYIa5obfFUhxcAuUmM4fJyeRYj2ti3KGf87Bc70J" + sad['i'] = pre + sad['s'] = 2 + sad['a'] = [] + serderKeri = SerderKERI(sad=sad, makify=True) + assert serderKeri.verify() + + ims = bytearray(serderKeri.raw) + + serder = SerderACDC(makify=True, proto=kering.Protos.acdc) # make defaults for ACDC + sad = serder.sad + isr = 'EO8CE5RH1X8QJwHHhPkj_S6LJQDRNOiGohW327FMA6D2' + sad['i'] = isr + serderAcdc = SerderACDC(sad=sad, makify=True) + assert serderAcdc.verify() + + ims.extend(serderAcdc.raw) + + ims.extend(b"Not a Serder here or there or anywhere.") + + assert ims == bytearray(b'{"v":"KERI10JSON00009d_","t":"ixn","d":"EPTgL0UEOa8xUWBqghryJYML' + b'Od2eYjmclndQN4bArjSf","i":"EDGnGYIa5obfFUhxcAuUmM4fJyeRYj2ti3KGf' + b'87Bc70J","s":2,"p":"","a":[]}{"v":"ACDC10JSON000086_","d":"EJxJ1' + b'GB8oGD4JAH7YpiMCSWKDV3ulpt37zg9vq1QnOh_","i":"EO8CE5RH1X8QJwHHhP' + b'kj_S6LJQDRNOiGohW327FMA6D2","s":""}Not a Serder here or there or' + b' anywhere.') + + serdery = Serdery(version=Version) + + serder = serdery.reap(ims) + assert isinstance(serder, SerderKERI) + assert serder.raw == serderKeri.raw + + serder = serdery.reap(ims) + assert isinstance(serder, SerderACDC) + assert serder.raw == serderAcdc.raw + + assert ims == bytearray(b'Not a Serder here or there or anywhere.') + + with pytest.raises(kering.VersionError): + serder = serdery.reap(ims) + + assert ims == bytearray(b'Not a Serder here or there or anywhere.') + + + """End Test""" + +def test_serdery_noversion(): + """Test Serdery unsupported version""" + #Create incoming message stream for Serdery to reap + + serder = SerderKERI(makify=True, ilk=kering.Ilks.ixn) # make with defaults + sad = serder.sad + pre = "EDGnGYIa5obfFUhxcAuUmM4fJyeRYj2ti3KGf87Bc70J" + sad['i'] = pre + sad['s'] = 2 + sad['a'] = [] + serderKeri = SerderKERI(sad=sad, makify=True) + assert serderKeri.verify() + + ims = bytearray(serderKeri.raw) + + serder = SerderACDC(makify=True, proto=kering.Protos.acdc) # make defaults for ACDC + sad = serder.sad + isr = 'EO8CE5RH1X8QJwHHhPkj_S6LJQDRNOiGohW327FMA6D2' + sad['i'] = isr + serderAcdc = SerderACDC(sad=sad, makify=True) + assert serderAcdc.verify() + + ims.extend(serderAcdc.raw) + + ims.extend(b"Not a Serder here or there or anywhere.") + + assert ims == bytearray(b'{"v":"KERI10JSON00009d_","t":"ixn","d":"EPTgL0UEOa8xUWBqghryJYML' + b'Od2eYjmclndQN4bArjSf","i":"EDGnGYIa5obfFUhxcAuUmM4fJyeRYj2ti3KGf' + b'87Bc70J","s":2,"p":"","a":[]}{"v":"ACDC10JSON000086_","d":"EJxJ1' + b'GB8oGD4JAH7YpiMCSWKDV3ulpt37zg9vq1QnOh_","i":"EO8CE5RH1X8QJwHHhP' + b'kj_S6LJQDRNOiGohW327FMA6D2","s":""}Not a Serder here or there or' + b' anywhere.') + + serdery = Serdery() # effective version is None + + serder = serdery.reap(ims) + assert isinstance(serder, SerderKERI) + assert serder.raw == serderKeri.raw + + serder = serdery.reap(ims) + assert isinstance(serder, SerderACDC) + assert serder.raw == serderAcdc.raw + + assert ims == bytearray(b'Not a Serder here or there or anywhere.') + + with pytest.raises(kering.VersionError): + serder = serdery.reap(ims) + + assert ims == bytearray(b'Not a Serder here or there or anywhere.') + + + """End Test""" + + +if __name__ == "__main__": + test_serder() + test_serderkeri() + test_serderkeri_icp() + test_serderkeri_rot() + test_serderkeri_ixn() + test_serderkeri_dip() + test_serderkeri_drt() + test_serderkeri_rct() + test_serderkeri_qry() + test_serderkeri_rpy() + test_serderkeri_pro() + test_serderkeri_bar() + test_serderkeri_exn() + test_serderkeri_vcp() + test_serderacdc() + test_serdery() + test_serdery_noversion() diff --git a/tests/core/test_weighted_threshold.py b/tests/core/test_weighted_threshold.py index abefcf3bc..aac8c1995 100644 --- a/tests/core/test_weighted_threshold.py +++ b/tests/core/test_weighted_threshold.py @@ -74,11 +74,11 @@ def test_weighted(): # wesKvy.process(ims=bytearray(msg)) # process local copy of msg wesK = wesKvy.kevers[wesPre] # kever created so event was validated assert wesK.prefixer.qb64 == wesPre - assert wesK.serder.saider.qb64 == wesSrdr.said # key state updated so event was validated + assert wesK.serder.said == wesSrdr.said # key state updated so event was validated # create interaction event for Wes wesSrdr = eventing.interact(pre=wesK.prefixer.qb64, - dig=wesK.serder.saider.qb64, + dig=wesK.serder.said, sn=wesK.sn + 1, data=[]) @@ -103,7 +103,7 @@ def test_weighted(): # apply msg to wes's Kevery parsing.Parser().parse(ims=bytearray(msg), kvy=wesKvy) # wesKvy.process(ims=bytearray(msg)) # process local copy of msg - assert wesK.serder.saider.qb64 == wesSrdr.said # key state updated so event was validated + assert wesK.serder.said == wesSrdr.said # key state updated so event was validated # Create rotation event for Wes # get current keys as verfers and next digests as digers @@ -113,7 +113,7 @@ def test_weighted(): wesSrdr = eventing.rotate(pre=wesK.prefixer.qb64, keys=[verfer.qb64 for verfer in verfers], isith=sith, - dig=wesK.serder.saider.qb64, + dig=wesK.serder.said, nsith=nxtsith, ndigs=[diger.qb64 for diger in digers], sn=wesK.sn + 1, @@ -147,7 +147,7 @@ def test_weighted(): # apply msg to Wes's Kevery parsing.Parser().parse(ims=bytearray(msg), kvy=wesKvy) # wesKvy.process(ims=bytearray(msg)) # process local copy of msg - assert wesK.serder.saider.qb64 == wesSrdr.said # key state updated so event was validated + assert wesK.serder.said == wesSrdr.said # key state updated so event was validated # Create rotation event for Wes # get current keys as verfers and next digests as digers @@ -159,7 +159,7 @@ def test_weighted(): wesSrdr = eventing.rotate(pre=wesK.prefixer.qb64, keys=[verfer.qb64 for verfer in verfers], isith=sith, - dig=wesK.serder.saider.qb64, + dig=wesK.serder.said, nsith=nxtsith, ndigs=[diger.qb64 for diger in digers], sn=wesK.sn + 1, @@ -193,7 +193,7 @@ def test_weighted(): # apply msg to Wes's Kevery parsing.Parser().parse(ims=bytearray(msg), kvy=wesKvy) # wesKvy.process(ims=bytearray(msg)) # process local copy of msg - assert wesK.serder.saider.qb64 == wesSrdr.said # key state updated so event was validated + assert wesK.serder.said == wesSrdr.said # key state updated so event was validated # Create rotation event for Wes # get current keys as verfers and next digests as digers @@ -205,7 +205,7 @@ def test_weighted(): wesSrdr = eventing.rotate(pre=wesK.prefixer.qb64, keys=[verfer.qb64 for verfer in verfers], isith=sith, - dig=wesK.serder.saider.qb64, + dig=wesK.serder.said, nsith=nxtsith, ndigs=[diger.qb64 for diger in digers], sn=wesK.sn + 1, @@ -244,7 +244,7 @@ def test_weighted(): # apply msg to Wes's Kevery parsing.Parser().parse(ims=bytearray(msg), kvy=wesKvy) # wesKvy.process(ims=bytearray(msg)) # process local copy of msg - assert wesK.serder.saider.qb64 == wesSrdr.said # key state updated so event was validated + assert wesK.serder.said == wesSrdr.said # key state updated so event was validated assert not os.path.exists(wesKS.path) assert not os.path.exists(wesDB.path) diff --git a/tests/core/test_witness.py b/tests/core/test_witness.py index 34b4412f8..0c34e46b2 100644 --- a/tests/core/test_witness.py +++ b/tests/core/test_witness.py @@ -7,7 +7,7 @@ from keri import help from keri.app import habbing -from keri.core import coring, eventing, parsing +from keri.core import coring, eventing, parsing, serdering from keri.db import dbing logger = help.ogler.getLogger() @@ -69,8 +69,8 @@ def test_indexed_witness_replay(): csith = '2' # hex str of threshold int camHab = camHby.makeHab(name='cam', isith=csith, icount=3, toad=2, wits=wits,) assert camHab.kever.prefixer.transferable - assert len(camHab.iserder.werfers) == len(wits) - for werfer in camHab.iserder.werfers: + assert len(camHab.iserder.berfers) == len(wits) + for werfer in camHab.iserder.berfers: assert werfer.qb64 in wits assert camHab.kever.wits == wits assert camHab.kever.toader.num == 2 @@ -97,7 +97,8 @@ def test_indexed_witness_replay(): kvy = camWitKvys[i] parsing.Parser().parse(ims=bytearray(camIcpMsg), kvy=kvy) assert kvy.kevers[camHab.pre].sn == 0 # accepted event - assert len(kvy.cues) == 1 # queued receipt cue + assert len(kvy.cues) >= 1 # at least queued receipt cue + # better to find receipt cue in cues exactly hab = camWitHabs[i] rctMsg = hab.processCues(kvy.cues) # process cue returns rct msg assert len(rctMsg) == 626 @@ -146,7 +147,8 @@ def test_indexed_witness_replay(): kvy = camWitKvys[i] parsing.Parser().parse(ims=bytearray(camIxnMsg), kvy=kvy) assert kvy.kevers[camHab.pre].sn == 1 # accepted event - assert len(kvy.cues) == 1 # queued receipt cue + assert len(kvy.cues) >= 1 # at least queued receipt cue + # better to find receipt cue in cues exactly hab = camWitHabs[i] rctMsg = hab.processCues(kvy.cues) # process cue returns rct msg assert len(rctMsg) == 281 @@ -214,7 +216,8 @@ def test_indexed_witness_replay(): kvy = camWitKvys[i] parsing.Parser().parse(ims=bytearray(camRotMsg), kvy=kvy) assert kvy.kevers[camHab.pre].sn == 2 # accepted event - assert len(kvy.cues) == 1 # queued receipt cue + assert len(kvy.cues) >= 1 # at least queued receipt cue + # better to find receipt cue in cues exactly hab = camWitHabs[i] rctMsg = hab.processCues(kvy.cues) # process cue returns rct msg assert len(rctMsg) == 281 @@ -333,8 +336,8 @@ def test_nonindexed_witness_receipts(): csith = '2' # hex str of threshold int camHab = camHby.makeHab(name='cam', isith=csith, icount=3, toad=2, wits=wits,) assert camHab.kever.prefixer.transferable - assert len(camHab.iserder.werfers) == len(wits) - for werfer in camHab.iserder.werfers: + assert len(camHab.iserder.berfers) == len(wits) + for werfer in camHab.iserder.berfers: assert werfer.qb64 in wits assert camHab.kever.wits == wits assert camHab.kever.toader.num == 2 @@ -361,7 +364,8 @@ def test_nonindexed_witness_receipts(): parsing.Parser().parse(ims=bytearray(camIcpMsg), kvy=kvy) # accepted event with cam sigs since own witness assert kvy.kevers[camHab.pre].sn == 0 - assert len(kvy.cues) == 1 # queued receipt cue + assert len(kvy.cues) >= 1 # at least queued receipt cue + # better to find receipt cue in cues exactly rctMsg = camWitHabs[i].processCues(kvy.cues) # process cue returns rct msg assert len(rctMsg) == 626 rctMsgs.append(rctMsg) @@ -419,7 +423,8 @@ def test_nonindexed_witness_receipts(): parsing.Parser().parse(ims=bytearray(camIxnMsg), kvy=kvy) # kvy.process(ims=bytearray(camIxnMsg)) # send copy of cam icp msg to witness assert kvy.kevers[camHab.pre].sn == 1 # accepted event - assert len(kvy.cues) == 1 # queued receipt cue + assert len(kvy.cues) >= 1 # at least queued receipt cue + # better to find receipt cue in cues exactly hab = camWitHabs[i] rctMsg = hab.processCues(kvy.cues) # process cue returns rct msg assert len(rctMsg) == 281 @@ -500,7 +505,8 @@ def test_nonindexed_witness_receipts(): for i, kvy in enumerate(camWitKvys): parsing.Parser().parse(ims=bytearray(camRotMsg), kvy=kvy) assert kvy.kevers[camHab.pre].sn == 2 # accepted event - assert len(kvy.cues) == 1 # queued receipt cue + assert len(kvy.cues) >= 1 # at least queued receipt cue + # better to find receipt cue in cues exactly hab = camWitHabs[i] rctMsg = hab.processCues(kvy.cues) # process cue returns rct msg assert len(rctMsg) == 281 @@ -586,7 +592,7 @@ def test_out_of_order_witnessed_events(): bobIcp = bobHab.makeOwnEvent(sn=0) parsing.Parser().parse(ims=bytearray(bobIcp), kvy=wesKvy) assert bobHab.pre in wesHab.kevers - iserder = coring.Serder(raw=bytearray(bobIcp)) + iserder = serdering.SerderKERI(raw=bytearray(bobIcp)) wesHab.receipt(serder=iserder) # Rotate and get Bob's rot, pass to Wes and generate receipt. @@ -594,7 +600,7 @@ def test_out_of_order_witnessed_events(): bobRotMsg = bobHab.makeOwnEvent(sn=1) parsing.Parser().parse(ims=bytearray(bobRotMsg), kvy=wesKvy) assert wesKvy.kevers[bobHab.pre].sn == 1 - bobRot = coring.Serder(raw=bobRotMsg) + bobRot = serdering.SerderKERI(raw=bobRotMsg) wesHab.receipt(serder=bobRot) # Get the receipted rotation event and pass, out of order to Bam diff --git a/tests/db/test_basing.py b/tests/db/test_basing.py index 5410779b8..b067985dc 100644 --- a/tests/db/test_basing.py +++ b/tests/db/test_basing.py @@ -5,21 +5,26 @@ """ import json import os +from dataclasses import dataclass, asdict import lmdb import pytest from hio.base import doing + +from tests.app import openMultiSig +from keri.kering import Versionage from keri.app import habbing -from keri.core import coring, eventing +from keri.core import coring, eventing, serdering from keri.core.coring import MtrDex from keri.core.coring import Serials, versify -from keri.core.coring import Signer, Salter +from keri.core.coring import Salter from keri.core.eventing import incept, rotate, interact, Kever from keri.db import basing from keri.db import dbing -from keri.db.basing import openDB, Baser +from keri.db.basing import openDB, Baser, KeyStateRecord from keri.db.dbing import (dgKey, onKey, snKey) from keri.db.dbing import openLMDB +from keri.help.helping import datify, dictify def test_baser(): @@ -1710,11 +1715,11 @@ def test_clean_baser(): assert natHab.kever.serder.said == natsaid ldig = bytes(natHab.db.getKeLast(dbing.snKey(natHab.pre, natHab.kever.sn))) assert ldig == natHab.kever.serder.saidb - serder = coring.Serder(raw=bytes(natHab.db.getEvt(dbing.dgKey(natHab.pre,ldig)))) + serder = serdering.SerderKERI(raw=bytes(natHab.db.getEvt(dbing.dgKey(natHab.pre,ldig)))) assert serder.said == natHab.kever.serder.said state = natHab.db.states.get(keys=natHab.pre) # Serder instance - assert state.sn == 6 - assert state.ked["f"] == '6' + assert state.s == '6' + assert state.f == '6' assert natHab.db.env.stat()['entries'] <= 96 #68 # test reopenDB with reuse (because temp) @@ -1722,7 +1727,7 @@ def test_clean_baser(): assert natHab.db.path == path ldig = bytes(natHab.db.getKeLast(dbing.snKey(natHab.pre, natHab.kever.sn))) assert ldig == natHab.kever.serder.saidb - serder = coring.Serder(raw=bytes(natHab.db.getEvt(dbing.dgKey(natHab.pre,ldig)))) + serder = serdering.SerderKERI(raw=bytes(natHab.db.getEvt(dbing.dgKey(natHab.pre,ldig)))) assert serder.said == natHab.kever.serder.said assert natHab.db.env.stat()['entries'] <= 96 #68 @@ -1736,9 +1741,11 @@ def test_clean_baser(): dig=natHab.kever.serder.said, sn=natHab.kever.sn+1, isith='2', - ndigs=[diger.qb64 for diger in natHab.kever.digers]) + ndigs=[diger.qb64 for diger in natHab.kever.ndigers]) fn, dts = natHab.kever.logEvent(serder=badsrdr, first=True) - natHab.db.states.pin(keys=natHab.pre, val=natHab.kever.state()) + natHab.db.states.pin(keys=natHab.pre, + val=datify(KeyStateRecord, + natHab.kever.state())) assert fn == 7 # verify garbage event in database @@ -1772,7 +1779,7 @@ def test_clean_baser(): assert natHab.db.path == path ldig = bytes(natHab.db.getKeLast(dbing.snKey(natHab.pre, natHab.kever.sn))) assert ldig == natHab.kever.serder.saidb - serder = coring.Serder(raw=bytes(natHab.db.getEvt(dbing.dgKey(natHab.pre,ldig)))) + serder = serdering.SerderKERI(raw=bytes(natHab.db.getEvt(dbing.dgKey(natHab.pre,ldig)))) assert serder.said == natHab.kever.serder.said assert natHab.db.env.stat()['entries'] >= 18 @@ -1780,8 +1787,8 @@ def test_clean_baser(): assert not natHab.db.getEvt(dbing.dgKey(natHab.pre, badsrdr.said)) assert not natHab.db.getFe(dbing.fnKey(natHab.pre, 7)) state = natHab.db.states.get(keys=natHab.pre) # Serder instance - assert state.sn == 6 - assert state.ked["f"] == '6' + assert state.s == '6' + assert state.f == '6' # verify name pre kom in db data = natHab.db.habs.get(keys=natHab.name) @@ -1869,7 +1876,7 @@ def test_fetchkeldel(): for val in vals2: assert db.addKe(key, val) == True - vals = [bytes(val) for val in db.getKelEstIter(preb)] + vals = [bytes(val) for val in db.getKelLastIter(preb)] lastvals = [vals0[-1], vals1[-1], vals2[-1]] assert vals == lastvals @@ -1939,7 +1946,7 @@ def test_usebaser(): serder = rotate(pre=kever.prefixer.qb64, keys=keys, isith=sith, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, ndigs=[coring.Diger(ser=key).qb64 for key in nxtkeys], sn=1) @@ -1951,7 +1958,7 @@ def test_usebaser(): # Event 2 Interaction serder = interact(pre=kever.prefixer.qb64, - dig=kever.serder.saider.qb64, + dig=kever.serder.said, sn=2) # sign serialization (keys don't change for signing) @@ -1964,6 +1971,105 @@ def test_usebaser(): """ End Test """ +def test_rawrecord(): + """ + Test RawRecord dataclass + """ + @dataclass + class TestRecord(basing.RawRecord): + x: str = "" + y: int = 0 + + record = TestRecord() + + assert isinstance(record, TestRecord) + assert isinstance(record, basing.RawRecord) + + assert "x" in record + assert "y" in record + + assert record.x == '' + assert record.y == 0 + + record = TestRecord(x="hi", y=3) + + assert record.x == 'hi' + assert record.y == 3 + + assert record._asdict() == {'x': 'hi', 'y': 3} + assert record._asjson() == b'{"x":"hi","y":3}' + assert record._ascbor() == b'\xa2axbhiay\x03' + assert record._asmgpk() == b'\x82\xa1x\xa2hi\xa1y\x03' + + """End Test""" + + + +def test_keystaterecord(): + """ + Test KeyStateRecord dataclass + """ + seer = basing.StateEERecord() + assert seer.s == '0' + assert seer.d == '' + assert seer._asdict() == {'s': '0', 'd': '', 'br': [], 'ba': []} + + ksr = basing.KeyStateRecord() + + assert isinstance(ksr, basing.KeyStateRecord) + assert ksr.i == '' + + ksn = asdict(ksr) # key state notice dict + assert ksn == {'vn': [], + 'i': '', + 's': '0', + 'p': '', + 'd': '', + 'f': '0', + 'dt': '', + 'et': '', + 'kt': '0', + 'k': [], + 'nt': '0', + 'n': [], + 'bt': '0', + 'b': [], + 'c': [], + 'ee': {'s': '0', 'd': '', 'br': [], 'ba': []}, + 'di': ''} + + assert ksr._asdict() == ksn + assert ksr._asjson() == (b'{"vn":[],"i":"","s":"0","p":"","d":"","f":"0","dt":"","et":"","kt":"0","k":[' + b'],"nt":"0","n":[],"bt":"0","b":[],"c":[],"ee":{"s":"0","d":"","br":[],"ba":[' + b']},"di":""}') + + assert ksr._ascbor() == (b'\xb1bvn\x80ai`asa0ap`ad`afa0bdt`bet`bkta0ak\x80bnta0an\x80bbta0ab\x80ac' + b'\x80bee\xa4asa0ad`bbr\x80bba\x80bdi`') + + assert ksr._asmgpk() == (b'\xde\x00\x11\xa2vn\x90\xa1i\xa0\xa1s\xa10\xa1p\xa0\xa1d\xa0\xa1f\xa10' + b'\xa2dt\xa0\xa2et\xa0\xa2kt\xa10\xa1k\x90\xa2nt\xa10\xa1n\x90\xa2bt\xa1' + b'0\xa1b\x90\xa1c\x90\xa2ee\x84\xa1s\xa10\xa1d\xa0\xa2br\x90\xa2ba\x90\xa2d' + b'i\xa0') + + + assert str(ksr) == repr(ksr) == ("KeyStateRecord(vn=[], i='', s='0', p='', d='', f='0', dt='', et='', kt='0', " + "k=[], nt='0', n=[], bt='0', b=[], c=[], ee=StateEERecord(s='0', d='', br=[], " + "ba=[]), di='')") + + dksn = dictify(ksr) + assert dksn == ksn + + dksr = datify(basing.KeyStateRecord, ksn) + assert dksr == ksr + + nksr = basing.KeyStateRecord._fromdict(ksn) + assert nksr == ksr + assert nksr._asdict() == ksn + + + """End Test""" + + def test_dbdict(): """ Test custom dbdict subclass of dict @@ -2028,21 +2134,24 @@ def test_dbdict(): dgkey = eventing.dgKey(pre=pre, dig=serder.said) db.putEvt(key=dgkey, val=serder.raw) assert db.getEvt(key=dgkey) is not None + db.states.pin(keys=pre, val=state) # put state in database - assert db.states.get(keys=pre) is not None + dbstate = db.states.get(keys=pre) + assert dbstate is not None + assert dbstate == state kever = eventing.Kever(state=state, db=db) - assert kever.state().ked == state.ked + assert kever.state() == state dkever = dbd[pre] # read through cache works here dstate = dkever.state() - assert dstate.ked == state.ked + assert dstate == state del dbd[pre] # not in dbd memory assert pre in dbd # read through cache works dkever = dbd[pre] dstate = dkever.state() - assert dstate.ked == state.ked + assert dstate == state db.states.rem(keys=pre) assert pre in dbd # still in memory @@ -2054,6 +2163,8 @@ def test_dbdict(): + + """End Test""" @@ -2114,13 +2225,55 @@ def test_baserdoer(): doist.do(doers=doers) assert doist.tyme == limit for doer in doers: - assert doer.baser.opened == False - assert doer.baser.env == None + assert doer.baser.opened is False + assert doer.baser.env is None assert not os.path.exists(doer.baser.path) """End Test""" +def test_group_members(): + with openMultiSig(prefix="test") as ((hby1, ghab1), (hby2, ghab2), (hby3, ghab3)): + keys = hby1.db.signingMembers(pre=ghab1.pre) + assert len(keys) == 3 + assert ghab1.mhab.pre in keys + assert ghab2.mhab.pre in keys + assert ghab3.mhab.pre in keys + + keys = hby2.db.signingMembers(pre=ghab1.pre) + assert len(keys) == 3 + assert ghab1.mhab.pre in keys + assert ghab2.mhab.pre in keys + assert ghab3.mhab.pre in keys + + keys = hby3.db.signingMembers(pre=ghab1.pre) + assert len(keys) == 3 + assert ghab1.mhab.pre in keys + assert ghab2.mhab.pre in keys + assert ghab3.mhab.pre in keys + + keys = hby1.db.rotationMembers(pre=ghab1.pre) + assert len(keys) == 3 + assert ghab1.mhab.pre in keys + assert ghab2.mhab.pre in keys + assert ghab3.mhab.pre in keys + + keys = hby2.db.rotationMembers(pre=ghab1.pre) + assert len(keys) == 3 + assert ghab1.mhab.pre in keys + assert ghab2.mhab.pre in keys + assert ghab3.mhab.pre in keys + + keys = hby3.db.rotationMembers(pre=ghab1.pre) + assert len(keys) == 3 + assert ghab1.mhab.pre in keys + assert ghab2.mhab.pre in keys + assert ghab3.mhab.pre in keys + + + """End Test""" + + if __name__ == "__main__": test_baser() test_clean_baser() diff --git a/tests/db/test_dbing.py b/tests/db/test_dbing.py index 8d1ea1f62..c88cca4f8 100644 --- a/tests/db/test_dbing.py +++ b/tests/db/test_dbing.py @@ -19,11 +19,8 @@ from keri.db.dbing import (dgKey, onKey, fnKey, snKey, dtKey, splitKey, splitKeyON, splitKeyFN, splitKeySN, splitKeyDT) from keri.db.dbing import LMDBer -from keri.db import basing -from keri.db.basing import openDB, Baser -from keri.core.coring import Signer, Prefixer, Serder -from keri.core.coring import MtrDex, MtrDex, MtrDex -from keri.core.coring import Serials, Vstrings, versify + + from keri.core.eventing import incept, rotate, interact, Kever, Kevery from keri.help import helping @@ -791,6 +788,64 @@ def test_lmdber(): assert dber.setIoSetVals(db, key2, vals3) assert dber.getIoSetVals(db, key2) == vals3 + # Empty keys cause lmdb.BalValsizeError so LMDBer now throws a KeyError + # if it catches this kind of thing in the various places where it gets + # thrown + empty_key = ''.encode('utf8') + some_value = 'foo'.encode('utf8') + with pytest.raises(KeyError): + dber.putVal(db, empty_key, some_value) + with pytest.raises(KeyError): + dber.setVal(db, empty_key, some_value) + with pytest.raises(KeyError): + dber.getVal(db, empty_key) + with pytest.raises(KeyError): + dber.delVal(db, empty_key) + dber.putIoSetVals(db, empty_key, [some_value]) + dber.addIoSetVal(db, empty_key, some_value) + dber.setIoSetVals(db, empty_key, [some_value]) + dber.appendIoSetVal(db, empty_key, some_value) + dber.getIoSetVals(db, empty_key) + [_ for _ in dber.getIoSetValsIter(db, empty_key)] + dber.getIoSetValLast(db, empty_key) + dber.cntIoSetVals(db, empty_key) + dber.delIoSetVals(db, empty_key) + dber.delIoSetVal(db, empty_key, some_value) + dber.getIoSetItems(db, empty_key) + dber.getIoSetItemsIter(db, empty_key) + with pytest.raises(KeyError): + dber.delIoSetIokey(db, empty_key) + with pytest.raises(KeyError): + dber.putVals(db, empty_key, [some_value]) + with pytest.raises(KeyError): + dber.addVal(db, empty_key, some_value) + with pytest.raises(KeyError): + dber.getVals(db, empty_key) + with pytest.raises(KeyError): + dber.getValLast(db, empty_key) + with pytest.raises(KeyError): + [_ for _ in dber.getValsIter(db, empty_key)] + with pytest.raises(KeyError): + dber.cntVals(db, empty_key) + with pytest.raises(KeyError): + dber.delVals(db, empty_key) + with pytest.raises(KeyError): + dber.putIoVals(db, empty_key, [some_value]) + with pytest.raises(KeyError): + dber.addIoVal(db, empty_key, some_value) + with pytest.raises(KeyError): + dber.getIoVals(db, empty_key) + with pytest.raises(KeyError): + [_ for _ in dber.getIoValsIter(db, empty_key)] + with pytest.raises(KeyError): + dber.getIoValLast(db, empty_key) + with pytest.raises(KeyError): + dber.cntIoVals(db, empty_key) + with pytest.raises(KeyError): + dber.delIoVals(db, empty_key) + with pytest.raises(KeyError): + dber.delIoVal(db, empty_key, some_value) + assert not os.path.exists(dber.path) """ End Test """ diff --git a/tests/db/test_escrowing.py b/tests/db/test_escrowing.py index c98991bd5..9ca309126 100644 --- a/tests/db/test_escrowing.py +++ b/tests/db/test_escrowing.py @@ -5,11 +5,11 @@ """ from keri import kering from keri.app import habbing -from keri.core import coring, eventing +from keri.core import coring, eventing, serdering from keri.core.eventing import SealEvent from keri.db import escrowing, dbing, subing from keri.help import helping -from keri.vdr import credentialing +from keri.vdr import credentialing, viring def test_broker(): @@ -40,10 +40,13 @@ def test_broker_nontrans(): rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() - stn = issuer.tever.state() - rpy = eventing.reply(route="/tsn/registry/" + issuer.regk, data=stn.ked) + rsr = issuer.tever.state() # registry state RegStateRecord + rpy = eventing.reply(route="/tsn/registry/" + issuer.regk, data=rsr._asdict()) wesHab = wesHby.makeHab(name="wes", isith='1', icount=1, transferable=False) bork = escrowing.Broker(db=brokerdb, subkey="test") @@ -54,9 +57,12 @@ def test_broker_nontrans(): ked = rpy.ked pre = ked['a']['i'] aid = "EBWY7LU2xwp0d4IhCvz1etbuv2iwcgBEigKJWnd-0Whs" - serder = coring.Serder(ked=ked) - tserder = coring.Serder(ked=ked["a"]) - saider, _ = coring.Saider.saidify(sad=ked, kind=coring.Serials.json, label=coring.Ids.d) + + serder = serdering.SerderKERI(sad=ked) + rrsr = viring.RegStateRecord._fromdict(ked["a"]) # reply RegStateRecord + #tserder = serdering.SerderKERI(sad=ked["a"]) + + saider, _ = coring.Saider.saidify(sad=ked, kind=coring.Serials.json, label=coring.Saids.d) dater = coring.Dater(dts=dts) cigars = wesHab.sign(ser=serder.raw, @@ -82,13 +88,13 @@ def process(**kwargs): assert [c.qb64 for c in kwargs["cigars"]] == [c.qb64 for c in cigars] assert kwargs["tsgs"] == [] assert kwargs["aid"] == aid - tser = coring.Serder(ked=kwargs["serder"].ked["a"]) - bork.updateState(aid=aid, serder=tser, saider=kwargs["saider"], dater=dater) + #tser = coring.Serder(ked=kwargs["serder"].ked["a"]) + bork.updateReply(aid=aid, serder=serder, saider=kwargs["saider"], dater=dater) bork.processEscrowState(typ=typ, processReply=process, extype=kering.OutOfOrderError) assert bork.escrowdb.get(keys=("test", saider.qb64, aid)) == [] - assert bork.serderdb.get(keys=(saider.qb64,)).raw == tserder.raw + assert bork.serderdb.get(keys=(saider.qb64,)).raw == serder.raw assert bork.saiderdb.get(keys=(pre, aid)).qb64 == saider.qb64 @@ -103,10 +109,13 @@ def test_broker_trans(): rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() - stn = issuer.tever.state() - rpy = eventing.reply(route="/tsn/registry/" + issuer.regk, data=stn.ked) + rsr = issuer.tever.state() # registry state RegStateRecord + rpy = eventing.reply(route="/tsn/registry/" + issuer.regk, data=rsr._asdict()) bobHab = bobHby.makeHab(name="bob", isith='1', icount=1, transferable=True) bork = escrowing.Broker(db=brokerdb, subkey="test") @@ -116,9 +125,10 @@ def test_broker_trans(): pre = issuer.regk aid = "EwWY7LU2xwp0d4IhCvz1etbuv2iwcgBEigKJWnd-0Whs" - serder = coring.Serder(ked=ked) - tserder = coring.Serder(ked=ked["a"]) - saider, _ = coring.Saider.saidify(sad=ked, kind=coring.Serials.json, label=coring.Ids.d) + serder = serdering.SerderKERI(sad=ked) + rrsr = viring.RegStateRecord._fromdict(ked["a"]) # reply RegStateRecord + #tserder = serdering.SerderKERI(sad=ked["a"]) + saider, _ = coring.Saider.saidify(sad=ked, kind=coring.Serials.json, label=coring.Saids.d) dater = coring.Dater(dts=dts) sigers = bobHab.sign(ser=serder.raw, @@ -149,13 +159,13 @@ def process(**kwargs): assert [s.qb64 for s in sigs] == [s.qb64 for s in sigers] assert kwargs["cigars"] == [] assert kwargs["aid"] == aid - tser = coring.Serder(ked=kwargs["serder"].ked["a"]) - bork.updateState(aid=aid, serder=tser, saider=kwargs["saider"], dater=dater) + #tser = coring.Serder(ked=kwargs["serder"].ked["a"]) + bork.updateReply(aid=aid, serder=serder, saider=kwargs["saider"], dater=dater) bork.processEscrowState(typ=typ, processReply=process, extype=kering.OutOfOrderError) assert bork.escrowdb.get(keys=("test", saider.qb64, aid)) == [] - assert bork.serderdb.get(keys=(saider.qb64,)).raw == tserder.raw + assert bork.serderdb.get(keys=(saider.qb64,)).raw == serder.raw assert bork.saiderdb.get(keys=(pre, aid)).qb64 == saider.qb64 diff --git a/tests/db/test_koming.py b/tests/db/test_koming.py index 6efb5aa1f..dfafa5327 100644 --- a/tests/db/test_koming.py +++ b/tests/db/test_koming.py @@ -96,6 +96,8 @@ def __iter__(self): actual = mydb.get(keys=keys) assert actual == sue + assert mydb.getDict(keys=keys) == asdict(actual) + result = mydb.pin(keys=keys, val=kip) assert result actual = mydb.get(keys=keys) @@ -121,6 +123,11 @@ def __iter__(self): assert actual.state == "UT" assert actual.zip == 84043 + assert mydb.getDict(keys=keys) == asdict(actual) + + # test None + assert mydb.getDict(keys=("bla, bal")) == None + mydb.rem(keys) actual = mydb.get(keys=keys) diff --git a/tests/db/test_subing.py b/tests/db/test_subing.py index 6cf9c2d17..c777dc278 100644 --- a/tests/db/test_subing.py +++ b/tests/db/test_subing.py @@ -9,7 +9,7 @@ import pysodium -from keri.core import coring, eventing +from keri.core import coring, eventing, serdering from keri.db import dbing, subing from keri.app import keeping from keri.help import helping @@ -618,7 +618,7 @@ def test_serder_suber(): keys = (pre, srdr0.said) sdb.put(keys=keys, val=srdr0) actual = sdb.get(keys=keys) - assert isinstance(actual, coring.Serder) + assert isinstance(actual, serdering.SerderKERI) assert actual.said == srdr0.said sdb.rem(keys) @@ -627,19 +627,19 @@ def test_serder_suber(): sdb.put(keys=keys, val=srdr0) actual = sdb.get(keys=keys) - assert isinstance(actual, coring.Serder) + assert isinstance(actual, serdering.SerderKERI) assert actual.said == srdr0.said srdr1 = eventing.rotate(pre=pre, keys=[pre], dig=srdr0.said) result = sdb.put(keys=keys, val=srdr1) assert not result - assert isinstance(actual, coring.Serder) + assert isinstance(actual, serdering.SerderKERI) assert actual.said == srdr0.said result = sdb.pin(keys=keys, val=srdr1) assert result actual = sdb.get(keys=keys) - assert isinstance(actual, coring.Serder) + assert isinstance(actual, serdering.SerderKERI) assert actual.said == srdr1.said # test with keys as string not tuple @@ -647,7 +647,7 @@ def test_serder_suber(): sdb.put(keys=keys, val=srdr1) actual = sdb.get(keys=keys) - assert isinstance(actual, coring.Serder) + assert isinstance(actual, serdering.SerderKERI) assert actual.said == srdr1.said sdb.rem(keys) diff --git a/tests/end/test_ending.py b/tests/end/test_ending.py index 32604ed05..c08a601fe 100644 --- a/tests/end/test_ending.py +++ b/tests/end/test_ending.py @@ -10,10 +10,11 @@ import falcon from falcon import testing from hio.base import tyming, doing +from hio.help import Hict from keri import help, kering from keri.app import habbing -from keri.core import coring +from keri.core import coring, serdering from keri.end import ending logger = help.ogler.getLogger() @@ -61,7 +62,8 @@ def test_signature_designature(): with habbing.openHby(name=name, base=base) as hby: # hby = habbing.Habery(name=name, base=base, temp=temp, free=True) hab = hby.makeHab(name=name, icount=3) - + print() + print([verfer.qb64 for verfer in hab.kever.verfers]) # setup habitat # hab = habbing.Habitat(name=name, ks=ks, db=db, temp=temp, icount=3) assert hab.pre == 'EGqHykT1gVyuWxsVW6LUUsz_KtLJGYMi_SrohInwvjC-' @@ -79,7 +81,9 @@ def test_signature_designature(): signage = ending.Signage(markers=sigers, indexed=None, signer=None, ordinal=None, digest=None, kind=None) header = ending.signature([signage]) # put it in a list - assert header == {'Signature': 'indexed="?1";0="AACsufRGYI-sRvS2c0rsOueSoSRtrjODaf48DYLJbLvvD8aHe7b2sWGebZ-y9ichhsxMF3Hhn-3LYSKIrnmH3oIN";1="ABDs7m2-h5l7vpjYtbFXtksicpZK5Oclm43EOkE2xoQOfr08doj73VrlKZOKNfJmRumD3tfaiFFgVZqPgiHuFVoA";2="ACDVOy2LvGgFINUneL4iwA55ypJR6vDpLLbdleEsiANmFazwZARypJMiw9vu2Iu0oL7XCUiUT4JncU8P3HdIp40F"'} + assert header == { + 'Signature': 'indexed="?1";0="AACsufRGYI-sRvS2c0rsOueSoSRtrjODaf48DYLJbLvvD8aHe7b2sWGebZ-y9ichhsxMF3Hhn' + '-3LYSKIrnmH3oIN";1="ABDs7m2-h5l7vpjYtbFXtksicpZK5Oclm43EOkE2xoQOfr08doj73VrlKZOKNfJmRumD3tfaiFFgVZqPgiHuFVoA";2="ACDVOy2LvGgFINUneL4iwA55ypJR6vDpLLbdleEsiANmFazwZARypJMiw9vu2Iu0oL7XCUiUT4JncU8P3HdIp40F"'} # test designature signages = ending.designature(header["Signature"]) @@ -100,7 +104,12 @@ def test_signature_designature(): digest=digest, kind="CESR") header = ending.signature([signage]) # put it in a list - assert header == {'Signature': 'indexed="?1";signer="EGqHykT1gVyuWxsVW6LUUsz_KtLJGYMi_SrohInwvjC-";ordinal="0";digest="EGqHykT1gVyuWxsVW6LUUsz_KtLJGYMi_SrohInwvjC-";kind="CESR";0="AACsufRGYI-sRvS2c0rsOueSoSRtrjODaf48DYLJbLvvD8aHe7b2sWGebZ-y9ichhsxMF3Hhn-3LYSKIrnmH3oIN";1="ABDs7m2-h5l7vpjYtbFXtksicpZK5Oclm43EOkE2xoQOfr08doj73VrlKZOKNfJmRumD3tfaiFFgVZqPgiHuFVoA";2="ACDVOy2LvGgFINUneL4iwA55ypJR6vDpLLbdleEsiANmFazwZARypJMiw9vu2Iu0oL7XCUiUT4JncU8P3HdIp40F"'} + assert header == { + 'Signature': 'indexed="?1";signer="EGqHykT1gVyuWxsVW6LUUsz_KtLJGYMi_SrohInwvjC-";ordinal="0";digest' + '="EGqHykT1gVyuWxsVW6LUUsz_KtLJGYMi_SrohInwvjC-";kind="CESR";0="AACsufRGYI' + '-sRvS2c0rsOueSoSRtrjODaf48DYLJbLvvD8aHe7b2sWGebZ-y9ichhsxMF3Hhn-3LYSKIrnmH3oIN";1="ABDs7m2' + '-h5l7vpjYtbFXtksicpZK5Oclm43EOkE2xoQOfr08doj73VrlKZOKNfJmRumD3tfaiFFgVZqPgiHuFVoA";2' + '="ACDVOy2LvGgFINUneL4iwA55ypJR6vDpLLbdleEsiANmFazwZARypJMiw9vu2Iu0oL7XCUiUT4JncU8P3HdIp40F"'} # test designature signages = ending.designature(header["Signature"]) @@ -120,8 +129,13 @@ def test_signature_designature(): signage = ending.Signage(markers=cigars, indexed=None, signer=None, ordinal=None, digest=None, kind=None) header = ending.signature([signage]) - assert header == {'Signature': 'indexed="?0";DAi2TaRNVtGmV8eSUvqHIBzTzIgrQi57vKzw5Svmy7jw="0BCsufRGYI-sRvS2c0rsOueSoSRtrjODaf48DYLJbLvvD8aHe7b2sWGebZ-y9ichhsxMF3Hhn-3LYSKIrnmH3oIN";DNK2KFnL0jUGlmvZHRse7HwNGVdtkM-ORvTZfFw7mDbt="0BDs7m2-h5l7vpjYtbFXtksicpZK5Oclm43EOkE2xoQOfr08doj73VrlKZOKNfJmRumD3tfaiFFgVZqPgiHuFVoA";DDvIoIYqeuXJ4Zb8e2luWfjPTg4FeIzfHzIO8lC56WjD="0BDVOy2LvGgFINUneL4iwA55ypJR6vDpLLbdleEsiANmFazwZARypJMiw9vu2Iu0oL7XCUiUT4JncU8P3HdIp40F"'} - + assert header == { + 'Signature': 'indexed="?0";DAi2TaRNVtGmV8eSUvqHIBzTzIgrQi57vKzw5Svmy7jw="0BCsufRGYI' + '-sRvS2c0rsOueSoSRtrjODaf48DYLJbLvvD8aHe7b2sWGebZ-y9ichhsxMF3Hhn-3LYSKIrnmH3oIN' + '";DNK2KFnL0jUGlmvZHRse7HwNGVdtkM-ORvTZfFw7mDbt="0BDs7m2' + '-h5l7vpjYtbFXtksicpZK5Oclm43EOkE2xoQOfr08doj73VrlKZOKNfJmRumD3tfaiFFgVZqPgiHuFVoA' + '";DDvIoIYqeuXJ4Zb8e2luWfjPTg4FeIzfHzIO8lC56WjD' + '="0BDVOy2LvGgFINUneL4iwA55ypJR6vDpLLbdleEsiANmFazwZARypJMiw9vu2Iu0oL7XCUiUT4JncU8P3HdIp40F"'} # test designature signages = ending.designature(header["Signature"]) @@ -141,7 +155,10 @@ def test_signature_designature(): ordinal=None, digest=None, kind="CESR")) header = ending.signature(signages) - assert header == {'Signature': 'indexed="?1";signer="EGqHykT1gVyuWxsVW6LUUsz_KtLJGYMi_SrohInwvjC-";kind="CESR";0="AACsufRGYI-sRvS2c0rsOueSoSRtrjODaf48DYLJbLvvD8aHe7b2sWGebZ-y9ichhsxMF3Hhn-3LYSKIrnmH3oIN";1="ABDs7m2-h5l7vpjYtbFXtksicpZK5Oclm43EOkE2xoQOfr08doj73VrlKZOKNfJmRumD3tfaiFFgVZqPgiHuFVoA";2="ACDVOy2LvGgFINUneL4iwA55ypJR6vDpLLbdleEsiANmFazwZARypJMiw9vu2Iu0oL7XCUiUT4JncU8P3HdIp40F",indexed="?0";signer="EGqHykT1gVyuWxsVW6LUUsz_KtLJGYMi_SrohInwvjC-";kind="CESR";DAi2TaRNVtGmV8eSUvqHIBzTzIgrQi57vKzw5Svmy7jw="0BCsufRGYI-sRvS2c0rsOueSoSRtrjODaf48DYLJbLvvD8aHe7b2sWGebZ-y9ichhsxMF3Hhn-3LYSKIrnmH3oIN";DNK2KFnL0jUGlmvZHRse7HwNGVdtkM-ORvTZfFw7mDbt="0BDs7m2-h5l7vpjYtbFXtksicpZK5Oclm43EOkE2xoQOfr08doj73VrlKZOKNfJmRumD3tfaiFFgVZqPgiHuFVoA";DDvIoIYqeuXJ4Zb8e2luWfjPTg4FeIzfHzIO8lC56WjD="0BDVOy2LvGgFINUneL4iwA55ypJR6vDpLLbdleEsiANmFazwZARypJMiw9vu2Iu0oL7XCUiUT4JncU8P3HdIp40F"'} + assert header == { + 'Signature': 'indexed="?1";signer="EGqHykT1gVyuWxsVW6LUUsz_KtLJGYMi_SrohInwvjC-";kind="CESR";0' + '="AACsufRGYI-sRvS2c0rsOueSoSRtrjODaf48DYLJbLvvD8aHe7b2sWGebZ-y9ichhsxMF3Hhn-3LYSKIrnmH3oIN' + '";1="ABDs7m2-h5l7vpjYtbFXtksicpZK5Oclm43EOkE2xoQOfr08doj73VrlKZOKNfJmRumD3tfaiFFgVZqPgiHuFVoA";2="ACDVOy2LvGgFINUneL4iwA55ypJR6vDpLLbdleEsiANmFazwZARypJMiw9vu2Iu0oL7XCUiUT4JncU8P3HdIp40F",indexed="?0";signer="EGqHykT1gVyuWxsVW6LUUsz_KtLJGYMi_SrohInwvjC-";kind="CESR";DAi2TaRNVtGmV8eSUvqHIBzTzIgrQi57vKzw5Svmy7jw="0BCsufRGYI-sRvS2c0rsOueSoSRtrjODaf48DYLJbLvvD8aHe7b2sWGebZ-y9ichhsxMF3Hhn-3LYSKIrnmH3oIN";DNK2KFnL0jUGlmvZHRse7HwNGVdtkM-ORvTZfFw7mDbt="0BDs7m2-h5l7vpjYtbFXtksicpZK5Oclm43EOkE2xoQOfr08doj73VrlKZOKNfJmRumD3tfaiFFgVZqPgiHuFVoA";DDvIoIYqeuXJ4Zb8e2luWfjPTg4FeIzfHzIO8lC56WjD="0BDVOy2LvGgFINUneL4iwA55ypJR6vDpLLbdleEsiANmFazwZARypJMiw9vu2Iu0oL7XCUiUT4JncU8P3HdIp40F"'} # test designature signages = ending.designature(header["Signature"]) @@ -173,7 +190,10 @@ def test_signature_designature(): ordinal=None, digest=None, kind="CESR")) header = ending.signature(signages) - assert header == {'Signature': 'indexed="?1";signer="EGqHykT1gVyuWxsVW6LUUsz_KtLJGYMi_SrohInwvjC-";kind="CESR";wit0="AACsufRGYI-sRvS2c0rsOueSoSRtrjODaf48DYLJbLvvD8aHe7b2sWGebZ-y9ichhsxMF3Hhn-3LYSKIrnmH3oIN";wit1="ABDs7m2-h5l7vpjYtbFXtksicpZK5Oclm43EOkE2xoQOfr08doj73VrlKZOKNfJmRumD3tfaiFFgVZqPgiHuFVoA";wit2="ACDVOy2LvGgFINUneL4iwA55ypJR6vDpLLbdleEsiANmFazwZARypJMiw9vu2Iu0oL7XCUiUT4JncU8P3HdIp40F",indexed="?0";signer="EGqHykT1gVyuWxsVW6LUUsz_KtLJGYMi_SrohInwvjC-";kind="CESR";wit0="0BCsufRGYI-sRvS2c0rsOueSoSRtrjODaf48DYLJbLvvD8aHe7b2sWGebZ-y9ichhsxMF3Hhn-3LYSKIrnmH3oIN";wit1="0BDs7m2-h5l7vpjYtbFXtksicpZK5Oclm43EOkE2xoQOfr08doj73VrlKZOKNfJmRumD3tfaiFFgVZqPgiHuFVoA";wit2="0BDVOy2LvGgFINUneL4iwA55ypJR6vDpLLbdleEsiANmFazwZARypJMiw9vu2Iu0oL7XCUiUT4JncU8P3HdIp40F"'} + assert header == { + 'Signature': 'indexed="?1";signer="EGqHykT1gVyuWxsVW6LUUsz_KtLJGYMi_SrohInwvjC-";kind="CESR";wit0' + '="AACsufRGYI-sRvS2c0rsOueSoSRtrjODaf48DYLJbLvvD8aHe7b2sWGebZ-y9ichhsxMF3Hhn-3LYSKIrnmH3oIN' + '";wit1="ABDs7m2-h5l7vpjYtbFXtksicpZK5Oclm43EOkE2xoQOfr08doj73VrlKZOKNfJmRumD3tfaiFFgVZqPgiHuFVoA";wit2="ACDVOy2LvGgFINUneL4iwA55ypJR6vDpLLbdleEsiANmFazwZARypJMiw9vu2Iu0oL7XCUiUT4JncU8P3HdIp40F",indexed="?0";signer="EGqHykT1gVyuWxsVW6LUUsz_KtLJGYMi_SrohInwvjC-";kind="CESR";wit0="0BCsufRGYI-sRvS2c0rsOueSoSRtrjODaf48DYLJbLvvD8aHe7b2sWGebZ-y9ichhsxMF3Hhn-3LYSKIrnmH3oIN";wit1="0BDs7m2-h5l7vpjYtbFXtksicpZK5Oclm43EOkE2xoQOfr08doj73VrlKZOKNfJmRumD3tfaiFFgVZqPgiHuFVoA";wit2="0BDVOy2LvGgFINUneL4iwA55ypJR6vDpLLbdleEsiANmFazwZARypJMiw9vu2Iu0oL7XCUiUT4JncU8P3HdIp40F"'} # test designature signages = ending.designature(header["Signature"]) @@ -334,10 +354,13 @@ def test_seid_api(): signage = ending.Signage(markers=sigers, indexed=None, signer=None, ordinal=None, digest=None, kind=None) header = ending.signature([signage]) - assert header == {'Signature': 'indexed="?1";0="AACuduac6au7JSqANK1IaHWP_GlLG9OhPC7Mg52_uRSoddogaYw8mfuyIM6x4lRhKAlxUVDRv_Fh0plB7wx-LSoE"'} + assert header == { + 'Signature': + 'indexed="?1";0="AACuduac6au7JSqANK1IaHWP_GlLG9OhPC7Mg52_uRSoddogaYw8mfuyIM6x4lRhKAlxUVDRv_Fh0plB7wx' + '-LSoE"'} endpath = "/end/{}/{}".format(aid, role) - assert endpath == f'/end/{aid0}/witness' # '/end/EFW3cL-Lv4tGnk_WnnruryH4WKaOXw4qeZNU5dG1hUve/witness' + assert endpath == f'/end/{aid0}/witness' # '/end/EFW3cL-Lv4tGnk_WnnruryH4WKaOXw4qeZNU5dG1hUve/witness' rep = client.simulate_post(path=endpath, content_type=falcon.MEDIA_JSON, headers=header, @@ -345,10 +368,9 @@ def test_seid_api(): assert rep.status == falcon.HTTP_OK assert rep.json == dict(aid=aid, role=role, data=data) assert rep.text == ('{"aid": "EAJAEHYWGxz0nJNBvbOzpFR8RonSWa_YyJxULjAH1XEv", "role": "witness", ' - '"data": {"seid": "BA89hKezugU2LFKiFVbitoHAxXqJh6HQ8Rn9tH7fxd68", "name": ' - '"wit0", "dts": "2021-01-01T00:00:00.000000+00:00", "scheme": "http", "host": ' - '"localhost", "port": 8080, "path": "/witness"}}') - + '"data": {"seid": "BA89hKezugU2LFKiFVbitoHAxXqJh6HQ8Rn9tH7fxd68", "name": ' + '"wit0", "dts": "2021-01-01T00:00:00.000000+00:00", "scheme": "http", "host": ' + '"localhost", "port": 8080, "path": "/witness"}}') """Done Test""" @@ -408,17 +430,104 @@ def test_get_oobi(): rep = client.simulate_get('/oobi', ) assert rep.status == falcon.HTTP_OK - serder = coring.Serder(raw=rep.text.encode("utf-8")) - assert serder.ked['t'] == coring.Ilks.rpy - assert serder.ked['r'] == "/loc/scheme" - assert serder.ked['a']['eid'] == hab.pre - assert serder.ked['a']['scheme'] == kering.Schemes.http - assert serder.ked['a']['url'] == "http://127.0.0.1:5555" - print(serder.pretty()) + serder = serdering.SerderKERI(raw=rep.text.encode("utf-8")) + assert serder.ked['t'] == coring.Ilks.icp + assert serder.ked['i'] == "EOaICQwhOy3wMwecjAuHQTbv_Cmuu1azTMnHi4QtUmEU" """Done Test""" +def test_siginput(mockHelpingNowUTC): + print() + with habbing.openHab(name="test", base="test", temp=True) as (hby, hab): + headers = Hict([ + ("Content-Type", "application/json"), + ("Content-Length", "256"), + ("Connection", "close"), + ("Signify-Resource", "EWJkQCFvKuyxZi582yJPb0wcwuW3VXmFNuvbQuBpgmIs"), + ("Signify-Timestamp", "2022-09-24T00:05:48.196795+00:00"), + ]) + + header, sig = ending.siginput("sig0", "POST", "/signify", headers, + fields=["Signify-Resource", "@method", + "@path", + "Signify-Timestamp"], + alg="ed25519", keyid=hab.pre, hab=hab) + + headers.extend(header) + signage = ending.Signage(markers=dict(sig0=sig), indexed=False, signer=None, ordinal=None, digest=None, + kind=None) + headers.extend(ending.signature([signage])) + + assert dict(headers) == {'Connection': 'close', + 'Content-Length': '256', + 'Content-Type': 'application/json', + 'Signify-Resource': 'EWJkQCFvKuyxZi582yJPb0wcwuW3VXmFNuvbQuBpgmIs', + 'Signify-Timestamp': '2022-09-24T00:05:48.196795+00:00', + 'Signature': 'indexed="?0";sig0="0BCF-Qc9q1YrNOP5Np4fy9mz0o8HQALANKP8ZjvItfjjmpYKYL_FS' + 'j4bcLZKFSd81bo9SeQn36bLt3dpbEzt2GgN"', + 'Signature-Input': 'sig0=("signify-resource" "@method" "@path" ' + '"signify-timestamp");created=1609459200;keyid="EIaGMMWJFPmtXznY1II' + 'iKDIrg-vIyge6mBl2QV8dDjI3";alg="ed25519"'} + + siginput = headers["Signature-Input"] + signature = headers["Signature"] + + inputs = ending.desiginput(siginput.encode("utf-8")) + assert len(inputs) == 1 + inputage = inputs[0] + + assert inputage.name == 'sig0' + assert inputage.fields == ['signify-resource', "@method", "@path", "signify-timestamp"] + assert inputage.created == 1609459200 + assert inputage.alg == "ed25519" + assert inputage.keyid == hab.pre + assert inputage.expires is None + assert inputage.nonce is None + assert inputage.context is None + + items = [] + for field in inputage.fields: + if field.startswith("@"): + if field == "@method": + items.append(f'"{field}": POST') + elif field == "@path": + items.append(f'"{field}": /signify') + + else: + field = field.lower() + if field not in headers: + continue + + value = ending.normalize(headers[field]) + items.append(f'"{field}": {value}') + + values = [f"({' '.join(inputage.fields)})", f"created={inputage.created}"] + if inputage.expires is not None: + values.append(f"expires={inputage.expires}") + if inputage.nonce is not None: + values.append(f"nonce={inputage.nonce}") + if inputage.keyid is not None: + values.append(f"keyid={inputage.keyid}") + if inputage.context is not None: + values.append(f"context={inputage.context}") + if inputage.alg is not None: + values.append(f"alg={inputage.alg}") + + params = ';'.join(values) + + items.append(f'"@signature-params: {params}"') + ser = "\n".join(items).encode("utf-8") + + signages = ending.designature(signature) + assert len(signages) == 1 + assert signages[0].indexed is False + assert "sig0" in signages[0].markers + + cig = signages[0].markers["sig0"] + assert hab.kever.verfers[0].verify(sig=cig.raw, ser=ser) is True + + def test_end_demo(): """ Run with rest api client like Paw or PostMan diff --git a/tests/peer/test_exchanging.py b/tests/peer/test_exchanging.py index 0a5eb8e99..378f08acb 100644 --- a/tests/peer/test_exchanging.py +++ b/tests/peer/test_exchanging.py @@ -3,38 +3,186 @@ tests.peer.test_exchanging module """ - from keri.app import habbing, forwarding, storing, signing -from keri.core import coring +from keri.core import coring, serdering from keri.peer import exchanging +from keri.vdr.eventing import incept + + +def test_nesting(): + paths = ['a'] + val = "-JAbccDefg" + pathed = dict() + + np = exchanging.nesting(paths, pathed, val) + assert np == pathed + assert pathed == {'a': '-JAbccDefg'} + + paths = ['a', 'b'] + val = "-JAbccDefg" + pathed = dict() + + np = exchanging.nesting(paths, pathed, val) + assert np == pathed + assert pathed == {'a': {'b': '-JAbccDefg'}} + + paths = ['a', 'b', 'c', 'd', 'e'] + val = "-JAbccDefg" + pathed = dict() + + np = exchanging.nesting(paths, pathed, val) + assert np == pathed + assert pathed == {'a': {'b': {'c': {'d': {'e': '-JAbccDefg'}}}}} + + paths = [] + val = "-JAbccDefg" + pathed = dict() + + np = exchanging.nesting(paths, pathed, val) + assert np == val + assert pathed == {} def test_exchanger(): - with habbing.openHab(name="sid", base="test", salt=b'0123456789abcdef') as (hby, hab): + with habbing.openHab(name="sid", base="test", salt=b'0123456789abcdef') as (hby, hab), \ + habbing.openHab(name="rec", base="test", salt=b'0123456789abcdef') as (recHby, recHab): mbx = storing.Mailboxer(hby=hby) forwarder = forwarding.ForwardHandler(hby=hby, mbx=mbx) - exc = exchanging.Exchanger(db=hby.db, handlers=[forwarder]) + exc = exchanging.Exchanger(hby=recHby, handlers=[forwarder]) + + msg = hab.makeOwnInception() + recHab.psr.parseOne(ims=msg) ser, sigs, _ = hab.getOwnEvent(sn=0) + sadsig = signing.SadPathSigGroup(pather=coring.Pather(path=[]), sigers=sigs) act = bytearray() - pather = coring.Pather(path=["a"]) + pather = coring.Pather(path=["e"]) sadsig.transpose(pather) act.extend(sadsig.proof) # create the forward message with payload embedded at `a` field - fwd = exchanging.exchange(route='/fwd', modifiers=dict(pre="EBCAFG", topic="/delegation"), - payload=ser.ked) + fwd, _ = exchanging.exchange(route='/fwd', sender=hab.pre, + modifiers=dict(pre="EBCAFG", topic="/delegation"), + payload={}, embeds=dict(evt=ser.raw)) exnsigs = hab.sign(ser=fwd.raw, - verfers=hab.kever.verfers, - indexed=True) + verfers=hab.kever.verfers, + indexed=True) + tsgs = [(hab.kever.prefixer, coring.Seqner(sn=hab.kever.sn), coring.Saider(qb64=hab.kever.serder.said), exnsigs)] + exc.processEvent(serder=fwd, source=hab.kever.prefixer, tsgs=tsgs) + + msgs = forwarder.mbx.getTopicMsgs(topic="EBCAFG/delegation") + assert len(msgs) == 0 # No pathed argument, so nothing to forward. + + +def test_hab_exchange(mockHelpingNowUTC): + with habbing.openHby() as hby: + hab = hby.makeHab(name="test") + assert hab.pre == "EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3" + + nonce = "AH3-1EZWXU9I0fv3Iz_9ZIhjj13JO7u4GNFYC3-l8_K-" + regser = incept(hab.pre, + baks=[], + toad=0, + cnfg=[], + nonce=nonce, + code=coring.MtrDex.Blake3_256) + seal = dict(i=regser.pre, s=regser.sn, d=regser.said) + msg = hab.interact(data=[seal]) + + embeds = dict( + vcp=regser.raw, + ixn=msg, + ) + + data = dict(m="Let's create a registry") + msg = hab.exchange(route="/multisig/registry/incept", recipient="", + payload=data, embeds=embeds) + assert msg == (b'{"v":"KERI10JSON000398_","t":"exn","d":"ECcmfGnlqnc5-1_oXNpbfowv' + b'RsEa-V8tfeKmQDRJJ50i","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2Q' + b'V8dDjI3","p":"","dt":"2021-01-01T00:00:00.000000+00:00","r":"/mu' + b'ltisig/registry/incept","q":{},"a":{"i":"","m":"Let\'s create a r' + b'egistry"},"e":{"vcp":{"v":"KERI10JSON00010f_","t":"vcp","d":"EI6' + b'hBlgkWoJgkZyfLW35_UyM4nIK44OgsSwFR_WOfvVB","i":"EI6hBlgkWoJgkZyf' + b'LW35_UyM4nIK44OgsSwFR_WOfvVB","ii":"EIaGMMWJFPmtXznY1IIiKDIrg-vI' + b'yge6mBl2QV8dDjI3","s":"0","c":[],"bt":"0","b":[],"n":"AH3-1EZWXU' + b'9I0fv3Iz_9ZIhjj13JO7u4GNFYC3-l8_K-"},"ixn":{"v":"KERI10JSON00013' + b'8_","t":"ixn","d":"EFuFnevyDFfpWG6il-6Qcv0ne0ZIItLwanCwI-SU8A9j"' + b',"i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","s":"1","p":' + b'"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","a":[{"i":"EI6hBl' + b'gkWoJgkZyfLW35_UyM4nIK44OgsSwFR_WOfvVB","s":0,"d":"EI6hBlgkWoJgk' + b'ZyfLW35_UyM4nIK44OgsSwFR_WOfvVB"}]},"d":"EL5Nkm6T7HG_0GW6uwqYSZw' + b'lH23khtXvsVE-dq8eO_eE"}}-FABEIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2' + b'QV8dDjI30AAAAAAAAAAAAAAAAAAAAAAAEIaGMMWJFPmtXznY1IIiKDIrg-vIyge6' + b'mBl2QV8dDjI3-AABAACahD6g7IwjUyQRyGUPGLvlr5-DsvLxeJtCUVIIECYfAQ_q' + b'p3Z2pe__HRqIl-NrUv85oQrZBm0kpKn8LBQtQfkO-LAa5AACAA-e-ixn-AABAADp' + b'rTWp4llIzVzBM7VVsDOgXVJdoiVXutsWJEbDJ2pMdjXjNi1xKALBSZ1ZgRoUsD--' + b'LgUQkXIdjLoQ19XPvJMJ') + + exn = serdering.SerderKERI(raw=msg) + + hab2 = hby.makeHab(name="respondant") + regser = incept(hab2.pre, + baks=[], + toad=0, + cnfg=[], + nonce=nonce, + code=coring.MtrDex.Blake3_256) + + seal = dict(i=regser.pre, s=regser.sn, d=regser.said) + msg = hab2.interact(data=[seal]) + + embeds = dict( + vcp=regser.raw, + ixn=msg, + ) - exc.processEvent(serder=fwd, source=hab.kever.prefixer, sigers=exnsigs, sadsigs=[(sadsig.pather, sadsig.sigers)]) + data = dict(m="Lets create this registry instead") + msg = hab2.exchange(route="/multisig/registry/incept", payload=data, recipient="", dig=exn.said, + embeds=embeds) + assert msg == (b'{"v":"KERI10JSON0003ce_","t":"exn","d":"EEMxkjO9HzZoekfzmjrkE19y' + b'pU259apUWuY7alFu_GmE","i":"EIREQlatUJODbKogZfa3IqXZ90XdZA0qJMVli' + b'I61Bcc2","p":"ECcmfGnlqnc5-1_oXNpbfowvRsEa-V8tfeKmQDRJJ50i","dt"' + b':"2021-01-01T00:00:00.000000+00:00","r":"/multisig/registry/ince' + b'pt","q":{},"a":{"i":"","m":"Lets create this registry instead"},' + b'"e":{"vcp":{"v":"KERI10JSON00010f_","t":"vcp","d":"EB5mts6qrWOZr' + b'xjma6lSTjAdPZ0NSHM1HC3IndbS_giB","i":"EB5mts6qrWOZrxjma6lSTjAdPZ' + b'0NSHM1HC3IndbS_giB","ii":"EIREQlatUJODbKogZfa3IqXZ90XdZA0qJMVliI' + b'61Bcc2","s":"0","c":[],"bt":"0","b":[],"n":"AH3-1EZWXU9I0fv3Iz_9' + b'ZIhjj13JO7u4GNFYC3-l8_K-"},"ixn":{"v":"KERI10JSON000138_","t":"i' + b'xn","d":"EOek9JVKNeuW-5UNeHYCTDe70_GtvRwP672oWMNBJpA5","i":"EIRE' + b'QlatUJODbKogZfa3IqXZ90XdZA0qJMVliI61Bcc2","s":"1","p":"EIREQlatU' + b'JODbKogZfa3IqXZ90XdZA0qJMVliI61Bcc2","a":[{"i":"EB5mts6qrWOZrxjm' + b'a6lSTjAdPZ0NSHM1HC3IndbS_giB","s":0,"d":"EB5mts6qrWOZrxjma6lSTjA' + b'dPZ0NSHM1HC3IndbS_giB"}]},"d":"EM3gLTzQ9GmKd50Rlm_kiIkeYkxb004eo' + b'OsWahz70TqJ"}}-FABEIREQlatUJODbKogZfa3IqXZ90XdZA0qJMVliI61Bcc20A' + b'AAAAAAAAAAAAAAAAAAAAAAEIREQlatUJODbKogZfa3IqXZ90XdZA0qJMVliI61Bc' + b'c2-AABAAAxpwQLr9-D7hOZYHvvDB_ffo5sRgBf0NufowF0g_YMI1wdnttlYA2o_d' + b'wtK_WNbfh_iAytFw9nHZziCED13AwH-LAa5AACAA-e-ixn-AABAACaoxfQp5L_Gd' + b'0nKqJXMbLTXzkrJJDd8RFxWdTSesAMydUzmJQlGt0T9h8L7SwIrq8yBinj990PLJ' + b'Hl7sXmq04I') - assert len(forwarder.msgs) == 1 - msg = forwarder.msgs.popleft() + # Test exn from non-transferable AID + hab = hby.makeHab(name="test1", transferable=False) + assert hab.pre == "BJZ_LF61JTCCSCIw2Q4ozE2MsbRC4m-N6-tFVlCeiZPG" - assert msg["payload"] == ser.ked - assert msg["modifiers"] == {'pre': 'EBCAFG', 'topic': '/delegation'} - assert msg["pre"].qb64b == hab.kever.prefixer.qb64b - assert msg["attachments"] == [] + embeds = dict( + vcp=hab.endorse(regser, pipelined=False) + ) + msg = hab.exchange(route="/multisig/registry/incept", payload=data, embeds=embeds, + recipient="") + assert msg == (b'{"v":"KERI10JSON000263_","t":"exn","d":"ENRFAVDU_ZbcVpx6l6lrC5Mu' + b'UqHXfT3N9VjUkvU4t29S","i":"BJZ_LF61JTCCSCIw2Q4ozE2MsbRC4m-N6-tFV' + b'lCeiZPG","p":"","dt":"2021-01-01T00:00:00.000000+00:00","r":"/mu' + b'ltisig/registry/incept","q":{},"a":{"i":"","m":"Lets create this' + b' registry instead"},"e":{"vcp":{"v":"KERI10JSON00010f_","t":"vcp' + b'","d":"EB5mts6qrWOZrxjma6lSTjAdPZ0NSHM1HC3IndbS_giB","i":"EB5mts' + b'6qrWOZrxjma6lSTjAdPZ0NSHM1HC3IndbS_giB","ii":"EIREQlatUJODbKogZf' + b'a3IqXZ90XdZA0qJMVliI61Bcc2","s":"0","c":[],"bt":"0","b":[],"n":"' + b'AH3-1EZWXU9I0fv3Iz_9ZIhjj13JO7u4GNFYC3-l8_K-"},"d":"ENC6w8wUj-Gp' + b'_RpAJN5q4Lf00IHstzNLUvkh3ZvgHGP_"}}-CABBJZ_LF61JTCCSCIw2Q4ozE2Ms' + b'bRC4m-N6-tFVlCeiZPG0BCxLApuSnk1MF9IUq1RJNjVmr6s-fLwvP6aAPa0ag34t' + b'4G7EKKk-UFwy74-0StSlHcS8KBkN5ZbtuHvV9tXRqUJ-LAl5AACAA-e-vcp-CABB' + b'JZ_LF61JTCCSCIw2Q4ozE2MsbRC4m-N6-tFVlCeiZPG0BDjOC4j0Co6P0giMylR4' + b'7149eJ8Yf_hO-32_TpY77KMVCWCf0U8GuZPIN76R2zsyT_eARvS_zQsX1ebjl3PM' + b'P0D') diff --git a/tests/vc/test_protocoling.py b/tests/vc/test_protocoling.py index 8c2601099..befb8dcbb 100644 --- a/tests/vc/test_protocoling.py +++ b/tests/vc/test_protocoling.py @@ -3,22 +3,18 @@ tests.vc.protocoling module """ -from hio.base import doing -from keri.app import habbing, indirecting, signing, storing, notifying -from keri.core import coring, scheming, eventing, parsing +from keri.app import habbing, notifying +from keri.core import coring, scheming, parsing from keri.core.eventing import SealEvent from keri.peer import exchanging from keri.vc import protocoling -from keri.vc.protocoling import IssueHandler, PresentationRequestHandler, PresentationProofHandler, \ - presentationExchangeExn from keri.vc.proving import credential -from keri.vdr import verifying, credentialing +from keri.vdr import credentialing, verifying -def test_issuing(seeder, mockCoringRandomNonce, mockHelpingNowIso8601): - """ Test Issuing ACDC """ - +def test_ipex(seeder, mockCoringRandomNonce, mockHelpingNowIso8601, mockHelpingNowUTC): + """ Test IPEX exchange protocol """ sidSalt = coring.Salter(raw=b'0123456789abcdef').qb64 assert sidSalt == '0AAwMTIzNDU2Nzg5YWJjZGVm' @@ -26,36 +22,17 @@ def test_issuing(seeder, mockCoringRandomNonce, mockHelpingNowIso8601): assert wanSalt == '0AB3YW5uLXRoZS13aXRuZXNz' with (habbing.openHby(name="red", base="test") as redHby, - habbing.openHby(name="sid", base="test", salt=sidSalt) as sidHby, - habbing.openHby(name="wan", base="test", salt=wanSalt) as wanHby): - - - # setup wan's Hab and doers - wanDoers = indirecting.setupWitness(alias="wan", - hby=wanHby, - tcpPort=5632, - httpPort=5642) - - wanHab = wanHby.habByName(name="wan") - wanPre = wanHab.pre - assert wanPre == 'BOigXdxpp1r43JhO--czUTwrCXzoWrIwW8i41KWDlr8s' - + habbing.openHby(name="sid", base="test", salt=sidSalt) as sidHby): seeder.seedSchema(redHby.db) seeder.seedSchema(sidHby.db) - seeder.seedSchema(wanHby.db) - limit = 1.0 - tock = 1.0 - doist = doing.Doist(limit=limit, tock=tock) - - sidHab = sidHby.makeHab(name="test", - wits=[wanHab.pre]) + sidHab = sidHby.makeHab(name="test") sidPre = sidHab.pre - assert sidPre == "EELPMtVeoAMwq-cEvyqQkPlVlHHj86nNxpb-77KcM3DZ" + assert sidPre == "EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3" - redKvy = eventing.Kevery(db=redHby.db) - redRgy = credentialing.Regery(hby=redHby, name="red", temp=True) - redVer = verifying.Verifier(hby=redHby, reger=redRgy.reger) + redHab = redHby.makeHab(name="test") + redPre = redHab.pre + assert redPre == "EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3" sidRgy = credentialing.Regery(hby=sidHby, name="bob", temp=True) sidVer = verifying.Verifier(hby=sidHby, reger=sidRgy.reger) @@ -65,12 +42,14 @@ def test_issuing(seeder, mockCoringRandomNonce, mockHelpingNowIso8601): rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() sidHab.interact(data=[rseal]) seqner = coring.Seqner(sn=sidHab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=sidHab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=sidHab.kever.serder.said)) sidRgy.processEscrows() - # Create Red's wallet and Issue Handler for receiving the credential - redIssueHandler = IssueHandler(hby=sidHby, rgy=sidRgy, notifier=notifier) - redExc = exchanging.Exchanger(db=sidHby.db, tymth=doist.tymen(), handlers=[redIssueHandler]) + sidExc = exchanging.Exchanger(hby=sidHby, handlers=[]) + protocoling.loadHandlers(hby=sidHby, exc=sidExc, notifier=notifier) schema = "EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC" @@ -81,201 +60,236 @@ def test_issuing(seeder, mockCoringRandomNonce, mockHelpingNowIso8601): dt="2021-06-27T21:26:21.233257+00:00", LEI="254900OPPU84GM83MG36", ) - _, d = scheming.Saider.saidify(sad=credSubject, code=coring.MtrDex.Blake3_256, label=scheming.Ids.d) + _, d = scheming.Saider.saidify(sad=credSubject, code=coring.MtrDex.Blake3_256, label=scheming.Saids.d) creder = credential(issuer=sidHab.pre, schema=schema, data=d, status=issuer.regk) - assert creder.said == "EIanW-Icbisj1noOeOJDfPIsIy0QZUB-smfTu0bOvN-a" + assert creder.said == "EDkftEwWBpohjTpemh_6xkaGNuoDsRU3qwvHdlvgfOyG" iss = issuer.issue(said=creder.said) + assert iss.raw == (b'{"v":"KERI10JSON0000ed_","t":"iss","d":"EK2WxcpF3oL1yqS3Z8i08WDYkHDcYhJL9afq' + b'dCIZjMy3","i":"EDkftEwWBpohjTpemh_6xkaGNuoDsRU3qwvHdlvgfOyG","s":"0","ri":"E' + b'O0_SyqPS1-EVYSITakYpUHaUZZpZGsjaXFOaO_kCfS4","dt":"2021-06-27T21:26:21.23325' + b'7+00:00"}') rseal = SealEvent(iss.pre, "0", iss.said)._asdict() sidHab.interact(data=[rseal]) seqner = coring.Seqner(sn=sidHab.kever.sn) - issuer.anchorMsg(pre=iss.pre, regd=iss.said, seqner=seqner, saider=sidHab.kever.serder.saider) + issuer.anchorMsg(pre=iss.pre, + regd=iss.said, + seqner=seqner, + saider=coring.Saider(qb64=sidHab.kever.serder.said)) sidRgy.processEscrows() - msg = signing.ratify(sidHab, serder=creder, pipelined=True) - assert msg == (b'{"v":"ACDC10JSON000197_","d":"EIanW-Icbisj1noOeOJDfPIsIy0QZUB-sm' - b'fTu0bOvN-a","i":"EELPMtVeoAMwq-cEvyqQkPlVlHHj86nNxpb-77KcM3DZ","' - b'ri":"EPzhcSAxNzgx-TgD_IJ59xJB7tAFCjIBWLzB9ZWesacD","s":"EMQWEcCn' - b'VRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC","a":{"d":"EOM45RCy4W3Kt6-U' - b'_oUhaK4SYvRp-9MbLwBmlkn-wY1_","dt":"2021-06-27T21:26:21.233257+0' - b'0:00","i":"EELPMtVeoAMwq-cEvyqQkPlVlHHj86nNxpb-77KcM3DZ","LEI":"' - b'254900OPPU84GM83MG36"}}-VA3-JAB6AABAAA--FABEELPMtVeoAMwq-cEvyqQk' - b'PlVlHHj86nNxpb-77KcM3DZ0AAAAAAAAAAAAAAAAAAAAAAAEELPMtVeoAMwq-cEv' - b'yqQkPlVlHHj86nNxpb-77KcM3DZ-AABAADx-hk7PsYCG3M5qyg2SZPV30BOpV2Wy' - b'7nVq7s90TlvrHnGA5KY9NNB25_Be1vyO7WKepIXD7LkGGG8sBNm1Q8B') - - # Create the `exn` message for issue credential - sidExcSrdr, atc = protocoling.credentialIssueExn(hab=sidHab, issuer=sidHab.pre, schema=creder.schema, - said=creder.said) - excMsg = bytearray(sidExcSrdr.raw) - excMsg.extend(atc) - # Parse the exn issue credential message on Red's side - - parsing.Parser().parse(ims=bytearray(msg), vry=sidVer) - - parsing.Parser().parse(ims=bytearray(msg), kvy=redKvy, exc=redExc, vry=redVer) - parsing.Parser().parse(ims=bytearray(excMsg), kvy=redKvy, exc=redExc) - doers = wanDoers + [redExc] - doist.do(doers=doers) - assert doist.tyme == limit - - ser = (b'{"v":"ACDC10JSON000197_","d":"EIanW-Icbisj1noOeOJDfPIsIy0QZUB-smfTu0bOvN-a",' - b'"i":"EELPMtVeoAMwq-cEvyqQkPlVlHHj86nNxpb-77KcM3DZ","ri":"EPzhcSAxNzgx-TgD_IJ' - b'59xJB7tAFCjIBWLzB9ZWesacD","s":"EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC' - b'","a":{"d":"EOM45RCy4W3Kt6-U_oUhaK4SYvRp-9MbLwBmlkn-wY1_","dt":"2021-06-27T2' - b'1:26:21.233257+00:00","i":"EELPMtVeoAMwq-cEvyqQkPlVlHHj86nNxpb-77KcM3DZ","LE' - b'I":"254900OPPU84GM83MG36"}}') - sig0 = (b'AADx-hk7PsYCG3M5qyg2SZPV30BOpV2Wy7nVq7s90TlvrHnGA5KY9NNB25_Be1vyO7WKepIXD7Lk' - b'GGG8sBNm1Q8B') - - # verify we can load serialized VC by SAID - creder, sadsigers, sadcigars = sidRgy.reger.cloneCred(said=creder.said) - assert creder.raw == ser - - # verify the signature - assert len(sadsigers) == 1 - (_, _, _, _, sigers) = sadsigers[0] - assert sigers[0].qb64b == sig0 - assert len(sadcigars) == 0 - - # verify we can look up credential by Schema SAID - schema = sidRgy.reger.schms.get(schema) - assert len(schema) == 1 - assert schema[0].qb64 == creder.said - - -def test_proving(seeder, mockCoringRandomNonce, mockHelpingNowIso8601): - sidSalt = coring.Salter(raw=b'0123456789abcdef').qb64 - hanSalt = coring.Salter(raw=b'abcdef0123456789').qb64 - vicSalt = coring.Salter(raw=b'fedcba9876543210').qb64 - - with habbing.openHby(name="han", base="test", salt=hanSalt) as hanHby, \ - habbing.openHby(name="sid", base="test", salt=sidSalt) as sidHby, \ - habbing.openHby(name="vic", base="test", salt=vicSalt) as vicHby: - limit = 1.0 - tock = 1.0 - doist = doing.Doist(limit=limit, tock=tock) - seeder.seedSchema(db=hanHby.db) - seeder.seedSchema(db=sidHby.db) - seeder.seedSchema(db=vicHby.db) - - # sidHab = habbing.Habitat(ks=sidKS, db=sidDB, salt=sidSalt, temp=True) - sidHab = sidHby.makeHab(name="test") - assert sidHab.pre == "EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3" - sidIcpMsg = sidHab.makeOwnInception() - - hanKvy = eventing.Kevery(db=hanHby.db) - parsing.Parser().parse(ims=bytearray(sidIcpMsg), kvy=hanKvy) - assert hanKvy.kevers[sidHab.pre].sn == 0 # accepted event - - # hanHab = habbing.Habitat(ks=hanKS, db=hanDB, salt=hanSalt, temp=True) - hanHab = hanHby.makeHab(name="test") - assert hanHab.pre == "EKiRAvVAoSwdTxOpHZZXojpY3RxVIYQffLUF7ITQDKT6" - hanIcpMsg = hanHab.makeOwnInception() - - vicKvy = eventing.Kevery(db=vicHby.db) - parsing.Parser().parse(ims=bytearray(hanIcpMsg), kvy=vicKvy) - assert vicKvy.kevers[hanHab.pre].sn == 0 # accepted event - - # vicHab = habbing.Habitat(ks=vicKS, db=vicDB, salt=vicSalt, temp=True) - vicHab = vicHby.makeHab(name="test") - assert vicHab.pre == "EFWujxD_N6DKo4Heaq-vSmv9a5RV09gbJUt68wBFIdAo" - vicIcpMsg = vicHab.makeOwnInception() - - parsing.Parser().parse(ims=bytearray(vicIcpMsg), kvy=hanKvy) - assert hanKvy.kevers[vicHab.pre].sn == 0 # accepted event - - schema = "EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC" - - hanReg = credentialing.Regery(hby=hanHby, name="han", temp=True) - issuer = hanReg.makeRegistry(prefix=hanHab.pre, name="han") - rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() - hanHab.interact(data=[rseal]) - seqner = coring.Seqner(sn=hanHab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=hanHab.kever.serder.saider) - hanReg.processEscrows() - - verifier = verifying.Verifier(hby=hanHby, reger=hanReg.reger) - - creder = credential(issuer=sidHab.pre, - schema=schema, - recipient=hanHab.pre, - data=dict( - LEI="254900OPPU84GM83MG36", - ), - status=issuer.regk, - ) - assert creder.said == "EEO1aft5aKWawxAIuN4_x0b2oeajvAikyR_w0sADoiXv" - - msg = signing.ratify(sidHab, serder=creder) - - iss = issuer.issue(said=creder.said) - rseal = SealEvent(iss.pre, "0", iss.said)._asdict() - hanHab.interact(data=[rseal]) - seqner = coring.Seqner(sn=hanHab.kever.sn) - issuer.anchorMsg(pre=iss.pre, regd=iss.said, seqner=seqner, saider=hanHab.kever.serder.saider) - hanReg.processEscrows() - - parsing.Parser().parse(ims=msg, vry=verifier) - - # verify we can load serialized VC by SAID - key = creder.said.encode("utf-8") - assert hanReg.reger.creds.get(key) is not None - - # Create Red's wallet and Issue Handler for receiving the credential - notifier = notifying.Notifier(hby=hanHby) - hanRequestHandler = PresentationRequestHandler(hby=hanHby, notifier=notifier) - hanPresentHandler = PresentationProofHandler(notifier=notifier) - hanExc = exchanging.Exchanger(db=hanHby.db, tymth=doist.tymen(), handlers=[hanRequestHandler, - hanPresentHandler]) - - # Create the issue credential payload - pl = dict( - s=schema - ) - - # Create the `exn` message for presentation request - vicExcSrdr = exchanging.exchange(route="/presentation/request", payload=pl) - excMsg = bytearray(vicExcSrdr.raw) - excMsg.extend(vicHab.endorse(vicExcSrdr, last=True)) - - # Parse the exn presentation request message on Han's side - parsing.Parser().parse(ims=bytearray(excMsg), kvy=hanKvy, exc=hanExc) - doist.do(doers=[hanExc]) - assert doist.tyme == limit - - resp = notifier.signaler.signals.popleft() - assert resp is not None - notifier.noter.rem(resp.rid) - - note = resp.attrs["note"] - a = note["a"] - assert a["schema"] == dict( - n=schema - ) - - exn, atc = presentationExchangeExn(hanHab, reger=hanReg.reger, said=creder.said) - assert exn.ked['r'] == "/presentation" - assert atc == bytearray(b'-HABEKiRAvVAoSwdTxOpHZZXojpY3RxVIYQffLUF7ITQDKT6-AABAADqyvceNUq0' - b'utmXQ6fFtE6juYK9B9lszFHgtM09FX5VCc5aESYM5lqgwHqOgaBjU11qfSMkIQ9K' - b'OrBRPNu_PMIP') - - msg = bytearray(exn.raw) - msg.extend(atc) - parsing.Parser().parse(ims=msg, kvy=hanKvy, exc=hanExc) - doist.do(doers=[hanExc]) - assert doist.tyme == limit * 2 - - resp = notifier.signaler.signals.popleft() - assert resp is not None - note = resp.attrs["note"] - a = note["a"] - assert a == {'credential': {'n': 'EEO1aft5aKWawxAIuN4_x0b2oeajvAikyR_w0sADoiXv'}, - 'issuer': {'i': 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3'}, - 'r': '/presentation', - 'schema': {'n': 'EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC'}} + msg = creder.raw + assert msg == (b'{"v":"ACDC10JSON000197_","d":"EDkftEwWBpohjTpemh_6xkaGNuoDsRU3qwvHdlvgfOyG",' + b'"i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","ri":"EO0_SyqPS1-EVYSITak' + b'YpUHaUZZpZGsjaXFOaO_kCfS4","s":"EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC' + b'","a":{"d":"EF2__B6DiLQHpdJZ_C0bddxy2o6nXIHEwchO9yylr3xx","dt":"2021-06-27T2' + b'1:26:21.233257+00:00","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","LE' + b'I":"254900OPPU84GM83MG36"}}') + atc = bytearray(msg) + atc.extend(coring.Counter(coring.CtrDex.SealSourceTriples, count=1).qb64b) + atc.extend(coring.Prefixer(qb64=iss.pre).qb64b) + atc.extend(coring.Seqner(sn=0).qb64b) + atc.extend(iss.saidb) + + assert atc == (b'{"v":"ACDC10JSON000197_","d":"EDkftEwWBpohjTpemh_6xkaGNuoDsRU3qw' + b'vHdlvgfOyG","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","' + b'ri":"EO0_SyqPS1-EVYSITakYpUHaUZZpZGsjaXFOaO_kCfS4","s":"EMQWEcCn' + b'VRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC","a":{"d":"EF2__B6DiLQHpdJZ' + b'_C0bddxy2o6nXIHEwchO9yylr3xx","dt":"2021-06-27T21:26:21.233257+0' + b'0:00","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","LEI":"' + b'254900OPPU84GM83MG36"}}-IABEDkftEwWBpohjTpemh_6xkaGNuoDsRU3qwvHd' + b'lvgfOyG0AAAAAAAAAAAAAAAAAAAAAAAEK2WxcpF3oL1yqS3Z8i08WDYkHDcYhJL9' + b'afqdCIZjMy3') + parsing.Parser().parseOne(ims=bytes(atc), vry=sidVer) + + # Successfully parsed credential is now saved in database. + assert sidVer.reger.saved.get(keys=(creder.said,)) is not None + + ipexhan = protocoling.IpexHandler(resource="/ipex/apply", hby=sidHby, notifier=notifier) + + apply0, apply0atc = protocoling.ipexApplyExn(sidHab, message="Please give me a credential", schema=schema, + recp=redPre, attrs={}) + + assert apply0.raw == (b'{"v":"KERI10JSON00016d_","t":"exn","d":"EI1MnUrT0aUprMN97FabgJdxVQtoCPqamVUp' + b'3iFgnDBE","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","p":"","dt":"20' + b'21-06-27T21:26:21.233257+00:00","r":"/ipex/apply","q":{},"a":{"m":"Please gi' + b've me a credential","s":"EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC","a":{' + b'},"i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3"},"e":{}}') + + # No requirements for apply, except that its first, no `p` + assert ipexhan.verify(serder=apply0) is True + + offer0, offer0atc = protocoling.ipexOfferExn(sidHab, "How about this", acdc=creder.raw, apply=apply0) + assert offer0.raw == (b'{"v":"KERI10JSON0002f0_","t":"exn","d":"EO_wiH5ZEikfLQb8rKBjPATnjiSOHGBvvN3m' + b'F0LDvaIC","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","p":"EI1MnUrT0a' + b'UprMN97FabgJdxVQtoCPqamVUp3iFgnDBE","dt":"2021-06-27T21:26:21.233257+00:00",' + b'"r":"/ipex/offer","q":{},"a":{"m":"How about this"},"e":{"acdc":{"v":"ACDC10' + b'JSON000197_","d":"EDkftEwWBpohjTpemh_6xkaGNuoDsRU3qwvHdlvgfOyG","i":"EIaGMMW' + b'JFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","ri":"EO0_SyqPS1-EVYSITakYpUHaUZZpZGs' + b'jaXFOaO_kCfS4","s":"EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC","a":{"d":"' + b'EF2__B6DiLQHpdJZ_C0bddxy2o6nXIHEwchO9yylr3xx","dt":"2021-06-27T21:26:21.2332' + b'57+00:00","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","LEI":"254900OP' + b'PU84GM83MG36"}},"d":"EOVRKHUAEjvfyWzQ8IL4icBiaVuy_CSTse_W_AssaAeE"}}') + + # This should fail because it is not first and the apply isn't persisted yet + assert ipexhan.verify(serder=offer0) is False + + # Now try to parse the offer before the apply, watch it fail + omsg = bytearray(offer0.raw) + omsg.extend(offer0atc) + + parsing.Parser().parse(ims=bytes(omsg), exc=sidExc) + + # Not saved because no apply + assert sidHby.db.exns.get(keys=(offer0.said,)) is None + + amsg = bytearray(apply0.raw) + amsg.extend(apply0atc) + + # Now parse both messages in order and both will save + parsing.Parser().parse(ims=amsg, exc=sidExc) + serder = sidHby.db.exns.get(keys=(apply0.said,)) + assert serder.ked == apply0.ked + parsing.Parser().parse(ims=omsg, exc=sidExc) + serder = sidHby.db.exns.get(keys=(offer0.said,)) + assert serder.ked == offer0.ked + + # Let's see if we can spurn a message we previously accepted. + spurn0, spurn0atc = protocoling.ipexSpurnExn(sidHab, "I reject you", spurned=apply0) + assert spurn0.raw == (b'{"v":"KERI10JSON00011d_","t":"exn","d":"EKvtmxPkOklgRNgWxLj-1ZW4Zb0MwZIUloWx' + b'A_dam95r","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","p":"EI1MnUrT0a' + b'UprMN97FabgJdxVQtoCPqamVUp3iFgnDBE","dt":"2021-06-27T21:26:21.233257+00:00",' + b'"r":"/ipex/spurn","q":{},"a":{"m":"I reject you"},"e":{}}') + + # This will fail, we've already responded with an offer + assert ipexhan.verify(spurn0) is False + + # Now lets try an offer without a pointer back to a reply + offer1, offer1atc = protocoling.ipexOfferExn(sidHab, "Here a credential offer", acdc=creder.raw) + assert offer1.raw == (b'{"v":"KERI10JSON0002cd_","t":"exn","d":"EMEmoi4k9gxWu4uZyYuEK3MvFPn-5B0LHnNx' + b'uQ4vRqRA","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","p":"","dt":"20' + b'21-06-27T21:26:21.233257+00:00","r":"/ipex/offer","q":{},"a":{"m":"Here a cr' + b'edential offer"},"e":{"acdc":{"v":"ACDC10JSON000197_","d":"EDkftEwWBpohjTpem' + b'h_6xkaGNuoDsRU3qwvHdlvgfOyG","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDj' + b'I3","ri":"EO0_SyqPS1-EVYSITakYpUHaUZZpZGsjaXFOaO_kCfS4","s":"EMQWEcCnVRk1hat' + b'TNyK3sIykYSrrFvafX3bHQ9Gkk1kC","a":{"d":"EF2__B6DiLQHpdJZ_C0bddxy2o6nXIHEwch' + b'O9yylr3xx","dt":"2021-06-27T21:26:21.233257+00:00","i":"EIaGMMWJFPmtXznY1IIi' + b'KDIrg-vIyge6mBl2QV8dDjI3","LEI":"254900OPPU84GM83MG36"}},"d":"EOVRKHUAEjvfyW' + b'zQ8IL4icBiaVuy_CSTse_W_AssaAeE"}}') + + # Will work because it is starting a new conversation + assert ipexhan.verify(serder=offer1) is True + + omsg = bytearray(offer1.raw) + omsg.extend(offer1atc) + parsing.Parser().parse(ims=omsg, exc=sidExc) + serder = sidHby.db.exns.get(keys=(offer1.said,)) + assert serder.ked == offer1.ked + + agree, argeeAtc = protocoling.ipexAgreeExn(sidHab, "I'll accept that offer", offer=offer0) + assert agree.raw == (b'{"v":"KERI10JSON000127_","t":"exn","d":"EGpJ9S0TqIVHkRmDsbgP59NC8ZLCaSUirslB' + b'KDeYKOR7","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","p":"EO_wiH5ZEi' + b'kfLQb8rKBjPATnjiSOHGBvvN3mF0LDvaIC","dt":"2021-06-27T21:26:21.233257+00:00",' + b'"r":"/ipex/agree","q":{},"a":{"m":"I\'ll accept that offer"},"e":{}}') + + # Can not create an agree without an offer, so this will pass since it has an offer that has no response + assert ipexhan.verify(serder=agree) is True + + amsg = bytearray(agree.raw) + amsg.extend(argeeAtc) + parsing.Parser().parse(ims=amsg, exc=sidExc) + serder = sidHby.db.exns.get(keys=(agree.said,)) + assert serder.ked == agree.ked + + # First try a bare grant (no prior agree) + anc = sidHab.makeOwnEvent(sn=2) + grant0, grant0atc = protocoling.ipexGrantExn(sidHab, message="Here's a credential", recp=sidHab.pre, + acdc=msg, iss=iss.raw, anc=anc) + assert grant0.raw == (b'{"v":"KERI10JSON000531_","t":"exn","d":"EJxM3em5fSpAIQsyXYovrr0UjblWLtmbTnFp' + b'xAUqnwG-","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","p":"","dt":"20' + b'21-06-27T21:26:21.233257+00:00","r":"/ipex/grant","q":{},"a":{"m":"Here\'' + b's a credential","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3"},"e":{"ac' + b'dc":{"v":"ACDC10JSON000197_","d":"EDkftEwWBpohjTpemh_6xkaGNuoDsRU3qwvHdlvgfO' + b'yG","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","ri":"EO0_SyqPS1-EVYS' + b'ITakYpUHaUZZpZGsjaXFOaO_kCfS4","s":"EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gk' + b'k1kC","a":{"d":"EF2__B6DiLQHpdJZ_C0bddxy2o6nXIHEwchO9yylr3xx","dt":"2021-06-' + b'27T21:26:21.233257+00:00","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3"' + b',"LEI":"254900OPPU84GM83MG36"}},"iss":{"v":"KERI10JSON0000ed_","t":"iss","d"' + b':"EK2WxcpF3oL1yqS3Z8i08WDYkHDcYhJL9afqdCIZjMy3","i":"EDkftEwWBpohjTpemh_6xka' + b'GNuoDsRU3qwvHdlvgfOyG","s":"0","ri":"EO0_SyqPS1-EVYSITakYpUHaUZZpZGsjaXFOaO_' + b'kCfS4","dt":"2021-06-27T21:26:21.233257+00:00"},"anc":{"v":"KERI10JSON00013a' + b'_","t":"ixn","d":"EOjAxp-AMLzicGz2h-DxvMK9kicajpZEwdN8-8k54hvz","i":"EIaGMMW' + b'JFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","s":"2","p":"EGKglEgIpdHuhuwl-IiSDG9x' + b'094gMrRxVaXGgXvCzCYM","a":[{"i":"EDkftEwWBpohjTpemh_6xkaGNuoDsRU3qwvHdlvgfOy' + b'G","s":"0","d":"EK2WxcpF3oL1yqS3Z8i08WDYkHDcYhJL9afqdCIZjMy3"}]},"d":"EI5mZX' + b'Z84Su4DrEUOxtl-NaUURQtTJeAn12xf146beg3"}}') + + assert ipexhan.verify(serder=grant0) is True + + # Lets save this bare offer so we can test full spurn workflow + gmsg = bytearray(grant0.raw) + gmsg.extend(grant0atc) + parsing.Parser().parse(ims=gmsg, exc=sidExc) + serder = sidHby.db.exns.get(keys=(grant0.said,)) + assert serder.ked == grant0.ked + + # Let's see if we can spurn a message we previously accepted. + spurn1, spurn1atc = protocoling.ipexSpurnExn(sidHab, "I reject you", spurned=grant0) + assert spurn1.raw == (b'{"v":"KERI10JSON00011d_","t":"exn","d":"EEs0bIGplWsjSOw5BMhAdFmgv-jm3-4nPgcK' + b'-LDv8tdB","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","p":"EJxM3em5fS' + b'pAIQsyXYovrr0UjblWLtmbTnFpxAUqnwG-","dt":"2021-06-27T21:26:21.233257+00:00",' + b'"r":"/ipex/spurn","q":{},"a":{"m":"I reject you"},"e":{}}') + smsg = bytearray(spurn1.raw) + smsg.extend(spurn1atc) + parsing.Parser().parse(ims=smsg, exc=sidExc) + serder = sidHby.db.exns.get(keys=(spurn1.said,)) + assert serder.ked == spurn1.ked # This credential grant has been spurned and not accepted into database + + # Now we'll run a grant pointing back to the agree all the way to the database + grant1, grant1atc = protocoling.ipexGrantExn(sidHab, message="Here's a credential", acdc=msg, iss=iss.raw, + recp=sidHab.pre, anc=anc, agree=agree) + assert grant1.raw == (b'{"v":"KERI10JSON00055d_","t":"exn","d":"EIqh-L9GnnVSdNLeqwmx-vpE9V1DvOQAlVWf' + b'wENpm8sW","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","p":"EGpJ9S0TqI' + b'VHkRmDsbgP59NC8ZLCaSUirslBKDeYKOR7","dt":"2021-06-27T21:26:21.233257+00:00",' + b'"r":"/ipex/grant","q":{},"a":{"m":"Here\'s a credential","i":"EIaGMMWJFPm' + b'tXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3"},"e":{"acdc":{"v":"ACDC10JSON000197_","d"' + b':"EDkftEwWBpohjTpemh_6xkaGNuoDsRU3qwvHdlvgfOyG","i":"EIaGMMWJFPmtXznY1IIiKDI' + b'rg-vIyge6mBl2QV8dDjI3","ri":"EO0_SyqPS1-EVYSITakYpUHaUZZpZGsjaXFOaO_kCfS4","' + b's":"EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC","a":{"d":"EF2__B6DiLQHpdJZ' + b'_C0bddxy2o6nXIHEwchO9yylr3xx","dt":"2021-06-27T21:26:21.233257+00:00","i":"E' + b'IaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","LEI":"254900OPPU84GM83MG36"}},' + b'"iss":{"v":"KERI10JSON0000ed_","t":"iss","d":"EK2WxcpF3oL1yqS3Z8i08WDYkHDcYh' + b'JL9afqdCIZjMy3","i":"EDkftEwWBpohjTpemh_6xkaGNuoDsRU3qwvHdlvgfOyG","s":"0","' + b'ri":"EO0_SyqPS1-EVYSITakYpUHaUZZpZGsjaXFOaO_kCfS4","dt":"2021-06-27T21:26:21' + b'.233257+00:00"},"anc":{"v":"KERI10JSON00013a_","t":"ixn","d":"EOjAxp-AMLzicG' + b'z2h-DxvMK9kicajpZEwdN8-8k54hvz","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8' + b'dDjI3","s":"2","p":"EGKglEgIpdHuhuwl-IiSDG9x094gMrRxVaXGgXvCzCYM","a":[{"i":' + b'"EDkftEwWBpohjTpemh_6xkaGNuoDsRU3qwvHdlvgfOyG","s":"0","d":"EK2WxcpF3oL1yqS3' + b'Z8i08WDYkHDcYhJL9afqdCIZjMy3"}]},"d":"EI5mZXZ84Su4DrEUOxtl-NaUURQtTJeAn12xf1' + b'46beg3"}}') + assert ipexhan.verify(serder=grant1) is True + + gmsg = bytearray(grant1.raw) + gmsg.extend(grant1atc) + parsing.Parser().parse(ims=gmsg, exc=sidExc) + serder = sidHby.db.exns.get(keys=(grant1.said,)) + assert serder.ked == grant1.ked + + # And now the last... admit the granted credential to complete the full flow + admit0, admit0atc = protocoling.ipexAdmitExn(sidHab, "Thanks for the credential", grant=grant1) + assert admit0.raw == (b'{"v":"KERI10JSON00012a_","t":"exn","d":"ELNz82kqV94vlbT7lJulVFWtf6_jhGRgH556' + b'Z-xYRaGY","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","p":"EIqh-L9Gnn' + b'VSdNLeqwmx-vpE9V1DvOQAlVWfwENpm8sW","dt":"2021-06-27T21:26:21.233257+00:00",' + b'"r":"/ipex/admit","q":{},"a":{"m":"Thanks for the credential"},"e":{}}') + assert ipexhan.verify(serder=admit0) is True + + amsg = bytearray(admit0.raw) + amsg.extend(admit0atc) + parsing.Parser().parse(ims=amsg, exc=sidExc) + serder = sidHby.db.exns.get(keys=(admit0.said,)) + assert serder.ked == admit0.ked diff --git a/tests/vc/test_proving.py b/tests/vc/test_proving.py index 0ba822af7..bdb273be1 100644 --- a/tests/vc/test_proving.py +++ b/tests/vc/test_proving.py @@ -6,15 +6,17 @@ import pytest from keri.app import habbing -from keri.core import coring, scheming, parsing +from keri.core import coring, scheming, parsing, serdering from keri.core.coring import Serials, Counter, CtrDex, Prefixer, Seqner, Diger, Siger from keri.core.scheming import CacheResolver from keri.kering import Versionage -from keri.vc.proving import Creder, credential +from keri.vc.proving import credential from keri.vdr import verifying, credentialing def test_proving(mockHelpingNowIso8601): + """Test credential proof with SerderACDC""" + sidSalt = coring.Salter(raw=b'0123456789abcdef').qb64 with habbing.openHby(name="sid", base="test", salt=sidSalt) as sidHby: @@ -64,7 +66,7 @@ def test_proving(mockHelpingNowIso8601): b'6mBl2QV8dDjI3-AABAAAmfpF4BjMS3b4kzvPdOpkSlH3PiVx7MSySulPyKFxtaS3' b'oxH45Y3kIvZg67u2DyxtUqVixVzRhOOTnMAB_SowI') - creder = Creder(raw=msg) + creder = serdering.SerderACDC(raw=msg) # Creder(raw=msg) proof = msg[creder.size:] ctr = Counter(qb64b=proof, strip=True) @@ -100,94 +102,107 @@ def test_proving(mockHelpingNowIso8601): siger = isigers[0] assert siger.verfer.verify(siger.raw, creder.raw) is True + """End Test""" + def test_credentialer(): + """Test SerderACDC as credential""" + with pytest.raises(ValueError): - Creder() + serdering.SerderACDC() # Creder() + sub = dict(a=123, b="abc", issuanceDate="2021-06-27T21:26:21.233257+00:00") d = dict( - v=coring.versify(ident=coring.Idents.acdc, kind=Serials.json, size=0), + v=coring.versify(proto=coring.Protos.acdc, kind=Serials.json, size=0), d="", + i="EF6maPM_d5ZN7U3NRFC1-6TM7k_E00_a8AG9YyLA4uWi", s="abc", - i="i", a=sub ) _, d = coring.Saider.saidify(sad=d) - said = 'EF6maPM_d5ZN7U3NRFC1-6TM7k_EKDz-8AG9YyLA4uWi' # creder.said + said = 'ENWScKaCtogzVvZfbDmvS3izq7bM7AOhHzjf-QL-VU5m' # creder.said - creder = Creder(ked=d) + creder = serdering.SerderACDC(sad=d) # Creder(ked=d) assert creder.said == said assert creder.kind == Serials.json - assert creder.issuer == "i" + assert creder.issuer == "EF6maPM_d5ZN7U3NRFC1-6TM7k_E00_a8AG9YyLA4uWi" assert creder.schema == "abc" - assert creder.subject == sub - assert creder.crd == d - assert creder.size == 168 + assert creder.attrib == sub + assert creder.sad == d + assert creder.size == 211 assert creder.size == len(creder.raw) - assert creder.raw == (b'{"v":"ACDC10JSON0000a8_","d":"EF6maPM_d5ZN7U3NRFC1-6TM7k_EKDz-8AG9YyLA4uWi",' - b'"s":"abc","i":"i","a":{"a":123,"b":"abc","issuanceDate":"2021-06-27T21:26:21' - b'.233257+00:00"}}') + assert creder.raw == (b'{"v":"ACDC10JSON0000d3_","d":"ENWScKaCtogzVvZfbDmvS3izq7bM7AOhHzjf-QL-VU5m",' + b'"i":"EF6maPM_d5ZN7U3NRFC1-6TM7k_E00_a8AG9YyLA4uWi","s":"abc","a":{"a":123,"b' + b'":"abc","issuanceDate":"2021-06-27T21:26:21.233257+00:00"}}') - raw1, idt1, knd1, ked1, ver1 = creder._exhale(ked=d) + raw1, ked1, knd1, ver1, knd1, size1 = creder._exhale(sad=d) assert raw1 == creder.raw assert knd1 == Serials.json assert ked1 == d assert ver1 == Versionage(major=1, minor=0) - creder = Creder(raw=raw1) + creder = serdering.SerderACDC(raw=raw1) # Creder(raw=raw1) assert creder.kind == Serials.json - assert creder.issuer == "i" - assert creder.crd == d - assert creder.size == 168 + assert creder.issuer == "EF6maPM_d5ZN7U3NRFC1-6TM7k_E00_a8AG9YyLA4uWi" + assert creder.sad == d + assert creder.size == 211 d2 = dict(d) - d2["v"] = coring.versify(ident=coring.Idents.acdc, kind=Serials.cbor, size=0) - creder = Creder(ked=d2) - assert creder.said == said # shouldnt this be different here? - assert creder.issuer == "i" + d2['d'] = "" + d2["v"] = coring.versify(proto=coring.Protos.acdc, kind=Serials.cbor, size=0) + _, d2 = coring.Saider.saidify(sad=d2) + + creder = serdering.SerderACDC(sad=d2) # Creder(ked=d2) + assert creder.said == "EJHxKgPiGfPmdH2EbybID30hXIl916ILZQgC3JOa0cvY" # shouldnt this be different here? + assert creder.issuer == "EF6maPM_d5ZN7U3NRFC1-6TM7k_E00_a8AG9YyLA4uWi" assert creder.schema == "abc" - assert creder.subject == sub - assert creder.size == 139 + assert creder.attrib == sub + assert creder.size == 183 assert creder.size == len(creder.raw) - assert creder.crd == d2 - assert creder.raw == (b'\xa5avqACDC10CBOR00008b_adx,EF6maPM_d5ZN7U3NRFC1-6TM7k_EKDz-8AG9YyLA4uWiasc' - b'abcaiaiaa\xa3aa\x18{abcabclissuanceDatex 2021-06-27T21:26:21.233257+00:00') + assert creder.sad == d2 + assert creder.raw == (b'\xa5avqACDC10CBOR0000b7_adx,EJHxKgPiGfPmdH2EbybID30hXIl916ILZQgC3JOa0cvYaix' + b',EF6maPM_d5ZN7U3NRFC1-6TM7k_E00_a8AG9YyLA4uWiascabcaa\xa3aa\x18{abcabcliss' + b'uanceDatex 2021-06-27T21:26:21.233257+00:00') raw2 = bytes(creder.raw) - creder = Creder(raw=raw2) - assert creder.said == said - assert creder.issuer == "i" + creder = serdering.SerderACDC(raw=raw2) # Creder(raw=raw2) + assert creder.said == "EJHxKgPiGfPmdH2EbybID30hXIl916ILZQgC3JOa0cvY" + assert creder.issuer == "EF6maPM_d5ZN7U3NRFC1-6TM7k_E00_a8AG9YyLA4uWi" assert creder.schema == "abc" - assert creder.subject == sub - assert creder.size == 139 + assert creder.attrib == sub + assert creder.size == 183 assert creder.size == len(creder.raw) - assert creder.crd == d2 + assert creder.sad == d2 d3 = dict(d) - d3["v"] = coring.versify(ident=coring.Idents.acdc, kind=Serials.mgpk, size=0) - creder = Creder(ked=d3) + d3["v"] = coring.versify(proto=coring.Protos.acdc, kind=Serials.mgpk, size=0) + _, d3 = coring.Saider.saidify(sad=d3) + creder = serdering.SerderACDC(sad=d3) # Creder(ked=d3) - assert creder.said == said # shouldn't this be different here - assert creder.issuer == "i" + assert creder.said == "EMZeK1yLZd1JV6Ktdq_YUt-YbyoTWB9UMcFzuiDly2Y6" + assert creder.issuer == "EF6maPM_d5ZN7U3NRFC1-6TM7k_E00_a8AG9YyLA4uWi" assert creder.schema == "abc" - assert creder.subject == sub - assert creder.size == 138 + assert creder.attrib == sub + assert creder.size == 182 assert creder.size == len(creder.raw) - assert creder.crd == d3 - assert creder.raw == (b'\x85\xa1v\xb1ACDC10MGPK00008a_\xa1d\xd9,EF6maPM_d5ZN7U3NRFC1-6TM7k_EKDz-8AG' - b'9YyLA4uWi\xa1s\xa3abc\xa1i\xa1i\xa1a\x83\xa1a{\xa1b\xa3abc\xacissuanceDate' - b'\xd9 2021-06-27T21:26:21.233257+00:00') + assert creder.sad == d3 + assert creder.raw == (b'\x85\xa1v\xb1ACDC10MGPK0000b6_\xa1d\xd9,EMZeK1yLZd1JV6Ktdq_YUt-YbyoTWB9UMcF' + b'zuiDly2Y6\xa1i\xd9,EF6maPM_d5ZN7U3NRFC1-6TM7k_E00_a8AG9YyLA4uWi\xa1s\xa3' + b'abc\xa1a\x83\xa1a{\xa1b\xa3abc\xacissuanceDate\xd9 2021-06-27T21:26:21.23' + b'3257+00:00') raw3 = bytes(creder.raw) - creder = Creder(raw=raw3) - assert creder.said == said - assert creder.issuer == "i" + creder = serdering.SerderACDC(raw=raw3) + assert creder.said == "EMZeK1yLZd1JV6Ktdq_YUt-YbyoTWB9UMcFzuiDly2Y6" + assert creder.issuer == "EF6maPM_d5ZN7U3NRFC1-6TM7k_E00_a8AG9YyLA4uWi" assert creder.schema == "abc" - assert creder.subject == sub - assert creder.size == 138 + assert creder.attrib == sub + assert creder.size == 182 assert creder.size == len(creder.raw) - assert creder.crd == d3 + assert creder.sad == d3 + + """End Test""" def test_credential(mockHelpingNowIso8601): @@ -203,7 +218,7 @@ def test_credential(mockHelpingNowIso8601): dict(qualifiedvLEIIssuervLEICredential="EGtyThM1rLBSMZ_ozM1uAnFvSfC0N1jaQ42aKU5sHYTGFD") ] - saider = coring.Saider(sad=d, code=coring.MtrDex.Blake3_256, label=scheming.Ids.d) + saider = coring.Saider(sad=d, code=coring.MtrDex.Blake3_256, label=scheming.Saids.d) assert saider.qb64 == 'EM_S2MdMaKgP6P2Yyno6-flV6GqrwPencTIw8tCMR7iB' d["i"] = saider.qb64 @@ -234,20 +249,21 @@ def test_privacy_preserving_credential(mockHelpingNowIso8601): recipient="EM_S2MdMaKgP6P2Yyno6-flV6GqrwPencTIw8tCMR7iB", private=True, salt=salt, - issuer="EYNHFK056fqNSG_MDE7d_Eqk0bazefvd4eeQLMPPNBnM", + issuer="EMZeK1yLZd1JV6Ktdq_YUt-YbyoTWB9UMcFzuiDly2Y6", data=d, status="ETQoH02zJRCTNz-Wl3nnkUD_RVSzSwcoNvmfa18AWt3M") assert cred.size == len(cred.raw) - assert "u" in cred.ked + assert "u" in cred.sad print(cred.raw) - assert cred.raw == (b'{"v":"ACDC10JSON00021c_","d":"EKwDvF8_PWMlw7X1Lb1fiilLIMiK4yTXUdmBasZWb0sF",' - b'"u":"0AAwMTIzNDU2Nzg5YWJjZGVm","i":"EYNHFK056fqNSG_MDE7d_Eqk0bazefvd4eeQLMPP' - b'NBnM","ri":"ETQoH02zJRCTNz-Wl3nnkUD_RVSzSwcoNvmfa18AWt3M","s":"EZllThM1rLBSM' + assert cred.raw == (b'{"v":"ACDC10JSON00021c_","d":"ELFOCm58xUlId994cS6m6bsfYOkNHEKoe15Cav-Sj8__",' + b'"u":"0AAwMTIzNDU2Nzg5YWJjZGVm","i":"EMZeK1yLZd1JV6Ktdq_YUt-YbyoTWB9UMcFzuiDl' + b'y2Y6","ri":"ETQoH02zJRCTNz-Wl3nnkUD_RVSzSwcoNvmfa18AWt3M","s":"EZllThM1rLBSM' b'Z_ozM1uAnFvSfC0N1jaQ42aKU5sCZ5Q","a":{"d":"EFwWs1d_fe_VeLZ0vQQKO-gkRvGrpfWAR' b'bI4e9tzcqlV","u":"0AAwMTIzNDU2Nzg5YWJjZGVm","i":"EM_S2MdMaKgP6P2Yyno6-flV6Gq' b'rwPencTIw8tCMR7iB","dt":"2021-06-27T21:26:21.233257+00:00","LEI":"254900OPPU' b'84GM83MG36","personLegalName":"John Doe","engagementContextRole":"Project Ma' b'nager"}}') + """End Test""" def test_credential_parsator(): @@ -267,7 +283,11 @@ def test_credential_parsator(): data=credSubject, status=issuer.regk) - msg = hab.endorse(serder=creder) + msg = bytearray(creder.raw) + msg.extend(coring.Counter(coring.CtrDex.SealSourceTriples, count=1).qb64b) + msg.extend(hab.kever.prefixer.qb64b) + msg.extend(coring.Seqner(sn=hab.kever.sn).qb64b) + msg.extend(hab.kever.serder.said.encode("utf-8")) verifier = verifying.Verifier(hby=hby) parsing.Parser().parse(ims=msg, vry=verifier) @@ -278,6 +298,8 @@ def test_credential_parsator(): q = cue["q"] assert q["ri"] == issuer.regk + """End Test""" + if __name__ == '__main__': test_proving() diff --git a/tests/vc/test_walleting.py b/tests/vc/test_walleting.py index 98b442f75..2f1c55f23 100644 --- a/tests/vc/test_walleting.py +++ b/tests/vc/test_walleting.py @@ -30,7 +30,10 @@ def test_wallet(seeder, mockCoringRandomNonce, mockHelpingNowIso8601): rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() sidHab.interact(data=[rseal]) seqner = coring.Seqner(sn=sidHab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=sidHab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=sidHab.kever.serder.said)) sidReg.processEscrows() creder = credential(issuer=sidHab.pre, @@ -44,20 +47,27 @@ def test_wallet(seeder, mockCoringRandomNonce, mockHelpingNowIso8601): rseal = SealEvent(iss.pre, "0", iss.said)._asdict() sidHab.interact(data=[rseal]) seqner = coring.Seqner(sn=sidHab.kever.sn) - issuer.anchorMsg(pre=iss.pre, regd=iss.said, seqner=seqner, saider=sidHab.kever.serder.saider) + issuer.anchorMsg(pre=iss.pre, + regd=iss.said, + seqner=seqner, + saider=coring.Saider(qb64=sidHab.kever.serder.said)) sidReg.processEscrows() - msg = signing.ratify(sidHab, serder=creder) + msg = bytearray(creder.raw) + msg.extend(coring.Counter(coring.CtrDex.SealSourceTriples, count=1).qb64b) + msg.extend(coring.Prefixer(qb64=iss.pre).qb64b) + msg.extend(coring.Seqner(sn=0).qb64b) + msg.extend(iss.saidb) + assert msg == (b'{"v":"ACDC10JSON000197_","d":"EOavcpdGvk4sTXjOQiNxHeNf3HYMjMINMh' b'ar4R5a3OfB","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","' b'ri":"EO0_SyqPS1-EVYSITakYpUHaUZZpZGsjaXFOaO_kCfS4","s":"EMQWEcCn' b'VRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC","a":{"d":"EFKsAdq9CZF_w9yv' b'ia8RiRdDeXLMjR6q7Lp7FKKIgJx-","i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIy' b'ge6mBl2QV8dDjI3","dt":"2021-06-27T21:26:21.233257+00:00","LEI":"' - b'254900OPPU84GM83MG36"}}-JAB6AABAAA--FABEIaGMMWJFPmtXznY1IIiKDIrg' - b'-vIyge6mBl2QV8dDjI30AAAAAAAAAAAAAAAAAAAAAAAEIaGMMWJFPmtXznY1IIiK' - b'DIrg-vIyge6mBl2QV8dDjI3-AABAABrPJSGOU5oUGZjYHvgTo6dblTHX0yNq-SAC' - b'Uc3mgg68RspLkw2rCmXGpZuxnKN0spAzU3Wj0UN2C98Yrab1uYA') + b'254900OPPU84GM83MG36"}}-IABEOavcpdGvk4sTXjOQiNxHeNf3HYMjMINMhar4' + b'R5a3OfB0AAAAAAAAAAAAAAAAAAAAAAAEIMoFDXHR3cNF0fADC5nLPme34n-ZsMEu' + b'n6eDFvN8Jgc') ser = (b'{"v":"ACDC10JSON000197_","d":"EOavcpdGvk4sTXjOQiNxHeNf3HYMjMINMhar4R5a3OfB",' b'"i":"EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3","ri":"EO0_SyqPS1-EVYSITak' @@ -66,22 +76,8 @@ def test_wallet(seeder, mockCoringRandomNonce, mockHelpingNowIso8601): b'znY1IIiKDIrg-vIyge6mBl2QV8dDjI3","dt":"2021-06-27T21:26:21.233257+00:00","LE' b'I":"254900OPPU84GM83MG36"}}') - sig0 = (b'AABrPJSGOU5oUGZjYHvgTo6dblTHX0yNq-SACUc3mgg68RspLkw2rCmXGpZuxnKN0spAzU3Wj0UN' - b'2C98Yrab1uYA') - parsing.Parser().parse(ims=msg, vry=verifier) # verify we can load serialized VC by SAID - creder, sadsigers, sadcigars = verifier.reger.cloneCred(said=creder.said) + creder, *_ = verifier.reger.cloneCred(said=creder.said) assert creder.raw == ser - - # verify the signature - assert len(sadsigers) == 1 - (_, _, _, _, sigers) = sadsigers[0] - assert sigers[0].qb64b == sig0 - assert len(sadcigars) == 0 - - # verify we can look up credential by Schema SAID - schema = verifier.reger.schms.get(schema) - assert len(schema) == 1 - assert schema[0].qb64 == creder.said diff --git a/tests/vdr/test_eventing.py b/tests/vdr/test_eventing.py index 7191bdb44..3da4f055c 100644 --- a/tests/vdr/test_eventing.py +++ b/tests/vdr/test_eventing.py @@ -6,9 +6,9 @@ import pytest from keri.app import habbing, keeping -from keri.core import coring +from keri.core import coring, serdering from keri.core import eventing as keventing -from keri.core.coring import versify, Serials, Ilks, MtrDex, Prefixer, Serder, Signer, Seqner +from keri.core.coring import versify, Serials, Ilks, MtrDex, Prefixer, Signer, Seqner, Saider from keri.db import basing from keri.db.dbing import snKey, dgKey from keri.kering import Version, EmptyMaterialError, DerivationError, MissingAnchorError, ValidationError, \ @@ -304,19 +304,19 @@ def test_backer_issue_revoke(mockHelpingNowUTC): dig = "EC2L3ycqK9645aEeQKP941xojSiuiHsw4Y6yTW-PmsBg" serder = backerIssue(vcdig=vcdig, regk=regk, regsn=sn, regd=regd) - assert serder.raw == (b'{"v":"KERI10JSON000160_","t":"bis","d":"EM2zBvc6fL7BvcRT-OhPR7dgja5p4Kc1G3l3' - b'5Plb0CYk","i":"DAtNTPnDFBnmlO6J44LXCrzZTAmpe-82b7BmQGtL4QhM","ii":"EE3Xv6CWw' - b'EMpW-99rhPD9IHFCR2LN5ienLVI8yG5faBw","s":"0","ra":{"i":"EE3Xv6CWwEMpW-99rhPD' - b'9IHFCR2LN5ienLVI8yG5faBw","s":3,"d":"EBpq06UecHwzy-K9FpNoRxCJp2wIGM9u2Edk-PL' - b'MZ1H4"},"dt":"2021-01-01T00:00:00.000000+00:00"}') + assert serder.raw == (b'{"v":"KERI10JSON000162_","t":"bis","d":"EK9X5Ih5z68pKA-dHMuEZXt_2avkzM8i1_gD' + b'KlFBGDM7","i":"DAtNTPnDFBnmlO6J44LXCrzZTAmpe-82b7BmQGtL4QhM","ii":"EE3Xv6CWw' + b'EMpW-99rhPD9IHFCR2LN5ienLVI8yG5faBw","s":"0","ra":{"i":"EE3Xv6CWwEMpW-99rhPD' + b'9IHFCR2LN5ienLVI8yG5faBw","s":"3","d":"EBpq06UecHwzy-K9FpNoRxCJp2wIGM9u2Edk-' + b'PLMZ1H4"},"dt":"2021-01-01T00:00:00.000000+00:00"}') serder = backerRevoke(vcdig=vcdig, regk=regk, regsn=sn, regd=regd, dig=dig) - assert serder.raw == (b'{"v":"KERI10JSON00015f_","t":"brv","d":"EAIZZ8ujQQl4XGMh8XPzxokkzqrWh8M6Ftxq' - b'kezbVtDu","i":"DAtNTPnDFBnmlO6J44LXCrzZTAmpe-82b7BmQGtL4QhM","s":"1","p":"EC' - b'2L3ycqK9645aEeQKP941xojSiuiHsw4Y6yTW-PmsBg","ra":{"i":"EE3Xv6CWwEMpW-99rhPD9' - b'IHFCR2LN5ienLVI8yG5faBw","s":3,"d":"EBpq06UecHwzy-K9FpNoRxCJp2wIGM9u2Edk-PLM' - b'Z1H4"},"dt":"2021-01-01T00:00:00.000000+00:00"}') + assert serder.raw == (b'{"v":"KERI10JSON000161_","t":"brv","d":"EMBHVoEIM4GfoLtelLD6erwNLyO39PUyEAcC' + b'-N77OGoq","i":"DAtNTPnDFBnmlO6J44LXCrzZTAmpe-82b7BmQGtL4QhM","s":"1","p":"EC' + b'2L3ycqK9645aEeQKP941xojSiuiHsw4Y6yTW-PmsBg","ra":{"i":"EE3Xv6CWwEMpW-99rhPD9' + b'IHFCR2LN5ienLVI8yG5faBw","s":"3","d":"EBpq06UecHwzy-K9FpNoRxCJp2wIGM9u2Edk-P' + b'LMZ1H4"},"dt":"2021-01-01T00:00:00.000000+00:00"}') """ End Test """ @@ -330,42 +330,48 @@ def test_prefixer(): prefixer = Prefixer() # vcp, backers allowed no backers + # ["v", "d", "i", "s", "t", "bt", "b", "c"] ked = dict(v=vs, - i="", + d="", # qb64 SAID + i="", # qb64 pre ii=pre, s="{:x}".format(0), t=Ilks.vcp, + bt=0, + b=[], c=[], - b=[] ) prefixer = Prefixer(ked=ked, code=MtrDex.Blake3_256) - assert prefixer.qb64 == 'ENJD01FI0RNNgoECWBacDCe50QDiJ4zSECcm4lGz8hBK' + assert prefixer.qb64 == 'EDj0Kq4tFBNGKxpdfX2nCfIvYJ-v1MJ24H1dsPfUqzmB' assert prefixer.verify(ked=ked) is True assert prefixer.verify(ked=ked, prefixed=True) is False # Invalid event type + #["v", "i", "s", "t", "ri", "dt"] ked = dict(v=vs, + d="", # qb64 SAID i="", - ii=pre, s="{:x}".format(0), t=Ilks.iss, - c=[], - b=[] + ri="", + dt="", ) - with pytest.raises(DerivationError): - prefixer = Prefixer(ked=ked, code=MtrDex.Blake3_256) + #with pytest.raises(DerivationError): + prefixer = Prefixer(ked=ked, code=MtrDex.Blake3_256) # vcp, no backers allowed ked = dict(v=vs, + d="", # qb64 SAID i="", ii=pre, s="{:x}".format(0), t=Ilks.vcp, + bt=0, + b=[], c=[keventing.TraitDex.NoBackers], - b=[] ) prefixer = Prefixer(ked=ked, code=MtrDex.Blake3_256) - assert prefixer.qb64 == 'ENqkr-pqrRYGOTUSj2X4_bs8_nQYniBQjyGM2nW1eH7w' + assert prefixer.qb64 == 'EDz0QmMxf4Dk0C9uiP-y3okN-Bej2IAXSj8UwQgb3NsL' assert prefixer.verify(ked=ked) is True assert prefixer.verify(ked=ked, prefixed=True) is False @@ -375,29 +381,33 @@ def test_prefixer(): # vcp, one backer ked = dict(v=vs, + d="", # qb64 SAID i="", ii=pre, s="{:x}".format(0), t=Ilks.vcp, + bt=1, + b=[bak1], c=[], - b=[bak1] ) prefixer = Prefixer(ked=ked, code=MtrDex.Blake3_256) - assert prefixer.qb64 == 'EDscp4TbmnCWqylvH_JPBiv_MRgNz3kkeOIiFQ_PqcZk' + assert prefixer.qb64 == 'EIDRsRwJQNw2ujTeoztpEPxN6XRmBB2bnxWlOzJ9OQHk' assert prefixer.verify(ked=ked) is True assert prefixer.verify(ked=ked, prefixed=True) is False # vcp, many backers ked = dict(v=vs, + d="", # qb64 SAID i="", ii=pre, s="{:x}".format(0), t=Ilks.vcp, + bt=2, + b=[bak1, bak2, bak3], c=[], - b=[bak1, bak2, bak3] ) prefixer = Prefixer(ked=ked, code=MtrDex.Blake3_256) - assert prefixer.qb64 == 'ECpnvP-0OByIB9Xk7BhOpaNrzM-XVx5v1orUQYnm55rB' + assert prefixer.qb64 == 'EMsbtrSOa_lNwDMhpdXphmbItPBcaB0qSboopPo9Ub-s' assert prefixer.verify(ked=ked) is True assert prefixer.verify(ked=ked, prefixed=True) is False @@ -465,16 +475,17 @@ def test_tever_escrow(mockCoringRandomNonce): regk = vcp.pre # successfully anchor to a rotation event - rseal = keventing.SealEvent(regk, vcp.ked["s"], vcp.saider.qb64) + rseal = keventing.SealEvent(regk, vcp.ked["s"], vcp.said) rot = hab.rotate(data=[rseal._asdict()]) - rotser = Serder(raw=rot) + rotser = serdering.SerderKERI(raw=rot) seqner = Seqner(sn=int(rotser.ked["s"], 16)) - diger = rotser.saider + #diger = rotser.saider + saider = Saider(qb64=rotser.said) with pytest.raises(MissingWitnessSignatureError): - Tever(serder=vcp, seqner=seqner, saider=diger, db=db, reger=reg) + Tever(serder=vcp, seqner=seqner, saider=saider, db=db, reger=reg) dgkey = dgKey(pre=regk, dig=vcp.said) vcp = reg.getTvt(dgkey) @@ -505,15 +516,16 @@ def test_tever_no_backers(mockHelpingNowUTC, mockCoringRandomNonce): regk = vcp.pre # successfully anchor to a rotation event - rseal = keventing.SealEvent(i=regk, s=vcp.ked["s"], d=vcp.saider.qb64) + rseal = keventing.SealEvent(i=regk, s=vcp.ked["s"], d=vcp.said) rot = hab.rotate(data=[rseal._asdict()]) - rotser = Serder(raw=rot) + rotser = serdering.SerderKERI(raw=rot) seqner = Seqner(sn=int(rotser.ked["s"], 16)) - diger = rotser.saider + #diger = rotser.saider + saider = Saider(qb64=rotser.said) - tev = Tever(serder=vcp, seqner=seqner, saider=diger, db=db, reger=reg) + tev = Tever(serder=vcp, seqner=seqner, saider=saider, db=db, reger=reg) assert tev.prefixer.qb64 == vcp.pre assert tev.sn == 0 @@ -532,28 +544,30 @@ def test_tever_no_backers(mockHelpingNowUTC, mockCoringRandomNonce): # try to rotate a backerless registry vrt = eventing.rotate(regk, dig=vcp.said) - rseal = keventing.SealEvent(regk, vrt.ked["s"], vrt.saider.qb64) + rseal = keventing.SealEvent(regk, vrt.ked["s"], vrt.said) rot = hab.rotate(data=[rseal._asdict()]) - rotser = Serder(raw=rot) + rotser = serdering.SerderKERI(raw=rot) seqner = Seqner(sn=int(rotser.ked["s"], 16)) - diger = rotser.saider + #diger = rotser.saider + saider = Saider(qb64=rotser.said) # should raise validation err because rotation is not supported with pytest.raises(ValidationError): - tev.update(serder=vrt, seqner=seqner, saider=diger) + tev.update(serder=vrt, seqner=seqner, saider=saider) vcdig = b'EEBp64Aw2rsjdJpAR0e2qCq3jX7q7gLld3LjAwZgaLXU' iss = eventing.issue(vcdig=vcdig.decode("utf-8"), regk=regk) # successfully anchor to a rotation event - rseal = keventing.SealEvent(iss.ked["i"], iss.ked["s"], iss.saider.qb64) + rseal = keventing.SealEvent(iss.ked["i"], iss.ked["s"], iss.said) rot = hab.rotate(data=[rseal._asdict()]) - rotser = Serder(raw=rot) + rotser = serdering.SerderKERI(raw=rot) seqner = Seqner(sn=int(rotser.ked["s"], 16)) - diger = rotser.saider + #diger = rotser.saider + saider = Saider(qb64=rotser.said) - tev.update(iss, seqner=seqner, saider=diger) + tev.update(iss, seqner=seqner, saider=saider) vci = vcdig dgkey = dgKey(pre=vci, dig=iss.said) @@ -567,13 +581,14 @@ def test_tever_no_backers(mockHelpingNowUTC, mockCoringRandomNonce): rev = eventing.revoke(vcdig=vcdig.decode("utf-8"), regk=regk, dig=iss.said) # successfully anchor to a rotation event - rseal = keventing.SealEvent(rev.ked["i"], rev.ked["s"], rev.saider.qb64) + rseal = keventing.SealEvent(rev.ked["i"], rev.ked["s"], rev.said) rot = hab.rotate(data=[rseal._asdict()]) - rotser = Serder(raw=rot) + rotser = serdering.SerderKERI(raw=rot) seqner = Seqner(sn=int(rotser.ked["s"], 16)) - diger = rotser.saider + #diger = rotser.saider + saider = Saider(qb64=rotser.said) - tev.update(rev, seqner=seqner, saider=diger) + tev.update(rev, seqner=seqner, saider=saider) dgkey = dgKey(pre=vci, dig=rev.said) assert bytes(reg.getTvt(dgkey)) == (b'{"v":"KERI10JSON000120_","t":"rev","d":"EGjtu2bIII28dwxA0BH8KeXCN03U7TN3SkLD' b'1KZi77pj","i":"EEBp64Aw2rsjdJpAR0e2qCq3jX7q7gLld3LjAwZgaLXU","s":"1","ri":"E' @@ -604,15 +619,16 @@ def test_tever_backers(mockHelpingNowUTC, mockCoringRandomNonce): valCigar = valSigner.sign(ser=vcp.raw, index=0) # successfully anchor to a rotation event - rseal = keventing.SealEvent(i=regk, s=vcp.ked["s"], d=vcp.saider.qb64) + rseal = keventing.SealEvent(i=regk, s=vcp.ked["s"], d=vcp.said) rot = hab.rotate(data=[rseal._asdict()]) - rotser = Serder(raw=rot) + rotser = serdering.SerderKERI(raw=rot) seqner = Seqner(sn=int(rotser.ked["s"], 16)) - diger = rotser.saider + #diger = rotser.saider + saider = Saider(qb64=rotser.said) - tev = Tever(serder=vcp, seqner=seqner, saider=diger, bigers=[valCigar], db=db, reger=reg) + tev = Tever(serder=vcp, seqner=seqner, saider=saider, bigers=[valCigar], db=db, reger=reg) dgkey = dgKey(pre=regk, dig=vcp.said) assert bytes(reg.getTvt(dgkey)) == (b'{"v":"KERI10JSON00013d_","t":"vcp","d":"EBgdJt_ASWeq7HjOmut2E8vQL8P1c9VTPDA0' @@ -639,13 +655,14 @@ def test_tever_backers(mockHelpingNowUTC, mockCoringRandomNonce): debCigar = debSigner.sign(ser=vrt.raw, index=1) # successfully anchor to a rotation event - rseal = keventing.SealEvent(regk, vrt.ked["s"], vrt.saider.qb64) + rseal = keventing.SealEvent(regk, vrt.ked["s"], vrt.said) rot = hab.rotate(data=[rseal._asdict()]) - rotser = Serder(raw=rot) + rotser = serdering.SerderKERI(raw=rot) seqner = Seqner(sn=int(rotser.ked["s"], 16)) - diger = rotser.saider + #diger = rotser.saider + saider = Saider(qb64=rotser.said) - tev.update(serder=vrt, seqner=seqner, saider=diger, bigers=[valCigar, debCigar]) + tev.update(serder=vrt, seqner=seqner, saider=saider, bigers=[valCigar, debCigar]) assert tev.baks == ['BPmRWtx8nwSzRdJ0zTvP5uBb0t3BSjjstDk0gTayFfjV', 'BJLT5kDB54CewL9oqnWdPBC5vxZV30u3i6o9HVcWMhZd'] @@ -657,21 +674,22 @@ def test_tever_backers(mockHelpingNowUTC, mockCoringRandomNonce): debCigar = debSigner.sign(ser=bis.raw, index=1) # successfully anchor to a rotation event - rseal = keventing.SealEvent(bis.ked["i"], bis.ked["s"], bis.saider.qb64) + rseal = keventing.SealEvent(bis.ked["i"], bis.ked["s"], bis.said) rot = hab.rotate(data=[rseal._asdict()]) - rotser = Serder(raw=rot) + rotser = serdering.SerderKERI(raw=rot) seqner = Seqner(sn=int(rotser.ked["s"], 16)) - diger = rotser.saider + #diger = rotser.saider + saider = Saider(qb64=rotser.said) - tev.update(bis, seqner=seqner, saider=diger, bigers=[valCigar, debCigar]) + tev.update(bis, seqner=seqner, saider=saider, bigers=[valCigar, debCigar]) vci = vcdig dgkey = dgKey(pre=vci, dig=bis.said) - assert bytes(reg.getTvt(dgkey)) == (b'{"v":"KERI10JSON000160_","t":"bis","d":"EJkHLOtyOkkQYkgyZPeVq-QjvdtMYtqnpKoL' - b'LklRpdir","i":"EEBp64Aw2rsjdJpAR0e2qCq3jX7q7gLld3LjAwZgaLXU","ii":"EBgdJt_AS' - b'Weq7HjOmut2E8vQL8P1c9VTPDA0Pdh4KsZX","s":"0","ra":{"i":"EBgdJt_ASWeq7HjOmut2' - b'E8vQL8P1c9VTPDA0Pdh4KsZX","s":1,"d":"EEc1UURU3liIomWOFeDVqCo-3QbZHFZCUorx8Md' - b'ZFZvy"},"dt":"2021-01-01T00:00:00.000000+00:00"}') + assert bytes(reg.getTvt(dgkey)) == (b'{"v":"KERI10JSON000162_","t":"bis","d":"EN01O_jV46iSPtJMFXLNB83OWHtUl0wEDnMT' + b'eHSWXJwf","i":"EEBp64Aw2rsjdJpAR0e2qCq3jX7q7gLld3LjAwZgaLXU","ii":"EBgdJt_AS' + b'Weq7HjOmut2E8vQL8P1c9VTPDA0Pdh4KsZX","s":"0","ra":{"i":"EBgdJt_ASWeq7HjOmut2' + b'E8vQL8P1c9VTPDA0Pdh4KsZX","s":"1","d":"EEc1UURU3liIomWOFeDVqCo-3QbZHFZCUorx8' + b'MdZFZvy"},"dt":"2021-01-01T00:00:00.000000+00:00"}') def test_tevery(): @@ -686,17 +704,18 @@ def test_tevery(): regk = vcp.pre # successfully anchor to a rotation event - rseal = keventing.SealEvent(i=regk, s=vcp.ked["s"], d=vcp.saider.qb64) + rseal = keventing.SealEvent(i=regk, s=vcp.ked["s"], d=vcp.said) rot = hab.rotate(data=[rseal._asdict()]) - rotser = Serder(raw=rot) + rotser = serdering.SerderKERI(raw=rot) seqner = Seqner(sn=int(rotser.ked["s"], 16)) - diger = rotser.saider + #diger = rotser.saider + saider = Saider(qb64=rotser.said) tvy = Tevery(reger=reg, db=db) - tvy.processEvent(serder=vcp, seqner=seqner, saider=diger) + tvy.processEvent(serder=vcp, seqner=seqner, saider=saider) assert regk in tvy.tevers tev = tvy.tevers[regk] @@ -705,7 +724,7 @@ def test_tevery(): # send vcp again, get error with pytest.raises(LikelyDuplicitousError): - tvy.processEvent(serder=vcp, seqner=seqner, saider=diger) + tvy.processEvent(serder=vcp, seqner=seqner, saider=saider) # process issue vc event vcdig = b'EEBp64Aw2rsjdJpAR0e2qCq3jX7q7gLld3LjAwZgaLXU' @@ -713,31 +732,33 @@ def test_tevery(): iss = eventing.issue(vcdig=vcdig.decode("utf-8"), regk=regk) # successfully anchor to a rotation event - rseal = keventing.SealEvent(iss.ked["i"], iss.ked["s"], iss.saider.qb64) + rseal = keventing.SealEvent(iss.ked["i"], iss.ked["s"], iss.said) rot = hab.rotate(data=[rseal._asdict()]) - rotser = Serder(raw=rot) + rotser = serdering.SerderKERI(raw=rot) seqner = Seqner(sn=int(rotser.ked["s"], 16)) - diger = rotser.saider + #diger = rotser.saider + saider = Saider(qb64=rotser.said) - tvy.processEvent(serder=iss, seqner=seqner, saider=diger) + tvy.processEvent(serder=iss, seqner=seqner, saider=saider) status = tev.vcState(vcdig.decode("utf-8")) - assert status.ked['et'] == Ilks.iss - assert status.sn == 0 + assert status.et == Ilks.iss + assert status.s == '0' # revoke the vc rev = eventing.revoke(vcdig=vcdig.decode("utf-8"), regk=regk, dig=iss.said) # successfully anchor to a rotation event - rseal = keventing.SealEvent(rev.ked["i"], rev.ked["s"], rev.saider.qb64) + rseal = keventing.SealEvent(rev.ked["i"], rev.ked["s"], rev.said) rot = hab.rotate(data=[rseal._asdict()]) - rotser = Serder(raw=rot) + rotser = serdering.SerderKERI(raw=rot) seqner = Seqner(sn=int(rotser.ked["s"], 16)) - diger = rotser.saider + #diger = rotser.saider + saider = Saider(qb64=rotser.said) - tvy.processEvent(serder=rev, seqner=seqner, saider=diger) + tvy.processEvent(serder=rev, seqner=seqner, saider=saider) status = tev.vcState(vcdig.decode("utf-8")) - assert status.ked["et"] == Ilks.rev - assert status.sn == 1 + assert status.et == Ilks.rev + assert status.s == '1' def test_tevery_process_escrow(mockCoringRandomNonce): @@ -752,7 +773,7 @@ def test_tevery_process_escrow(mockCoringRandomNonce): regk = vcp.pre # successfully anchor to a rotation event - rseal = keventing.SealEvent(i=regk, s=vcp.ked["s"], d=vcp.saider.qb64) + rseal = keventing.SealEvent(i=regk, s=vcp.ked["s"], d=vcp.said) seqner = Seqner(sn=1) # said of rotation @@ -767,7 +788,7 @@ def test_tevery_process_escrow(mockCoringRandomNonce): assert regk not in tvy.tevers rot = hab.rotate(data=[rseal._asdict()]) # Now rotate so the achoring KEL event gets into the database - rotser = coring.Serder(raw=rot) + rotser = serdering.SerderKERI(raw=rot) assert rotser.saidb == diger.qb64b tvy.processEscrows() # process escrows and now the Tever event is good. @@ -779,5 +800,6 @@ def test_tevery_process_escrow(mockCoringRandomNonce): if __name__ == "__main__": - test_tever_escrow() - test_tevery_process_escrow() + #test_tever_escrow() + #test_tevery_process_escrow() + test_prefixer() diff --git a/tests/vdr/test_issuing.py b/tests/vdr/test_issuing.py index a23af20b5..54270db65 100644 --- a/tests/vdr/test_issuing.py +++ b/tests/vdr/test_issuing.py @@ -59,7 +59,10 @@ def test_issuer(mockHelpingNowUTC): rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() assert issuer.regk in regery.reger.tevers @@ -71,14 +74,20 @@ def test_issuer(mockHelpingNowUTC): rseal = SealEvent(iss.pre, "0", iss.said)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=iss.pre, regd=iss.said, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=iss.pre, + regd=iss.said, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() rev = issuer.revoke(said=creder.said) rseal = SealEvent(rev.pre, "1", rev.said)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=rev.pre, regd=rev.said, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=rev.pre, + regd=rev.said, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() with basing.openDB(name="bob") as db, keeping.openKS(name="bob") as kpr: @@ -89,7 +98,10 @@ def test_issuer(mockHelpingNowUTC): rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() assert issuer.regk in regery.reger.tevers @@ -103,7 +115,10 @@ def test_issuer(mockHelpingNowUTC): rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() assert issuer.regk in regery.reger.tevers @@ -113,19 +128,25 @@ def test_issuer(mockHelpingNowUTC): rseal = SealEvent(iss.pre, "0", iss.said)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=iss.pre, regd=iss.said, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=iss.pre, + regd=iss.said, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() state = issuer.tever.vcState(vci=creder.said) - assert state.ked["et"] == coring.Ilks.iss + assert state.et == coring.Ilks.iss rev = issuer.revoke(said=creder.said) rseal = SealEvent(rev.pre, "1", rev.said)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=rev.pre, regd=rev.said, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=rev.pre, + regd=rev.said, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() state = issuer.tever.vcState(vci=creder.said) - assert state.ked["et"] == coring.Ilks.rev + assert state.et == coring.Ilks.rev with basing.openDB(name="bob") as db, keeping.openKS(name="bob") as kpr: hby, hab = buildHab(db, kpr) @@ -137,7 +158,10 @@ def test_issuer(mockHelpingNowUTC): rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() assert issuer.regk in regery.reger.tevers @@ -146,10 +170,13 @@ def test_issuer(mockHelpingNowUTC): rseal = SealEvent(iss.pre, "0", iss.said)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=iss.pre, regd=iss.said, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=iss.pre, + regd=iss.said, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() state = issuer.tever.vcState(vci=creder.said) - assert state.ked["et"] == coring.Ilks.bis + assert state.et == coring.Ilks.bis rot = issuer.rotate(adds=["BCDfgIp33muOuCI0L8db_TldMJXv892UmW8yfpUuKzkw", "BBC_BBLMeVwKFbfYSWU7aATS9itLSrGtIFQzCkfoKnjk"]) @@ -157,19 +184,25 @@ def test_issuer(mockHelpingNowUTC): rseal = SealEvent(rot.pre, rseq.snh, rot.said)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=rot.pre, regd=rot.said, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=rot.pre, + regd=rot.said, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() state = issuer.tever.state() - assert state.ked["et"] == coring.Ilks.vrt + assert state.et == coring.Ilks.vrt rev = issuer.revoke(said=creder.said) rseal = SealEvent(rev.pre, "1", rev.said)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=rev.pre, regd=rev.said, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=rev.pre, + regd=rev.said, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() state = issuer.tever.vcState(vci=creder.said) - assert state.ked["et"] == coring.Ilks.brv + assert state.et == coring.Ilks.brv with basing.openDB(name="bob") as db, keeping.openKS(name="bob") as kpr: hby, hab = buildHab(db, kpr) @@ -180,7 +213,10 @@ def test_issuer(mockHelpingNowUTC): rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() assert issuer.regk in regery.reger.tevers @@ -189,19 +225,25 @@ def test_issuer(mockHelpingNowUTC): rseal = SealEvent(iss.pre, "0", iss.said)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=iss.pre, regd=iss.said, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=iss.pre, + regd=iss.said, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() state = issuer.tever.vcState(vci=creder.said) - assert state.ked["et"] == coring.Ilks.iss + assert state.et == coring.Ilks.iss rev = issuer.revoke(said=creder.said) rseal = SealEvent(rev.pre, "1", rev.said)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=rev.pre, regd=rev.said, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=rev.pre, + regd=rev.said, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() state = issuer.tever.vcState(vci=creder.said) - assert state.ked["et"] == coring.Ilks.rev + assert state.et == coring.Ilks.rev with pytest.raises(ValueError): issuer.rotate(adds=["BAFbQvUaS4EirvZVPUav7R_KDHB8AKmSfXNpWnZU_YEU"]) @@ -216,7 +258,10 @@ def test_issuer(mockHelpingNowUTC): rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() hab.rotate(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() assert issuer.regk in regery.reger.tevers @@ -226,20 +271,26 @@ def test_issuer(mockHelpingNowUTC): rseal = SealEvent(rot.pre, rseq.snh, rot.said)._asdict() hab.rotate(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=rot.pre, regd=rot.said, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=rot.pre, + regd=rot.said, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() state = issuer.tever.state() - assert state.ked["et"] == coring.Ilks.vrt + assert state.et == coring.Ilks.vrt creder = credential(hab=hab, regk=issuer.regk) iss = issuer.issue(said=creder.said) rseal = SealEvent(iss.pre, "0", iss.said)._asdict() hab.rotate(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=iss.pre, regd=iss.said, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=iss.pre, + regd=iss.said, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() state = issuer.tever.vcState(vci=creder.said) - assert state.ked["et"] == coring.Ilks.bis + assert state.et == coring.Ilks.bis # rotate to 2 backers rot = issuer.rotate(toad=2, cuts=["BAFbQvUaS4EirvZVPUav7R_KDHB8AKmSfXNpWnZU_YEU"]) @@ -247,19 +298,25 @@ def test_issuer(mockHelpingNowUTC): rseal = SealEvent(rot.pre, rseq.snh, rot.said)._asdict() hab.rotate(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=rot.pre, regd=rot.said, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=rot.pre, + regd=rot.said, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() state = issuer.tever.state() - assert state.ked["et"] == coring.Ilks.vrt + assert state.et == coring.Ilks.vrt rev = issuer.revoke(said=creder.said) rseal = SealEvent(rev.pre, "1", rev.said)._asdict() hab.rotate(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=rev.pre, regd=rev.said, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=rev.pre, + regd=rev.said, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() state = issuer.tever.vcState(vci=creder.said) - assert state.ked["et"] == coring.Ilks.brv + assert state.et == coring.Ilks.brv """ End Test """ diff --git a/tests/vdr/test_txn_state.py b/tests/vdr/test_txn_state.py index fee3cdffa..fa24dbe2f 100644 --- a/tests/vdr/test_txn_state.py +++ b/tests/vdr/test_txn_state.py @@ -1,5 +1,7 @@ +from dataclasses import asdict + from keri.app import habbing -from keri.core import routing, parsing, coring +from keri.core import routing, parsing, coring, serdering from keri.core.eventing import Kevery, SealEvent from keri.vc import proving @@ -21,7 +23,10 @@ def test_tsn_message_out_of_order(mockHelpingNowUTC, mockCoringRandomNonce): rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() bobHab.interact(data=[rseal]) seqner = coring.Seqner(sn=bobHab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=bobHab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=bobHab.kever.serder.said)) regery.processEscrows() assert issuer.regk == 'ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6' @@ -38,15 +43,20 @@ def test_tsn_message_out_of_order(mockHelpingNowUTC, mockCoringRandomNonce): parsing.Parser().parse(ims=msgs, kvy=bamKvy, rvy=bamRvy) tever = issuer.tevers[issuer.regk] - tsn = tever.state() + rsr = tever.state() - assert tsn.raw == (b'{"v":"KERI10JSON000158_","i":"ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6",' - b'"s":"0","d":"ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6","ii":"EA_SbBUZYwq' - b'LVlAAn14d6QUBQCSReJlZ755JqTgmRhXH","dt":"2021-01-01T00:00:00.000000+00:00","' - b'et":"vcp","a":{"s":1,"d":"EIei8AjSQ9pGJp-UfcFNcxQxzsVHQCgCsViNr81Hl3pd"},"bt' - b'":"0","br":[],"ba":[],"b":[],"c":["NB"]}') + assert asdict(rsr) == {'b': [], + 'bt': '0', + 'c': ['NB'], + 'd': 'ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6', + 'dt': '2021-01-01T00:00:00.000000+00:00', + 'et': 'vcp', + 'i': 'ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6', + 'ii': 'EA_SbBUZYwqLVlAAn14d6QUBQCSReJlZ755JqTgmRhXH', + 's': '0', + 'vn': [1, 0]} - rpy = bobHab.reply(route="/tsn/registry/" + bobHab.pre, data=tsn.ked) + rpy = bobHab.reply(route="/tsn/registry/" + bobHab.pre, data=rsr._asdict()) bamReger = viring.Reger(name="bam", temp=True) bamTvy = eventing.Tevery(reger=bamReger, db=bamHby.db, lax=False, local=False, rvy=bamRvy) @@ -59,7 +69,7 @@ def test_tsn_message_out_of_order(mockHelpingNowUTC, mockCoringRandomNonce): assert cue['q']['ri'] == issuer.regk saider = bamReger.txnsb.escrowdb.get(keys=("registry-ooo", issuer.regk, bobHab.pre)) - assert saider[0].qb64b == b'EEPziN_7emWTt94juY7X3Nlo44dTAaoCz7PGorkZAWVo' + assert saider[0].qb64b == b'ELprJNCkha3b3t7YKOPzfv-prTZvgJFM_xTnaaJop-3A' tmsgs = bytearray() cloner = regery.reger.clonePreIter(pre=issuer.regk, fn=0) # create iterator at 0 @@ -91,7 +101,10 @@ def test_tsn_message_missing_anchor(mockHelpingNowUTC, mockCoringRandomNonce): rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() bobHab.interact(data=[rseal]) seqner = coring.Seqner(sn=bobHab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=bobHab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=bobHab.kever.serder.said)) regery.processEscrows() assert issuer.regk == 'ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6' @@ -104,13 +117,18 @@ def test_tsn_message_missing_anchor(mockHelpingNowUTC, mockCoringRandomNonce): tever = issuer.tevers[issuer.regk] tsn = tever.state() - assert tsn.raw == (b'{"v":"KERI10JSON000158_","i":"ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6",' - b'"s":"0","d":"ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6","ii":"EA_SbBUZYwq' - b'LVlAAn14d6QUBQCSReJlZ755JqTgmRhXH","dt":"2021-01-01T00:00:00.000000+00:00","' - b'et":"vcp","a":{"s":1,"d":"EIei8AjSQ9pGJp-UfcFNcxQxzsVHQCgCsViNr81Hl3pd"},"bt' - b'":"0","br":[],"ba":[],"b":[],"c":["NB"]}') + assert asdict(tsn) == {'b': [], + 'bt': '0', + 'c': ['NB'], + 'd': 'ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6', + 'dt': '2021-01-01T00:00:00.000000+00:00', + 'et': 'vcp', + 'i': 'ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6', + 'ii': 'EA_SbBUZYwqLVlAAn14d6QUBQCSReJlZ755JqTgmRhXH', + 's': '0', + 'vn': [1, 0]} - rpy = bobHab.reply(route="/tsn/registry/" + bobHab.pre, data=tsn.ked) + rpy = bobHab.reply(route="/tsn/registry/" + bobHab.pre, data=asdict(tsn)) bamReger = viring.Reger(name="bam", temp=True) bamTvy = eventing.Tevery(reger=bamReger, db=bamHby.db, lax=False, local=False, rvy=bamRvy) @@ -118,7 +136,7 @@ def test_tsn_message_missing_anchor(mockHelpingNowUTC, mockCoringRandomNonce): parsing.Parser().parse(ims=bytearray(rpy), tvy=bamTvy, rvy=bamRvy) saider = bamReger.txnsb.escrowdb.get(keys=("registry-mae", issuer.regk, bobHab.pre)) - said = b'EEPziN_7emWTt94juY7X3Nlo44dTAaoCz7PGorkZAWVo' + said = b'ELprJNCkha3b3t7YKOPzfv-prTZvgJFM_xTnaaJop-3A' assert saider[0].qb64b == said assert len(bamTvy.cues) == 1 cue = bamTvy.cues.popleft() @@ -184,7 +202,10 @@ def test_tsn_from_witness(mockHelpingNowUTC, mockCoringRandomNonce): rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() bobHab.interact(data=[rseal]) seqner = coring.Seqner(sn=bobHab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=bobHab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=bobHab.kever.serder.said)) regery.processEscrows() assert issuer.regk == 'EBrr1pxZoY5nY38YifrGvn5HSMv0sAwvTTAQ5e3_-ivP' @@ -194,7 +215,7 @@ def test_tsn_from_witness(mockHelpingNowUTC, mockCoringRandomNonce): for msg in bobHby.db.clonePreIter(pre=bobHab.pre, fn=0): parsing.Parser().parse(ims=bytearray(msg), kvy=wesKvy) - iserder = coring.Serder(raw=bytearray(msg)) + iserder = serdering.SerderKERI(raw=bytearray(msg)) wesHab.receipt(serder=iserder) assert bobHab.pre in wesHab.kevers @@ -216,13 +237,18 @@ def test_tsn_from_witness(mockHelpingNowUTC, mockCoringRandomNonce): tever = wesReger.tevers[issuer.regk] tsn = tever.state() - assert tsn.raw == (b'{"v":"KERI10JSON000158_","i":"EBrr1pxZoY5nY38YifrGvn5HSMv0sAwvTTAQ5e3_-ivP",' - b'"s":"0","d":"EBrr1pxZoY5nY38YifrGvn5HSMv0sAwvTTAQ5e3_-ivP","ii":"EDroh9lTel0' - b'P1YQaiL7shXG63SRSzKSDek7PaceOs6bY","dt":"2021-01-01T00:00:00.000000+00:00","' - b'et":"vcp","a":{"s":1,"d":"EHXALltba7vs56tixvcYsZv-JVI1-MlZ60Jk40DuyWFh"},"bt' - b'":"0","br":[],"ba":[],"b":[],"c":["NB"]}') + assert asdict(tsn) == {'b': [], + 'bt': '0', + 'c': ['NB'], + 'd': 'EBrr1pxZoY5nY38YifrGvn5HSMv0sAwvTTAQ5e3_-ivP', + 'dt': '2021-01-01T00:00:00.000000+00:00', + 'et': 'vcp', + 'i': 'EBrr1pxZoY5nY38YifrGvn5HSMv0sAwvTTAQ5e3_-ivP', + 'ii': 'EDroh9lTel0P1YQaiL7shXG63SRSzKSDek7PaceOs6bY', + 's': '0', + 'vn': [1, 0]} - rpy = wesHab.reply(route="/tsn/registry/" + wesHab.pre, data=tsn.ked) + rpy = wesHab.reply(route="/tsn/registry/" + wesHab.pre, data=asdict(tsn)) bamRtr = routing.Router() bamRvy = routing.Revery(db=bamHby.db, rtr=bamRtr) @@ -233,7 +259,7 @@ def test_tsn_from_witness(mockHelpingNowUTC, mockCoringRandomNonce): parsing.Parser().parse(ims=bytearray(rpy), tvy=bamTvy, rvy=bamRvy) saider = bamReger.txnsb.escrowdb.get(keys=("registry-mae", issuer.regk, wesHab.pre)) - said = b'EMpMvmRARP3rm_JmO57iZ6zhEELmAuVVELo660CMapg5' + said = b'EMoqBJpoPJCkCejSUZ8IShTeGd_WQkF0zOQc2l0HQusn' assert saider[0].qb64b == said assert len(bamTvy.cues) == 1 cue = bamTvy.cues.popleft() @@ -299,7 +325,10 @@ def test_tsn_from_no_one(mockHelpingNowUTC, mockCoringRandomNonce): rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() bobHab.interact(data=[rseal]) seqner = coring.Seqner(sn=bobHab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=bobHab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=bobHab.kever.serder.said)) regery.processEscrows() assert issuer.regk == 'ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6' @@ -330,14 +359,18 @@ def test_tsn_from_no_one(mockHelpingNowUTC, mockCoringRandomNonce): tever = wesReger.tevers[issuer.regk] tsn = tever.state() - assert tsn.raw == (b'{"v":"KERI10JSON000158_","i":"ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6",' - b'"s":"0","d":"ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6","ii":"EA_SbBUZYwq' - b'LVlAAn14d6QUBQCSReJlZ755JqTgmRhXH","dt":"2021-01-01T00:00:00.000000+00:00","' - b'et":"vcp","a":{"s":1,"d":"EIei8AjSQ9pGJp-UfcFNcxQxzsVHQCgCsViNr81Hl3pd"},"bt' - b'":"0","br":[],"ba":[],"b":[],"c":["NB"]}') - + assert asdict(tsn) == {'b': [], + 'bt': '0', + 'c': ['NB'], + 'd': 'ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6', + 'dt': '2021-01-01T00:00:00.000000+00:00', + 'et': 'vcp', + 'i': 'ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6', + 'ii': 'EA_SbBUZYwqLVlAAn14d6QUBQCSReJlZ755JqTgmRhXH', + 's': '0', + 'vn': [1, 0]} - rpy = wesHab.reply(route="/tsn/registry/" + wesHab.pre, data=tsn.ked) + rpy = wesHab.reply(route="/tsn/registry/" + wesHab.pre, data=asdict(tsn)) bamRtr = routing.Router() bamRvy = routing.Revery(db=bamHby.db, rtr=bamRtr) @@ -379,7 +412,10 @@ def test_credential_tsn_message(mockHelpingNowUTC, mockCoringRandomNonce, mockHe rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() bobHab.interact(data=[rseal]) seqner = coring.Seqner(sn=bobHab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=bobHab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=bobHab.kever.serder.said)) regery.processEscrows() assert issuer.regk == 'ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6' @@ -403,26 +439,38 @@ def test_credential_tsn_message(mockHelpingNowUTC, mockCoringRandomNonce, mockHe rseal = SealEvent(iss.pre, "0", iss.said)._asdict() bobHab.interact(data=[rseal]) seqner = coring.Seqner(sn=bobHab.kever.sn) - issuer.anchorMsg(pre=iss.pre, regd=iss.said, seqner=seqner, saider=bobHab.kever.serder.saider) + issuer.anchorMsg(pre=iss.pre, + regd=iss.said, + seqner=seqner, + saider=coring.Saider(qb64=bobHab.kever.serder.said)) regery.processEscrows() tever = issuer.tevers[issuer.regk] - tsn = tever.state() - - assert tsn.raw == (b'{"v":"KERI10JSON000158_","i":"ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6",' - b'"s":"0","d":"ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6","ii":"EA_SbBUZYwq' - b'LVlAAn14d6QUBQCSReJlZ755JqTgmRhXH","dt":"2021-06-27T21:26:21.233257+00:00","' - b'et":"vcp","a":{"s":1,"d":"EIei8AjSQ9pGJp-UfcFNcxQxzsVHQCgCsViNr81Hl3pd"},"bt' - b'":"0","br":[],"ba":[],"b":[],"c":["NB"]}') + rsr = tever.state() + + assert rsr._asdict() == {'vn': [1, 0], + 'i': 'ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6', + 's': '0', + 'd': 'ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6', + 'ii': 'EA_SbBUZYwqLVlAAn14d6QUBQCSReJlZ755JqTgmRhXH', + 'dt': '2021-06-27T21:26:21.233257+00:00', + 'et': 'vcp', + 'bt': '0', + 'b': [], + 'c': ['NB']} ctsn = tever.vcState(vci=creder.said) - assert ctsn.raw == (b'{"v":"KERI10JSON000135_","i":"EEqcwL-ew_OaQphSQvy8bRGtnKlL_g_SkXjsAGWgtFGl",' - b'"s":"0","d":"EIxhyBA8h6BMmtWEJzqNkoAquIkMucpXbdY3kQX25GQu","ri":"ECbNKwkTjZq' - b'sfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6","ra":{},"a":{"s":2,"d":"EGJIFzWexy6LQbW4-' - b'IQqhGLD6wA9yR7pcLzxomjb40Ku"},"dt":"2021-06-27T21:26:21.233257+00:00","et":"' - b'iss"}') - - rpy = bobHab.reply(route="/tsn/credential/" + bobHab.pre, data=ctsn.ked) + assert asdict(ctsn) == {'a': {'d': 'EGJIFzWexy6LQbW4-IQqhGLD6wA9yR7pcLzxomjb40Ku', 's': 2}, + 'd': 'EIxhyBA8h6BMmtWEJzqNkoAquIkMucpXbdY3kQX25GQu', + 'dt': '2021-06-27T21:26:21.233257+00:00', + 'et': 'iss', + 'i': 'EEqcwL-ew_OaQphSQvy8bRGtnKlL_g_SkXjsAGWgtFGl', + 'ra': {}, + 'ri': 'ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6', + 's': '0', + 'vn': [1, 0]} + + rpy = bobHab.reply(route="/tsn/credential/" + bobHab.pre, data=asdict(ctsn)) bamReger = viring.Reger(name="bam", temp=True) bamTvy = eventing.Tevery(reger=bamReger, db=bamHby.db, lax=False, local=False, rvy=bamRvy) @@ -430,7 +478,7 @@ def test_credential_tsn_message(mockHelpingNowUTC, mockCoringRandomNonce, mockHe parsing.Parser().parse(ims=bytearray(rpy), tvy=bamTvy, rvy=bamRvy) saider = bamReger.txnsb.escrowdb.get(keys=("credential-mre", creder.said, bobHab.pre)) - assert saider[0].qb64b == b'EAxn6pBNsNuGXP7Ngrr80t5PbJ_XuzDvY7DQ5SYodpin' + assert saider[0].qb64b == b'EAslzIz1FKVBY1zd_0gF-gclpvHMf1V4arW3puZlPv4K' assert len(bamTvy.cues) == 1 cue = bamTvy.cues.popleft() assert cue["kin"] == "telquery" @@ -459,7 +507,7 @@ def test_credential_tsn_message(mockHelpingNowUTC, mockCoringRandomNonce, mockHe assert cue['q']['ri'] == issuer.regk saider = bamReger.txnsb.escrowdb.get(keys=("credential-ooo", creder.said, bobHab.pre)) - assert saider[0].qb64b == b'EAxn6pBNsNuGXP7Ngrr80t5PbJ_XuzDvY7DQ5SYodpin' + assert saider[0].qb64b == b'EAslzIz1FKVBY1zd_0gF-gclpvHMf1V4arW3puZlPv4K' vci = creder.said tmsgs = bytearray() @@ -474,5 +522,32 @@ def test_credential_tsn_message(mockHelpingNowUTC, mockCoringRandomNonce, mockHe # check to make sure the tsn escrow state is clear assert bamReger.txnsb.escrowdb.get(keys=(creder.said, bobHab.pre)) == [] # check to make sure the tsn has been saved - saider = bamReger.txnsb.saiderdb.get(keys=(creder.said, bobHab.pre)) + keys = (creder.said, bobHab.pre) + saider = bamReger.txnsb.saiderdb.get(keys=keys) assert saider.qb64b == b'EIxhyBA8h6BMmtWEJzqNkoAquIkMucpXbdY3kQX25GQu' + + +def test_tever_reload(mockHelpingNowUTC, mockCoringRandomNonce, mockHelpingNowIso8601): + + with habbing.openHby(name="bob", base="test") as hby: + + bobHab = hby.makeHab(name="bob", isith='1', icount=1,) + assert bobHab.pre == 'EA_SbBUZYwqLVlAAn14d6QUBQCSReJlZ755JqTgmRhXH' + + regery = credentialing.Regery(hby=hby, name="test", temp=True) + issuer = regery.makeRegistry(prefix=bobHab.pre, name=bobHab.name) + rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() + bobHab.interact(data=[rseal]) + seqner = coring.Seqner(sn=bobHab.kever.sn) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=bobHab.kever.serder.said)) + regery.processEscrows() + + assert issuer.regk == 'ECbNKwkTjZqsfwNLxTnraPImegy1YeQ2-pCrTBQmu3i6' + + rsr = regery.reger.states.get(keys=issuer.regk) + tever = eventing.Tever(rsr=rsr, reger=regery.reger) + assert tever.regk == issuer.regk + assert tever.pre == bobHab.pre diff --git a/tests/vdr/test_verifying.py b/tests/vdr/test_verifying.py index eac4b40ff..9a5fbb578 100644 --- a/tests/vdr/test_verifying.py +++ b/tests/vdr/test_verifying.py @@ -26,17 +26,17 @@ def test_verifier_query(mockHelpingNowUTC, mockCoringRandomNonce): "EA8Ih8hxLi3mmkyItXK1u55cnHl4WgNZ_RE-gKXqgcX4", route="tels") assert msg == (b'{"v":"KERI10JSON0000fe_","t":"qry","d":"EHraBkp-XMf1x_bo70O2x3br' - b'BCHlJHa7q_MzsBNeYz2_","dt":"2021-01-01T00:00:00.000000+00:00","r' - b'":"tels","rr":"","q":{"i":"EA8Ih8hxLi3mmkyItXK1u55cnHl4WgNZ_RE-g' - b'KXqgcX4","ri":"EO0_SyqPS1-EVYSITakYpUHaUZZpZGsjaXFOaO_kCfS4"}}-V' - b'Aj-HABEIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3-AABAABUWETZTw' - b'TVuh0mNvN5KsJ_9V1epoP5wqgW32x8nUnGB20aI8xQBAhQ-aVP61ZEq97BDGSnxO' - b'hU6tGCfDmvtugI') + b'BCHlJHa7q_MzsBNeYz2_","dt":"2021-01-01T00:00:00.000000+00:00","r' + b'":"tels","rr":"","q":{"i":"EA8Ih8hxLi3mmkyItXK1u55cnHl4WgNZ_RE-g' + b'KXqgcX4","ri":"EO0_SyqPS1-EVYSITakYpUHaUZZpZGsjaXFOaO_kCfS4"}}-V' + b'Aj-HABEIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3-AABAABUWETZTw' + b'TVuh0mNvN5KsJ_9V1epoP5wqgW32x8nUnGB20aI8xQBAhQ-aVP61ZEq97BDGSnxO' + b'hU6tGCfDmvtugI') def test_verifier(seeder): with (habbing.openHab(name="sid", temp=True, salt=b'0123456789abcdef') as (hby, hab), - habbing.openHab(name="recp", transferable=True, temp=True) as (recpHby, recp)): + habbing.openHab(name="recp", transferable=True, temp=True) as (recpHby, recp)): seeder.seedSchema(db=hby.db) seeder.seedSchema(db=recpHby.db) assert hab.pre == "EKC8085pwSwzLwUGzh-HrEoFDwZnCJq27bVp5atdMT9o" @@ -46,7 +46,10 @@ def test_verifier(seeder): rseal = SealEvent(issuer.regk, "0", issuer.regd)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=issuer.regk, regd=issuer.regd, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=issuer.regk, + regd=issuer.regd, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() verifier = verifying.Verifier(hby=hby, reger=regery.reger) @@ -57,17 +60,17 @@ def test_verifier(seeder): dt=helping.nowIso8601(), LEI="254900OPPU84GM83MG36", ) - _, d = scheming.Saider.saidify(sad=credSubject, code=coring.MtrDex.Blake3_256, label=scheming.Ids.d) + _, d = scheming.Saider.saidify(sad=credSubject, code=coring.MtrDex.Blake3_256, label=scheming.Saids.d) creder = proving.credential(issuer=hab.pre, schema="EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC", data=d, status=issuer.regk) - - sadsigers, sadcigars = signing.signPaths(hab=hab, serder=creder, paths=[[]]) missing = False try: - verifier.processCredential(creder, sadsigers=sadsigers, sadcigars=sadcigars) + # Specify an anchor directly in the KEL + verifier.processCredential(creder, prefixer=hab.kever.prefixer, seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) except kering.MissingRegistryError: missing = True @@ -81,7 +84,10 @@ def test_verifier(seeder): rseal = SealEvent(iss.pre, "0", iss.said)._asdict() hab.interact(data=[rseal]) seqner = coring.Seqner(sn=hab.kever.sn) - issuer.anchorMsg(pre=iss.pre, regd=iss.said, seqner=seqner, saider=hab.kever.serder.saider) + issuer.anchorMsg(pre=iss.pre, + regd=iss.said, + seqner=seqner, + saider=coring.Saider(qb64=hab.kever.serder.said)) regery.processEscrows() # Now that the credential has been issued, process escrows and it will find the TEL event @@ -92,10 +98,9 @@ def test_verifier(seeder): assert cue["kin"] == "saved" assert cue["creder"].raw == creder.raw - dcre, sadsigers, sadcigars = regery.reger.cloneCred(said=creder.saider.qb64) + dcre, *_ = regery.reger.cloneCred(said=creder.said) assert dcre.raw == creder.raw - assert len(sadsigers) == 1 saider = regery.reger.issus.get(hab.pre) assert saider[0].qb64 == creder.said @@ -295,6 +300,7 @@ def test_verifier(seeder): def test_verifier_chained_credential(seeder): qviSchema = "EFgnk_c08WmZGgv9_mpldibRuqFMTQN-rAgtD-TCOwbs" vLeiSchema = "ED892b40P_GcESs3wOcc2zFvL_GVi2Ybzp9isNTZKqP0" + optionalIssueeSchema = "EAv8omZ-o3Pk45h72_WnIpt6LTWNzc8hmLjeblpxB9vz" with habbing.openHab(name="ron", temp=True, salt=b'0123456789abcdef') as (ronHby, ron), \ habbing.openHab(name="ian", temp=True, salt=b'0123456789abcdef') as (ianHby, ian), \ @@ -317,7 +323,10 @@ def test_verifier_chained_credential(seeder): rseal = SealEvent(roniss.regk, "0", roniss.regd)._asdict() ron.interact(data=[rseal]) seqner = coring.Seqner(sn=ron.kever.sn) - roniss.anchorMsg(pre=roniss.regk, regd=roniss.regd, seqner=seqner, saider=ron.kever.serder.saider) + roniss.anchorMsg(pre=roniss.regk, + regd=roniss.regd, + seqner=seqner, + saider=coring.Saider(qb64=ron.kever.serder.said)) ronreg.processEscrows() ronverfer = verifying.Verifier(hby=ronHby, reger=ronreg.reger) @@ -328,18 +337,17 @@ def test_verifier_chained_credential(seeder): dt=helping.nowIso8601(), LEI="5493001KJTIIGC8Y1R12", ) - _, d = scheming.Saider.saidify(sad=credSubject, code=coring.MtrDex.Blake3_256, label=scheming.Ids.d) + _, d = scheming.Saider.saidify(sad=credSubject, code=coring.MtrDex.Blake3_256, label=scheming.Saids.d) creder = proving.credential(issuer=ron.pre, schema=qviSchema, data=d, status=roniss.regk) - sadsigers, sadcigars = signing.signPaths(hab=ron, serder=creder, paths=[[]]) - missing = False try: - ronverfer.processCredential(creder, sadsigers=sadsigers, sadcigars=sadcigars) + ronverfer.processCredential(creder, prefixer=ron.kever.prefixer, seqner=seqner, + saider=coring.Saider(qb64=ron.kever.serder.said)) except kering.MissingRegistryError: missing = True @@ -354,7 +362,10 @@ def test_verifier_chained_credential(seeder): rseal = SealEvent(iss.pre, "0", iss.said)._asdict() ron.interact(data=[rseal]) seqner = coring.Seqner(sn=ron.kever.sn) - roniss.anchorMsg(pre=iss.pre, regd=iss.said, seqner=seqner, saider=ron.kever.serder.saider) + roniss.anchorMsg(pre=iss.pre, + regd=iss.said, + seqner=seqner, + saider=coring.Saider(qb64=ron.kever.serder.said)) ronreg.processEscrows() # Now that the credential has been issued, process escrows and it will find the TEL event @@ -365,18 +376,8 @@ def test_verifier_chained_credential(seeder): assert cue["kin"] == "saved" assert cue["creder"].raw == creder.raw - dcre, sadsig, sadcig = ronreg.reger.cloneCred(said=creder.said) + dcre, *_ = ronreg.reger.cloneCred(said=creder.said) assert dcre.raw == creder.raw - assert len(sadsig) == 1 - assert len(sadcig) == 0 - - expect = [m.qb64 for m in sadsig[0][:-1]] - actual = [m.qb64 for m in sadsigers[0][:-1]] - assert expect == actual - - sig0 = sadsig[-1][0] - sig1 = sadsigers[-1][0] - assert sig0.qb64b == sig1.qb64b saider = ronreg.reger.issus.get(ron.pre) assert saider[0].qb64 == creder.said @@ -389,7 +390,10 @@ def test_verifier_chained_credential(seeder): rseal = SealEvent(ianiss.regk, "0", ianiss.regd)._asdict() ian.interact(data=[rseal]) seqner = coring.Seqner(sn=ian.kever.sn) - ianiss.anchorMsg(pre=ianiss.regk, regd=ianiss.regd, seqner=seqner, saider=ian.kever.serder.saider) + ianiss.anchorMsg(pre=ianiss.regk, + regd=ianiss.regd, + seqner=seqner, + saider=coring.Saider(qb64=ian.kever.serder.said)) ianreg.processEscrows() ianverfer = verifying.Verifier(hby=ianHby, reger=ianreg.reger) @@ -400,7 +404,7 @@ def test_verifier_chained_credential(seeder): dt=helping.nowIso8601(), LEI="254900OPPU84GM83MG36", ) - _, d = scheming.Saider.saidify(sad=leiCredSubject, code=coring.MtrDex.Blake3_256, label=scheming.Ids.d) + _, d = scheming.Saider.saidify(sad=leiCredSubject, code=coring.MtrDex.Blake3_256, label=scheming.Saids.d) chain = dict( d=creder.said, @@ -418,11 +422,10 @@ def test_verifier_chained_credential(seeder): usageDisclaimer="Use carefully." )]) - vLeiSadsigers, vLeiSadcigars = signing.signPaths(hab=ian, serder=vLeiCreder, paths=[[]]) - missing = False try: - ianverfer.processCredential(vLeiCreder, sadsigers=vLeiSadsigers, sadcigars=vLeiSadcigars) + ianverfer.processCredential(vLeiCreder, prefixer=ian.kever.prefixer, seqner=seqner, + saider=coring.Saider(qb64=ian.kever.serder.said)) except kering.MissingRegistryError: missing = True @@ -437,26 +440,19 @@ def test_verifier_chained_credential(seeder): rseal = SealEvent(iss.pre, "0", iss.said)._asdict() ian.interact(data=[rseal]) seqner = coring.Seqner(sn=ian.kever.sn) - ianiss.anchorMsg(pre=iss.pre, regd=iss.said, seqner=seqner, saider=ian.kever.serder.saider) + ianiss.anchorMsg(pre=iss.pre, + regd=iss.said, + seqner=seqner, + saider=coring.Saider(qb64=ian.kever.serder.said)) ianreg.processEscrows() # Now that the credential has been issued, process escrows and it will find the TEL event ianverfer.processEscrows() - dcre, sadsig, sadcig = ianreg.reger.cloneCred(said=vLeiCreder.said) + dcre, *_ = ianreg.reger.cloneCred(said=vLeiCreder.said) assert dcre.raw == vLeiCreder.raw - assert len(sadsig) == 1 - assert len(sadcig) == 0 - - expect = [m.qb64 for m in sadsig[0][:-1]] - actual = [m.qb64 for m in vLeiSadsigers[0][:-1]] - assert expect == actual - sig0 = sadsig[-1][0] - sig1 = vLeiSadsigers[-1][0] - assert sig0.qb64b == sig1.qb64b - - dater = ianreg.reger.mce.get(vLeiCreder.saider.qb64b) + dater = ianreg.reger.mce.get(vLeiCreder.saidb) assert dater is not None assert len(ianverfer.cues) == 1 @@ -476,7 +472,8 @@ def test_verifier_chained_credential(seeder): for msg in ronverfer.reger.clonePreIter(pre=creder.said): parsing.Parser().parse(ims=bytearray(msg), kvy=iankvy, tvy=iantvy) - ianverfer.processCredential(creder, sadsigers=sadsigers, sadcigars=sadcigars) + ianverfer.processCredential(creder, prefixer=ron.kever.prefixer, seqner=seqner, + saider=coring.Saider(qb64=ron.kever.serder.said)) # Process the escrows to get Ian's credential out of missing chain escrow ianverfer.processEscrows() @@ -489,6 +486,109 @@ def test_verifier_chained_credential(seeder): saider = ianreg.reger.schms.get(vLeiSchema) assert saider[0].qb64 == vLeiCreder.said + # test operators + + untargetedSubject = dict( + d="", + dt=helping.nowIso8601(), + claim="An outrageous claim.", + ) + _, d = scheming.Saider.saidify(sad=untargetedSubject, code=coring.MtrDex.Blake3_256, label=scheming.Saids.d) + + chainSad = dict( + d='', + targetedEdge=dict( + n=vLeiCreder.said, + ), + ) + _, chain = scheming.Saider.saidify(sad=chainSad, code=coring.MtrDex.Blake3_256, label=scheming.Saids.d) + + untargetedCreder = proving.credential(issuer=ian.pre, + schema=optionalIssueeSchema, + data=d, + status=ianiss.regk, + source=chain, + rules={}) + + missing = False + try: + ianverfer.processCredential(untargetedCreder, prefixer=ian.kever.prefixer, seqner=seqner, + saider=coring.Saider(qb64=ian.kever.serder.said)) + except kering.MissingRegistryError: + missing = True + + assert missing is True + assert len(ianverfer.cues) == 3 + cue = ianverfer.cues.popleft() + assert cue["kin"] == "saved" + cue["creder"] = untargetedCreder.raw + + iss = ianiss.issue(said=untargetedCreder.said) + rseal = SealEvent(iss.pre, "0", iss.said)._asdict() + ian.interact(data=[rseal]) + seqner = coring.Seqner(sn=ian.kever.sn) + ianiss.anchorMsg(pre=iss.pre, + regd=iss.said, + seqner=seqner, + saider=coring.Saider(qb64=ian.kever.serder.said)) + ianreg.processEscrows() + + # Now that the credential has been issued, process escrows and it will find the TEL event + ianverfer.processEscrows() + + chainedSubject = dict( + d="", + dt=helping.nowIso8601(), + claim="An outrageous claim.", + ) + _, d = scheming.Saider.saidify(sad=chainedSubject, code=coring.MtrDex.Blake3_256, label=scheming.Saids.d) + + chainSad = dict( + d='', + untargetedButI2I=dict( + n=untargetedCreder.said, + o="I2I" + ), + ) + _, chain = scheming.Saider.saidify(sad=chainSad, code=coring.MtrDex.Blake3_256, label=scheming.Saids.d) + + chainedCreder = proving.credential(issuer=ian.pre, + schema=optionalIssueeSchema, + data=d, + status=ianiss.regk, + source=chain, + rules={}) + + missing = False + try: + ianverfer.processCredential(chainedCreder, prefixer=ian.kever.prefixer, seqner=seqner, + saider=coring.Saider(qb64=ian.kever.serder.said)) + except kering.MissingRegistryError: + missing = True + + assert missing is True + assert len(ianverfer.cues) == 4 + cue = ianverfer.cues.popleft() + assert cue["kin"] == "saved" + cue["creder"] = chainedCreder.raw + + iss = ianiss.issue(said=chainedCreder.said) + rseal = SealEvent(iss.pre, "0", iss.said)._asdict() + ian.interact(data=[rseal]) + seqner = coring.Seqner(sn=ian.kever.sn) + ianiss.anchorMsg(pre=iss.pre, + regd=iss.said, + seqner=seqner, + saider=coring.Saider(qb64=ian.kever.serder.said)) + ianreg.processEscrows() + + # Ensure that when specifying I2I it is enforced + try: + ianverfer.processCredential(chainedCreder, prefixer=ian.kever.prefixer, seqner=seqner, + saider=coring.Saider(qb64=ian.kever.serder.said)) + except kering.MissingChainError: + pass + # Now lets get Ron's credential into Vic's Tevers and Database vickvy = ceventing.Kevery(db=vic.db, lax=False, local=False) victvy = eventing.Tevery(reger=vicreg.reger, db=vic.db, local=False) @@ -501,7 +601,8 @@ def test_verifier_chained_credential(seeder): for msg in ronverfer.reger.clonePreIter(pre=creder.said): parsing.Parser().parse(ims=bytearray(msg), kvy=vickvy, tvy=victvy) - vicverfer.processCredential(creder, sadsigers=sadsigers, sadcigars=sadcigars) + vicverfer.processCredential(creder, prefixer=ian.kever.prefixer, seqner=seqner, + saider=coring.Saider(qb64=ian.kever.serder.said)) assert len(vicverfer.cues) == 1 cue = vicverfer.cues.popleft() assert cue["kin"] == "saved" @@ -517,7 +618,8 @@ def test_verifier_chained_credential(seeder): parsing.Parser().parse(ims=bytearray(msg), kvy=vickvy, tvy=victvy) # And now verify the credential: - vicverfer.processCredential(vLeiCreder, sadsigers=vLeiSadsigers, sadcigars=vLeiSadcigars) + vicverfer.processCredential(vLeiCreder, prefixer=ian.kever.prefixer, seqner=seqner, + saider=coring.Saider(qb64=ian.kever.serder.said)) assert len(vicverfer.cues) == 1 cue = vicverfer.cues.popleft() @@ -531,7 +633,10 @@ def test_verifier_chained_credential(seeder): rseal = SealEvent(rev.pre, rseq.snh, rev.said)._asdict() ron.interact(data=[rseal]) seqner = coring.Seqner(sn=ron.kever.sn) - roniss.anchorMsg(pre=rev.pre, regd=rev.said, seqner=seqner, saider=ron.kever.serder.saider) + roniss.anchorMsg(pre=rev.pre, + regd=rev.said, + seqner=seqner, + saider=coring.Saider(qb64=ron.kever.serder.said)) ronreg.processEscrows() for msg in ron.db.clonePreIter(pre=ron.pre): @@ -542,6 +647,7 @@ def test_verifier_chained_credential(seeder): parsing.Parser().parse(ims=bytearray(msg), kvy=vickvy, tvy=victvy) with pytest.raises(kering.RevokedChainError): - vicverfer.processCredential(vLeiCreder, sadsigers=vLeiSadsigers, sadcigars=vLeiSadcigars) + vicverfer.processCredential(vLeiCreder, prefixer=ian.kever.prefixer, seqner=seqner, + saider=coring.Saider(qb64=ian.kever.serder.said)) """End Test"""