├── .github ├── ISSUE_TEMPLATE │ ├── bug.yaml │ └── feature.yaml └── workflows │ ├── main.yml │ ├── publish-npm.yml │ └── update-documentation.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── codecov.yaml ├── config ├── keria.json └── witness-demo │ ├── wan.json │ ├── wes.json │ ├── wil.json │ ├── wit.json │ ├── wub.json │ └── wyz.json ├── diagrams ├── account-creation-webpage-workflow.mmd ├── account-creation-webpage-workflow.png ├── account-creation-workflow.mmd └── account-creation-workflow.png ├── docker-compose.yaml ├── eslint.config.mjs ├── package-lock.json ├── package.json ├── publish.sh ├── src ├── exports.ts ├── index.ts ├── keri │ ├── app │ │ ├── aiding.ts │ │ ├── clienting.ts │ │ ├── contacting.ts │ │ ├── controller.ts │ │ ├── coring.ts │ │ ├── credentialing.ts │ │ ├── delegating.ts │ │ ├── escrowing.ts │ │ ├── exchanging.ts │ │ ├── grouping.ts │ │ ├── habery.ts │ │ └── notifying.ts │ ├── core │ │ ├── authing.ts │ │ ├── base64.ts │ │ ├── bexter.ts │ │ ├── cigar.ts │ │ ├── cipher.ts │ │ ├── core.ts │ │ ├── counter.ts │ │ ├── decrypter.ts │ │ ├── diger.ts │ │ ├── encrypter.ts │ │ ├── eventing.ts │ │ ├── httping.ts │ │ ├── indexer.ts │ │ ├── keeping.ts │ │ ├── kering.ts │ │ ├── keyState.ts │ │ ├── manager.ts │ │ ├── matter.ts │ │ ├── number.ts │ │ ├── pather.ts │ │ ├── prefixer.ts │ │ ├── saider.ts │ │ ├── salter.ts │ │ ├── seqner.ts │ │ ├── serder.ts │ │ ├── siger.ts │ │ ├── signer.ts │ │ ├── tholder.ts │ │ ├── utils.ts │ │ ├── vdring.ts │ │ └── verfer.ts │ └── end │ │ └── ending.ts └── ready.ts ├── test-integration ├── challenge.test.ts ├── credentials.test.ts ├── delegation-multisig.test.ts ├── delegation.test.ts ├── externalModule.test.ts ├── modules │ └── bip39_shim.ts ├── multisig-holder.test.ts ├── multisig-inception.test.ts ├── multisig-join.test.ts ├── multisig-vlei-issuance.test.ts ├── multisig.test.ts ├── randy.test.ts ├── salty.test.ts ├── singlesig-dip.test.ts ├── singlesig-drt.test.ts ├── singlesig-ixn.test.ts ├── singlesig-rot.test.ts ├── singlesig-vlei-issuance.test.ts ├── test-setup-clients.test.ts ├── test-setup-single-client.test.ts ├── utils │ ├── multisig-utils.ts │ ├── resolve-env.ts │ ├── retry.ts │ ├── test-setup.ts │ ├── test-step.ts │ └── test-util.ts └── witness.test.ts ├── test ├── app │ ├── aiding.test.ts │ ├── clienting.test.ts │ ├── contacting.test.ts │ ├── controller.test.ts │ ├── coring.test.ts │ ├── credentialing.test.ts │ ├── delegating.test.ts │ ├── escrowing.test.ts │ ├── exchanging.test.ts │ ├── grouping.test.ts │ ├── habery.test.ts │ ├── notifying.test.ts │ ├── registry.test.ts │ └── test-utils.ts ├── core │ ├── authing.test.ts │ ├── base64.test.ts │ ├── bexter.test.ts │ ├── coring.test.ts │ ├── counter.test.ts │ ├── decrypter.test.ts │ ├── diger.test.ts │ ├── encrypter.test.ts │ ├── eventing.test.ts │ ├── httping.test.ts │ ├── indexer.test.ts │ ├── manager.test.ts │ ├── matter.test.ts │ ├── number.test.ts │ ├── pather.test.ts │ ├── prefixer.test.ts │ ├── saider.test.ts │ ├── salter.test.ts │ ├── seqner.test.ts │ ├── serder.test.ts │ ├── signer.test.ts │ ├── tholder.test.ts │ ├── utils.test.ts │ ├── vdring.test.ts │ └── verfer.test.ts └── end │ └── ending.test.ts ├── tsconfig.build.json ├── tsconfig.json ├── vitest.config.ts └── vitest.integration.ts /.github/ISSUE_TEMPLATE/bug.yaml: -------------------------------------------------------------------------------- 1 | name: "Bug report" 2 | description: Bug report template 3 | title: "Bug: short description" 4 | labels: ["bug", "triage"] 5 | body: 6 | - type: input 7 | id: version 8 | attributes: 9 | label: Version 10 | validations: 11 | required: true 12 | - type: input 13 | id: Evnironment 14 | attributes: 15 | label: Environment 16 | description: (OS, Node.js version) 17 | - type: textarea 18 | id: expected-behavior 19 | attributes: 20 | label: Expected behavior 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: actual-behavior 25 | attributes: 26 | label: Actual behavior 27 | validations: 28 | required: true 29 | - type: textarea 30 | id: steps-to-reproduce 31 | attributes: 32 | label: Steps to reproduce 33 | validations: 34 | required: true 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yaml: -------------------------------------------------------------------------------- 1 | name: "Feature request" 2 | description: Feature request template 3 | title: "Feature request: short description" 4 | labels: ["feature request", "triage"] 5 | body: 6 | - type: textarea 7 | id: desired-behavior 8 | attributes: 9 | label: Feature request description/rationale 10 | validations: 11 | required: true 12 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | on: 3 | push: 4 | branches: 5 | - 'main' 6 | - 'development' 7 | pull_request: 8 | branches: 9 | - 'main' 10 | - 'development' 11 | workflow_dispatch: 12 | jobs: 13 | build: 14 | name: Build, lint, and test on Node ${{ matrix.node-version }} and ${{ matrix.os }} 15 | 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | matrix: 19 | os: [macOS-latest, ubuntu-latest] 20 | node-version: ['18', '20', '22'] 21 | 22 | steps: 23 | - name: Checkout repo 24 | uses: actions/checkout@v4 25 | 26 | - uses: actions/setup-node@v4 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | cache: 'npm' 30 | 31 | - name: Install dependencies 32 | run: | 33 | npm cache clean --force 34 | npm set registry https://registry.npmjs.org/ 35 | npm ci 36 | 37 | - name: Check formatting 38 | run: npm run pretty:check 39 | 40 | - name: Lint 41 | run: npm run lint 42 | 43 | - name: Build 44 | run: npm run build 45 | 46 | - name: Test 47 | run: npm test -- --coverage=true 48 | 49 | - name: Upload coverage reports to Codecov 50 | uses: codecov/codecov-action@v4 51 | env: 52 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 53 | 54 | test: 55 | name: Run integration test using keria:${{ matrix.keria-version }} 56 | runs-on: ${{ matrix.os }} 57 | strategy: 58 | matrix: 59 | os: ['ubuntu-latest'] 60 | keria-version: ['0.2.0-dev6'] 61 | node-version: ['20'] 62 | env: 63 | KERIA_IMAGE_TAG: ${{ matrix.keria-version }} 64 | steps: 65 | - name: Checkout repo 66 | uses: actions/checkout@v4 67 | 68 | - uses: actions/setup-node@v4 69 | with: 70 | node-version: ${{ matrix.node-version }} 71 | cache: 'npm' 72 | - name: install deps 73 | run: npm ci 74 | - name: Build 75 | run: npm run build 76 | - name: Print docker compose config 77 | run: docker compose config 78 | - name: Start dependencies 79 | run: docker compose up --wait --pull always 80 | - name: Run integration test 81 | run: npm run test:integration 82 | - name: Print logs 83 | run: docker compose logs 84 | if: always() 85 | 86 | audit: 87 | name: Find vulnerabilities 88 | runs-on: ubuntu-latest 89 | steps: 90 | - name: Checkout repo 91 | uses: actions/checkout@v4 92 | 93 | - uses: actions/setup-node@v4 94 | with: 95 | node-version: '20' 96 | cache: 'npm' 97 | - name: Run audit 98 | run: npm audit 99 | -------------------------------------------------------------------------------- /.github/workflows/publish-npm.yml: -------------------------------------------------------------------------------- 1 | name: Publish NPM 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | dist-tag: 7 | type: choice 8 | options: 9 | - dev 10 | - latest 11 | default: dev 12 | description: Npm dist tag 13 | jobs: 14 | publish: 15 | name: Publish NPM 16 | permissions: 17 | contents: write 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout repo 21 | uses: actions/checkout@v4 22 | 23 | - uses: actions/setup-node@v4 24 | with: 25 | node-version: '20' 26 | cache: 'npm' 27 | registry-url: 'https://registry.npmjs.org' 28 | 29 | - name: Publish package 30 | env: 31 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 32 | NPM_PUBLISH_TAG: ${{ inputs.dist-tag }} 33 | NPM_PACKAGE_SCOPE: ${{ vars.NPM_PACKAGE_SCOPE }} 34 | run: ./publish.sh 35 | -------------------------------------------------------------------------------- /.github/workflows/update-documentation.yml: -------------------------------------------------------------------------------- 1 | name: Publish Documentation 2 | on: 3 | push: 4 | branches: 5 | - 'main' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | 13 | jobs: 14 | build: 15 | name: Build docs 16 | 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | - name: Setup Node 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: "20" 26 | cache: "npm" 27 | - name: Install dependencies 28 | run: | 29 | npm set registry https://registry.npmjs.org/ 30 | npm ci 31 | - name: Build documentation 32 | run: npx typedoc src/index.ts 33 | - name: Upload artifact 34 | uses: actions/upload-pages-artifact@v3 35 | with: 36 | path: ./docs 37 | 38 | deploy: 39 | environment: 40 | name: github-pages 41 | url: ${{ steps.deployment.outputs.page_url }} 42 | runs-on: ubuntu-latest 43 | needs: build 44 | steps: 45 | - name: Deploy to GitHub Pages 46 | id: deployment 47 | uses: actions/deploy-pages@v4 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | 6 | 7 | # IntelliJ Project Files 8 | .idea 9 | coverage/* 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | diagrams/ 2 | docs/ 3 | package.json 4 | package-lock.json 5 | .github/ 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "es5", 4 | "singleQuote": true, 5 | "printWidth": 80, 6 | "tabWidth": 4 7 | } 8 | -------------------------------------------------------------------------------- /codecov.yaml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: 55 6 | paths: ['src'] 7 | patch: 8 | default: 9 | target: 75 10 | paths: ['src'] 11 | -------------------------------------------------------------------------------- /config/keria.json: -------------------------------------------------------------------------------- 1 | { 2 | "dt": "2023-12-01T10:05:25.062609+00:00", 3 | "keria": { 4 | "dt": "2023-12-01T10:05:25.062609+00:00", 5 | "curls": ["http://keria:3902/"] 6 | }, 7 | "iurls": [ 8 | "http://witness-demo:5642/oobi/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha/controller", 9 | "http://witness-demo:5643/oobi/BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM/controller", 10 | "http://witness-demo:5644/oobi/BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX/controller", 11 | "http://witness-demo:5645/oobi/BM35JN8XeJSEfpxopjn5jr7tAHCE5749f0OobhMLCorE/controller", 12 | "http://witness-demo:5646/oobi/BIj15u5V11bkbtAxMA7gcNJZcax-7TgaBMLsQnMHpYHP/controller", 13 | "http://witness-demo:5647/oobi/BF2rZTW79z4IXocYRQnjjsOuvFUQv-ptCf8Yltd7PfsM/controller" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /config/witness-demo/wan.json: -------------------------------------------------------------------------------- 1 | { 2 | "dt": "2022-01-20T12:57:59.823350+00:00", 3 | "wan": { 4 | "dt": "2022-01-20T12:57:59.823350+00:00", 5 | "curls": ["tcp://witness-demo:5632/", "http://witness-demo:5642/"] 6 | }, 7 | "iurls": [] 8 | } 9 | -------------------------------------------------------------------------------- /config/witness-demo/wes.json: -------------------------------------------------------------------------------- 1 | { 2 | "wes": { 3 | "dt": "2022-01-20T12:57:59.823350+00:00", 4 | "curls": ["tcp://witness-demo:5634/", "http://witness-demo:5644/"] 5 | }, 6 | "dt": "2022-01-20T12:57:59.823350+00:00", 7 | "iurls": [] 8 | } 9 | -------------------------------------------------------------------------------- /config/witness-demo/wil.json: -------------------------------------------------------------------------------- 1 | { 2 | "wil": { 3 | "dt": "2022-01-20T12:57:59.823350+00:00", 4 | "curls": ["tcp://witness-demo:5633/", "http://witness-demo:5643/"] 5 | }, 6 | "dt": "2022-01-20T12:57:59.823350+00:00", 7 | "iurls": [] 8 | } 9 | -------------------------------------------------------------------------------- /config/witness-demo/wit.json: -------------------------------------------------------------------------------- 1 | { 2 | "wit": { 3 | "dt": "2022-01-20T12:57:59.823350+00:00", 4 | "curls": ["tcp://witness-demo:5635/", "http://witness-demo:5645/"] 5 | }, 6 | "dt": "2022-01-20T12:57:59.823350+00:00", 7 | "iurls": [] 8 | } 9 | -------------------------------------------------------------------------------- /config/witness-demo/wub.json: -------------------------------------------------------------------------------- 1 | { 2 | "wub": { 3 | "dt": "2022-01-20T12:57:59.823350+00:00", 4 | "curls": ["tcp://witness-demo:5636/", "http://witness-demo:5646/"] 5 | }, 6 | "dt": "2022-01-20T12:57:59.823350+00:00", 7 | "iurls": [] 8 | } 9 | -------------------------------------------------------------------------------- /config/witness-demo/wyz.json: -------------------------------------------------------------------------------- 1 | { 2 | "wyz": { 3 | "dt": "2022-01-20T12:57:59.823350+00:00", 4 | "curls": ["tcp://witness-demo:5637/", "http://witness-demo:5647/"] 5 | }, 6 | "dt": "2022-01-20T12:57:59.823350+00:00", 7 | "iurls": [] 8 | } 9 | -------------------------------------------------------------------------------- /diagrams/account-creation-webpage-workflow.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | actor u as User 3 | participant a as Web Page App 4 | participant s as Signify 5 | participant c as Cloud Agent 6 | u ->> a: Create Account 7 | a ->>+ s: Generate Pub/Pri Keys 8 | s ->>- a: Return new Pub/Pri Keypair 9 | a ->>+ c: Request ICP Event Creation with Keys 10 | note over s,c: This call can not be secured 11 | note right of c: Creates ICP Event 12 | c ->>- a: Return ICP event 13 | a ->>+ s: Sign ICP Event 14 | s ->>- a: Return Signed Event 15 | a ->>+ c: Create Account with Signed ICP Event 16 | note over s,c: Call Signed by new Keys 17 | note right of c: Parses and Saves ICP 18 | note right of c: Create Account with new AID 19 | c ->>- a: Return New Account KeyState 20 | a ->>+ s: Save Key Information 21 | s ->>+ c: Save Key Information 22 | c ->>- s: Key Information Saved 23 | a ->> u: Return New Account Information -------------------------------------------------------------------------------- /diagrams/account-creation-webpage-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebOfTrust/signify-ts/78d0a694522b4a7ce3c8a77de417d96ed739397f/diagrams/account-creation-webpage-workflow.png -------------------------------------------------------------------------------- /diagrams/account-creation-workflow.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | actor u as User 3 | participant s as Signify 4 | participant c as Cloud Agent 5 | u ->> s: Create Account 6 | s ->> s: Generate Pub/Pri Keys 7 | s ->>+ c: Request ICP Event Creation with Keys 8 | note over s,c: This call can not be secured 9 | note right of c: Creates ICP Event 10 | c ->>- s: Return ICP event 11 | s ->>+ c: Create Account with Signed ICP Event 12 | note over s,c: Call Signed by new Keys 13 | note right of c: Parses and Saves ICP 14 | note right of c: Create Account with new AID 15 | c ->>- s: Return New Account KeyState 16 | s ->>+ c: Save Key Information 17 | c ->>- s: Key Information Saved 18 | s ->> u: Return New Account Information -------------------------------------------------------------------------------- /diagrams/account-creation-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebOfTrust/signify-ts/78d0a694522b4a7ce3c8a77de417d96ed739397f/diagrams/account-creation-workflow.png -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | x-healthcheck: &healthcheck 2 | interval: 2s 3 | timeout: 3s 4 | retries: 5 5 | start_period: 2s 6 | 7 | x-python-env: &python-env 8 | PYTHONUNBUFFERED: 1 9 | PYTHONIOENCODING: UTF-8 10 | 11 | services: 12 | vlei-server: 13 | image: gleif/vlei 14 | environment: 15 | <<: *python-env 16 | command: vLEI-server -s ./schema/acdc -c ./samples/acdc/ -o ./samples/oobis/ 17 | healthcheck: 18 | test: curl -f http://localhost:7723/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao 19 | <<: *healthcheck 20 | ports: 21 | - 7723:7723 22 | 23 | keria: 24 | image: ${KERIA_IMAGE:-weboftrust/keria}:${KERIA_IMAGE_TAG:-0.2.0-rc1} 25 | environment: 26 | KERI_AGENT_CORS: 1 27 | <<: *python-env 28 | volumes: 29 | - ./config/keria.json:/keria/config/keri/cf/keria.json 30 | command: start --config-dir /keria/config --config-file keria --name agent 31 | healthcheck: 32 | test: wget --spider http://keria:3902/spec.yaml 33 | <<: *healthcheck 34 | ports: 35 | - 3901:3901 36 | - 3902:3902 37 | - 3903:3903 38 | 39 | witness-demo: 40 | image: weboftrust/keri-witness-demo:1.1.0 41 | environment: 42 | <<: *python-env 43 | healthcheck: 44 | test: curl -f http://localhost:5642/oobi 45 | <<: *healthcheck 46 | volumes: 47 | - ./config/witness-demo:/keripy/scripts/keri/cf/main 48 | ports: 49 | - 5642:5642 50 | - 5643:5643 51 | - 5644:5644 52 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import eslint from '@eslint/js'; 4 | import tseslint from 'typescript-eslint'; 5 | import prettier from 'eslint-config-prettier'; 6 | 7 | export default tseslint.config( 8 | eslint.configs.recommended, 9 | tseslint.configs.recommended, 10 | { rules: prettier.rules }, 11 | { 12 | // These are files with more lenient lint config because they have not been "fixed" yet 13 | // Once a directory here is fixed, it should be removed from here so the strict rules applies 14 | files: ['src/keri/app/**', 'src/keri/core/**', 'test-integration/**'], 15 | rules: { 16 | 'prefer-const': 'warn', 17 | 'no-var': 'warn', 18 | 'no-self-assign': 'warn', 19 | 'no-case-declarations': 'warn', 20 | 'no-constant-condition': 'warn', 21 | 'no-empty': 'warn', 22 | '@typescript-eslint/no-non-null-asserted-optional-chain': 'warn', 23 | '@typescript-eslint/no-explicit-any': 'warn', 24 | '@typescript-eslint/no-namespace': 'warn', 25 | // '@typescript-eslint/ban-types': 'warn', 26 | '@typescript-eslint/no-unused-vars': 'warn', 27 | }, 28 | } 29 | ); 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "signify-ts", 3 | "version": "0.3.0-rc1", 4 | "description": "Signing at the edge for KERI, ACDC, and KERIA", 5 | "keywords": [ 6 | "keri", 7 | "acdc", 8 | "keria", 9 | "signify", 10 | "signify-ts", 11 | "decentralized identity", 12 | "authentic data", 13 | "zero trust architecture" 14 | ], 15 | "author": "Phil Feairheller", 16 | "homepage": "https://github.com/WebOfTrust/signify-ts", 17 | "repo": { 18 | "type": "git", 19 | "url": "git+https://github.com/WebOfTrust/signify-ts.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/WebOfTrust/signify-ts/issues" 23 | }, 24 | "license": "Apache-2.0", 25 | "exports": { 26 | "import": "./dist/index.js" 27 | }, 28 | "type": "module", 29 | "files": [ 30 | "dist" 31 | ], 32 | "scripts": { 33 | "start": "npm run build -- --watch", 34 | "build": "tsc -p tsconfig.build.json && tsc -p tsconfig.json --noEmit", 35 | "test": "vitest", 36 | "prepare": "tsc -p tsconfig.build.json", 37 | "test:integration": "vitest -c vitest.integration.ts", 38 | "lint": "eslint src test test-integration", 39 | "generate-docs": "typedoc src/index.ts", 40 | "pretty": "prettier --write .", 41 | "pretty:check": "prettier --check ." 42 | }, 43 | "devDependencies": { 44 | "@eslint/js": "^9.22.0", 45 | "@types/libsodium-wrappers-sumo": "^0.7.5", 46 | "@types/node": "^22.13.11", 47 | "@vitest/coverage-v8": "^3.0.9", 48 | "bip39": "^3.1.0", 49 | "eslint": "^9.22.0", 50 | "eslint-config-prettier": "^10.1.1", 51 | "minami": "^1.2.3", 52 | "prettier": "^3.5.3", 53 | "ts-mockito": "^2.6.1", 54 | "typedoc": "^0.28.0", 55 | "typescript": "^5.8.2", 56 | "typescript-eslint": "^8.27.0", 57 | "vite-tsconfig-paths": "^5.1.4", 58 | "vitest": "^3.0.9" 59 | }, 60 | "dependencies": { 61 | "@noble/curves": "^1.8.1", 62 | "@noble/hashes": "^1.3.2", 63 | "base64-js": "^1.5.1", 64 | "libsodium-wrappers-sumo": "^0.7.9", 65 | "mathjs": "^12.4.0", 66 | "structured-headers": "^0.5.0" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | package_info=$(cat package.json) 5 | scope=${NPM_PACKAGE_SCOPE:-$(echo "$package_info" | jq .name -r | grep '/' | cut -d'/' -f1)} 6 | name=${NPM_PACKAGE_NAME:-$(echo "$package_info" | jq .name -r | cut -d'/' -f2)} 7 | 8 | version=$(echo "$package_info" | jq .version -r) 9 | tag=${NPM_PUBLISH_TAG:-dev} 10 | 11 | if [ "$scope" != "" ]; then 12 | name="${scope}/${name}" 13 | fi 14 | 15 | if [ "$tag" = "dev" ]; then 16 | version="${version}-dev.$(git rev-parse --short HEAD)" 17 | fi 18 | 19 | npm ci 20 | npm run build 21 | 22 | # Creating a temporary directory for publishing. 23 | # 24 | # This allows us to modify the version and name of the published package 25 | # without having to commit changes to the repo. Which is useful for tagged 26 | # and scoped package releases. 27 | publish_dir="$(mktemp -d)" 28 | cp -r README.md LICENSE dist package.json package-lock.json "${publish_dir}/" 29 | jq ".version = \"${version}\" | .name = \"${name}\" | del(.scripts.prepare)" package.json > "${publish_dir}/package.json" 30 | 31 | if [ -z "$DRY_RUN" ]; then 32 | npm publish "${publish_dir}" --tag "${tag}" 33 | else 34 | npm publish "${publish_dir}" --tag "${tag}" --dry-run 35 | fi 36 | -------------------------------------------------------------------------------- /src/exports.ts: -------------------------------------------------------------------------------- 1 | export * from './ready.ts'; 2 | 3 | export * from './keri/app/habery.ts'; 4 | export * from './keri/app/controller.ts'; 5 | 6 | export * from './keri/app/aiding.ts'; 7 | export * from './keri/app/clienting.ts'; 8 | export * from './keri/app/contacting.ts'; 9 | export * from './keri/app/coring.ts'; 10 | export * from './keri/app/credentialing.ts'; 11 | export * from './keri/app/escrowing.ts'; 12 | export * from './keri/app/exchanging.ts'; 13 | export * from './keri/app/grouping.ts'; 14 | export * from './keri/app/notifying.ts'; 15 | 16 | export * from './keri/core/authing.ts'; 17 | export * from './keri/core/cigar.ts'; 18 | export * from './keri/core/cipher.ts'; 19 | export * from './keri/core/core.ts'; 20 | export * from './keri/core/counter.ts'; 21 | export * from './keri/core/decrypter.ts'; 22 | export * from './keri/core/diger.ts'; 23 | export * from './keri/core/encrypter.ts'; 24 | export * from './keri/core/eventing.ts'; 25 | export * from './keri/core/httping.ts'; 26 | export * from './keri/core/indexer.ts'; 27 | export * from './keri/core/keeping.ts'; 28 | export * from './keri/core/kering.ts'; 29 | export * from './keri/core/manager.ts'; 30 | export * from './keri/core/matter.ts'; 31 | export * from './keri/core/number.ts'; 32 | export * from './keri/core/prefixer.ts'; 33 | export * from './keri/core/saider.ts'; 34 | export * from './keri/core/salter.ts'; 35 | export * from './keri/core/seqner.ts'; 36 | export * from './keri/core/serder.ts'; 37 | export * from './keri/core/siger.ts'; 38 | export * from './keri/core/signer.ts'; 39 | export * from './keri/core/tholder.ts'; 40 | export * from './keri/core/utils.ts'; 41 | export * from './keri/core/verfer.ts'; 42 | export * from './keri/core/keyState.ts'; 43 | 44 | export * from './keri/end/ending.ts'; 45 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as exp from './exports.ts'; 2 | export * from './exports.ts'; 3 | export default exp; 4 | -------------------------------------------------------------------------------- /src/keri/app/delegating.ts: -------------------------------------------------------------------------------- 1 | import { EventResult } from './aiding.ts'; 2 | import { SignifyClient } from './clienting.ts'; 3 | 4 | export class Delegations { 5 | public client: SignifyClient; 6 | /** 7 | * Delegations 8 | * @param {SignifyClient} client 9 | */ 10 | constructor(client: SignifyClient) { 11 | this.client = client; 12 | } 13 | 14 | /** 15 | * Approve the delegation via interaction event 16 | * @async 17 | * @param {string} name Name or alias of the identifier 18 | * @param {any} [data] The anchoring interaction event 19 | * @returns {Promise} A promise to the delegated approval result 20 | */ 21 | async approve(name: string, data?: any): Promise { 22 | const { serder, sigs, jsondata } = await this.client 23 | .identifiers() 24 | .createInteract(name, data); 25 | 26 | const res = await this.client.fetch( 27 | '/identifiers/' + name + '/delegation', 28 | 'POST', 29 | jsondata 30 | ); 31 | return new EventResult(serder, sigs, res); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/keri/app/escrowing.ts: -------------------------------------------------------------------------------- 1 | import { SignifyClient } from './clienting.ts'; 2 | 3 | /** 4 | * Escrows 5 | */ 6 | export class Escrows { 7 | client: SignifyClient; 8 | 9 | /** 10 | * Escrows 11 | * @param {SignifyClient} client 12 | */ 13 | constructor(client: SignifyClient) { 14 | this.client = client; 15 | } 16 | 17 | /** 18 | * List replay messages 19 | * @async 20 | * @param {string} [route] Optional route in the replay message 21 | * @returns {Promise} A promise to the list of replay messages 22 | */ 23 | async listReply(route?: string): Promise { 24 | const params = new URLSearchParams(); 25 | if (route !== undefined) { 26 | params.append('route', route); 27 | } 28 | 29 | const path = `/escrows/rpy` + '?' + params.toString(); 30 | const method = 'GET'; 31 | const res = await this.client.fetch(path, method, null); 32 | return await res.json(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/keri/app/grouping.ts: -------------------------------------------------------------------------------- 1 | import { SignifyClient } from './clienting.ts'; 2 | import { Dict } from '../core/core.ts'; 3 | 4 | /** 5 | * Groups 6 | */ 7 | export class Groups { 8 | client: SignifyClient; 9 | 10 | /** 11 | * Groups 12 | * @param {SignifyClient} client 13 | */ 14 | constructor(client: SignifyClient) { 15 | this.client = client; 16 | } 17 | 18 | /** 19 | * Get group request messages 20 | * @async 21 | * @param {string} [said] SAID of exn message to load 22 | * @returns {Promise} A promise to the list of replay messages 23 | */ 24 | async getRequest(said: string): Promise { 25 | const path = `/multisig/request/` + said; 26 | const method = 'GET'; 27 | const res = await this.client.fetch(path, method, null); 28 | return await res.json(); 29 | } 30 | 31 | /** 32 | * Send multisig exn request messages to other group members 33 | * @async 34 | * @param {string} [name] human readable name of group AID 35 | * @param {Dict} [exn] exn message to send to other members 36 | * @param {string[]} [sigs] signature of the participant over the exn 37 | * @param {string} [atc] additional attachments from embedded events in exn 38 | * @returns {Promise} A promise to the list of replay messages 39 | */ 40 | async sendRequest( 41 | name: string, 42 | exn: Dict, 43 | sigs: string[], 44 | atc: string 45 | ): Promise { 46 | const path = `/identifiers/${name}/multisig/request`; 47 | const method = 'POST'; 48 | const data = { 49 | exn: exn, 50 | sigs: sigs, 51 | atc: atc, 52 | }; 53 | const res = await this.client.fetch(path, method, data); 54 | return await res.json(); 55 | } 56 | 57 | /** 58 | * Join multisig group using rotation event. 59 | * This can be used by participants being asked to contribute keys to a rotation event to join an existing group. 60 | * @async 61 | * @param {string} [name] human readable name of group AID 62 | * @param {any} [rot] rotation event 63 | * @param {any} [sigs] signatures 64 | * @param {string} [gid] prefix 65 | * @param {string[]} [smids] array of particpants 66 | * @param {string[]} [rmids] array of particpants 67 | * @returns {Promise} A promise to the list of replay messages 68 | */ 69 | async join( 70 | name: string, 71 | rot: any, 72 | sigs: any, //string[], 73 | gid: string, 74 | smids: string[], 75 | rmids: string[] 76 | ): Promise { 77 | const path = `/identifiers/${name}/multisig/join`; 78 | const method = 'POST'; 79 | const data = { 80 | tpc: 'multisig', 81 | rot: rot.sad, 82 | sigs: sigs, 83 | gid: gid, 84 | smids: smids, 85 | rmids: rmids, 86 | }; 87 | const res = await this.client.fetch(path, method, data); 88 | return await res.json(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/keri/app/habery.ts: -------------------------------------------------------------------------------- 1 | import { Algos, Manager } from '../core/manager.ts'; 2 | import { MtrDex } from '../core/matter.ts'; 3 | import { Salter } from '../core/salter.ts'; 4 | import { Verfer } from '../core/verfer.ts'; 5 | import { Diger } from '../core/diger.ts'; 6 | import { incept } from '../core/eventing.ts'; 7 | import { Serder } from '../core/serder.ts'; 8 | 9 | export class TraitCodex { 10 | EstOnly: string = 'EO'; // Only allow establishment events 11 | DoNotDelegate: string = 'DND'; // Dot not allow delegated identifiers 12 | NoBackers: string = 'NB'; // Do not allow backers 13 | } 14 | 15 | export const TraitDex = new TraitCodex(); 16 | 17 | export interface HaberyArgs { 18 | name: string; 19 | passcode?: string; 20 | seed?: string | undefined; 21 | aeid?: string | undefined; 22 | pidx?: number | undefined; 23 | salt?: string | undefined; 24 | tier?: string | undefined; 25 | } 26 | 27 | export interface MakeHabArgs { 28 | code?: string; 29 | transferable?: boolean; 30 | isith?: string; 31 | icount?: number; 32 | nsith?: string; 33 | ncount?: number; 34 | toad?: string | number; 35 | wits?: Array; 36 | delpre?: string; 37 | estOnly?: boolean; 38 | DnD?: boolean; 39 | data?: any; 40 | } 41 | 42 | export class Hab { 43 | public name: string; 44 | public serder: Serder; 45 | 46 | constructor(name: string, icp: Serder) { 47 | this.name = name; 48 | this.serder = icp; 49 | } 50 | 51 | get pre(): string { 52 | return this.serder.sad['i']; 53 | } 54 | } 55 | 56 | export class Habery { 57 | private readonly _name: string; 58 | private readonly _mgr: Manager; 59 | private readonly _habs: Map = new Map(); 60 | 61 | constructor({ name, passcode, seed, aeid, pidx, salt }: HaberyArgs) { 62 | this._name = name; 63 | if (passcode != undefined && seed == undefined) { 64 | if (passcode.length < 21) { 65 | throw new Error('Bran (passcode seed material) too short.'); 66 | } 67 | 68 | const bran = MtrDex.Salt_128 + 'A' + passcode.substring(0, 21); // qb64 salt for seed 69 | const signer = new Salter({ qb64: bran }).signer( 70 | MtrDex.Ed25519_Seed, 71 | false 72 | ); 73 | seed = signer.qb64; 74 | if (aeid == undefined) { 75 | aeid = signer.verfer.qb64; // lest it remove encryption 76 | } 77 | } 78 | let algo; 79 | const salter = 80 | salt != undefined ? new Salter({ qb64: salt }) : undefined; 81 | if (salt != undefined) { 82 | algo = Algos.salty; 83 | } else { 84 | algo = Algos.randy; 85 | } 86 | 87 | this._mgr = new Manager({ 88 | seed: seed, 89 | aeid: aeid, 90 | pidx: pidx, 91 | algo: algo, 92 | salter: salter, 93 | }); 94 | } 95 | 96 | get mgr(): Manager { 97 | return this._mgr; 98 | } 99 | 100 | get habs(): Array { 101 | return Array.from(this._habs.values()); 102 | } 103 | 104 | habByName(name: string): Hab | undefined { 105 | return this._habs.get(name); 106 | } 107 | 108 | makeHab( 109 | name: string, 110 | { 111 | code = MtrDex.Blake3_256, 112 | transferable = true, 113 | isith = undefined, 114 | icount = 1, 115 | nsith = undefined, 116 | ncount = undefined, 117 | toad = undefined, 118 | wits = undefined, 119 | delpre = undefined, 120 | estOnly = false, 121 | DnD = false, 122 | data = undefined, 123 | }: MakeHabArgs 124 | ): Hab { 125 | if (nsith == undefined) { 126 | nsith = isith; 127 | } 128 | if (ncount == undefined) { 129 | ncount = icount; 130 | } 131 | if (!transferable) { 132 | ncount = 0; 133 | nsith = '0'; 134 | code = MtrDex.Ed25519N; 135 | } 136 | 137 | const [verfers, digers] = this._mgr.incept({ 138 | icount: icount, 139 | ncount: ncount, 140 | stem: this.name, 141 | transferable: transferable, 142 | temp: false, 143 | }); 144 | 145 | icount = verfers.length; 146 | ncount = digers != undefined ? digers.length : 0; 147 | if (isith == undefined) { 148 | isith = `${Math.max(1, Math.ceil(icount / 2)).toString(16)}`; 149 | } 150 | if (nsith == undefined) { 151 | nsith = `${Math.max(1, Math.ceil(ncount / 2)).toString(16)}`; 152 | } 153 | 154 | const cnfg = new Array(); 155 | if (estOnly) { 156 | cnfg.push(TraitDex.EstOnly); 157 | } 158 | if (DnD) { 159 | cnfg.push(TraitDex.DoNotDelegate); 160 | } 161 | 162 | const keys = Array.from(verfers, (verfer: Verfer) => verfer.qb64); 163 | const ndigs = Array.from(digers, (diger: Diger) => diger.qb64); 164 | 165 | const icp = incept({ 166 | keys, 167 | isith, 168 | ndigs, 169 | nsith, 170 | toad, 171 | wits, 172 | cnfg, 173 | data, 174 | code, 175 | delpre, 176 | }); 177 | const hab = new Hab(name, icp); 178 | this._habs.set(name, hab); 179 | return hab; 180 | } 181 | 182 | get name(): string { 183 | return this._name; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/keri/app/notifying.ts: -------------------------------------------------------------------------------- 1 | import { SignifyClient } from './clienting.ts'; 2 | import { parseRangeHeaders } from '../core/httping.ts'; 3 | 4 | /** 5 | * Notifications 6 | */ 7 | export class Notifications { 8 | client: SignifyClient; 9 | 10 | /** 11 | * Notifications 12 | * @param {SignifyClient} client 13 | */ 14 | constructor(client: SignifyClient) { 15 | this.client = client; 16 | } 17 | 18 | /** 19 | * List notifications 20 | * @async 21 | * @param {number} [start=0] Start index of list of notifications, defaults to 0 22 | * @param {number} [end=24] End index of list of notifications, defaults to 24 23 | * @returns {Promise} A promise to the list of notifications 24 | */ 25 | async list(start: number = 0, end: number = 24): Promise { 26 | const extraHeaders = new Headers(); 27 | extraHeaders.append('Range', `notes=${start}-${end}`); 28 | 29 | const path = `/notifications`; 30 | const method = 'GET'; 31 | const res = await this.client.fetch(path, method, null, extraHeaders); 32 | 33 | const cr = res.headers.get('content-range'); 34 | const range = parseRangeHeaders(cr, 'notes'); 35 | const notes = await res.json(); 36 | 37 | return { 38 | start: range.start, 39 | end: range.end, 40 | total: range.total, 41 | notes: notes, 42 | }; 43 | } 44 | 45 | /** 46 | * Mark a notification as read 47 | * @async 48 | * @param {string} said SAID of the notification 49 | * @returns {Promise} A promise to the result of the marking 50 | */ 51 | async mark(said: string): Promise { 52 | const path = `/notifications/` + said; 53 | const method = 'PUT'; 54 | const res = await this.client.fetch(path, method, null); 55 | return await res.text(); 56 | } 57 | 58 | /** 59 | * Delete a notification 60 | * @async 61 | * @param {string} said SAID of the notification 62 | * @returns {Promise} 63 | */ 64 | async delete(said: string): Promise { 65 | const path = `/notifications/` + said; 66 | const method = 'DELETE'; 67 | await this.client.fetch(path, method, undefined); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/keri/core/authing.ts: -------------------------------------------------------------------------------- 1 | import { Signer } from './signer.ts'; 2 | import { Verfer } from './verfer.ts'; 3 | import { 4 | desiginput, 5 | HEADER_SIG_INPUT, 6 | HEADER_SIG_TIME, 7 | normalize, 8 | siginput, 9 | } from './httping.ts'; 10 | import { Signage, signature, designature } from '../end/ending.ts'; 11 | import { Cigar } from './cigar.ts'; 12 | import { Siger } from './siger.ts'; 13 | export class Authenticater { 14 | static DefaultFields = [ 15 | '@method', 16 | '@path', 17 | 'signify-resource', 18 | HEADER_SIG_TIME.toLowerCase(), 19 | ]; 20 | private _verfer: Verfer; 21 | private readonly _csig: Signer; 22 | 23 | constructor(csig: Signer, verfer: Verfer) { 24 | this._csig = csig; 25 | this._verfer = verfer; 26 | } 27 | 28 | verify(headers: Headers, method: string, path: string): boolean { 29 | const siginput = headers.get(HEADER_SIG_INPUT); 30 | if (siginput == null) { 31 | return false; 32 | } 33 | const signature = headers.get('Signature'); 34 | if (signature == null) { 35 | return false; 36 | } 37 | let inputs = desiginput(siginput); 38 | inputs = inputs.filter((input) => input.name == 'signify'); 39 | if (inputs.length == 0) { 40 | return false; 41 | } 42 | inputs.forEach((input) => { 43 | const items = new Array(); 44 | input.fields!.forEach((field: string) => { 45 | if (field.startsWith('@')) { 46 | if (field == '@method') { 47 | items.push(`"${field}": ${method}`); 48 | } else if (field == '@path') { 49 | items.push(`"${field}": ${path}`); 50 | } 51 | } else { 52 | if (headers.has(field)) { 53 | const value = normalize(headers.get(field) as string); 54 | items.push(`"${field}": ${value}`); 55 | } 56 | } 57 | }); 58 | const values = new Array(); 59 | values.push(`(${input.fields!.join(' ')})`); 60 | values.push(`created=${input.created}`); 61 | if (input.expires != undefined) { 62 | values.push(`expires=${input.expires}`); 63 | } 64 | if (input.nonce != undefined) { 65 | values.push(`nonce=${input.nonce}`); 66 | } 67 | if (input.keyid != undefined) { 68 | values.push(`keyid=${input.keyid}`); 69 | } 70 | if (input.context != undefined) { 71 | values.push(`context=${input.context}`); 72 | } 73 | if (input.alg != undefined) { 74 | values.push(`alg=${input.alg}`); 75 | } 76 | const params = values.join(';'); 77 | items.push(`"@signature-params: ${params}"`); 78 | const ser = items.join('\n'); 79 | const signage = designature(signature!); 80 | const markers = signage[0].markers as Map; 81 | const cig = markers.get(input.name); 82 | if (!cig || !this._verfer.verify(cig.raw, ser)) { 83 | throw new Error(`Signature for ${input.keyid} invalid.`); 84 | } 85 | }); 86 | 87 | return true; 88 | } 89 | 90 | sign( 91 | headers: Headers, 92 | method: string, 93 | path: string, 94 | fields?: Array 95 | ): Headers { 96 | if (fields == undefined) { 97 | fields = Authenticater.DefaultFields; 98 | } 99 | 100 | const [header, sig] = siginput(this._csig, { 101 | name: 'signify', 102 | method, 103 | path, 104 | headers, 105 | fields, 106 | alg: 'ed25519', 107 | keyid: this._csig.verfer.qb64, 108 | }); 109 | 110 | header.forEach((value, key) => { 111 | headers.append(key, value); 112 | }); 113 | 114 | const markers = new Map(); 115 | markers.set('signify', sig); 116 | const signage = new Signage(markers, false); 117 | const signed = signature([signage]); 118 | signed.forEach((value, key) => { 119 | headers.append(key, value); 120 | }); 121 | 122 | return headers; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/keri/core/base64.ts: -------------------------------------------------------------------------------- 1 | import { fromByteArray, toByteArray } from 'base64-js'; 2 | 3 | export function encodeBase64Url(input: Uint8Array): string { 4 | return fromByteArray(input) 5 | .replace(/\+/g, '-') 6 | .replace(/\//g, '_') 7 | .replace(/=+/, ''); 8 | } 9 | 10 | export function decodeBase64Url(input: string): Uint8Array { 11 | if (!(typeof input === 'string')) { 12 | throw new TypeError('`input` must be a string.'); 13 | } 14 | 15 | const n = input.length % 4; 16 | const padded = input + '='.repeat(n > 0 ? 4 - n : n); 17 | const base64String = padded.replace(/-/g, '+').replace(/_/g, '/'); 18 | return toByteArray(base64String); 19 | } 20 | -------------------------------------------------------------------------------- /src/keri/core/bexter.ts: -------------------------------------------------------------------------------- 1 | import { BexDex, Matter, MatterArgs, MtrDex } from './matter.ts'; 2 | import { EmptyMaterialError } from './kering.ts'; 3 | import { decodeBase64Url, encodeBase64Url } from './base64.ts'; 4 | import { concat } from './core.ts'; 5 | 6 | const B64REX = '^[A-Za-z0-9\\-_]*$'; 7 | export const Reb64 = new RegExp(B64REX); 8 | 9 | /* 10 | 11 | Bexter is subclass of Matter, cryptographic material, for variable length 12 | strings that only contain Base64 URL safe characters, i.e. Base64 text (bext). 13 | When created using the 'bext' paramaeter, the encoded matter in qb64 format 14 | in the text domain is more compact than would be the case if the string were 15 | passed in as raw bytes. The text is used as is to form the value part of the 16 | qb64 version not including the leader. 17 | 18 | Due to ambiguity that arises from pre-padding bext whose length is a multiple of 19 | three with one or more 'A' chars. Any bext that starts with an 'A' and whose length 20 | is either a multiple of 3 or 4 may not round trip. Bext with a leading 'A' 21 | whose length is a multiple of four may have the leading 'A' stripped when 22 | round tripping. 23 | 24 | Bexter(bext='ABBB').bext == 'BBB' 25 | Bexter(bext='BBB').bext == 'BBB' 26 | Bexter(bext='ABBB').qb64 == '4AABABBB' == Bexter(bext='BBB').qb64 27 | 28 | To avoid this problem, only use for applications of base 64 strings that 29 | never start with 'A' 30 | 31 | Examples: base64 text strings: 32 | 33 | bext = "" 34 | qb64 = '4AAA' 35 | 36 | bext = "-" 37 | qb64 = '6AABAAA-' 38 | 39 | bext = "-A" 40 | qb64 = '5AABAA-A' 41 | 42 | bext = "-A-" 43 | qb64 = '4AABA-A-' 44 | 45 | bext = "-A-B" 46 | qb64 = '4AAB-A-B' 47 | 48 | 49 | Example uses: 50 | CESR encoded paths for nested SADs and SAIDs 51 | CESR encoded fractionally weighted threshold expressions 52 | 53 | 54 | Attributes: 55 | 56 | Inherited Properties: (See Matter) 57 | .pad is int number of pad chars given raw 58 | 59 | .code is str derivation code to indicate cypher suite 60 | .raw is bytes crypto material only without code 61 | .index is int count of attached crypto material by context (receipts) 62 | .qb64 is str in Base64 fully qualified with derivation code + crypto mat 63 | .qb64b is bytes in Base64 fully qualified with derivation code + crypto mat 64 | .qb2 is bytes in binary with derivation code + crypto material 65 | .transferable is Boolean, True when transferable derivation code False otherwise 66 | 67 | Properties: 68 | .text is the Base64 text value, .qb64 with text code and leader removed. 69 | 70 | Hidden: 71 | ._pad is method to compute .pad property 72 | ._code is str value for .code property 73 | ._raw is bytes value for .raw property 74 | ._index is int value for .index property 75 | ._infil is method to compute fully qualified Base64 from .raw and .code 76 | ._exfil is method to extract .code and .raw from fully qualified Base64 77 | 78 | Methods: 79 | 80 | 81 | 82 | 83 | */ 84 | 85 | export class Bexter extends Matter { 86 | constructor( 87 | { raw, code = MtrDex.StrB64_L0, qb64b, qb64, qb2 }: MatterArgs, 88 | bext?: string 89 | ) { 90 | if ( 91 | raw === undefined && 92 | qb64b === undefined && 93 | qb64 === undefined && 94 | qb2 === undefined 95 | ) { 96 | if (bext === undefined) 97 | throw new EmptyMaterialError('Missing bext string.'); 98 | 99 | const match = Reb64.exec(bext); 100 | if (!match) throw new Error('Invalid Base64.'); 101 | 102 | raw = Bexter._rawify(bext); 103 | } 104 | 105 | super({ raw, code, qb64b, qb64, qb2 }); 106 | 107 | if (!BexDex.has(this.code)) 108 | throw new Error(`Invalid code = ${this.code} for Bexter.`); 109 | } 110 | 111 | static _rawify(bext: string): Uint8Array { 112 | const ts = bext.length % 4; // bext size mod 4 113 | const ws = (4 - ts) % 4; // pre conv wad size in chars 114 | const ls = (3 - ts) % 3; // post conv lead size in bytes 115 | const wad = new Array(ws); 116 | wad.fill('A'); 117 | const base = wad.join('') + bext; // pre pad with wad of zeros in Base64 == 'A' 118 | const raw = decodeBase64Url(base); // [ls:] // convert and remove leader 119 | 120 | return Uint8Array.from(raw).subarray(ls); // raw binary equivalent of text 121 | } 122 | 123 | get bext(): string { 124 | const sizage = Matter.Sizes.get(this.code); 125 | const wad = Uint8Array.from(new Array(sizage?.ls).fill(0)); 126 | const bext = encodeBase64Url(concat(wad, this.raw)); 127 | 128 | let ws = 0; 129 | if (sizage?.ls === 0 && bext !== undefined) { 130 | if (bext[0] === 'A') { 131 | ws = 1; 132 | } 133 | } else { 134 | ws = (sizage?.ls! + 1) % 4; 135 | } 136 | 137 | return bext.substring(ws); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/keri/core/cigar.ts: -------------------------------------------------------------------------------- 1 | import { Verfer } from './verfer.ts'; 2 | import { Matter, MatterArgs } from './matter.ts'; 3 | 4 | export class Cigar extends Matter { 5 | private _verfer: Verfer | undefined; 6 | constructor({ raw, code, qb64, qb64b, qb2 }: MatterArgs, verfer?: Verfer) { 7 | super({ raw, code, qb64, qb64b, qb2 }); 8 | this._verfer = verfer; 9 | } 10 | 11 | get verfer(): Verfer | undefined { 12 | return this._verfer; 13 | } 14 | 15 | set verfer(verfer: Verfer | undefined) { 16 | this._verfer = verfer; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/keri/core/cipher.ts: -------------------------------------------------------------------------------- 1 | import { Matter, MatterArgs, MtrDex } from './matter.ts'; 2 | import { Decrypter } from './decrypter.ts'; 3 | 4 | export class Cipher extends Matter { 5 | constructor({ raw, code, qb64, qb64b, qb2 }: MatterArgs) { 6 | if (raw != undefined && code == undefined) { 7 | if (raw.length == Matter._rawSize(MtrDex.X25519_Cipher_Salt)) { 8 | code = MtrDex.X25519_Cipher_Salt; 9 | } else if ( 10 | raw.length == Matter._rawSize(MtrDex.X25519_Cipher_Seed) 11 | ) { 12 | code = MtrDex.X25519_Cipher_Salt; 13 | } 14 | } 15 | super({ raw: raw, code: code, qb64b: qb64b, qb64: qb64, qb2: qb2 }); 16 | 17 | if ( 18 | !Array.from([ 19 | MtrDex.X25519_Cipher_Salt, 20 | MtrDex.X25519_Cipher_Seed, 21 | ]).includes(this.code) 22 | ) { 23 | throw new Error(`Unsupported Cipher code == ${this.code}`); 24 | } 25 | } 26 | 27 | decrypt( 28 | prikey: Uint8Array | undefined = undefined, 29 | seed: Uint8Array | undefined = undefined 30 | ) { 31 | const decrypter = new Decrypter({ qb64b: prikey }, seed); 32 | return decrypter.decrypt(this.qb64b); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/keri/core/decrypter.ts: -------------------------------------------------------------------------------- 1 | import libsodium from 'libsodium-wrappers-sumo'; 2 | 3 | import { Matter, MatterArgs, MtrDex } from './matter.ts'; 4 | import { Signer } from './signer.ts'; 5 | import { Cipher } from './cipher.ts'; 6 | import { EmptyMaterialError } from './kering.ts'; 7 | import { Salter } from './salter.ts'; 8 | 9 | export class Decrypter extends Matter { 10 | private readonly _decrypt: any; 11 | constructor( 12 | { raw, code = MtrDex.X25519_Private, qb64, qb64b, qb2 }: MatterArgs, 13 | seed: Uint8Array | undefined = undefined 14 | ) { 15 | try { 16 | super({ raw, code, qb64, qb64b, qb2 }); 17 | } catch (e) { 18 | if (e instanceof EmptyMaterialError) { 19 | if (seed != undefined) { 20 | const signer = new Signer({ qb64b: seed }); 21 | if (signer.code != MtrDex.Ed25519_Seed) { 22 | throw new Error( 23 | `Unsupported signing seed derivation code ${signer.code}` 24 | ); 25 | } 26 | const sigkey = new Uint8Array( 27 | signer.raw.length + signer.verfer.raw.length 28 | ); 29 | sigkey.set(signer.raw); 30 | sigkey.set(signer.verfer.raw, signer.raw.length); 31 | raw = 32 | libsodium.crypto_sign_ed25519_sk_to_curve25519(sigkey); 33 | super({ raw, code, qb64, qb64b, qb2 }); 34 | } else { 35 | throw e; 36 | } 37 | } else { 38 | throw e; 39 | } 40 | } 41 | 42 | if (this.code == MtrDex.X25519_Private) { 43 | this._decrypt = this._x25519; 44 | } else { 45 | throw new Error(`Unsupported decrypter code = ${this.code}.`); 46 | } 47 | } 48 | 49 | decrypt( 50 | ser: Uint8Array | null = null, 51 | cipher: Cipher | null = null, 52 | transferable: boolean = false 53 | ) { 54 | if (ser == null && cipher == null) { 55 | throw new EmptyMaterialError('Neither ser or cipher were provided'); 56 | } 57 | 58 | if (ser != null) { 59 | cipher = new Cipher({ qb64b: ser }); 60 | } 61 | 62 | return this._decrypt(cipher, this.raw, transferable); 63 | } 64 | 65 | _x25519(cipher: Cipher, prikey: Uint8Array, transferable: boolean = false) { 66 | const pubkey = libsodium.crypto_scalarmult_base(prikey); 67 | const plain = libsodium.crypto_box_seal_open( 68 | cipher.raw, 69 | pubkey, 70 | prikey 71 | ); 72 | if (cipher.code == MtrDex.X25519_Cipher_Salt) { 73 | return new Salter({ qb64b: plain }); 74 | } else if (cipher.code == MtrDex.X25519_Cipher_Seed) { 75 | return new Signer({ qb64b: plain, transferable: transferable }); 76 | } else { 77 | throw new Error(`Unsupported cipher text code == ${cipher.code}`); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/keri/core/diger.ts: -------------------------------------------------------------------------------- 1 | import { blake3 } from '@noble/hashes/blake3'; 2 | import { Matter, MatterArgs, MtrDex } from './matter.ts'; 3 | 4 | /** 5 | * @description : Diger is subset of Matter and is used to verify the digest of serialization 6 | * It uses .raw : as digest 7 | * .code as digest algorithm 8 | * 9 | */ 10 | 11 | export class Diger extends Matter { 12 | private readonly _verify: (a: Uint8Array, b: Uint8Array) => boolean; 13 | 14 | // This constructor will assign digest verification function to ._verify 15 | constructor( 16 | { raw, code = MtrDex.Blake3_256, qb64, qb64b, qb2 }: MatterArgs, 17 | ser: Uint8Array | null = null 18 | ) { 19 | try { 20 | super({ raw, code, qb64, qb64b, qb2 }); 21 | } catch (error) { 22 | if (ser == null) { 23 | throw error; 24 | } 25 | 26 | if (code === MtrDex.Blake3_256) { 27 | const dig = blake3.create({ dkLen: 32 }).update(ser).digest(); 28 | super({ raw: dig, code: code }); 29 | } else { 30 | throw new Error(`Unsupported code = ${code} for digester.`); 31 | } 32 | } 33 | 34 | if (code === MtrDex.Blake3_256) { 35 | this._verify = this.blake3_256; 36 | } else { 37 | throw new Error(`Unsupported code = ${code} for digester.`); 38 | } 39 | } 40 | 41 | /** 42 | * 43 | * @param {Uint8Array} ser serialization bytes 44 | * @description This method will return true if digest of bytes serialization ser matches .raw 45 | * using .raw as reference digest for ._verify digest algorithm determined 46 | by .code 47 | */ 48 | verify(ser: Uint8Array): boolean { 49 | return this._verify(ser, this.raw); 50 | } 51 | 52 | compare( 53 | ser: Uint8Array, 54 | dig: Uint8Array | null = null, 55 | diger: Diger | null = null 56 | ) { 57 | if (dig != null) { 58 | if (dig.toString() == this.qb64) { 59 | return true; 60 | } 61 | 62 | diger = new Diger({ qb64b: dig }); 63 | } else if (diger != null) { 64 | if (diger.qb64b == this.qb64b) { 65 | return true; 66 | } 67 | } else { 68 | throw new Error('Both dig and diger may not be None.'); 69 | } 70 | 71 | if (diger.code == this.code) { 72 | return false; 73 | } 74 | 75 | return diger.verify(ser) && this.verify(ser); 76 | } 77 | 78 | blake3_256(ser: Uint8Array, dig: Uint8Array) { 79 | const digest = blake3.create({ dkLen: 32 }).update(ser).digest(); 80 | 81 | return ( 82 | digest.length == dig.length && digest.toString() === dig.toString() 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/keri/core/encrypter.ts: -------------------------------------------------------------------------------- 1 | import libsodium from 'libsodium-wrappers-sumo'; 2 | 3 | import { Matter, MatterArgs, MtrDex } from './matter.ts'; 4 | import { Verfer } from './verfer.ts'; 5 | import { Signer } from './signer.ts'; 6 | import { Cipher } from './cipher.ts'; 7 | import { arrayEquals } from './utils.ts'; 8 | 9 | export class Encrypter extends Matter { 10 | private _encrypt: any; 11 | constructor( 12 | { raw, code = MtrDex.X25519, qb64, qb64b, qb2 }: MatterArgs, 13 | verkey: Uint8Array | null = null 14 | ) { 15 | if (raw == undefined && verkey != null) { 16 | const verfer = new Verfer({ qb64b: verkey }); 17 | if ( 18 | !Array.from([MtrDex.Ed25519N, MtrDex.Ed25519]).includes( 19 | verfer.code 20 | ) 21 | ) { 22 | throw new Error( 23 | `Unsupported verkey derivation code = ${verfer.code}.` 24 | ); 25 | } 26 | raw = libsodium.crypto_sign_ed25519_pk_to_curve25519(verfer.raw); 27 | } 28 | 29 | super({ raw, code, qb64, qb64b, qb2 }); 30 | 31 | if (this.code == MtrDex.X25519) { 32 | this._encrypt = this._x25519; 33 | } else { 34 | throw new Error(`Unsupported encrypter code = ${this.code}.`); 35 | } 36 | } 37 | 38 | verifySeed(seed: Uint8Array) { 39 | const signer = new Signer({ qb64b: seed }); 40 | const keypair = libsodium.crypto_sign_seed_keypair(signer.raw); 41 | const pubkey = libsodium.crypto_sign_ed25519_pk_to_curve25519( 42 | keypair.publicKey 43 | ); 44 | return arrayEquals(pubkey, this.raw); 45 | } 46 | 47 | encrypt(ser: Uint8Array | null = null, matter: Matter | null = null) { 48 | if (ser == null && matter == null) { 49 | throw new Error('Neither ser nor matter are provided.'); 50 | } 51 | 52 | if (ser != null) { 53 | matter = new Matter({ qb64b: ser }); 54 | } 55 | 56 | let code; 57 | if (matter!.code == MtrDex.Salt_128) { 58 | code = MtrDex.X25519_Cipher_Salt; 59 | } else { 60 | code = MtrDex.X25519_Cipher_Seed; 61 | } 62 | 63 | return this._encrypt(matter!.qb64, this.raw, code); 64 | } 65 | 66 | _x25519(ser: Uint8Array, pubkey: Uint8Array, code: string) { 67 | const raw = libsodium.crypto_box_seal(ser, pubkey); 68 | return new Cipher({ raw: raw, code: code }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/keri/core/kering.ts: -------------------------------------------------------------------------------- 1 | export class EmptyMaterialError { 2 | private readonly _err: Error; 3 | constructor(err: string) { 4 | this._err = new Error(err); 5 | } 6 | 7 | get err() { 8 | return this._err; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/keri/core/keyState.ts: -------------------------------------------------------------------------------- 1 | import { Algos } from './manager.ts'; 2 | import { Tier } from './salter.ts'; 3 | 4 | export interface KeyState { 5 | vn: [number, number]; 6 | i: string; 7 | s: string; 8 | p?: string; 9 | d: string; 10 | f: string; 11 | dt: string; 12 | et: string; 13 | kt: string | string[]; 14 | k: string[]; 15 | nt: string | string[]; 16 | n: string[]; 17 | bt: string; 18 | b: string[]; 19 | c: string[]; 20 | ee: EstablishmentState; 21 | di?: string; 22 | } 23 | 24 | export interface EstablishmentState { 25 | d: string; 26 | s: string; 27 | } 28 | 29 | /** 30 | * Marker interface for state configuring an IdentifierManager. 31 | */ 32 | // eslint-disable-next-line @typescript-eslint/no-empty-object-type 33 | export interface IdentifierManagerState {} 34 | 35 | /** 36 | * Interface defining configuration parameters for a specified, deterministic salt of an IdentifierManager. 37 | */ 38 | export interface SaltyKeyState extends IdentifierManagerState { 39 | /** 40 | * Encrypted 41 | */ 42 | sxlt: string; 43 | pidx: number; 44 | kidx: number; 45 | stem: string; 46 | tier: Tier; 47 | dcode: string; 48 | icodes: string[]; 49 | ncodes: string[]; 50 | transferable: boolean; 51 | } 52 | 53 | /** 54 | * Interface defining configuration parameters for a random seed identifier manager. 55 | */ 56 | export interface RandyKeyState extends IdentifierManagerState { 57 | prxs: string[]; 58 | nxts: string[]; 59 | } 60 | 61 | /** 62 | * Interface defining properties a multi-signature group identifier manager. 63 | */ 64 | export interface GroupKeyState extends IdentifierManagerState { 65 | mhab: HabState; 66 | keys: string[]; 67 | ndigs: string[]; 68 | } 69 | 70 | /** 71 | * Interface defining properties for an external module identifier manager that uses externally managed keys such as in an HSM or a KMS system. 72 | */ 73 | export interface ExternState extends IdentifierManagerState { 74 | extern_type: string; 75 | pidx: number; 76 | [key: string]: unknown; 77 | } 78 | 79 | /** 80 | * Interface defining properties of an identifier habitat, know as a Hab in KERIpy. 81 | */ 82 | export interface HabState { 83 | name: string; 84 | prefix: string; 85 | transferable: boolean; 86 | state: KeyState; 87 | windexes: unknown[]; 88 | icp_dt: string; 89 | [Algos.salty]?: SaltyKeyState; 90 | [Algos.randy]?: RandyKeyState; 91 | [Algos.group]?: GroupKeyState; 92 | [Algos.extern]?: ExternState; 93 | } 94 | -------------------------------------------------------------------------------- /src/keri/core/number.ts: -------------------------------------------------------------------------------- 1 | import { Matter, MatterArgs, NumDex } from './matter.ts'; 2 | import { bytesToInt, intToBytes } from './utils.ts'; 3 | 4 | export class CesrNumber extends Matter { 5 | constructor( 6 | { raw, code, qb64b, qb64, qb2 }: MatterArgs, 7 | num?: number | string, 8 | numh?: string 9 | ) { 10 | let _num; 11 | if ( 12 | raw == undefined && 13 | qb64 == undefined && 14 | qb64b == undefined && 15 | qb2 == undefined 16 | ) { 17 | if (typeof num == 'number') { 18 | _num = num; 19 | } else if (numh != undefined) { 20 | _num = parseInt(numh, 16); 21 | } else { 22 | _num = 0; 23 | } 24 | } 25 | 26 | if (_num == undefined) { 27 | throw new Error('Invalid whole number'); 28 | } 29 | 30 | if (_num <= 256 ** 2 - 1) { 31 | // make short version of code 32 | code = NumDex.Short; 33 | } else if (_num <= 256 ** 4 - 1) { 34 | // make long version of code 35 | code = code = NumDex.Long; 36 | } else if (_num <= 256 ** 8 - 1) { 37 | // make big version of code 38 | code = code = NumDex.Big; 39 | } else if (_num <= 256 ** 16 - 1) { 40 | // make huge version of code 41 | code = code = NumDex.Huge; 42 | } else { 43 | throw new Error(`Invalid num = ${num}, too large to encode.`); 44 | } 45 | 46 | raw = intToBytes(_num, Matter._rawSize(code)); 47 | 48 | super({ raw, code, qb64b, qb64, qb2 }); 49 | 50 | if (!NumDex.has(this.code)) { 51 | throw new Error('Invalid code ' + code + ' for Number'); 52 | } 53 | } 54 | 55 | get num(): number { 56 | return bytesToInt(this.raw); 57 | } 58 | 59 | get numh(): string { 60 | return this.num.toString(16); 61 | } 62 | 63 | get positive(): boolean { 64 | return this.num > 0; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/keri/core/pather.ts: -------------------------------------------------------------------------------- 1 | import { Bexter, Reb64 } from './bexter.ts'; 2 | import { MatterArgs, MtrDex } from './matter.ts'; 3 | import { EmptyMaterialError } from './kering.ts'; 4 | 5 | /* 6 | Pather is a subclass of Bexter that provides SAD Path language specific functionality 7 | for variable length strings that only contain Base64 URL safe characters. Pather allows 8 | the specification of SAD Paths as a list of field components which will be converted to the 9 | Base64 URL safe character representation. 10 | 11 | Additionally, Pather provides .rawify for extracting and serializing the content targeted by 12 | .path for a SAD, represented as an instance of Serder. Pather enforces Base64 URL character 13 | safety by leveraging the fact that SADs must have static field ordering. Any field label can 14 | be replaced by its field ordinal to allow for path specification and traversal for any field 15 | labels that contain non-Base64 URL safe characters. 16 | 17 | 18 | Examples: strings: 19 | path = [] 20 | text = "-" 21 | qb64 = '6AABAAA-' 22 | 23 | path = ["A"] 24 | text = "-A" 25 | qb64 = '5AABAA-A' 26 | 27 | path = ["A", "B"] 28 | text = "-A-B" 29 | qb64 = '4AAB-A-B' 30 | 31 | path = ["A", 1, "B", 3] 32 | text = "-A-1-B-3" 33 | qb64 = '4AAC-A-1-B-3' 34 | 35 | */ 36 | 37 | export class Pather extends Bexter { 38 | constructor( 39 | { raw, code = MtrDex.StrB64_L0, qb64b, qb64, qb2 }: MatterArgs, 40 | bext?: string, 41 | path?: string[] 42 | ) { 43 | if ( 44 | raw === undefined && 45 | bext === undefined && 46 | qb64b === undefined && 47 | qb64 === undefined && 48 | qb2 === undefined 49 | ) { 50 | if (path === undefined) 51 | throw new EmptyMaterialError('Missing bext string.'); 52 | 53 | bext = Pather._bextify(path); 54 | } 55 | 56 | super({ raw, code, qb64b, qb64, qb2 }, bext); 57 | } 58 | 59 | // TODO: implement SAD access methods like resolve, root, strip, startswith and tail 60 | 61 | get path(): string[] { 62 | if (!this.bext.startsWith('-')) { 63 | throw new Error('invalid SAD ptr'); 64 | } 65 | 66 | let path = this.bext; 67 | while (path.charAt(0) === '-') { 68 | path = path.substring(1); 69 | } 70 | 71 | const apath = path.split('-'); 72 | if (apath[0] !== '') { 73 | return apath; 74 | } else { 75 | return []; 76 | } 77 | } 78 | 79 | static _bextify(path: any[]): string { 80 | const vath = []; 81 | for (const p of path) { 82 | let sp = ''; 83 | if (typeof p === 'number') { 84 | sp = p.toString(); 85 | } else { 86 | sp = p; 87 | } 88 | 89 | const match = Reb64.exec(sp); 90 | if (!match) { 91 | throw new Error(`"Non Base64 path component = ${p}.`); 92 | } 93 | 94 | vath.push(sp); 95 | } 96 | return '-' + vath.join('-'); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/keri/core/saider.ts: -------------------------------------------------------------------------------- 1 | import { DigiDex, Matter, MatterArgs, MtrDex } from './matter.ts'; 2 | import { deversify, Dict, Serials } from './core.ts'; 3 | import { EmptyMaterialError } from './kering.ts'; 4 | import { dumps, sizeify } from './serder.ts'; 5 | import { blake3 } from '@noble/hashes/blake3'; 6 | 7 | const Dummy = '#'; 8 | 9 | export enum Ids { 10 | d = 'd', 11 | } 12 | 13 | export class Saider extends Matter { 14 | constructor( 15 | { raw, code, qb64b, qb64, qb2 }: MatterArgs, 16 | sad?: Dict, 17 | kind?: Serials, 18 | label: string = Ids.d 19 | ) { 20 | try { 21 | super({ raw, code, qb64b, qb64, qb2 }); 22 | } catch (e) { 23 | if (e instanceof EmptyMaterialError) { 24 | if (sad == undefined || !(label in sad)) { 25 | throw e; 26 | } 27 | 28 | if (code == undefined) { 29 | if (sad[label] != '') { 30 | super({ qb64: sad[label], code: code }); 31 | code = this.code; 32 | } else { 33 | code = MtrDex.Blake3_256; 34 | } 35 | } 36 | 37 | if (!DigiDex.has(code)) { 38 | throw new Error(`Unsupported digest code = ${code}`); 39 | } 40 | 41 | [raw] = Saider._derive({ ...sad }, code, kind, label); 42 | super({ raw: raw, code: code }); 43 | } else { 44 | throw e; 45 | } 46 | } 47 | 48 | if (!this.digestive) { 49 | throw new Error(`Unsupported digest code = ${this.code}.`); 50 | } 51 | } 52 | 53 | private static _derive( 54 | sad: Dict, 55 | code: string, 56 | kind: Serials | undefined, 57 | label: string 58 | ): [Uint8Array, Dict] { 59 | if (!DigiDex.has(code)) { 60 | throw new Error(`Unsupported digest code = ${code}.`); 61 | } 62 | 63 | sad = { ...sad }; 64 | sad[label] = ''.padStart(Matter.Sizes.get(code)!.fs!, Dummy); 65 | if ('v' in sad) { 66 | [, , kind, sad] = sizeify(sad, kind); 67 | } 68 | 69 | const ser = { ...sad }; 70 | 71 | const cpa = Saider._serialze(ser, kind); 72 | 73 | switch (code) { 74 | case MtrDex.Blake3_256: 75 | return [blake3.create({ dkLen: 32 }).update(cpa).digest(), sad]; 76 | default: 77 | throw new Error(`Unsupported digest code = ${code}.`); 78 | } 79 | } 80 | 81 | public derive( 82 | sad: Dict, 83 | code: string, 84 | kind: Serials | undefined, 85 | label: string 86 | ): [Uint8Array, Dict] { 87 | code = code != undefined ? code : this.code; 88 | return Saider._derive(sad, code, kind, label); 89 | } 90 | 91 | public verify( 92 | sad: Dict, 93 | prefixed: boolean = false, 94 | versioned: boolean = false, 95 | kind?: Serials, 96 | label: string = Ids.d 97 | ): boolean { 98 | try { 99 | const [raw, dsad] = Saider._derive(sad, this.code, kind, label); 100 | const saider = new Saider({ raw: raw, code: this.code }); 101 | if (this.qb64 != saider.qb64) { 102 | return false; 103 | } 104 | 105 | if ('v' in sad && versioned) { 106 | if (sad['v'] != dsad['v']) { 107 | return false; 108 | } 109 | } 110 | 111 | if (prefixed && sad[label] != this.qb64) { 112 | return false; 113 | } 114 | } catch (e) { 115 | return false; 116 | } 117 | 118 | return true; 119 | } 120 | 121 | private static _serialze(sad: Dict, kind?: Serials): string { 122 | let knd = Serials.JSON; 123 | if ('v' in sad) { 124 | [, knd] = deversify(sad['v']); 125 | } 126 | 127 | if (kind == undefined) { 128 | kind = knd; 129 | } 130 | 131 | return dumps(sad, kind); 132 | } 133 | 134 | public static saidify( 135 | sad: Dict, 136 | code: string = MtrDex.Blake3_256, 137 | kind: Serials = Serials.JSON, 138 | label: string = Ids.d 139 | ): [Saider, Dict] { 140 | if (!(label in sad)) { 141 | throw new Error(`Missing id field labeled=${label} in sad.`); 142 | } 143 | let raw; 144 | [raw, sad] = Saider._derive(sad, code, kind, label); 145 | const saider = new Saider( 146 | { raw: raw, code: code }, 147 | undefined, 148 | kind, 149 | label 150 | ); 151 | sad[label] = saider.qb64; 152 | return [saider, sad]; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/keri/core/seqner.ts: -------------------------------------------------------------------------------- 1 | import { Matter, MatterArgs, MtrDex } from './matter.ts'; 2 | import { intToBytes, bytesToInt } from './utils.ts'; 3 | /** 4 | * @description Seqner: subclass of Matter, cryptographic material, for ordinal numbers 5 | * such as sequence numbers or first seen ordering numbers. 6 | * Seqner provides fully qualified format for ordinals (sequence numbers etc) 7 | * when provided as attached cryptographic material elements. 8 | */ 9 | export class Seqner extends Matter { 10 | constructor({ 11 | raw, 12 | code = MtrDex.Salt_128, 13 | qb64, 14 | qb64b, 15 | qb2, 16 | sn, 17 | snh, 18 | ...kwa 19 | }: MatterArgs & { sn?: number; snh?: string }) { 20 | if (!raw && !qb64b && !qb64 && !qb2) { 21 | if (sn === undefined) { 22 | if (snh === undefined) { 23 | sn = 0; 24 | } else { 25 | sn = parseInt(snh, 16); 26 | } 27 | } 28 | 29 | raw = intToBytes(sn, Matter._rawSize(MtrDex.Salt_128)); 30 | } 31 | 32 | super({ raw, code, qb64, qb64b, qb2, ...kwa }); 33 | 34 | if (this.code !== MtrDex.Salt_128) { 35 | throw new Error(`Invalid code = ${this.code} for Seqner.`); 36 | } 37 | } 38 | 39 | get sn(): number { 40 | return bytesToInt(this.raw); //To check if other readUInt64 is needed 41 | } 42 | 43 | get snh(): string { 44 | return this.sn.toString(16); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/keri/core/serder.ts: -------------------------------------------------------------------------------- 1 | import { MtrDex } from './matter.ts'; 2 | import { 3 | deversify, 4 | Dict, 5 | Protocols, 6 | Serials, 7 | versify, 8 | Version, 9 | Vrsn_1_0, 10 | } from './core.ts'; 11 | import { Verfer } from './verfer.ts'; 12 | import { Diger } from './diger.ts'; 13 | import { CesrNumber } from './number.ts'; 14 | 15 | export class Serder { 16 | private _kind: Serials; 17 | private _raw: string = ''; 18 | private _sad: Dict = {}; 19 | private _proto: Protocols = Protocols.KERI; 20 | private _size: number = 0; 21 | private _version: Version = Vrsn_1_0; 22 | private readonly _code: string; 23 | 24 | /** 25 | * Creates a new Serder object from a self-addressing data dictionary. 26 | * @param sad self-addressing data dictionary. 27 | * @param kind serialization type to produce 28 | * @param code derivation code for the prefix 29 | */ 30 | constructor( 31 | sad: Dict, 32 | kind: Serials = Serials.JSON, 33 | code: string = MtrDex.Blake3_256 34 | ) { 35 | const [raw, proto, eKind, eSad, version] = this._exhale(sad, kind); 36 | this._raw = raw; 37 | this._sad = eSad; 38 | this._proto = proto; 39 | this._version = version; 40 | this._code = code; 41 | this._kind = eKind; 42 | this._size = raw.length; 43 | } 44 | 45 | get sad(): Dict { 46 | return this._sad; 47 | } 48 | 49 | get pre(): string { 50 | return this._sad['i']; 51 | } 52 | 53 | get code(): string { 54 | return this._code; 55 | } 56 | 57 | get raw(): string { 58 | return this._raw; 59 | } 60 | 61 | get said(): string { 62 | return this._sad['d']; 63 | } 64 | 65 | get sner(): CesrNumber { 66 | return new CesrNumber({}, this.sad['s']); 67 | } 68 | 69 | get sn(): number { 70 | return this.sner.num; 71 | } 72 | 73 | get kind(): Serials { 74 | return this._kind; 75 | } 76 | 77 | /** 78 | * Serializes a self-addressing data dictionary from the dictionary passed in 79 | * using the specified serialization type. 80 | * @param sad self-addressing data dictionary. 81 | * @param kind serialization type to produce 82 | * @private 83 | */ 84 | private _exhale( 85 | sad: Dict, 86 | kind: Serials 87 | ): [string, Protocols, Serials, Dict, Version] { 88 | return sizeify(sad, kind); 89 | } 90 | 91 | get proto(): Protocols { 92 | return this._proto; 93 | } 94 | 95 | get size(): number { 96 | return this._size; 97 | } 98 | 99 | get version(): Version { 100 | return this._version; 101 | } 102 | get verfers(): Verfer[] { 103 | let keys: any = []; 104 | if ('k' in this._sad) { 105 | // establishment event 106 | keys = this._sad['k']; 107 | } else { 108 | // non-establishment event 109 | keys = []; 110 | } 111 | // create a new Verfer for each key 112 | const verfers = []; 113 | for (const key of keys) { 114 | verfers.push(new Verfer({ qb64: key })); 115 | } 116 | return verfers; 117 | } 118 | 119 | get digers(): Diger[] { 120 | let keys: any = []; 121 | if ('n' in this._sad) { 122 | // establishment event 123 | keys = this._sad['n']; 124 | } else { 125 | // non-establishment event 126 | keys = []; 127 | } 128 | // create a new Verfer for each key 129 | const digers = []; 130 | for (const key of keys) { 131 | digers.push(new Diger({ qb64: key })); 132 | } 133 | return digers; 134 | } 135 | 136 | pretty() { 137 | return JSON.stringify(this._sad, undefined, 2); 138 | } 139 | } 140 | 141 | export function dumps(sad: object, kind: Serials.JSON): string { 142 | if (kind == Serials.JSON) { 143 | return JSON.stringify(sad); 144 | } else { 145 | throw new Error('unsupported event encoding'); 146 | } 147 | } 148 | 149 | export function sizeify( 150 | ked: Dict, 151 | kind?: Serials 152 | ): [string, Protocols, Serials, Dict, Version] { 153 | if (!('v' in ked)) { 154 | throw new Error('Missing or empty version string'); 155 | } 156 | 157 | const [proto, knd, version] = deversify(ked['v'] as string); 158 | if (version != Vrsn_1_0) { 159 | throw new Error(`unsupported version ${version.toString()}`); 160 | } 161 | 162 | if (kind == undefined) { 163 | kind = knd; 164 | } 165 | 166 | let raw = dumps(ked, kind); 167 | const size = new TextEncoder().encode(raw).length; 168 | 169 | ked['v'] = versify(proto, version, kind, size); 170 | 171 | raw = dumps(ked, kind); 172 | 173 | return [raw, proto, kind, ked, version]; 174 | } 175 | -------------------------------------------------------------------------------- /src/keri/core/siger.ts: -------------------------------------------------------------------------------- 1 | import { IdxSigDex, Indexer, IndexerArgs } from './indexer.ts'; 2 | import { Verfer } from './verfer.ts'; 3 | 4 | /** 5 | Siger is subclass of Indexer, indexed signature material, 6 | Adds .verfer property which is instance of Verfer that provides 7 | associated signature verifier. 8 | 9 | See Indexer for inherited attributes and properties: 10 | 11 | Attributes: 12 | 13 | Properties: 14 | .verfer is Verfer object instance 15 | 16 | Methods: 17 | **/ 18 | 19 | export class Siger extends Indexer { 20 | private _verfer?: Verfer; 21 | constructor( 22 | { raw, code, index, ondex, qb64, qb64b, qb2 }: IndexerArgs, 23 | verfer?: Verfer 24 | ) { 25 | super({ raw, code, index, ondex, qb64, qb64b, qb2 }); 26 | 27 | if (!IdxSigDex.has(this.code)) { 28 | throw new Error(`Invalid code = ${this.code} for Siger.`); 29 | } 30 | this._verfer = verfer; 31 | } 32 | 33 | get verfer(): Verfer | undefined { 34 | return this._verfer; 35 | } 36 | 37 | set verfer(verfer: Verfer | undefined) { 38 | this._verfer = verfer; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/keri/core/signer.ts: -------------------------------------------------------------------------------- 1 | import { EmptyMaterialError } from './kering.ts'; 2 | 3 | export {}; 4 | import libsodium from 'libsodium-wrappers-sumo'; 5 | import { Matter } from './matter.ts'; 6 | import { MtrDex } from './matter.ts'; 7 | import { Verfer } from './verfer.ts'; 8 | import { Cigar } from './cigar.ts'; 9 | import { Siger } from './siger.ts'; 10 | import { IdrDex } from './indexer.ts'; 11 | import { concat } from './core.ts'; 12 | 13 | /** 14 | * @description Signer is Matter subclass with method to create signature of serialization 15 | * It will use .raw as signing (private) key seed 16 | * .code as cipher suite for signing and new property .verfer whose property 17 | * .raw is public key for signing. 18 | * If not provided .verfer is generated from private key seed using .code 19 | as cipher suite for creating key-pair. 20 | */ 21 | interface SignerArgs { 22 | raw?: Uint8Array | undefined; 23 | code?: string; 24 | qb64b?: Uint8Array | undefined; 25 | qb64?: string; 26 | qb2?: Uint8Array | undefined; 27 | transferable?: boolean; 28 | } 29 | 30 | export class Signer extends Matter { 31 | // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type 32 | private readonly _sign: Function; 33 | private readonly _verfer: Verfer; 34 | 35 | constructor({ 36 | raw, 37 | code = MtrDex.Ed25519_Seed, 38 | qb64, 39 | qb64b, 40 | qb2, 41 | transferable = true, 42 | }: SignerArgs) { 43 | try { 44 | super({ raw, code, qb64, qb64b, qb2 }); 45 | } catch (e) { 46 | if (e instanceof EmptyMaterialError) { 47 | if (code == MtrDex.Ed25519_Seed) { 48 | const raw = libsodium.randombytes_buf( 49 | libsodium.crypto_sign_SEEDBYTES 50 | ); 51 | super({ raw, code, qb64, qb64b, qb2 }); 52 | } else { 53 | throw new Error(`Unsupported signer code = ${code}.`); 54 | } 55 | } else { 56 | throw e; 57 | } 58 | } 59 | let verfer; 60 | if (this.code == MtrDex.Ed25519_Seed) { 61 | this._sign = this._ed25519; 62 | const keypair = libsodium.crypto_sign_seed_keypair(this.raw); 63 | verfer = new Verfer({ 64 | raw: keypair.publicKey, 65 | code: transferable ? MtrDex.Ed25519 : MtrDex.Ed25519N, 66 | }); 67 | } else { 68 | throw new Error(`Unsupported signer code = ${this.code}.`); 69 | } 70 | 71 | this._verfer = verfer; 72 | } 73 | 74 | /** 75 | * @description Property verfer: 76 | Returns Verfer instance 77 | Assumes ._verfer is correctly assigned 78 | */ 79 | get verfer(): Verfer { 80 | return this._verfer; 81 | } 82 | 83 | sign( 84 | ser: Uint8Array, 85 | index: number | null = null, 86 | only: boolean = false, 87 | ondex: number | undefined = undefined 88 | ): Siger | Cigar { 89 | return this._sign(ser, this.raw, this.verfer, index, only, ondex); 90 | } 91 | 92 | _ed25519( 93 | ser: Uint8Array, 94 | seed: Uint8Array, 95 | verfer: Verfer, 96 | index: number | null, 97 | only: boolean = false, 98 | ondex: number | undefined 99 | ) { 100 | const sig = libsodium.crypto_sign_detached( 101 | ser, 102 | concat(seed, verfer.raw) 103 | ); 104 | 105 | if (index == null) { 106 | return new Cigar({ raw: sig, code: MtrDex.Ed25519_Sig }, verfer); 107 | } else { 108 | let code; 109 | if (only) { 110 | ondex = undefined; 111 | if (index <= 63) { 112 | code = IdrDex.Ed25519_Crt_Sig; 113 | } else { 114 | code = IdrDex.Ed25519_Big_Crt_Sig; 115 | } 116 | } else { 117 | if (ondex == undefined) { 118 | ondex = index; 119 | } 120 | 121 | if (ondex == index && index <= 63) 122 | // both same and small 123 | code = IdrDex.Ed25519_Sig; // use small both same 124 | // otherwise big or both not same so use big both 125 | else code = IdrDex.Ed25519_Big_Sig; // use use big both 126 | } 127 | 128 | return new Siger( 129 | { raw: sig, code: code, index: index, ondex: ondex }, 130 | verfer 131 | ); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/keri/core/utils.ts: -------------------------------------------------------------------------------- 1 | import { Counter, CtrDex } from './counter.ts'; 2 | import { Seqner } from './seqner.ts'; 3 | import { Prefixer } from './prefixer.ts'; 4 | import { Saider } from './saider.ts'; 5 | import { Serder } from './serder.ts'; 6 | import { b } from './core.ts'; 7 | 8 | export function pad(n: any, width = 3, z = 0) { 9 | return (String(z).repeat(width) + String(n)).slice(String(n).length); 10 | } 11 | 12 | /** 13 | * @description Returns list of depth first recursively extracted values from elements of 14 | key event dict ked whose flabels are in lables list 15 | 16 | * @param {*} ked ked is key event dict 17 | * @param {*} labels labels is list of element labels in ked from which to extract values 18 | */ 19 | export function extractValues(ked: any, labels: any) { 20 | let values = []; 21 | for (const label of labels) { 22 | values = extractElementValues(ked[label], values); 23 | } 24 | 25 | return values; 26 | } 27 | 28 | export function arrayEquals(ar1: Uint8Array, ar2: Uint8Array) { 29 | return ( 30 | ar1.length === ar2.length && 31 | ar1.every((val, index) => val === ar2[index]) 32 | ); 33 | } 34 | 35 | /** 36 | * @description Recusive depth first search that recursively extracts value(s) from element 37 | and appends to values list 38 | Assumes that extracted values are str 39 | 40 | * @param {*} element 41 | * @param {*} values 42 | */ 43 | 44 | function extractElementValues(element: any, values: any) { 45 | let data = []; 46 | 47 | try { 48 | if (element instanceof Array && !(typeof element == 'string')) { 49 | for (const k in element) extractElementValues(element[k], values); 50 | } else if (typeof element == 'string') { 51 | values.push(element); 52 | } 53 | data = values; 54 | } catch (error) { 55 | throw new Error(error as string); 56 | } 57 | 58 | return data; 59 | } 60 | 61 | /** 62 | * @description Returns True if obj is non-string iterable, False otherwise 63 | 64 | * @param {*} obj 65 | */ 66 | 67 | // function nonStringIterable(obj) { 68 | // obj instanceof (String) 69 | // return instanceof(obj, (str, bytes)) && instanceof(obj, Iterable)) 70 | // } 71 | 72 | export function nowUTC(): Date { 73 | return new Date(); 74 | } 75 | 76 | export function range(start: number, stop: number, step: number) { 77 | if (typeof stop == 'undefined') { 78 | // one param defined 79 | stop = start; 80 | start = 0; 81 | } 82 | 83 | if (typeof step == 'undefined') { 84 | step = 1; 85 | } 86 | 87 | if ((step > 0 && start >= stop) || (step < 0 && start <= stop)) { 88 | return []; 89 | } 90 | 91 | const result = new Array(); 92 | for (let i: number = start; step > 0 ? i < stop : i > stop; i += step) { 93 | result.push(i); 94 | } 95 | 96 | return result; 97 | } 98 | 99 | export function intToBytes(value: number, length: number): Uint8Array { 100 | const byteArray = new Uint8Array(length); // Assuming a 4-byte integer (32 bits) 101 | 102 | for (let index = byteArray.length - 1; index >= 0; index--) { 103 | const byte = value & 0xff; 104 | byteArray[index] = byte; 105 | value = (value - byte) / 256; 106 | } 107 | return byteArray; 108 | } 109 | 110 | export function bytesToInt(ar: Uint8Array): number { 111 | let value = 0; 112 | for (let i = 0; i < ar.length; i++) { 113 | value = value * 256 + ar[i]; 114 | } 115 | return value; 116 | } 117 | 118 | export function serializeACDCAttachment(anc: Serder): Uint8Array { 119 | const prefixer = new Prefixer({ qb64: anc.pre }); 120 | const seqner = new Seqner({ sn: anc.sn }); 121 | const saider = new Saider({ qb64: anc.sad['d'] }); 122 | const craw = new Uint8Array(); 123 | const ctr = new Counter({ code: CtrDex.SealSourceTriples, count: 1 }).qb64b; 124 | const prefix = prefixer.qb64b; 125 | const seq = seqner.qb64b; 126 | const said = saider.qb64b; 127 | const newCraw = new Uint8Array( 128 | craw.length + ctr.length + prefix.length + seq.length + said.length 129 | ); 130 | newCraw.set(craw); 131 | newCraw.set(ctr, craw.length); 132 | newCraw.set(prefix, craw.length + ctr.length); 133 | newCraw.set(seq, craw.length + ctr.length + prefix.length); 134 | newCraw.set(said, craw.length + ctr.length + prefix.length + seq.length); 135 | return newCraw; 136 | } 137 | 138 | export function serializeIssExnAttachment(anc: Serder): Uint8Array { 139 | const seqner = new Seqner({ sn: anc.sn }); 140 | const ancSaider = new Saider({ qb64: anc.sad['d'] }); 141 | const coupleArray = new Uint8Array( 142 | seqner.qb64b.length + ancSaider.qb64b.length 143 | ); 144 | coupleArray.set(seqner.qb64b); 145 | coupleArray.set(ancSaider.qb64b, seqner.qb64b.length); 146 | const counter = new Counter({ 147 | code: CtrDex.SealSourceCouples, 148 | count: 1, 149 | }); 150 | const counterQb64b = counter.qb64b; 151 | const atc = new Uint8Array(counter.qb64b.length + coupleArray.length); 152 | atc.set(counterQb64b); 153 | atc.set(coupleArray, counterQb64b.length); 154 | 155 | if (atc.length % 4 !== 0) { 156 | throw new Error( 157 | `Invalid attachments size: ${atc.length}, non-integral quadlets detected.` 158 | ); 159 | } 160 | const pcnt = new Counter({ 161 | code: CtrDex.AttachedMaterialQuadlets, 162 | count: Math.floor(atc.length / 4), 163 | }); 164 | const msg = new Uint8Array(pcnt.qb64b.length + atc.length); 165 | msg.set(pcnt.qb64b); 166 | msg.set(atc, pcnt.qb64b.length); 167 | 168 | return msg; 169 | } 170 | -------------------------------------------------------------------------------- /src/keri/core/vdring.ts: -------------------------------------------------------------------------------- 1 | import { randomNonce } from '../app/coring.ts'; 2 | import { TraitDex } from '../app/habery.ts'; 3 | import { 4 | Serials, 5 | Vrsn_1_0, 6 | Version, 7 | Protocols, 8 | versify, 9 | Ilks, 10 | } from '../core/core.ts'; 11 | import { ample } from './eventing.ts'; 12 | import { MtrDex } from './matter.ts'; 13 | import { Prefixer } from './prefixer.ts'; 14 | import { Serder } from './serder.ts'; 15 | 16 | namespace vdr { 17 | export interface VDRInceptArgs { 18 | pre: string; 19 | toad?: number | string; 20 | nonce?: string; 21 | baks?: string[]; 22 | cnfg?: string[]; 23 | version?: Version; 24 | kind?: Serials; 25 | code?: string; 26 | } 27 | 28 | export function incept({ 29 | pre, 30 | toad, 31 | nonce = randomNonce(), 32 | baks = [], 33 | cnfg = [], 34 | version = Vrsn_1_0, 35 | kind = Serials.JSON, 36 | code = MtrDex.Blake3_256, 37 | }: VDRInceptArgs): Serder { 38 | const vs = versify(Protocols.KERI, version, kind, 0); 39 | const isn = 0; 40 | const ilk = Ilks.vcp; 41 | 42 | if (cnfg.includes(TraitDex.NoBackers) && baks.length > 0) { 43 | throw new Error( 44 | `${baks.length} backers specified for NB vcp, 0 allowed` 45 | ); 46 | } 47 | 48 | if (new Set(baks).size < baks.length) { 49 | throw new Error(`Invalid baks ${baks} has duplicates`); 50 | } 51 | 52 | let _toad: number; 53 | if (toad === undefined) { 54 | if (baks.length === 0) { 55 | _toad = 0; 56 | } else { 57 | _toad = ample(baks.length); 58 | } 59 | } else { 60 | _toad = +toad; 61 | } 62 | 63 | if (baks.length > 0) { 64 | if (_toad < 1 || _toad > baks.length) { 65 | throw new Error(`Invalid toad ${_toad} for baks in ${baks}`); 66 | } 67 | } else { 68 | if (_toad != 0) { 69 | throw new Error(`Invalid toad ${_toad} for no baks`); 70 | } 71 | } 72 | 73 | const sad = { 74 | v: vs, 75 | t: ilk, 76 | d: '', 77 | i: '', 78 | ii: pre, 79 | s: '' + isn, 80 | c: cnfg, 81 | bt: _toad.toString(16), 82 | b: baks, 83 | n: nonce, 84 | }; 85 | 86 | const prefixer = new Prefixer({ code }, sad); 87 | sad.i = prefixer.qb64; 88 | sad.d = prefixer.qb64; 89 | 90 | return new Serder(sad); 91 | } 92 | } 93 | 94 | export { vdr }; 95 | -------------------------------------------------------------------------------- /src/keri/core/verfer.ts: -------------------------------------------------------------------------------- 1 | import libsodium from 'libsodium-wrappers-sumo'; 2 | import { Matter, MatterArgs, MtrDex } from './matter.ts'; 3 | import { p256 } from '@noble/curves/p256'; 4 | import { b } from './core.ts'; 5 | 6 | const VERFER_CODES = new Set([ 7 | MtrDex.Ed25519N, 8 | MtrDex.Ed25519, 9 | MtrDex.ECDSA_256r1N, 10 | MtrDex.ECDSA_256r1, 11 | ]); 12 | 13 | /** 14 | * @description Verfer :sublclass of Matter,helps to verify signature of serialization 15 | * using .raw as verifier key and .code as signature cypher suite 16 | */ 17 | export class Verfer extends Matter { 18 | constructor({ raw, code, qb64, qb64b, qb2 }: MatterArgs) { 19 | super({ raw, code, qb64, qb64b, qb2 }); 20 | 21 | if (!VERFER_CODES.has(this.code)) { 22 | throw new Error(`Unsupported code = ${this.code} for verifier.`); 23 | } 24 | } 25 | 26 | verify(sig: Uint8Array, ser: Uint8Array | string): boolean { 27 | switch (this.code) { 28 | case MtrDex.Ed25519: 29 | case MtrDex.Ed25519N: { 30 | return libsodium.crypto_sign_verify_detached( 31 | sig, 32 | ser, 33 | this.raw 34 | ); 35 | } 36 | case MtrDex.ECDSA_256r1: 37 | case MtrDex.ECDSA_256r1N: { 38 | const message = typeof ser === 'string' ? b(ser) : ser; 39 | return p256.verify(sig, message, this.raw); 40 | } 41 | default: 42 | throw new Error( 43 | `Unsupported code = ${this.code} for verifier.` 44 | ); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/keri/end/ending.ts: -------------------------------------------------------------------------------- 1 | import { Siger } from '../core/siger.ts'; 2 | import { Cigar } from '../core/cigar.ts'; 3 | 4 | export const FALSY = [false, 0, '?0', 'no', 'false', 'False', 'off']; 5 | export const TRUTHY = [true, 1, '?1', 'yes', 'true', 'True', 'on']; 6 | 7 | export class Signage { 8 | constructor( 9 | public readonly markers: 10 | | (Siger | Cigar)[] 11 | | Map, 12 | public readonly indexed?: boolean, 13 | public readonly signer?: string, 14 | public readonly ordinal?: string, 15 | public readonly digest?: string, 16 | public readonly kind?: string 17 | ) {} 18 | } 19 | 20 | export function signature(signages: Signage[]): Headers { 21 | const values: string[] = []; 22 | 23 | for (const signage of signages) { 24 | let markers: Array; 25 | let tags: string[]; 26 | 27 | if (signage.markers instanceof Map) { 28 | markers = Array.from(signage.markers.values()); 29 | tags = Array.from(signage.markers.keys()); 30 | } else { 31 | markers = signage.markers as Array; 32 | tags = []; 33 | } 34 | 35 | const items = new Array(); 36 | const indexed = signage.indexed ?? markers[0] instanceof Siger; 37 | 38 | if (indexed) { 39 | items.push('indexed="?1"'); 40 | } else { 41 | items.push('indexed="?0"'); 42 | } 43 | 44 | if (signage.signer != undefined) { 45 | items.push(`signer="${signage.signer}"`); 46 | } 47 | if (signage.ordinal != undefined) { 48 | items.push(`ordinal="${signage.ordinal}"`); 49 | } 50 | if (signage.digest != undefined) { 51 | items.push(`digest="${signage.digest}"`); 52 | } 53 | if (signage.kind != undefined) { 54 | items.push(`kind="${signage.kind}"`); 55 | } 56 | 57 | items.push( 58 | ...markers.map((marker, idx) => { 59 | let tag: string | undefined = undefined; 60 | let val: string; 61 | 62 | if (tags != undefined && tags.length > idx) { 63 | tag = tags[idx]; 64 | } 65 | 66 | if (marker instanceof Siger) { 67 | if (!indexed) { 68 | throw new Error( 69 | `Indexed signature marker ${marker} when indexed False.` 70 | ); 71 | } 72 | 73 | tag = tag ?? marker.index.toString(); 74 | val = marker.qb64; 75 | } else if (marker instanceof Cigar) { 76 | if (indexed) { 77 | throw new Error( 78 | `Unindexed signature marker ${marker} when indexed True.` 79 | ); 80 | } 81 | if (!marker.verfer) { 82 | throw new Error( 83 | `Indexed signature marker is missing verfer` 84 | ); 85 | } 86 | 87 | tag = tag ?? marker.verfer.qb64; 88 | val = marker.qb64; 89 | } else { 90 | tag = tag ?? idx.toString(); 91 | val = marker; 92 | } 93 | 94 | return `${tag}="${val}"`; 95 | }) 96 | ); 97 | 98 | values.push(items.join(';')); 99 | } 100 | 101 | return new Headers([['Signature', values.join(',')]]); 102 | } 103 | 104 | export function designature(value: string) { 105 | const values = value.replace(' ', '').split(','); 106 | 107 | const signages = values.map((val) => { 108 | const dict = new Map(); 109 | val.split(';').forEach((v) => { 110 | const splits = v.split('=', 2); 111 | dict.set(splits[0], splits[1].replaceAll('"', '')); 112 | }); 113 | 114 | if (!dict.has('indexed')) { 115 | throw new Error( 116 | 'Missing indexed field in Signature header signage.' 117 | ); 118 | } 119 | const item = dict.get('indexed')!; 120 | const indexed = !FALSY.includes(item); 121 | dict.delete('indexed'); 122 | 123 | let signer; 124 | if (dict.has('signer')) { 125 | signer = dict.get('signer') as string; 126 | dict.delete('signer'); 127 | } 128 | 129 | let ordinal; 130 | if (dict.has('ordinal')) { 131 | ordinal = dict.get('ordinal') as string; 132 | dict.delete('ordinal'); 133 | } 134 | 135 | let digest; 136 | if (dict.has('digest')) { 137 | digest = dict.get('digest') as string; 138 | dict.delete('digest'); 139 | } 140 | 141 | let kind; 142 | if (dict.has('kind')) { 143 | kind = dict.get('kind') as string; 144 | dict.delete('kind'); 145 | } else { 146 | kind = 'CESR'; 147 | } 148 | 149 | if (kind == 'CESR') { 150 | const markers = new Map(); 151 | 152 | for (const [key, val] of dict.entries()) { 153 | if (indexed) { 154 | markers.set(key, new Siger({ qb64: val })); 155 | } else { 156 | markers.set(key, new Cigar({ qb64: val })); 157 | } 158 | } 159 | 160 | return new Signage(markers, indexed, signer, ordinal, digest, kind); 161 | } else { 162 | return new Signage(dict, indexed, signer, ordinal, digest, kind); 163 | } 164 | }); 165 | 166 | return signages; 167 | } 168 | -------------------------------------------------------------------------------- /src/ready.ts: -------------------------------------------------------------------------------- 1 | import _sodium from 'libsodium-wrappers-sumo'; 2 | 3 | export const ready: () => Promise = async () => { 4 | await _sodium.ready; 5 | }; 6 | -------------------------------------------------------------------------------- /test-integration/challenge.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, test } from 'vitest'; 2 | import signify, { Serder } from 'signify-ts'; 3 | import { resolveEnvironment } from './utils/resolve-env.ts'; 4 | import { 5 | assertOperations, 6 | resolveOobi, 7 | waitOperation, 8 | } from './utils/test-util.ts'; 9 | 10 | const { url, bootUrl } = resolveEnvironment(); 11 | 12 | test('challenge', async () => { 13 | await signify.ready(); 14 | const bran1 = signify.randomPasscode(); 15 | const bran2 = signify.randomPasscode(); 16 | const client1 = new signify.SignifyClient( 17 | url, 18 | bran1, 19 | signify.Tier.low, 20 | bootUrl 21 | ); 22 | const client2 = new signify.SignifyClient( 23 | url, 24 | bran2, 25 | signify.Tier.low, 26 | bootUrl 27 | ); 28 | await client1.boot(); 29 | await client2.boot(); 30 | await client1.connect(); 31 | await client2.connect(); 32 | const state1 = await client1.state(); 33 | const state2 = await client2.state(); 34 | console.log( 35 | 'Client 1 connected. Client AID:', 36 | state1.controller.state.i, 37 | 'Agent AID: ', 38 | state1.agent.i 39 | ); 40 | console.log( 41 | 'Client 2 connected. Client AID:', 42 | state2.controller.state.i, 43 | 'Agent AID: ', 44 | state2.agent.i 45 | ); 46 | 47 | // Generate challenge words 48 | const challenge1_small = await client1.challenges().generate(128); 49 | assert.equal(challenge1_small.words.length, 12); 50 | const challenge1_big = await client1.challenges().generate(256); 51 | assert.equal(challenge1_big.words.length, 24); 52 | 53 | // Create two identifiers, one for each client 54 | const icpResult1 = await client1.identifiers().create('alice', { 55 | toad: 3, 56 | wits: [ 57 | 'BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha', 58 | 'BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM', 59 | 'BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX', 60 | ], 61 | }); 62 | const { response: aid1 } = await waitOperation( 63 | client1, 64 | await icpResult1.op() 65 | ); 66 | const rpyResult1 = await client1 67 | .identifiers() 68 | .addEndRole('alice', 'agent', client1!.agent!.pre); 69 | await waitOperation(client1, await rpyResult1.op()); 70 | console.log("Alice's AID:", aid1.i); 71 | 72 | const icpResult2 = await client2.identifiers().create('bob', { 73 | toad: 3, 74 | wits: [ 75 | 'BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha', 76 | 'BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM', 77 | 'BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX', 78 | ], 79 | }); 80 | const { response: aid2 } = await waitOperation( 81 | client2, 82 | await icpResult2.op() 83 | ); 84 | const rpyResult2 = await client2 85 | .identifiers() 86 | .addEndRole('bob', 'agent', client2!.agent!.pre); 87 | await waitOperation(client2, await rpyResult2.op()); 88 | 89 | // Exchenge OOBIs 90 | const oobi1 = await client1.oobis().get('alice', 'agent'); 91 | const oobi2 = await client2.oobis().get('bob', 'agent'); 92 | 93 | await resolveOobi(client1, oobi2.oobis[0], 'bob'); 94 | console.log("Client 1 resolved Bob's OOBI"); 95 | await resolveOobi(client2, oobi1.oobis[0], 'alice'); 96 | console.log("Client 2 resolved Alice's OOBI"); 97 | 98 | // List Client 1 contacts 99 | let contacts1 = await client1.contacts().list(); 100 | let bobContact = contacts1.find((contact) => contact.alias === 'bob'); 101 | assert.equal(bobContact?.alias, 'bob'); 102 | assert(Array.isArray(bobContact?.challenges)); 103 | assert.strictEqual(bobContact.challenges.length, 0); 104 | 105 | // Bob responds to Alice challenge 106 | await client2.challenges().respond('bob', aid1.i, challenge1_small.words); 107 | console.log('Bob responded to Alice challenge with signed words'); 108 | 109 | // Alice verifies Bob's response 110 | const verifyOperation = await waitOperation( 111 | client1, 112 | await client1.challenges().verify(aid2.i, challenge1_small.words) 113 | ); 114 | console.log('Alice verified challenge response'); 115 | 116 | //Alice mark response as accepted 117 | const verifyResponse = verifyOperation.response as { 118 | exn: Record; 119 | }; 120 | const exn = new Serder(verifyResponse.exn); 121 | 122 | await client1.challenges().responded(aid2.i, exn.sad.d); 123 | console.log('Alice marked challenge response as accepted'); 124 | 125 | // Check Bob's challenge in conctats 126 | contacts1 = await client1.contacts().list(); 127 | bobContact = contacts1.find((contact) => contact.alias === 'bob'); 128 | 129 | assert(Array.isArray(bobContact?.challenges)); 130 | assert.strictEqual(bobContact?.challenges[0].authenticated, true); 131 | 132 | await assertOperations(client1, client2); 133 | }, 30000); 134 | -------------------------------------------------------------------------------- /test-integration/delegation.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, test } from 'vitest'; 2 | import signify from 'signify-ts'; 3 | import { resolveEnvironment } from './utils/resolve-env.ts'; 4 | import { 5 | assertOperations, 6 | getOrCreateContact, 7 | resolveOobi, 8 | waitOperation, 9 | } from './utils/test-util.ts'; 10 | import { retry } from './utils/retry.ts'; 11 | import { step } from './utils/test-step.ts'; 12 | 13 | const { url, bootUrl } = resolveEnvironment(); 14 | 15 | test('delegation', async () => { 16 | await signify.ready(); 17 | // Boot two clients 18 | const bran1 = signify.randomPasscode(); 19 | const bran2 = signify.randomPasscode(); 20 | const client1 = new signify.SignifyClient( 21 | url, 22 | bran1, 23 | signify.Tier.low, 24 | bootUrl 25 | ); 26 | const client2 = new signify.SignifyClient( 27 | url, 28 | bran2, 29 | signify.Tier.low, 30 | bootUrl 31 | ); 32 | await client1.boot(); 33 | await client2.boot(); 34 | await client1.connect(); 35 | await client2.connect(); 36 | const state1 = await client1.state(); 37 | const state2 = await client2.state(); 38 | console.log( 39 | 'Client 1 connected. Client AID:', 40 | state1.controller.state.i, 41 | 'Agent AID: ', 42 | state1.agent.i 43 | ); 44 | console.log( 45 | 'Client 2 connected. Client AID:', 46 | state2.controller.state.i, 47 | 'Agent AID: ', 48 | state2.agent.i 49 | ); 50 | 51 | // Client 1 create delegator AID 52 | const icpResult1 = await client1.identifiers().create('delegator', { 53 | toad: 3, 54 | wits: [ 55 | 'BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha', 56 | 'BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM', 57 | 'BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX', 58 | ], 59 | }); 60 | await waitOperation(client1, await icpResult1.op()); 61 | const ator = await client1.identifiers().get('delegator'); 62 | const rpyResult1 = await client1 63 | .identifiers() 64 | .addEndRole('delegator', 'agent', client1!.agent!.pre); 65 | await waitOperation(client1, await rpyResult1.op()); 66 | console.log("Delegator's AID:", ator.prefix); 67 | 68 | // Client 2 resolves delegator OOBI 69 | console.log('Client 2 resolving delegator OOBI'); 70 | const oobi1 = await client1.oobis().get('delegator', 'agent'); 71 | await resolveOobi(client2, oobi1.oobis[0], 'delegator'); 72 | console.log('OOBI resolved'); 73 | 74 | // Client 2 creates delegate AID 75 | const icpResult2 = await client2 76 | .identifiers() 77 | .create('delegate', { delpre: ator.prefix }); 78 | const op2 = await icpResult2.op(); 79 | const delegatePrefix = op2.name.split('.')[1]; 80 | console.log("Delegate's prefix:", delegatePrefix); 81 | console.log('Delegate waiting for approval...'); 82 | 83 | // Client 1 approves delegation 84 | const anchor = { 85 | i: delegatePrefix, 86 | s: '0', 87 | d: delegatePrefix, 88 | }; 89 | 90 | await step('delegator approves delegation', async () => { 91 | const result = await retry(async () => { 92 | const apprDelRes = await client1 93 | .delegations() 94 | .approve('delegator', anchor); 95 | await waitOperation(client1, await apprDelRes.op()); 96 | console.log('Delegator approve delegation submitted'); 97 | return apprDelRes; 98 | }); 99 | assert.equal( 100 | JSON.stringify(result.serder.sad.a[0]), 101 | JSON.stringify(anchor) 102 | ); 103 | }); 104 | 105 | const op3 = await client2.keyStates().query(ator.prefix, '1'); 106 | await waitOperation(client2, op3); 107 | 108 | // Client 2 check approval 109 | await waitOperation(client2, op2); 110 | const aid2 = await client2.identifiers().get('delegate'); 111 | assert.equal(aid2.prefix, delegatePrefix); 112 | console.log('Delegation approved for aid:', aid2.prefix); 113 | 114 | await assertOperations(client1, client2); 115 | const rpyResult2 = await client2 116 | .identifiers() 117 | .addEndRole('delegate', 'agent', client2!.agent!.pre); 118 | await waitOperation(client2, await rpyResult2.op()); 119 | const oobis = await client2.oobis().get('delegate'); 120 | 121 | const contactId = await getOrCreateContact( 122 | client1, 123 | 'delegate', 124 | oobis.oobis[0].split('/agent/')[0] 125 | ); 126 | 127 | assert.equal(contactId, aid2.prefix); 128 | }, 600000); 129 | -------------------------------------------------------------------------------- /test-integration/externalModule.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, test } from 'vitest'; 2 | import signify from 'signify-ts'; 3 | import { BIP39Shim } from './modules/bip39_shim.ts'; 4 | import { resolveEnvironment } from './utils/resolve-env.ts'; 5 | import { assertOperations, waitOperation } from './utils/test-util.ts'; 6 | 7 | const { url, bootUrl } = resolveEnvironment(); 8 | 9 | test('bip39_shim', async () => { 10 | await signify.ready(); 11 | const bran1 = signify.randomPasscode(); 12 | const externalModule: signify.ExternalModule = { 13 | type: 'bip39_shim', 14 | name: 'bip39_shim', 15 | module: BIP39Shim, 16 | }; 17 | const client1 = new signify.SignifyClient( 18 | url, 19 | bran1, 20 | signify.Tier.low, 21 | bootUrl, 22 | [externalModule] 23 | ); 24 | await client1.boot(); 25 | await client1.connect(); 26 | const state1 = await client1.state(); 27 | console.log( 28 | 'Client 1 connected. Client AID:', 29 | state1.controller.state.i, 30 | 'Agent AID: ', 31 | state1.agent.i 32 | ); 33 | const words = new BIP39Shim(0, {}).generateMnemonic(256); 34 | const icpResult = await client1.identifiers().create('aid1', { 35 | algo: signify.Algos.extern, 36 | extern_type: 'bip39_shim', 37 | extern: { mnemonics: words }, 38 | }); 39 | const op = await waitOperation(client1, await icpResult.op()); 40 | assert.equal(op['done'], true); 41 | 42 | await assertOperations(client1); 43 | }, 30000); 44 | -------------------------------------------------------------------------------- /test-integration/modules/bip39_shim.ts: -------------------------------------------------------------------------------- 1 | import { mnemonicToSeedSync, generateMnemonic } from 'bip39'; 2 | import { 3 | Diger, 4 | Signer, 5 | MtrDex, 6 | IdentifierManager, 7 | IdentifierManagerResult, 8 | Algos, 9 | } from 'signify-ts'; 10 | 11 | export class BIP39Shim implements IdentifierManager { 12 | private icount: number; 13 | private ncount: number; 14 | private dcode: string | undefined; 15 | private pidx: number = 0; 16 | private kidx: number = 0; 17 | private transferable: boolean; 18 | private stem: string; 19 | private mnemonics: string = ''; 20 | algo: Algos = Algos.extern; 21 | signers: Signer[] = []; 22 | 23 | constructor(pidx: number, kargs: any) { 24 | this.icount = kargs.icount ?? 1; 25 | this.ncount = kargs.ncount ?? 1; 26 | this.pidx = pidx; 27 | this.kidx = kargs.kidx ?? 0; 28 | this.transferable = kargs.transferable ?? true; 29 | this.stem = kargs.stem ?? 'bip39_shim'; 30 | if (kargs.extern != undefined && kargs.extern.mnemonics != undefined) { 31 | this.mnemonics = kargs.extern.mnemonics; 32 | } 33 | this.dcode = kargs.dcode; 34 | } 35 | 36 | params() { 37 | return { 38 | pidx: this.pidx, 39 | kidx: this.kidx, 40 | mnemonics: this.mnemonics, 41 | }; 42 | } 43 | 44 | keys(count: number, kidx: number, transferable: boolean) { 45 | const keys = []; 46 | for (let idx = 0; idx < count; idx++) { 47 | const keyId = `${this.stem}-${this.pidx}-${kidx + idx}`; 48 | const seed = mnemonicToSeedSync(this.mnemonics, keyId); 49 | const signer = new Signer({ 50 | raw: new Uint8Array(seed), 51 | code: MtrDex.Ed25519_Seed, 52 | transferable: transferable, 53 | }); 54 | keys.push(signer); 55 | } 56 | return keys; 57 | } 58 | 59 | async incept(transferable: boolean): Promise { 60 | const signers = this.keys(this.icount, this.kidx, transferable); 61 | const verfers = signers.map((signer) => signer.verfer.qb64); 62 | 63 | const nsigners = this.keys( 64 | this.ncount, 65 | this.kidx + this.icount, 66 | transferable 67 | ); 68 | const digers = nsigners.map( 69 | (nsigner) => 70 | new Diger({ code: this.dcode }, nsigner.verfer.qb64b).qb64 71 | ); 72 | return [verfers, digers]; 73 | } 74 | 75 | async rotate( 76 | // TODO: This signature is incompatible with Keeper 77 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 78 | count: any, //number, 79 | transferable: boolean 80 | ): Promise { 81 | const signers = this.keys( 82 | this.ncount, 83 | this.kidx + this.icount, 84 | transferable 85 | ); 86 | const verfers = signers.map((signer) => signer.verfer.qb64); 87 | 88 | this.kidx = this.kidx + this.icount; 89 | this.icount = this.ncount; 90 | 91 | // TODO: Due to incompatible signature. 92 | this.ncount = count as number; 93 | 94 | const nsigners = this.keys( 95 | this.ncount, 96 | this.kidx + this.icount, 97 | this.transferable 98 | ); 99 | const digers = nsigners.map( 100 | (nsigner) => 101 | new Diger({ code: this.dcode }, nsigner.verfer.qb64b).qb64 102 | ); 103 | 104 | return [verfers, digers]; 105 | } 106 | 107 | async sign( 108 | ser: Uint8Array, 109 | indexed = true, 110 | indices: number[] | undefined = undefined, 111 | ondices: number[] | undefined = undefined 112 | ) { 113 | const signers = this.keys(this.icount, this.kidx, this.transferable); 114 | 115 | if (indexed) { 116 | const sigers = []; 117 | let i = 0; 118 | for (const [j, signer] of signers.entries()) { 119 | if (indices != undefined) { 120 | i = indices![j]; 121 | if (typeof i != 'number' || i < 0) { 122 | throw new Error( 123 | `Invalid signing index = ${i}, not whole number.` 124 | ); 125 | } 126 | } else { 127 | i = j; 128 | } 129 | let o = 0; 130 | if (ondices != undefined) { 131 | o = ondices![j]; 132 | if ( 133 | (o == undefined || 134 | (typeof o == 'number' && 135 | typeof o != 'number' && 136 | o >= 0))! 137 | ) { 138 | throw new Error( 139 | `Invalid ondex = ${o}, not whole number.` 140 | ); 141 | } 142 | } else { 143 | o = i; 144 | } 145 | sigers.push( 146 | signer.sign(ser, i, o == undefined ? true : false, o) 147 | ); 148 | } 149 | return sigers.map((siger) => siger.qb64); 150 | } else { 151 | const cigars = []; 152 | for (const [, signer] of signers.entries()) { 153 | cigars.push(signer.sign(ser)); 154 | } 155 | return cigars.map((cigar) => cigar.qb64); 156 | } 157 | } 158 | 159 | generateMnemonic(strength: number) { 160 | return generateMnemonic(strength); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /test-integration/multisig-inception.test.ts: -------------------------------------------------------------------------------- 1 | import signify from 'signify-ts'; 2 | import { 3 | getOrCreateClient, 4 | getOrCreateIdentifier, 5 | resolveOobi, 6 | waitForNotifications, 7 | waitOperation, 8 | } from './utils/test-util.ts'; 9 | import { 10 | acceptMultisigIncept, 11 | startMultisigIncept, 12 | } from './utils/multisig-utils.ts'; 13 | import { assert, test } from 'vitest'; 14 | import { step } from './utils/test-step.ts'; 15 | 16 | test('multisig inception', async () => { 17 | await signify.ready(); 18 | const [client1, client2] = await Promise.all([ 19 | getOrCreateClient(), 20 | getOrCreateClient(), 21 | ]); 22 | 23 | const [[aid1], [aid2]] = await Promise.all([ 24 | getOrCreateIdentifier(client1, 'member1'), 25 | getOrCreateIdentifier(client2, 'member2'), 26 | ]); 27 | 28 | await step('Resolve oobis', async () => { 29 | const oobi1 = await client1.oobis().get('member1', 'agent'); 30 | const oobi2 = await client2.oobis().get('member2', 'agent'); 31 | 32 | await Promise.all([ 33 | resolveOobi(client1, oobi2.oobis[0], 'member2'), 34 | resolveOobi(client2, oobi1.oobis[0], 'member1'), 35 | ]); 36 | }); 37 | 38 | await step('Create multisig group', async () => { 39 | const groupName = 'multisig'; 40 | const op1 = await startMultisigIncept(client1, { 41 | groupName, 42 | localMemberName: 'member1', 43 | participants: [aid1, aid2], 44 | toad: 2, 45 | isith: 2, 46 | nsith: 2, 47 | wits: [ 48 | 'BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha', 49 | 'BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM', 50 | 'BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX', 51 | ], 52 | }); 53 | console.log( 54 | 'Member1 initiated multisig, waiting for others to join...' 55 | ); 56 | 57 | // Second member check notifications and join the multisig 58 | const notifications = await waitForNotifications( 59 | client2, 60 | '/multisig/icp' 61 | ); 62 | await Promise.all( 63 | notifications.map((note) => client2.notifications().mark(note.i)) 64 | ); 65 | const msgSaid = notifications[notifications.length - 1].a.d; 66 | assert(msgSaid, 'msgSaid not defined'); 67 | const op2 = await acceptMultisigIncept(client2, { 68 | localMemberName: 'member2', 69 | groupName, 70 | msgSaid, 71 | }); 72 | console.log('Member2 joined multisig, waiting for others...'); 73 | 74 | // Check for completion 75 | await Promise.all([ 76 | waitOperation(client1, op1), 77 | waitOperation(client2, op2), 78 | ]); 79 | console.log('Multisig created!'); 80 | 81 | const multisig1 = await client1.identifiers().get(groupName); 82 | const multisig2 = await client2.identifiers().get(groupName); 83 | assert.strictEqual(multisig1.prefix, multisig2.prefix); 84 | const members = await client1.identifiers().members(groupName); 85 | assert.strictEqual(members.signing.length, 2); 86 | assert.strictEqual(members.rotation.length, 2); 87 | assert.strictEqual(members.signing[0].aid, aid1); 88 | assert.strictEqual(members.signing[1].aid, aid2); 89 | assert.strictEqual(members.rotation[0].aid, aid1); 90 | assert.strictEqual(members.rotation[1].aid, aid2); 91 | }); 92 | 93 | await step('Test creating another group', async () => { 94 | const groupName = 'multisig2'; 95 | const op1 = await startMultisigIncept(client1, { 96 | groupName, 97 | localMemberName: 'member1', 98 | participants: [aid1, aid2], 99 | toad: 0, 100 | isith: 2, 101 | nsith: 2, 102 | wits: [], 103 | }); 104 | console.log( 105 | 'Member1 initiated multisig, waiting for others to join...' 106 | ); 107 | 108 | // Second member check notifications and join the multisig 109 | const notifications = await waitForNotifications( 110 | client2, 111 | '/multisig/icp' 112 | ); 113 | await Promise.all( 114 | notifications.map((note) => client2.notifications().mark(note.i)) 115 | ); 116 | const msgSaid = notifications[notifications.length - 1].a.d; 117 | assert(msgSaid, 'msgSaid not defined'); 118 | const op2 = await acceptMultisigIncept(client2, { 119 | localMemberName: 'member2', 120 | groupName, 121 | msgSaid, 122 | }); 123 | 124 | await Promise.all([ 125 | waitOperation(client1, op1), 126 | waitOperation(client2, op2), 127 | ]); 128 | 129 | // TODO: https://github.com/WebOfTrust/keria/issues/189 130 | // const members = await client1.identifiers().members(groupName); 131 | // assert.strictEqual(members.signing.length, 2); 132 | // assert.strictEqual(members.rotating.length, 2); 133 | }); 134 | }, 30000); 135 | -------------------------------------------------------------------------------- /test-integration/randy.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, test } from 'vitest'; 2 | import signify from 'signify-ts'; 3 | import { resolveEnvironment } from './utils/resolve-env.ts'; 4 | import { assertOperations, waitOperation } from './utils/test-util.ts'; 5 | 6 | const { url, bootUrl } = resolveEnvironment(); 7 | 8 | test('randy', async () => { 9 | await signify.ready(); 10 | // Boot client 11 | const bran1 = signify.randomPasscode(); 12 | const client1 = new signify.SignifyClient( 13 | url, 14 | bran1, 15 | signify.Tier.low, 16 | bootUrl 17 | ); 18 | await client1.boot(); 19 | await client1.connect(); 20 | await client1.state(); 21 | 22 | let icpResult = await client1 23 | .identifiers() 24 | .create('aid1', { algo: signify.Algos.randy }); 25 | let op = await waitOperation(client1, await icpResult.op()); 26 | assert.equal(op['done'], true); 27 | let aid = op['response']; 28 | const icp = new signify.Serder(aid); 29 | assert.equal(icp.verfers.length, 1); 30 | assert.equal(icp.digers.length, 1); 31 | assert.equal(icp.sad['kt'], '1'); 32 | assert.equal(icp.sad['nt'], '1'); 33 | 34 | let aids = await client1.identifiers().list(); 35 | assert.equal(aids.aids.length, 1); 36 | aid = aids.aids[0]; 37 | assert.equal(aid.name, 'aid1'); 38 | assert.equal(aid.prefix, icp.pre); 39 | 40 | icpResult = await client1.identifiers().interact('aid1', [icp.pre]); 41 | op = await waitOperation(client1, await icpResult.op()); 42 | let ked = op['response']; 43 | const ixn = new signify.Serder(ked); 44 | assert.equal(ixn.sad['s'], '1'); 45 | assert.deepEqual([...ixn.sad['a']], [icp.pre]); 46 | 47 | aids = await client1.identifiers().list(); 48 | assert.equal(aids.aids.length, 1); 49 | aid = aids.aids[0]; 50 | 51 | const events = client1.keyEvents(); 52 | let log = await events.get(aid['prefix']); 53 | assert.equal(log.length, 2); 54 | 55 | icpResult = await client1.identifiers().rotate('aid1'); 56 | op = await waitOperation(client1, await icpResult.op()); 57 | ked = op['response']; 58 | const rot = new signify.Serder(ked); 59 | assert.equal(rot.sad['s'], '2'); 60 | assert.equal(rot.verfers.length, 1); 61 | assert.equal(rot.digers.length, 1); 62 | assert.notEqual(rot.verfers[0].qb64, icp.verfers[0].qb64); 63 | assert.notEqual(rot.digers[0].qb64, icp.digers[0].qb64); 64 | const dig = new signify.Diger( 65 | { code: signify.MtrDex.Blake3_256 }, 66 | rot.verfers[0].qb64b 67 | ); 68 | assert.equal(dig.qb64, icp.digers[0].qb64); 69 | log = await events.get(aid['prefix']); 70 | assert.equal(log.length, 3); 71 | 72 | await assertOperations(client1); 73 | }, 30000); 74 | -------------------------------------------------------------------------------- /test-integration/singlesig-dip.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, assert, beforeAll, describe, test } from 'vitest'; 2 | import { CreateIdentiferArgs, SignifyClient } from 'signify-ts'; 3 | import { 4 | assertOperations, 5 | getOrCreateClients, 6 | getOrCreateContact, 7 | getOrCreateIdentifier, 8 | waitOperation, 9 | } from './utils/test-util.ts'; 10 | import { resolveEnvironment } from './utils/resolve-env.ts'; 11 | 12 | let client1: SignifyClient, client2: SignifyClient; 13 | let name1_id: string, name1_oobi: string; 14 | let contact1_id: string; 15 | 16 | beforeAll(async () => { 17 | [client1, client2] = await getOrCreateClients(2); 18 | }); 19 | beforeAll(async () => { 20 | [name1_id, name1_oobi] = await getOrCreateIdentifier(client1, 'name1'); 21 | }); 22 | beforeAll(async () => { 23 | contact1_id = await getOrCreateContact(client2, 'contact1', name1_oobi); 24 | }); 25 | afterAll(async () => { 26 | await assertOperations(client1, client2); 27 | }); 28 | 29 | describe('singlesig-dip', () => { 30 | test('delegate1a', async () => { 31 | // delegate creates identifier without witnesses 32 | let kargs: CreateIdentiferArgs = { 33 | delpre: name1_id, 34 | }; 35 | let result = await client2.identifiers().create('delegate1', kargs); 36 | let op = await result.op(); 37 | let delegate1 = await client2.identifiers().get('delegate1'); 38 | assert.equal(op.name, `delegation.${delegate1.prefix}`); 39 | 40 | delegate1 = await client2.identifiers().get('delegate1'); 41 | let seal = { 42 | i: delegate1.prefix, 43 | s: '0', 44 | d: delegate1.prefix, 45 | }; 46 | result = await client1.identifiers().interact('name1', seal); 47 | let op1 = await result.op(); 48 | 49 | // refresh keystate to sn=1 50 | let op2 = await client2.keyStates().query(name1_id, '1'); 51 | 52 | await Promise.all([ 53 | (op = await waitOperation(client2, op)), 54 | waitOperation(client1, op1), 55 | waitOperation(client2, op2), 56 | ]); 57 | 58 | delegate1 = await client2.identifiers().get('delegate1'); 59 | assert.equal(delegate1.prefix, op.response.i); 60 | 61 | // delegate creates identifier with default witness config 62 | const env = resolveEnvironment(); 63 | kargs = { 64 | delpre: name1_id, 65 | toad: env.witnessIds.length, 66 | wits: env.witnessIds, 67 | }; 68 | result = await client2.identifiers().create('delegate2', kargs); 69 | op = await result.op(); 70 | let delegate2 = await client2.identifiers().get('delegate2'); 71 | assert.equal(op.name, `delegation.${delegate2.prefix}`); 72 | 73 | // delegator approves delegate 74 | delegate2 = await client2.identifiers().get('delegate2'); 75 | seal = { 76 | i: delegate2.prefix, 77 | s: '0', 78 | d: delegate2.prefix, 79 | }; 80 | result = await client1.identifiers().interact('name1', seal); 81 | op1 = await result.op(); 82 | 83 | // refresh keystate to seal event 84 | op2 = await client2.keyStates().query(name1_id, undefined, seal); 85 | 86 | await Promise.all([ 87 | (op = await waitOperation(client2, op)), 88 | waitOperation(client1, op1), 89 | waitOperation(client2, op2), 90 | ]); 91 | 92 | // delegate waits for completion 93 | delegate2 = await client2.identifiers().get('delegate2'); 94 | assert.equal(delegate2.prefix, op.response.i); 95 | 96 | // make sure query with seal is idempotent 97 | op = await client2.keyStates().query(name1_id, undefined, seal); 98 | await waitOperation(client2, op); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /test-integration/singlesig-drt.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, assert, beforeAll, describe, test } from 'vitest'; 2 | import { CreateIdentiferArgs, SignifyClient } from 'signify-ts'; 3 | import { 4 | assertOperations, 5 | getOrCreateClients, 6 | getOrCreateContact, 7 | getOrCreateIdentifier, 8 | waitOperation, 9 | } from './utils/test-util.ts'; 10 | 11 | let delegator: SignifyClient, delegate: SignifyClient; 12 | let name1_id: string, name1_oobi: string; 13 | let contact1_id: string; 14 | 15 | beforeAll(async () => { 16 | [delegator, delegate] = await getOrCreateClients(2); 17 | }); 18 | beforeAll(async () => { 19 | [name1_id, name1_oobi] = await getOrCreateIdentifier(delegator, 'name1'); 20 | }); 21 | beforeAll(async () => { 22 | contact1_id = await getOrCreateContact(delegate, 'contact1', name1_oobi); 23 | }); 24 | afterAll(async () => { 25 | await assertOperations(delegator, delegate); 26 | }); 27 | 28 | describe('singlesig-drt', () => { 29 | test('delegate1a', async () => { 30 | // delegate creates identifier without witnesses 31 | let kargs: CreateIdentiferArgs = { 32 | delpre: name1_id, 33 | }; 34 | let result = await delegate.identifiers().create('delegate1', kargs); 35 | let op = await result.op(); 36 | let delegate1 = await delegate.identifiers().get('delegate1'); 37 | assert.equal(op.name, `delegation.${delegate1.prefix}`); 38 | 39 | // delegator approves delegate 40 | let seal = { 41 | i: delegate1.prefix, 42 | s: '0', 43 | d: delegate1.prefix, 44 | }; 45 | result = await delegator.identifiers().interact('name1', seal); 46 | let op1 = await result.op(); 47 | 48 | let op2 = await delegate.keyStates().query(name1_id, '1'); 49 | 50 | await Promise.all([ 51 | waitOperation(delegate, op), 52 | waitOperation(delegator, op1), 53 | waitOperation(delegate, op2), 54 | ]); 55 | 56 | kargs = {}; 57 | result = await delegate.identifiers().rotate('delegate1', kargs); 58 | op = await result.op(); 59 | assert.equal(op.name, `delegation.${result.serder.sad.d}`); 60 | 61 | // delegator approves delegate 62 | delegate1 = await delegate.identifiers().get('delegate1'); 63 | 64 | seal = { 65 | i: delegate1.prefix, 66 | s: '1', 67 | d: delegate1.state.d, 68 | }; 69 | 70 | result = await delegator.identifiers().interact('name1', seal); 71 | op1 = await result.op(); 72 | op2 = await delegate.keyStates().query(name1_id, '2'); 73 | 74 | await Promise.all([ 75 | (op = await waitOperation(delegate, op)), 76 | waitOperation(delegator, op1), 77 | waitOperation(delegate, op2), 78 | ]); 79 | 80 | assert.equal(op.response.t, `drt`); 81 | assert.equal(op.response.s, `1`); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test-integration/singlesig-ixn.test.ts: -------------------------------------------------------------------------------- 1 | import { EventResult, SignifyClient } from 'signify-ts'; 2 | import { 3 | assertOperations, 4 | getOrCreateClients, 5 | getOrCreateContact, 6 | getOrCreateIdentifier, 7 | waitOperation, 8 | } from './utils/test-util.ts'; 9 | import { afterAll, assert, beforeAll, describe, expect, test } from 'vitest'; 10 | 11 | let client1: SignifyClient, client2: SignifyClient; 12 | let name1_id: string, name1_oobi: string; 13 | let contact1_id: string; 14 | 15 | beforeAll(async () => { 16 | [client1, client2] = await getOrCreateClients(2); 17 | }); 18 | beforeAll(async () => { 19 | [name1_id, name1_oobi] = await getOrCreateIdentifier(client1, 'name1'); 20 | }); 21 | beforeAll(async () => { 22 | contact1_id = await getOrCreateContact(client2, 'contact1', name1_oobi); 23 | }); 24 | afterAll(async () => { 25 | await assertOperations(client1, client2); 26 | }); 27 | 28 | interface KeyState { 29 | i: string; 30 | s: string; 31 | [property: string]: any; 32 | } 33 | 34 | describe('singlesig-ixn', () => { 35 | test('step1', async () => { 36 | assert.equal(name1_id, contact1_id); 37 | 38 | const keystate1 = await client1.keyStates().get(name1_id); 39 | assert.strictEqual(keystate1.length, 1); 40 | 41 | const keystate2 = await client2.keyStates().get(contact1_id); 42 | assert.strictEqual(keystate2.length, 1); 43 | 44 | // local and remote keystate sequence match 45 | assert.equal(keystate1[0].s, keystate2[0].s); 46 | }); 47 | test('ixn1', async () => { 48 | // local keystate before ixn 49 | const keystate0: KeyState = ( 50 | await client1.keyStates().get(name1_id) 51 | ).at(0); 52 | expect(keystate0).not.toBeNull(); 53 | 54 | // ixn 55 | const result: EventResult = await client1 56 | .identifiers() 57 | .interact('name1', {}); 58 | await waitOperation(client1, await result.op()); 59 | 60 | // local keystate after ixn 61 | const keystate1: KeyState = ( 62 | await client1.keyStates().get(name1_id) 63 | ).at(0); 64 | expect(parseInt(keystate1.s)).toBeGreaterThan(0); 65 | // sequence has incremented 66 | assert.equal(parseInt(keystate1.s), parseInt(keystate0.s) + 1); 67 | 68 | // remote keystate after ixn 69 | const keystate2: KeyState = ( 70 | await client2.keyStates().get(contact1_id) 71 | ).at(0); 72 | // remote keystate is one behind 73 | assert.equal(parseInt(keystate2.s), parseInt(keystate1.s) - 1); 74 | 75 | // refresh remote keystate 76 | let op = await client2 77 | .keyStates() 78 | .query(contact1_id, keystate1.s, undefined); 79 | op = await waitOperation(client2, op); 80 | const keystate3: KeyState = op.response; 81 | // local and remote keystate match 82 | assert.equal(keystate3.s, keystate1.s); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test-integration/singlesig-rot.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, assert, beforeAll, describe, expect, test } from 'vitest'; 2 | import { EventResult, RotateIdentifierArgs, SignifyClient } from 'signify-ts'; 3 | import { 4 | assertOperations, 5 | getOrCreateClients, 6 | getOrCreateContact, 7 | getOrCreateIdentifier, 8 | waitOperation, 9 | } from './utils/test-util.ts'; 10 | 11 | let client1: SignifyClient, client2: SignifyClient; 12 | let name1_id: string, name1_oobi: string; 13 | let contact1_id: string; 14 | 15 | beforeAll(async () => { 16 | [client1, client2] = await getOrCreateClients(2); 17 | }); 18 | beforeAll(async () => { 19 | [name1_id, name1_oobi] = await getOrCreateIdentifier(client1, 'name1'); 20 | }); 21 | beforeAll(async () => { 22 | contact1_id = await getOrCreateContact(client2, 'contact1', name1_oobi); 23 | }); 24 | afterAll(async () => { 25 | await assertOperations(client1, client2); 26 | }); 27 | 28 | interface KeyState { 29 | i: string; 30 | s: string; 31 | k: string[]; 32 | n: string[]; 33 | [property: string]: any; 34 | } 35 | 36 | describe('singlesig-rot', () => { 37 | test('step1', async () => { 38 | assert.equal(name1_id, contact1_id); 39 | 40 | const keystate1 = await client1.keyStates().get(name1_id); 41 | assert.strictEqual(keystate1.length, 1); 42 | 43 | const keystate2 = await client2.keyStates().get(contact1_id); 44 | assert.strictEqual(keystate2.length, 1); 45 | 46 | // local and remote keystate sequence match 47 | assert.equal(keystate1[0].s, keystate2[0].s); 48 | }); 49 | test('rot1', async () => { 50 | // local keystate before rot 51 | const keystate0: KeyState = ( 52 | await client1.keyStates().get(name1_id) 53 | ).at(0); 54 | expect(keystate0).not.toBeNull(); 55 | assert.strictEqual(keystate0.k.length, 1); 56 | assert.strictEqual(keystate0.n.length, 1); 57 | 58 | // rot 59 | const args: RotateIdentifierArgs = {}; 60 | const result: EventResult = await client1 61 | .identifiers() 62 | .rotate('name1', args); 63 | await waitOperation(client1, await result.op()); 64 | 65 | // local keystate after rot 66 | const keystate1: KeyState = ( 67 | await client1.keyStates().get(name1_id) 68 | ).at(0); 69 | expect(parseInt(keystate1.s)).toBeGreaterThan(0); 70 | // sequence has incremented 71 | assert.equal(parseInt(keystate1.s), parseInt(keystate0.s) + 1); 72 | // current keys changed 73 | expect(keystate1.k[0]).not.toEqual(keystate0.k[0]); 74 | // next key hashes changed 75 | expect(keystate1.n[0]).not.toEqual(keystate0.n[0]); 76 | 77 | // remote keystate after rot 78 | const keystate2: KeyState = ( 79 | await client2.keyStates().get(contact1_id) 80 | ).at(0); 81 | // remote keystate is one behind 82 | assert.equal(parseInt(keystate2.s), parseInt(keystate1.s) - 1); 83 | 84 | // refresh remote keystate 85 | let op = await client2 86 | .keyStates() 87 | .query(contact1_id, keystate1.s, undefined); 88 | op = await waitOperation(client2, op); 89 | const keystate3: KeyState = op.response; 90 | // local and remote keystate match 91 | assert.equal(keystate3.s, keystate1.s); 92 | assert.equal(keystate3.k[0], keystate1.k[0]); 93 | assert.equal(keystate3.n[0], keystate1.n[0]); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /test-integration/test-setup-clients.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, assert, beforeAll, describe, test } from 'vitest'; 2 | import { SignifyClient } from 'signify-ts'; 3 | import { 4 | assertOperations, 5 | getOrCreateClients, 6 | getOrCreateContact, 7 | getOrCreateIdentifier, 8 | } from './utils/test-util.ts'; 9 | 10 | let client1: SignifyClient, client2: SignifyClient; 11 | let name1_id: string, name1_oobi: string; 12 | let name2_id: string, name2_oobi: string; 13 | let contact1_id: string, contact2_id: string; 14 | 15 | beforeAll(async () => { 16 | // create two clients with random secrets 17 | [client1, client2] = await getOrCreateClients(2); 18 | }); 19 | beforeAll(async () => { 20 | [name1_id, name1_oobi] = await getOrCreateIdentifier(client1, 'name1'); 21 | [name2_id, name2_oobi] = await getOrCreateIdentifier(client2, 'name2'); 22 | }); 23 | beforeAll(async () => { 24 | contact1_id = await getOrCreateContact(client2, 'contact1', name1_oobi); 25 | contact2_id = await getOrCreateContact(client1, 'contact2', name2_oobi); 26 | }); 27 | afterAll(async () => { 28 | await assertOperations(client1, client2); 29 | }); 30 | 31 | describe('test-setup-clients', () => { 32 | test('step1', async () => { 33 | assert.equal(name1_id, contact1_id); 34 | }); 35 | test('step2', async () => { 36 | assert.equal(name2_id, contact2_id); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test-integration/test-setup-single-client.test.ts: -------------------------------------------------------------------------------- 1 | import { SignifyClient } from 'signify-ts'; 2 | import { resolveEnvironment } from './utils/resolve-env.ts'; 3 | import { 4 | assertOperations, 5 | getOrCreateClients, 6 | getOrCreateIdentifier, 7 | } from './utils/test-util.ts'; 8 | import { afterAll, assert, beforeAll, describe, test } from 'vitest'; 9 | 10 | let client: SignifyClient; 11 | let name1_id: string, name1_oobi: string; 12 | 13 | beforeAll(async () => { 14 | // Create client with pre-defined secret. Allows working with known identifiers 15 | [client] = await getOrCreateClients(1, ['0ADF2TpptgqcDE5IQUF1HeTp']); 16 | [name1_id, name1_oobi] = await getOrCreateIdentifier(client, 'name1'); 17 | }); 18 | 19 | afterAll(async () => { 20 | await assertOperations(client); 21 | }); 22 | 23 | describe('test-setup-single-client', () => { 24 | test('step1', async () => { 25 | assert.equal( 26 | client.controller?.pre, 27 | 'EB3UGWwIMq7ppzcQ697ImQIuXlBG5jzh-baSx-YG3-tY' 28 | ); 29 | }); 30 | 31 | test('step2', async () => { 32 | const env = resolveEnvironment(); 33 | const oobi = await client.oobis().get('name1', 'witness'); 34 | assert.strictEqual(oobi.oobis.length, 3); 35 | switch (env.preset) { 36 | case 'local': 37 | assert.equal( 38 | name1_oobi, 39 | `http://127.0.0.1:3902/oobi/${name1_id}/agent/${client.agent?.pre}` 40 | ); 41 | assert.equal( 42 | oobi.oobis[0], 43 | `http://127.0.0.1:5642/oobi/${name1_id}/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha` 44 | ); 45 | assert.equal( 46 | oobi.oobis[1], 47 | `http://127.0.0.1:5643/oobi/${name1_id}/witness/BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM` 48 | ); 49 | assert.equal( 50 | oobi.oobis[2], 51 | `http://127.0.0.1:5644/oobi/${name1_id}/witness/BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX` 52 | ); 53 | break; 54 | case 'docker': 55 | assert.equal( 56 | name1_oobi, 57 | `http://keria:3902/oobi/${name1_id}/agent/${client.agent?.pre}` 58 | ); 59 | assert.equal( 60 | oobi.oobis[0], 61 | `http://witness-demo:5642/oobi/${name1_id}/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha` 62 | ); 63 | assert.equal( 64 | oobi.oobis[1], 65 | `http://witness-demo:5643/oobi/${name1_id}/witness/BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM` 66 | ); 67 | assert.equal( 68 | oobi.oobis[2], 69 | `http://witness-demo:5644/oobi/${name1_id}/witness/BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX` 70 | ); 71 | break; 72 | } 73 | }); 74 | 75 | test('validate config', async () => { 76 | const env = resolveEnvironment(); 77 | const config = await client.config().get(); 78 | switch (env.preset) { 79 | case 'local': 80 | assert.deepEqual(config, { 81 | iurls: [ 82 | 'http://127.0.0.1:5642/oobi/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha/controller?name=Wan&tag=witness', 83 | 'http://127.0.0.1:5643/oobi/BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM/controller?name=Wes&tag=witness', 84 | 'http://127.0.0.1:5644/oobi/BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX/controller?name=Wil&tag=witness', 85 | ], 86 | }); 87 | break; 88 | case 'docker': 89 | assert.deepEqual(config, { 90 | iurls: [ 91 | 'http://witness-demo:5642/oobi/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha/controller', 92 | 'http://witness-demo:5643/oobi/BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM/controller', 93 | 'http://witness-demo:5644/oobi/BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX/controller', 94 | 'http://witness-demo:5645/oobi/BM35JN8XeJSEfpxopjn5jr7tAHCE5749f0OobhMLCorE/controller', 95 | 'http://witness-demo:5646/oobi/BIj15u5V11bkbtAxMA7gcNJZcax-7TgaBMLsQnMHpYHP/controller', 96 | 'http://witness-demo:5647/oobi/BF2rZTW79z4IXocYRQnjjsOuvFUQv-ptCf8Yltd7PfsM/controller', 97 | ], 98 | }); 99 | break; 100 | } 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /test-integration/utils/resolve-env.ts: -------------------------------------------------------------------------------- 1 | export type TestEnvironmentPreset = 'local' | 'docker'; 2 | 3 | export interface TestEnvironment { 4 | preset: TestEnvironmentPreset; 5 | url: string; 6 | bootUrl: string; 7 | vleiServerUrl: string; 8 | witnessUrls: string[]; 9 | witnessIds: string[]; 10 | } 11 | 12 | const WAN = 'BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha'; 13 | const WIL = 'BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM'; 14 | const WES = 'BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX'; 15 | 16 | export function resolveEnvironment( 17 | input?: TestEnvironmentPreset 18 | ): TestEnvironment { 19 | const preset = input ?? process.env.TEST_ENVIRONMENT ?? 'docker'; 20 | 21 | const url = 'http://127.0.0.1:3901'; 22 | const bootUrl = 'http://127.0.0.1:3903'; 23 | 24 | switch (preset) { 25 | case 'docker': 26 | return { 27 | preset: preset, 28 | url, 29 | bootUrl, 30 | witnessUrls: [ 31 | 'http://witness-demo:5642', 32 | 'http://witness-demo:5643', 33 | 'http://witness-demo:5644', 34 | ], 35 | witnessIds: [WAN, WIL, WES], 36 | vleiServerUrl: 'http://vlei-server:7723', 37 | }; 38 | case 'local': 39 | return { 40 | preset: preset, 41 | url, 42 | bootUrl, 43 | vleiServerUrl: 'http://localhost:7723', 44 | witnessUrls: [ 45 | 'http://localhost:5642', 46 | 'http://localhost:5643', 47 | 'http://localhost:5644', 48 | ], 49 | witnessIds: [WAN, WIL, WES], 50 | }; 51 | default: 52 | throw new Error(`Unknown test environment preset '${preset}'`); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test-integration/utils/retry.ts: -------------------------------------------------------------------------------- 1 | import { setTimeout } from 'timers/promises'; 2 | 3 | export interface RetryOptions { 4 | maxSleep?: number; 5 | minSleep?: number; 6 | maxRetries?: number; 7 | timeout?: number; 8 | signal?: AbortSignal; 9 | } 10 | 11 | export async function retry( 12 | fn: () => Promise, 13 | options: RetryOptions = {} 14 | ): Promise { 15 | const { 16 | maxSleep = 1000, 17 | minSleep = 10, 18 | maxRetries, 19 | timeout = 10000, 20 | } = options; 21 | 22 | const increaseFactor = 50; 23 | 24 | let retries = 0; 25 | let cause: Error | null = null; 26 | const start = Date.now(); 27 | 28 | while ( 29 | (options.signal === undefined || options.signal.aborted === false) && 30 | Date.now() - start < timeout && 31 | (maxRetries === undefined || retries < maxRetries) 32 | ) { 33 | try { 34 | const result = await fn(); 35 | return result; 36 | } catch (err) { 37 | cause = err as Error; 38 | const delay = Math.max( 39 | minSleep, 40 | Math.min(maxSleep, 2 ** retries * increaseFactor) 41 | ); 42 | retries++; 43 | await setTimeout(delay, undefined, { signal: options.signal }); 44 | } 45 | } 46 | 47 | if (!cause) { 48 | cause = new Error(`Failed after ${retries} attempts`); 49 | } 50 | 51 | Object.assign(cause, { retries, maxAttempts: maxRetries }); 52 | throw cause; 53 | } 54 | -------------------------------------------------------------------------------- /test-integration/utils/test-setup.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CreateIdentiferArgs, 3 | EventResult, 4 | SignifyClient, 5 | Tier, 6 | randomPasscode, 7 | ready, 8 | } from 'signify-ts'; 9 | import { resolveEnvironment } from './resolve-env.ts'; 10 | import { waitOperation } from './test-util.ts'; 11 | 12 | /** 13 | * Connect or boot a SignifyClient instance 14 | */ 15 | export async function getOrCreateClient( 16 | bran: string | undefined = undefined 17 | ): Promise { 18 | const env = resolveEnvironment(); 19 | await ready(); 20 | bran ??= randomPasscode(); 21 | bran = bran.padEnd(21, '_'); 22 | const client = new SignifyClient(env.url, bran, Tier.low, env.bootUrl); 23 | try { 24 | await client.connect(); 25 | } catch { 26 | const res = await client.boot(); 27 | if (!res.ok) throw new Error(); 28 | await client.connect(); 29 | } 30 | console.log('client', { 31 | agent: client.agent?.pre, 32 | controller: client.controller.pre, 33 | }); 34 | return client; 35 | } 36 | 37 | /** 38 | * Get or create a Keri identifier. Uses default witness config from `resolveEnvironment` 39 | * @example 40 | * Create a Keri identifier before running tests 41 | * let name1_id: string, name1_oobi: string; 42 | * beforeAll(async () => { 43 | * [name1_id, name1_oobi] = await getOrCreateIdentifier(client1, "name1"); 44 | * }); 45 | * @see resolveEnvironment 46 | */ 47 | export async function getOrCreateIdentifier( 48 | client: SignifyClient, 49 | name: string, 50 | kargs: CreateIdentiferArgs | undefined = undefined 51 | ): Promise<[string, string]> { 52 | let id: string; 53 | try { 54 | const identfier = await client.identifiers().get(name); 55 | // console.log("identifiers.get", identfier); 56 | id = identfier.prefix; 57 | } catch { 58 | const env = resolveEnvironment(); 59 | kargs ??= { 60 | toad: env.witnessIds.length, 61 | wits: env.witnessIds, 62 | }; 63 | const result: EventResult = await client 64 | .identifiers() 65 | .create(name, kargs); 66 | let op = await result.op(); 67 | op = await waitOperation(client, op); 68 | // console.log("identifiers.create", op); 69 | id = op.response.i; 70 | } 71 | const eid = client.agent?.pre; 72 | if (!eid) { 73 | throw new Error('No agent on client'); 74 | } 75 | if (!(await hasEndRole(client, name, 'agent', eid))) { 76 | const result: EventResult = await client 77 | .identifiers() 78 | .addEndRole(name, 'agent', eid); 79 | const op = await result.op(); 80 | await waitOperation(client, op); 81 | // console.log("identifiers.addEndRole", op); 82 | } 83 | const oobi = await client.oobis().get(name, 'agent'); 84 | const result: [string, string] = [id, oobi.oobis[0]]; 85 | console.log(name, result); 86 | return result; 87 | } 88 | 89 | /** 90 | * Get list of end role authorizations for a Keri idenfitier 91 | */ 92 | export async function getEndRoles( 93 | client: SignifyClient, 94 | alias: string, 95 | role?: string 96 | ): Promise { 97 | const path = 98 | role !== undefined 99 | ? `/identifiers/${alias}/endroles/${role}` 100 | : `/identifiers/${alias}/endroles`; 101 | const response: Response = await client.fetch(path, 'GET', null); 102 | if (!response.ok) throw new Error(await response.text()); 103 | const result = await response.json(); 104 | // console.log("getEndRoles", result); 105 | return result; 106 | } 107 | 108 | /** 109 | * Test if end role is authorized for a Keri identifier 110 | */ 111 | export async function hasEndRole( 112 | client: SignifyClient, 113 | alias: string, 114 | role: string, 115 | eid: string 116 | ): Promise { 117 | const list = await getEndRoles(client, alias, role); 118 | for (const i of list) { 119 | if (i.role === role && i.eid === eid) { 120 | return true; 121 | } 122 | } 123 | return false; 124 | } 125 | 126 | /** 127 | * Get or resolve a Keri contact 128 | * @example 129 | * Create a Keri contact before running tests 130 | * let contact1_id: string; 131 | * beforeAll(async () => { 132 | * contact1_id = await getOrCreateContact(client2, "contact1", name1_oobi); 133 | * }); 134 | */ 135 | export async function getOrCreateContact( 136 | client: SignifyClient, 137 | name: string, 138 | oobi: string 139 | ): Promise { 140 | const list = await client.contacts().list(undefined, 'alias', `^${name}$`); 141 | // console.log("contacts.list", list); 142 | if (list.length > 0) { 143 | const contact = list[0]; 144 | if (contact.oobi === oobi) { 145 | // console.log("contacts.id", contact.id); 146 | return contact.id; 147 | } 148 | } 149 | let op = await client.oobis().resolve(oobi, name); 150 | op = await waitOperation(client, op); 151 | // console.log("oobis.resolve", op); 152 | return op.response.i; 153 | } 154 | -------------------------------------------------------------------------------- /test-integration/utils/test-step.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides a way to group logically related test steps in an integration test 3 | * 4 | * Can be useful to provide logging when a step succeeds, or to be able to use 5 | * locally scoped variables. 6 | * 7 | * In long tests it can also be useful to create visual groups. 8 | * @param description 9 | * @param fn 10 | * @returns 11 | */ 12 | export async function step( 13 | description: string, 14 | fn: () => Promise 15 | ): Promise { 16 | try { 17 | const start = Date.now(); 18 | const response = await fn(); 19 | 20 | // Bypassing console.log to avoid the verbose log output from test runner 21 | process.stdout.write( 22 | `Step - ${description} - finished (${Date.now() - start}ms)\n` 23 | ); 24 | return response; 25 | } catch (error) { 26 | throw new Error(`Step - ${description} - failed`, { cause: error }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test-integration/witness.test.ts: -------------------------------------------------------------------------------- 1 | // This scrip also work if you start keria with no config file with witness urls 2 | import { assert, test } from 'vitest'; 3 | import signify from 'signify-ts'; 4 | import { resolveEnvironment } from './utils/resolve-env.ts'; 5 | import { resolveOobi, waitOperation } from './utils/test-util.ts'; 6 | 7 | const WITNESS_AID = 'BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha'; 8 | const { url, bootUrl, witnessUrls } = resolveEnvironment(); 9 | 10 | test('test witness', async () => { 11 | await signify.ready(); 12 | // Boot client 13 | const bran1 = signify.randomPasscode(); 14 | const client1 = new signify.SignifyClient( 15 | url, 16 | bran1, 17 | signify.Tier.low, 18 | bootUrl 19 | ); 20 | await client1.boot(); 21 | await client1.connect(); 22 | const state1 = await client1.state(); 23 | console.log( 24 | 'Client connected. Client AID:', 25 | state1.controller.state.i, 26 | 'Agent AID: ', 27 | state1.agent.i 28 | ); 29 | 30 | // Client 1 resolves witness OOBI 31 | await resolveOobi(client1, witnessUrls[0] + `/oobi/${WITNESS_AID}`, 'wit'); 32 | console.log('Witness OOBI resolved'); 33 | 34 | // Client 1 creates AID with 1 witness 35 | let icpResult1 = await client1.identifiers().create('aid1', { 36 | toad: 1, 37 | wits: [WITNESS_AID], 38 | }); 39 | await waitOperation(client1, await icpResult1.op()); 40 | let aid1 = await client1.identifiers().get('aid1'); 41 | console.log('AID:', aid1.prefix); 42 | assert.equal(aid1.state.b.length, 1); 43 | assert.equal(aid1.state.b[0], WITNESS_AID); 44 | 45 | icpResult1 = await client1.identifiers().rotate('aid1'); 46 | await waitOperation(client1, await icpResult1.op()); 47 | aid1 = await client1.identifiers().get('aid1'); 48 | assert.equal(aid1.state.b.length, 1); 49 | assert.equal(aid1.state.b[0], WITNESS_AID); 50 | 51 | // Remove witness 52 | icpResult1 = await client1 53 | .identifiers() 54 | .rotate('aid1', { cuts: [WITNESS_AID] }); 55 | await waitOperation(client1, await icpResult1.op()); 56 | aid1 = await client1.identifiers().get('aid1'); 57 | assert.equal(aid1.state.b.length, 0); 58 | 59 | // Add witness again 60 | 61 | icpResult1 = await client1 62 | .identifiers() 63 | .rotate('aid1', { adds: [WITNESS_AID] }); 64 | 65 | await waitOperation(client1, await icpResult1.op()); 66 | aid1 = await client1.identifiers().get('aid1'); 67 | assert.equal(aid1.state.b.length, 1); 68 | assert.equal(aid1.state.b.length, 1); 69 | assert.equal(aid1.state.b[0], WITNESS_AID); 70 | }, 60000); 71 | -------------------------------------------------------------------------------- /test/app/controller.test.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '../../src/keri/app/controller.ts'; 2 | import { assert, describe, it } from 'vitest'; 3 | import libsodium from 'libsodium-wrappers-sumo'; 4 | import { openManager } from '../../src/keri/core/manager.ts'; 5 | import { Signer } from '../../src/keri/core/signer.ts'; 6 | import { MtrDex } from '../../src/keri/core/matter.ts'; 7 | import { Tier, randomPasscode } from '../../src/index.ts'; 8 | 9 | describe('Controller', () => { 10 | it('manage account AID signing and agent verification', async () => { 11 | await libsodium.ready; 12 | let passcode = '0123456789abcdefghijk'; 13 | const mgr = openManager(passcode); 14 | assert.equal(mgr.aeid, 'BMbZTXzB7LmWPT2TXLGV88PQz5vDEM2L2flUs2yxn3U9'); 15 | 16 | const raw = new Uint8Array([ 17 | 187, 140, 234, 145, 219, 254, 20, 194, 16, 18, 97, 194, 140, 192, 18 | 61, 145, 222, 110, 59, 160, 152, 2, 72, 122, 87, 143, 109, 39, 98, 19 | 153, 192, 148, 20 | ]); 21 | const agentSigner = new Signer({ 22 | raw: raw, 23 | code: MtrDex.Ed25519_Seed, 24 | transferable: false, 25 | }); 26 | assert.equal( 27 | agentSigner.verfer.qb64, 28 | 'BHptu91ecGv_mxO8T3b98vNQUCghT8nfYkWRkVqOZark' 29 | ); 30 | 31 | // New account needed. Send to remote my name and encryption pubk and get back 32 | // their pubk and and my encrypted account package 33 | // let pkg = {} 34 | let controller = new Controller(passcode, Tier.low); 35 | assert.equal( 36 | controller.pre, 37 | 'ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose' 38 | ); 39 | 40 | passcode = 'abcdefghijk0123456789'; 41 | controller = new Controller(passcode, Tier.low); 42 | assert.equal( 43 | controller.pre, 44 | 'EIIY2SgE_bqKLl2MlnREUawJ79jTuucvWwh-S6zsSUFo' 45 | ); 46 | }); 47 | 48 | it('should generate unique controller AIDs per passcode', async () => { 49 | await libsodium.ready; 50 | const passcode1 = randomPasscode(); 51 | const passcode2 = randomPasscode(); 52 | 53 | const controller1 = new Controller(passcode1, Tier.low); 54 | const controller2 = new Controller(passcode2, Tier.low); 55 | 56 | assert.notEqual(controller1.pre, controller2.pre); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/app/delegating.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it } from 'vitest'; 2 | import { Tier } from '../../src/index.ts'; 3 | import libsodium from 'libsodium-wrappers-sumo'; 4 | import { SignifyClient } from '../../src/keri/app/clienting.ts'; 5 | import { createMockFetch } from './test-utils.ts'; 6 | 7 | const fetchMock = createMockFetch(); 8 | 9 | const url = 'http://127.0.0.1:3901'; 10 | const boot_url = 'http://127.0.0.1:3903'; 11 | 12 | describe('delegate', () => { 13 | it('approve delegation', async () => { 14 | await libsodium.ready; 15 | const bran = '0123456789abcdefghijk'; 16 | const client = new SignifyClient(url, bran, Tier.low, boot_url); 17 | await client.boot(); 18 | await client.connect(); 19 | const delegations = client.delegations(); 20 | await delegations.approve( 21 | 'EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao' 22 | ); 23 | const lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1]!; 24 | assert.equal( 25 | lastCall[0]!, 26 | url + 27 | '/identifiers/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao/delegation' 28 | ); 29 | assert.equal(lastCall[1]!.method, 'POST'); 30 | const expectedBody = { 31 | ixn: { 32 | v: 'KERI10JSON0000cf_', 33 | t: 'ixn', 34 | d: 'EBPt7hivibUQN-dlRyE9x_Y5LgFCGJ8QoNLSJrIkBYIg', 35 | i: 'ELUvZ8aJEHAQE-0nsevyYTP98rBbGJUrTj5an-pCmwrK', 36 | s: '1', 37 | p: 'ELUvZ8aJEHAQE-0nsevyYTP98rBbGJUrTj5an-pCmwrK', 38 | a: [null], 39 | }, 40 | sigs: [ 41 | 'AAC4StAw-0IiV_LujceAXB3tnkaK011rPYPBKLgz-u6jI7hwfWGTCu5LDvBUsON4CqXbZAwPgIv6JqYjIusWKv0G', 42 | ], 43 | salty: { 44 | sxlt: '1AAHnNQTkD0yxOC9tSz_ukbB2e-qhDTStH18uCsi5PCwOyXLONDR3MeKwWv_AVJKGKGi6xiBQH25_R1RXLS2OuK3TN3ovoUKH7-A', 45 | pidx: 0, 46 | kidx: 0, 47 | stem: 'signify:aid', 48 | tier: 'low', 49 | icodes: ['A'], 50 | ncodes: ['A'], 51 | dcode: 'E', 52 | transferable: true, 53 | }, 54 | }; 55 | assert.equal( 56 | lastCall[1]?.body?.toString(), 57 | JSON.stringify(expectedBody) 58 | ); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/app/escrowing.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it } from 'vitest'; 2 | import { SignifyClient } from '../../src/keri/app/clienting.ts'; 3 | import { Tier } from '../../src/keri/core/salter.ts'; 4 | import libsodium from 'libsodium-wrappers-sumo'; 5 | import { createMockFetch } from './test-utils.ts'; 6 | 7 | const fetchMock = createMockFetch(); 8 | 9 | const url = 'http://127.0.0.1:3901'; 10 | const boot_url = 'http://127.0.0.1:3903'; 11 | 12 | describe('SignifyClient', () => { 13 | it('Escrows', async () => { 14 | await libsodium.ready; 15 | const bran = '0123456789abcdefghijk'; 16 | 17 | const client = new SignifyClient(url, bran, Tier.low, boot_url); 18 | 19 | await client.boot(); 20 | await client.connect(); 21 | 22 | const escrows = client.escrows(); 23 | 24 | let lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1]!; 25 | await escrows.listReply('/presentation/request'); 26 | lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1]!; 27 | assert.equal( 28 | lastCall[0]!, 29 | url + '/escrows/rpy?route=%2Fpresentation%2Frequest' 30 | ); 31 | assert.equal(lastCall[1]!.method, 'GET'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/app/grouping.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it } from 'vitest'; 2 | import { SignifyClient } from '../../src/keri/app/clienting.ts'; 3 | import { Tier } from '../../src/keri/core/salter.ts'; 4 | import libsodium from 'libsodium-wrappers-sumo'; 5 | import { createMockFetch } from './test-utils.ts'; 6 | 7 | const fetchMock = createMockFetch(); 8 | 9 | const url = 'http://127.0.0.1:3901'; 10 | const boot_url = 'http://127.0.0.1:3903'; 11 | 12 | describe('Grouping', () => { 13 | it('Groups', async () => { 14 | await libsodium.ready; 15 | const bran = '0123456789abcdefghijk'; 16 | 17 | const client = new SignifyClient(url, bran, Tier.low, boot_url); 18 | 19 | await client.boot(); 20 | await client.connect(); 21 | 22 | const groups = client.groups(); 23 | let lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1]!; 24 | await groups.sendRequest('aid1', {}, [], ''); 25 | lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1]!; 26 | assert.equal(lastCall[0]!, url + '/identifiers/aid1/multisig/request'); 27 | assert.equal(lastCall[1]!.method, 'POST'); 28 | 29 | await groups.getRequest( 30 | 'ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose00' 31 | ); 32 | lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1]!; 33 | assert.equal( 34 | lastCall[0]!, 35 | url + 36 | '/multisig/request/ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose00' 37 | ); 38 | assert.equal(lastCall[1]!.method, 'GET'); 39 | 40 | await groups.join( 41 | 'aid1', 42 | { sad: {} }, 43 | ['sig'], 44 | 'ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose00', 45 | ['1', '2', '3'], 46 | ['a', 'b', 'c'] 47 | ); 48 | lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1]!; 49 | assert.equal(lastCall[0]!, url + '/identifiers/aid1/multisig/join'); 50 | assert.equal(lastCall[1]!.method, 'POST'); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/app/habery.test.ts: -------------------------------------------------------------------------------- 1 | import { Habery } from '../../src/keri/app/habery.ts'; 2 | import { assert, describe, it } from 'vitest'; 3 | import libsodium from 'libsodium-wrappers-sumo'; 4 | import { Salter } from '../../src/keri/core/salter.ts'; 5 | import { b } from '../../src/keri/core/core.ts'; 6 | import { MtrDex } from '../../src/keri/core/matter.ts'; 7 | 8 | describe('Habery', () => { 9 | it('should manage AID creation and rotation', async () => { 10 | await libsodium.ready; 11 | const salt = new Salter({ raw: b('0123456789abcdef') }).qb64; 12 | const hby = new Habery({ 13 | name: 'signify', 14 | salt: salt, 15 | passcode: '0123456789abcdefghijk', 16 | }); 17 | 18 | assert.equal( 19 | hby.mgr.aeid, 20 | 'BMbZTXzB7LmWPT2TXLGV88PQz5vDEM2L2flUs2yxn3U9' 21 | ); 22 | 23 | const hab = hby.makeHab('test', {}); 24 | 25 | assert.deepStrictEqual(hab.serder.sad['k'], [ 26 | 'DAQVURvW74OJH1Q0C6YLim_tdBYoXABwg6GsAlPaUJXE', 27 | ]); 28 | assert.deepStrictEqual(hab.serder.sad['n'], [ 29 | 'ENBWnU8wNHqq9oqJIimWhxUtNDHReUXtiCwwtjg9zKY0', 30 | ]); 31 | }); 32 | 33 | it('should use passcode as salt', async () => { 34 | await libsodium.ready; 35 | const passcode = '0123456789abcdefghijk'; 36 | if (passcode.length < 21) { 37 | throw new Error('Bran (passcode seed material) too short.'); 38 | } 39 | 40 | const bran = MtrDex.Salt_128 + 'A' + passcode.substring(0, 21); // qb64 salt for seed 41 | const salter = new Salter({ qb64: bran }); 42 | const signer = salter.signer(MtrDex.Ed25519_Seed, true); 43 | assert.equal( 44 | signer.qb64, 45 | 'AKeXgiAUIN7OHGXO6rbw_IzWeaQTr1LF7jWD6YEdrpa6' 46 | ); 47 | assert.equal( 48 | signer.verfer.qb64, 49 | 'DMbZTXzB7LmWPT2TXLGV88PQz5vDEM2L2flUs2yxn3U9' 50 | ); 51 | 52 | const hby = new Habery({ name: 'test', salt: salter.qb64 }); 53 | const hab = hby.makeHab('test', { transferable: true }); 54 | 55 | assert.equal(hab.pre, 'EMRbh7mWJTijcWiQKT3uxozncpa9_gEX1IU0fM1wnKxi'); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/app/notifying.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it } from 'vitest'; 2 | import { Tier } from '../../src/keri/core/salter.ts'; 3 | import { SignifyClient } from '../../src/keri/app/clienting.ts'; 4 | import libsodium from 'libsodium-wrappers-sumo'; 5 | import { createMockFetch } from './test-utils.ts'; 6 | 7 | const fetchMock = createMockFetch(); 8 | 9 | const url = 'http://127.0.0.1:3901'; 10 | const boot_url = 'http://127.0.0.1:3903'; 11 | 12 | describe('SignifyClient', () => { 13 | it('Notifications', async () => { 14 | await libsodium.ready; 15 | const bran = '0123456789abcdefghijk'; 16 | 17 | const client = new SignifyClient(url, bran, Tier.low, boot_url); 18 | 19 | await client.boot(); 20 | await client.connect(); 21 | 22 | const notifications = client.notifications(); 23 | 24 | await notifications.list(20, 40); 25 | let lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1]!; 26 | assert.equal(lastCall[0]!, url + '/notifications'); 27 | assert.equal(lastCall[1]!.method, 'GET'); 28 | const lastHeaders = new Headers(lastCall[1]!.headers!); 29 | assert.equal(lastHeaders.get('Range'), 'notes=20-40'); 30 | 31 | await notifications.mark('notificationSAID'); 32 | lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1]!; 33 | assert.equal(lastCall[0]!, url + '/notifications/notificationSAID'); 34 | assert.equal(lastCall[1]!.method, 'PUT'); 35 | 36 | await notifications.delete('notificationSAID'); 37 | lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1]!; 38 | assert.equal(lastCall[0]!, url + '/notifications/notificationSAID'); 39 | assert.equal(lastCall[1]!.method, 'DELETE'); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/app/registry.test.ts: -------------------------------------------------------------------------------- 1 | import { SignifyClient } from '../../src/keri/app/clienting.ts'; 2 | import { anyOfClass, anything, instance, mock, when } from 'ts-mockito'; 3 | import libsodium from 'libsodium-wrappers-sumo'; 4 | import { Registries } from '../../src/keri/app/credentialing.ts'; 5 | import { 6 | Identifier, 7 | IdentifierManagerFactory, 8 | SaltyIdentifierManager, 9 | } from '../../src/index.ts'; 10 | import { assert, describe, expect, it } from 'vitest'; 11 | import { HabState, KeyState } from '../../src/keri/core/keyState.ts'; 12 | 13 | describe('registry', () => { 14 | it('should create a registry', async () => { 15 | await libsodium.ready; 16 | const mockedClient = mock(SignifyClient); 17 | const mockedIdentifiers = mock(Identifier); 18 | const mockedKeyManager = mock(IdentifierManagerFactory); 19 | const mockedKeeper = mock(SaltyIdentifierManager); 20 | 21 | const hab = { 22 | prefix: 'hab prefix', 23 | state: { s: '0', d: 'a digest' } as KeyState, 24 | } as HabState; 25 | 26 | when(mockedClient.manager).thenReturn(instance(mockedKeyManager)); 27 | when(mockedKeyManager.get(hab)).thenReturn(instance(mockedKeeper)); 28 | 29 | when(mockedKeeper.sign(anyOfClass(Uint8Array))).thenResolve([ 30 | 'a signature', 31 | ]); 32 | 33 | when(mockedIdentifiers.get('a name')).thenResolve(hab); 34 | when(mockedClient.identifiers()).thenReturn( 35 | instance(mockedIdentifiers) 36 | ); 37 | 38 | const mockedResponse = mock(Response); 39 | when( 40 | mockedClient.fetch( 41 | '/identifiers/a name/registries', 42 | 'POST', 43 | anything() 44 | ) 45 | ).thenResolve(instance(mockedResponse)); 46 | 47 | const registries = new Registries(instance(mockedClient)); 48 | 49 | const actual = await registries.create({ 50 | name: 'a name', 51 | registryName: 'a registry name', 52 | nonce: '', 53 | }); 54 | 55 | assert.equal( 56 | actual.regser.raw, 57 | '{"v":"KERI10JSON0000c5_","t":"vcp","d":"EMppKX_JxXBuL_xE3A_a6lOcseYwaB7jAvZ0YFdgecXX","i":"EMppKX_JxXBuL_xE3A_a6lOcseYwaB7jAvZ0YFdgecXX","ii":"hab prefix","s":"0","c":["NB"],"bt":"0","b":[],"n":""}' 58 | ); 59 | assert.equal( 60 | actual.serder.raw, 61 | '{"v":"KERI10JSON0000f4_","t":"ixn","d":"EE5R61289Xnpxc2M-euPtsAkp849tUdNJ7DuyBeSiRtm","i":"hab prefix","s":"1","p":"a digest","a":[{"i":"EMppKX_JxXBuL_xE3A_a6lOcseYwaB7jAvZ0YFdgecXX","s":"0","d":"EMppKX_JxXBuL_xE3A_a6lOcseYwaB7jAvZ0YFdgecXX"}]}' 62 | ); 63 | }); 64 | 65 | it('should fail on estanblishmnet only for now', async () => { 66 | await libsodium.ready; 67 | const mockedClient = mock(SignifyClient); 68 | const mockedIdentifiers = mock(Identifier); 69 | 70 | const hab = { 71 | prefix: 'hab prefix', 72 | state: { s: 0, d: 'a digest', c: ['EO'] } as unknown as KeyState, 73 | name: 'a name', 74 | transferable: true, 75 | windexes: [], 76 | icp_dt: '2023-12-01T10:05:25.062609+00:00', 77 | } as HabState; 78 | 79 | when(mockedIdentifiers.get('a name')).thenResolve(hab); 80 | when(mockedClient.identifiers()).thenReturn( 81 | instance(mockedIdentifiers) 82 | ); 83 | 84 | const registries = new Registries(instance(mockedClient)); 85 | 86 | await expect(async () => { 87 | await registries.create({ 88 | name: 'a name', 89 | registryName: 'a registry name', 90 | nonce: '', 91 | }); 92 | }).rejects.toThrowError('establishment only not implemented'); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /test/core/authing.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it, vitest } from 'vitest'; 2 | import libsodium from 'libsodium-wrappers-sumo'; 3 | import { Salter } from '../../src/keri/core/salter.ts'; 4 | import { b } from '../../src/keri/core/core.ts'; 5 | import { Authenticater } from '../../src/keri/core/authing.ts'; 6 | import * as utilApi from '../../src/keri/core/utils.ts'; 7 | import { Verfer } from '../../src/keri/core/verfer.ts'; 8 | 9 | describe('Authenticater.verify', () => { 10 | it('verify signature on Response', async () => { 11 | await libsodium.ready; 12 | const salt = '0123456789abcdef'; 13 | const salter = new Salter({ raw: b(salt) }); 14 | const signer = salter.signer(); 15 | const aaid = 'DMZh_y-H5C3cSbZZST-fqnsmdNTReZxIh0t2xSTOJQ8a'; 16 | const verfer = new Verfer({ qb64: aaid }); 17 | 18 | const headers = new Headers([ 19 | ['Content-Length', '898'], 20 | ['Content-Type', 'application/json'], 21 | [ 22 | 'Signature', 23 | [ 24 | 'indexed="?0"', 25 | 'signify="0BDLh8QCytVBx1YMam4Vt8s4b9HAW1dwfE4yU5H_w1V6gUvPBoVGWQlIMdC16T3WFWHDHCbMcuceQzrr6n9OULsK"', 26 | ].join(';'), 27 | ], 28 | [ 29 | 'Signature-Input', 30 | [ 31 | 'signify=("signify-resource" "@method" "@path" "signify-timestamp")', 32 | 'created=1684715820', 33 | 'keyid="EEXekkGu9IAzav6pZVJhkLnjtjM5v3AcyA-pdKUcaGei"', 34 | 'alg="ed25519"', 35 | ].join(';'), 36 | ], 37 | [ 38 | 'Signify-Resource', 39 | 'EEXekkGu9IAzav6pZVJhkLnjtjM5v3AcyA-pdKUcaGei', 40 | ], 41 | ['Signify-Timestamp', '2023-05-22T00:37:00.248708+00:00'], 42 | ]); 43 | 44 | const authn = new Authenticater(signer, verfer); 45 | assert.notEqual(authn, undefined); 46 | 47 | assert.equal( 48 | authn.verify(new Headers(headers), 'GET', '/identifiers/aid1'), 49 | true 50 | ); 51 | }); 52 | }); 53 | 54 | describe('Authenticater.sign', () => { 55 | it('Create signed headers for a request', async () => { 56 | await libsodium.ready; 57 | const salt = '0123456789abcdef'; 58 | const salter = new Salter({ raw: b(salt) }); 59 | const signer = salter.signer(); 60 | const aaid = 'DDK2N5_fVCWIEO9d8JLhk7hKrkft6MbtkUhaHQsmABHY'; 61 | const verfer = new Verfer({ qb64: aaid }); 62 | 63 | const headers = new Headers([ 64 | ['Content-Type', 'application/json'], 65 | ['Content-Length', '256'], 66 | ['Connection', 'close'], 67 | [ 68 | 'Signify-Resource', 69 | 'EWJkQCFvKuyxZi582yJPb0wcwuW3VXmFNuvbQuBpgmIs', 70 | ], 71 | ['Signify-Timestamp', '2022-09-24T00:05:48.196795+00:00'], 72 | ]); 73 | vitest 74 | .spyOn(utilApi, 'nowUTC') 75 | .mockReturnValue(new Date('2021-01-01T00:00:00.000000+00:00')); 76 | 77 | const authn = new Authenticater(signer, verfer); 78 | const result = authn.sign(headers, 'POST', '/boot'); 79 | 80 | assert.equal(result.has('Signature-Input'), true); 81 | assert.equal(result.has('Signature'), true); 82 | 83 | const expectedSignatureInput = [ 84 | 'signify=("@method" "@path" "signify-resource" "signify-timestamp")', 85 | 'created=1609459200', 86 | 'keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt"', 87 | 'alg="ed25519"', 88 | ].join(';'); 89 | assert.equal(result.get('Signature-Input'), expectedSignatureInput); 90 | 91 | const expectedSignature = [ 92 | 'indexed="?0"', 93 | 'signify="0BChvN_BWAf-mgEuTnWfNnktgHdWOuOh9cWc4o0GFWuZOwra3DyJT5dJ_6BX7AANDOTnIlAKh5Sg_9qGQXHjj5oJ"', 94 | ].join(';'); 95 | assert.equal(result.get('Signature'), expectedSignature); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/core/base64.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, test } from 'vitest'; 2 | import { 3 | decodeBase64Url, 4 | encodeBase64Url, 5 | } from '../../src/keri/core/base64.ts'; 6 | 7 | test('encode', () => { 8 | assert.equal(encodeBase64Url(Uint8Array.from([102])), 'Zg'); 9 | assert.equal(encodeBase64Url(Uint8Array.from([102, 105])), 'Zmk'); 10 | assert.equal(encodeBase64Url(Uint8Array.from([102, 105, 115])), 'Zmlz'); 11 | assert.equal( 12 | encodeBase64Url(Uint8Array.from([102, 105, 115, 104])), 13 | 'ZmlzaA' 14 | ); 15 | assert.equal(encodeBase64Url(Uint8Array.from([248])), '-A'); 16 | assert.equal(encodeBase64Url(Uint8Array.from([252])), '_A'); 17 | }); 18 | 19 | test('decode', () => { 20 | assert.deepEqual(decodeBase64Url('Zg'), Uint8Array.from([102])); 21 | assert.deepEqual(decodeBase64Url('Zmk'), Uint8Array.from([102, 105])); 22 | assert.deepEqual(decodeBase64Url('Zmlz'), Uint8Array.from([102, 105, 115])); 23 | assert.deepEqual( 24 | decodeBase64Url('ZmlzaA'), 25 | Uint8Array.from([102, 105, 115, 104]) 26 | ); 27 | assert.deepEqual(Uint8Array.from([248]), decodeBase64Url('-A')); 28 | assert.deepEqual(Uint8Array.from([252]), decodeBase64Url('_A')); 29 | }); 30 | 31 | test('Test encode / decode compare with built in node Buffer', () => { 32 | const text = '🏳️🏳️'; 33 | const b64url = '8J-Ps--4j_Cfj7PvuI8'; 34 | const data = Uint8Array.from([ 35 | 240, 159, 143, 179, 239, 184, 143, 240, 159, 143, 179, 239, 184, 143, 36 | ]); 37 | 38 | assert.deepEqual(b64url, encodeBase64Url(new TextEncoder().encode(text))); 39 | assert.deepEqual(data, decodeBase64Url(b64url)); 40 | }); 41 | -------------------------------------------------------------------------------- /test/core/bexter.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it } from 'vitest'; 2 | import { Bexter } from '../../src/keri/core/bexter.ts'; 3 | import { b, MtrDex } from '../../src/index.ts'; 4 | 5 | describe('Bexter', () => { 6 | it('should bext-ify stuff (and back again)', () => { 7 | assert.throws(() => { 8 | new Bexter({}); 9 | }); 10 | 11 | let bext = '@!'; 12 | assert.throws(() => { 13 | new Bexter({}, bext); 14 | }); 15 | 16 | bext = ''; 17 | let bexter = new Bexter({}, bext); 18 | assert.equal(bexter.code, MtrDex.StrB64_L0); 19 | assert.equal(bexter.both, '4AAA'); 20 | assert.deepStrictEqual(bexter.raw, b('')); 21 | assert.equal(bexter.qb64, '4AAA'); 22 | assert.equal(bexter.bext, bext); 23 | 24 | bext = '-'; 25 | bexter = new Bexter({}, bext); 26 | assert.equal(bexter.code, MtrDex.StrB64_L2); 27 | assert.equal(bexter.both, '6AAB'); 28 | assert.deepStrictEqual(bexter.raw, b('>')); 29 | assert.equal(bexter.qb64, '6AABAAA-'); 30 | assert.equal(bexter.bext, bext); 31 | 32 | bext = '-A'; 33 | bexter = new Bexter({}, bext); 34 | assert.equal(bexter.code, MtrDex.StrB64_L1); 35 | assert.equal(bexter.both, '5AAB'); 36 | assert.deepStrictEqual(bexter.raw, Uint8Array.from([15, 128])); 37 | assert.equal(bexter.qb64, '5AABAA-A'); 38 | assert.equal(bexter.bext, bext); 39 | 40 | bext = '-A-'; 41 | bexter = new Bexter({}, bext); 42 | assert.equal(bexter.code, MtrDex.StrB64_L0); 43 | assert.equal(bexter.both, '4AAB'); 44 | assert.deepStrictEqual(bexter.raw, Uint8Array.from([3, 224, 62])); 45 | assert.equal(bexter.qb64, '4AABA-A-'); 46 | assert.equal(bexter.bext, bext); 47 | 48 | bext = '-A-B'; 49 | bexter = new Bexter({}, bext); 50 | assert.equal(bexter.code, MtrDex.StrB64_L0); 51 | assert.equal(bexter.both, '4AAB'); 52 | assert.deepStrictEqual(bexter.raw, Uint8Array.from([248, 15, 129])); 53 | assert.equal(bexter.qb64, '4AAB-A-B'); 54 | assert.equal(bexter.bext, bext); 55 | 56 | bext = 'A'; 57 | bexter = new Bexter({}, bext); 58 | assert.equal(bexter.code, MtrDex.StrB64_L2); 59 | assert.equal(bexter.both, '6AAB'); 60 | assert.deepStrictEqual(bexter.raw, Uint8Array.from([0])); 61 | assert.equal(bexter.qb64, '6AABAAAA'); 62 | assert.equal(bexter.bext, bext); 63 | 64 | bext = 'AA'; 65 | bexter = new Bexter({}, bext); 66 | assert.equal(bexter.code, MtrDex.StrB64_L1); 67 | assert.equal(bexter.both, '5AAB'); 68 | assert.deepStrictEqual(bexter.raw, Uint8Array.from([0, 0])); 69 | assert.equal(bexter.qb64, '5AABAAAA'); 70 | assert.equal(bexter.bext, bext); 71 | 72 | bext = 'AAA'; 73 | bexter = new Bexter({}, bext); 74 | assert.equal(bexter.code, MtrDex.StrB64_L0); 75 | assert.equal(bexter.both, '4AAB'); 76 | assert.deepStrictEqual(bexter.raw, Uint8Array.from([0, 0, 0])); 77 | assert.equal(bexter.qb64, '4AABAAAA'); 78 | assert.equal(bexter.bext, bext); 79 | 80 | bext = 'AAAA'; 81 | bexter = new Bexter({}, bext); 82 | assert.equal(bexter.code, MtrDex.StrB64_L0); 83 | assert.equal(bexter.both, '4AAB'); 84 | assert.deepStrictEqual(bexter.raw, Uint8Array.from([0, 0, 0])); 85 | assert.equal(bexter.qb64, '4AABAAAA'); 86 | assert.equal(bexter.bext, 'AAA'); 87 | assert.notEqual(bexter.bext, bext); 88 | 89 | bext = 'ABB'; 90 | bexter = new Bexter({}, bext); 91 | assert.equal(bexter.code, MtrDex.StrB64_L0); 92 | assert.equal(bexter.both, '4AAB'); 93 | assert.deepStrictEqual(bexter.raw, Uint8Array.from([0, 0, 65])); 94 | assert.equal(bexter.qb64, '4AABAABB'); 95 | assert.equal(bexter.bext, bext); 96 | 97 | bext = 'BBB'; 98 | bexter = new Bexter({}, bext); 99 | assert.equal(bexter.code, MtrDex.StrB64_L0); 100 | assert.equal(bexter.both, '4AAB'); 101 | assert.deepStrictEqual(bexter.raw, Uint8Array.from([0, 16, 65])); 102 | assert.equal(bexter.qb64, '4AABABBB'); 103 | assert.equal(bexter.bext, bext); 104 | 105 | bext = 'ABBB'; 106 | bexter = new Bexter({}, bext); 107 | assert.equal(bexter.code, MtrDex.StrB64_L0); 108 | assert.equal(bexter.both, '4AAB'); 109 | assert.deepStrictEqual(bexter.raw, Uint8Array.from([0, 16, 65])); 110 | assert.equal(bexter.qb64, '4AABABBB'); 111 | assert.equal(bexter.bext, 'BBB'); 112 | assert.notEqual(bexter.bext, bext); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /test/core/coring.test.ts: -------------------------------------------------------------------------------- 1 | import libsodium from 'libsodium-wrappers-sumo'; 2 | import { 3 | b, 4 | d, 5 | b64ToInt, 6 | intToB64, 7 | intToB64b, 8 | } from '../../src/keri/core/core.ts'; 9 | import { assert, describe, it } from 'vitest'; 10 | import { bytesToInt, intToBytes } from '../../src/keri/core/utils.ts'; 11 | 12 | describe('int to b64 and back', () => { 13 | it('should encode and decode stuff', async () => { 14 | await libsodium.ready; 15 | 16 | let cs = intToB64(0); 17 | assert.equal(cs, 'A'); 18 | let i = b64ToInt(cs); 19 | assert.equal(i, 0); 20 | 21 | cs = intToB64(0, 0); 22 | assert.equal(cs, ''); 23 | assert.throws(() => { 24 | i = b64ToInt(cs); 25 | }); 26 | 27 | assert.throws(() => { 28 | i = b64ToInt(cs); 29 | }); 30 | 31 | let csb = intToB64b(0); 32 | assert.deepStrictEqual(csb, b('A')); 33 | i = b64ToInt(d(csb)); 34 | assert.equal(i, 0); 35 | 36 | cs = intToB64(27); 37 | assert.equal(cs, 'b'); 38 | i = b64ToInt(cs); 39 | assert.equal(i, 27); 40 | 41 | csb = intToB64b(27); 42 | assert.deepStrictEqual(csb, b('b')); 43 | i = b64ToInt(d(csb)); 44 | assert.equal(i, 27); 45 | 46 | cs = intToB64(27, 2); 47 | assert.equal(cs, 'Ab'); 48 | i = b64ToInt(cs); 49 | assert.equal(i, 27); 50 | 51 | csb = intToB64b(27, 2); 52 | assert.deepStrictEqual(csb, b('Ab')); 53 | i = b64ToInt(d(csb)); 54 | assert.equal(i, 27); 55 | 56 | cs = intToB64(80); 57 | assert.equal(cs, 'BQ'); 58 | i = b64ToInt(cs); 59 | assert.equal(i, 80); 60 | 61 | csb = intToB64b(80); 62 | assert.deepStrictEqual(csb, b('BQ')); 63 | i = b64ToInt(d(csb)); 64 | assert.equal(i, 80); 65 | 66 | cs = intToB64(4095); 67 | assert.equal(cs, '__'); 68 | i = b64ToInt(cs); 69 | assert.equal(i, 4095); 70 | 71 | csb = intToB64b(4095); 72 | assert.deepStrictEqual(csb, b('__')); 73 | i = b64ToInt(d(csb)); 74 | assert.equal(i, 4095); 75 | 76 | cs = intToB64(4096); 77 | assert.equal(cs, 'BAA'); 78 | i = b64ToInt(cs); 79 | assert.equal(i, 4096); 80 | 81 | csb = intToB64b(4096); 82 | assert.deepStrictEqual(csb, b('BAA')); 83 | i = b64ToInt(d(csb)); 84 | assert.equal(i, 4096); 85 | 86 | cs = intToB64(6011); 87 | assert.equal(cs, 'Bd7'); 88 | i = b64ToInt(cs); 89 | assert.equal(i, 6011); 90 | 91 | csb = intToB64b(6011); 92 | assert.deepStrictEqual(csb, b('Bd7')); 93 | i = b64ToInt(d(csb)); 94 | assert.equal(i, 6011); 95 | }); 96 | }); 97 | 98 | describe('int to bytes and back', () => { 99 | it('should encode and decode stuff', async () => { 100 | let b = intToBytes(0, 8); 101 | let n = bytesToInt(b); 102 | assert.equal(n, 0); 103 | b = intToBytes(1, 8); 104 | n = bytesToInt(b); 105 | assert.equal(n, 1); 106 | 107 | b = intToBytes(0, 16); 108 | n = bytesToInt(b); 109 | assert.equal(n, 0); 110 | b = intToBytes(1, 16); 111 | n = bytesToInt(b); 112 | assert.equal(n, 1); 113 | 114 | b = intToBytes(0, 2); 115 | n = bytesToInt(b); 116 | assert.equal(n, 0); 117 | b = intToBytes(1, 2); 118 | n = bytesToInt(b); 119 | assert.equal(n, 1); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /test/core/diger.test.ts: -------------------------------------------------------------------------------- 1 | import { Matter } from '../../src/keri/core/matter.ts'; 2 | import { assert, describe, it } from 'vitest'; 3 | import { blake3 } from '@noble/hashes/blake3'; 4 | 5 | import { Diger } from '../../src/keri/core/diger.ts'; 6 | import { MtrDex } from '../../src/keri/core/matter.ts'; 7 | import { concat } from '../../src/keri/core/core.ts'; 8 | 9 | function encodeText(text: string) { 10 | return new TextEncoder().encode(text); 11 | } 12 | 13 | describe('Diger', () => { 14 | it('should generate digests', () => { 15 | // Create something to digest and verify 16 | const ser = encodeText('abcdefghijklmnopqrstuvwxyz0123456789'); 17 | 18 | const digest = blake3.create({ dkLen: 32 }).update(ser).digest(); 19 | 20 | let diger = new Diger({ raw: digest }); 21 | assert.deepStrictEqual(diger.code, MtrDex.Blake3_256); 22 | 23 | let sizage = Matter.Sizes.get(diger.code); 24 | assert.deepStrictEqual(diger.qb64.length, sizage!.fs); 25 | let result = diger.verify(ser); 26 | assert.equal(result, true); 27 | 28 | result = diger.verify(concat(ser, encodeText('2j2idjpwjfepjtgi'))); 29 | assert.equal(result, false); 30 | diger = new Diger({ raw: digest, code: MtrDex.Blake3_256 }); 31 | assert.deepStrictEqual(diger.code, MtrDex.Blake3_256); 32 | 33 | assert.equal( 34 | diger.qb64, 35 | 'ELC5L3iBVD77d_MYbYGGCUQgqQBju1o4x1Ud-z2sL-ux' 36 | ); 37 | sizage = Matter.Sizes.get(diger.code); 38 | assert.deepStrictEqual(diger.qb64.length, sizage!.fs); 39 | 40 | result = diger.verify(ser); 41 | assert.equal(result, true); 42 | 43 | diger = new Diger({}, ser); 44 | assert.equal( 45 | diger.qb64, 46 | 'ELC5L3iBVD77d_MYbYGGCUQgqQBju1o4x1Ud-z2sL-ux' 47 | ); 48 | sizage = Matter.Sizes.get(diger.code); 49 | assert.deepStrictEqual(diger.qb64.length, sizage!.fs); 50 | result = diger.verify(ser); 51 | assert.equal(result, true); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/core/encrypter.test.ts: -------------------------------------------------------------------------------- 1 | import { Matter } from '../../src/keri/core/matter.ts'; 2 | 3 | import { assert, describe, it } from 'vitest'; 4 | import { MtrDex } from '../../src/keri/core/matter.ts'; 5 | import libsodium from 'libsodium-wrappers-sumo'; 6 | import { Signer } from '../../src/keri/core/signer.ts'; 7 | import { Encrypter } from '../../src/keri/core/encrypter.ts'; 8 | import { Verfer } from '../../src/keri/core/verfer.ts'; 9 | import { d } from '../../src/keri/core/core.ts'; 10 | 11 | describe('Encrypter', () => { 12 | it('should encrypt stuff', async () => { 13 | await libsodium.ready; 14 | 15 | // (b'\x18;0\xc4\x0f*vF\xfa\xe3\xa2Eee\x1f\x96o\xce)G\x85\xe3X\x86\xda\x04\xf0\xdc\xde\x06\xc0+') 16 | const seed = new Uint8Array([ 17 | 24, 59, 48, 196, 15, 42, 118, 70, 250, 227, 162, 69, 101, 101, 31, 18 | 150, 111, 206, 41, 71, 133, 227, 88, 134, 218, 4, 240, 220, 222, 6, 19 | 192, 43, 20 | ]); 21 | const seedqb64b = new Matter({ raw: seed, code: MtrDex.Ed25519_Seed }) 22 | .qb64b; 23 | 24 | assert.equal( 25 | d(seedqb64b), 26 | 'ABg7MMQPKnZG-uOiRWVlH5ZvzilHheNYhtoE8NzeBsAr' 27 | ); 28 | 29 | // b'6\x08d\r\xa1\xbb9\x8dp\x8d\xa0\xc0\x13J\x87r' 30 | const salt = new Uint8Array([ 31 | 54, 8, 100, 13, 161, 187, 57, 141, 112, 141, 160, 192, 19, 74, 135, 32 | 114, 33 | ]); 34 | const saltqb64 = new Matter({ raw: salt, code: MtrDex.Salt_128 }).qb64; 35 | const saltqb64b = new Matter({ raw: salt, code: MtrDex.Salt_128 }) 36 | .qb64b; 37 | assert.equal(saltqb64, '0AA2CGQNobs5jXCNoMATSody'); 38 | 39 | // b'h,#|\x8ap"\x12\xc43t2\xa6\xe1\x18\x19\xf0f2,y\xc4\xc21@\xf5@\x15.\xa2\x1a\xcf' 40 | const cryptseed = new Uint8Array([ 41 | 104, 44, 35, 124, 138, 112, 34, 18, 196, 51, 116, 50, 166, 225, 24, 42 | 25, 240, 102, 50, 44, 121, 196, 194, 49, 64, 245, 64, 21, 46, 162, 43 | 26, 207, 44 | ]); 45 | const cryptsigner = new Signer({ 46 | raw: cryptseed, 47 | code: MtrDex.Ed25519_Seed, 48 | transferable: true, 49 | }); 50 | const keypair = libsodium.crypto_sign_seed_keypair(cryptseed); // raw 51 | const pubkey = libsodium.crypto_sign_ed25519_pk_to_curve25519( 52 | keypair.publicKey 53 | ); 54 | const prikey = libsodium.crypto_sign_ed25519_sk_to_curve25519( 55 | keypair.privateKey 56 | ); 57 | 58 | assert.throws(function () { 59 | new Encrypter({}); 60 | }); 61 | 62 | let encrypter = new Encrypter({ raw: pubkey }); 63 | assert.equal(encrypter.code, MtrDex.X25519); 64 | assert.equal( 65 | encrypter.qb64, 66 | 'CAF7Wr3XNq5hArcOuBJzaY6Nd23jgtUVI6KDfb3VngkR' 67 | ); 68 | assert.deepStrictEqual(encrypter.raw, pubkey); 69 | assert.equal(encrypter.verifySeed(cryptsigner.qb64b), true); 70 | 71 | let cipher = encrypter.encrypt(seedqb64b); 72 | assert.equal(cipher.code, MtrDex.X25519_Cipher_Seed); 73 | let uncb = libsodium.crypto_box_seal_open( 74 | cipher.raw, 75 | encrypter.raw, 76 | prikey 77 | ); 78 | assert.deepStrictEqual(uncb, seedqb64b); 79 | 80 | cipher = encrypter.encrypt(saltqb64b); 81 | assert.equal(cipher.code, MtrDex.X25519_Cipher_Salt); 82 | uncb = libsodium.crypto_box_seal_open( 83 | cipher.raw, 84 | encrypter.raw, 85 | prikey 86 | ); 87 | assert.deepStrictEqual(uncb, saltqb64b); 88 | 89 | const verfer = new Verfer({ 90 | raw: keypair.publicKey, 91 | code: MtrDex.Ed25519, 92 | }); 93 | 94 | encrypter = new Encrypter({}, verfer.qb64b); 95 | assert.equal(encrypter.code, MtrDex.X25519); 96 | assert.equal( 97 | encrypter.qb64, 98 | 'CAF7Wr3XNq5hArcOuBJzaY6Nd23jgtUVI6KDfb3VngkR' 99 | ); 100 | assert.deepStrictEqual(encrypter.raw, pubkey); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /test/core/httping.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it, vitest } from 'vitest'; 2 | import libsodium from 'libsodium-wrappers-sumo'; 3 | import { Salter } from '../../src/keri/core/salter.ts'; 4 | import { b } from '../../src/keri/core/core.ts'; 5 | import { 6 | siginput, 7 | desiginput, 8 | SiginputArgs, 9 | } from '../../src/keri/core/httping.ts'; 10 | import * as utilApi from '../../src/keri/core/utils.ts'; 11 | 12 | describe('siginput', () => { 13 | it('create valid Signature-Input header with signature', async () => { 14 | await libsodium.ready; 15 | const salt = '0123456789abcdef'; 16 | const salter = new Salter({ raw: b(salt) }); 17 | const signer = salter.signer(); 18 | 19 | const headers: Headers = new Headers([ 20 | ['Content-Type', 'application/json'], 21 | ['Content-Length', '256'], 22 | ['Connection', 'close'], 23 | [ 24 | 'Signify-Resource', 25 | 'EWJkQCFvKuyxZi582yJPb0wcwuW3VXmFNuvbQuBpgmIs', 26 | ], 27 | ['Signify-Timestamp', '2022-09-24T00:05:48.196795+00:00'], 28 | ]); 29 | vitest 30 | .spyOn(utilApi, 'nowUTC') 31 | .mockReturnValue(new Date('2021-01-01T00:00:00.000000+00:00')); 32 | 33 | const [header, sig] = siginput(signer, { 34 | name: 'sig0', 35 | method: 'POST', 36 | path: '/signify', 37 | headers, 38 | fields: [ 39 | 'Signify-Resource', 40 | '@method', 41 | '@path', 42 | 'Signify-Timestamp', 43 | ], 44 | alg: 'ed25519', 45 | keyid: signer.verfer.qb64, 46 | } as SiginputArgs); 47 | 48 | assert.equal(header.size, 1); 49 | assert.equal(header.has('Signature-Input'), true); 50 | const sigipt = header.get('Signature-Input'); 51 | assert.equal( 52 | sigipt, 53 | 'sig0=("Signify-Resource" "@method" "@path" "Signify-Timestamp");created=1609459200;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"' 54 | ); 55 | assert.equal( 56 | sig.qb64, 57 | '0BAJWoDvZXYKnq_9rFTy_mucctxk3rVK6szopNi1rq5WQcJSNIw-_PocSQNoQGD1Ow_s2mDI5-Qqm34Y56gUKQcF' 58 | ); 59 | }); 60 | }); 61 | 62 | describe('desiginput', () => { 63 | it('create valid Signature-Input header with signature', async () => { 64 | await libsodium.ready; 65 | const siginput = 66 | 'sig0=("signify-resource" "@method" "@path" "signify-timestamp");created=1609459200;keyid="EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3";alg="ed25519"'; 67 | 68 | const inputs = desiginput(siginput); 69 | assert.equal(inputs.length, 1); 70 | const input = inputs[0]; 71 | assert.deepStrictEqual(input.fields, [ 72 | 'signify-resource', 73 | '@method', 74 | '@path', 75 | 'signify-timestamp', 76 | ]); 77 | assert.equal(input.created, 1609459200); 78 | assert.equal(input.alg, 'ed25519'); 79 | assert.equal( 80 | input.keyid, 81 | 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3' 82 | ); 83 | assert.equal(input.expires, undefined); 84 | assert.equal(input.nonce, undefined); 85 | assert.equal(input.context, undefined); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /test/core/matter.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it } from 'vitest'; 2 | 3 | import { Sizage } from '../../src/keri/core/matter.ts'; 4 | 5 | describe('Sizage', () => { 6 | it('should hold size values in 4 properties', async () => { 7 | const sizage = new Sizage(1, 2, 3, 4); 8 | assert.equal(sizage.hs, 1); 9 | assert.equal(sizage.ss, 2); 10 | assert.equal(sizage.fs, 3); 11 | assert.equal(sizage.ls, 4); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/core/number.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it } from 'vitest'; 2 | import { CesrNumber } from '../../src/keri/core/number.ts'; 3 | 4 | describe('THolder', () => { 5 | it('should hold thresholds', async () => { 6 | let n = new CesrNumber({}, undefined, '0'); 7 | assert.equal(n.num, 0); 8 | assert.equal(n.numh, '0'); 9 | n = new CesrNumber({}, 0); 10 | assert.equal(n.num, 0); 11 | assert.equal(n.numh, '0'); 12 | 13 | n = new CesrNumber({}, 1); 14 | assert.equal(n.num, 1); 15 | assert.equal(n.numh, '1'); 16 | 17 | n = new CesrNumber({}, 15); 18 | assert.equal(n.num, 15); 19 | assert.equal(n.numh, 'f'); 20 | 21 | n = new CesrNumber({}, undefined, '1'); 22 | assert.equal(n.num, 1); 23 | assert.equal(n.numh, '1'); 24 | 25 | n = new CesrNumber({}, undefined, 'f'); 26 | assert.equal(n.num, 15); 27 | assert.equal(n.numh, 'f'); 28 | 29 | n = new CesrNumber({}, undefined, '15'); 30 | assert.equal(n.num, 21); 31 | assert.equal(n.numh, '15'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/core/pather.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it } from 'vitest'; 2 | import { Pather } from '../../src/keri/core/pather.ts'; 3 | import { b } from '../../src/index.ts'; 4 | 5 | describe('Pather', () => { 6 | it('should path-ify stuff (and back again)', () => { 7 | assert.throws(() => { 8 | new Pather({}); 9 | }); 10 | 11 | let path: string[] = []; 12 | let pather = new Pather({}, undefined, path); 13 | assert.equal(pather.bext, '-'); 14 | assert.equal(pather.qb64, '6AABAAA-'); 15 | assert.deepStrictEqual(pather.raw, b('>')); 16 | assert.deepStrictEqual(pather.path, path); 17 | 18 | path = ['a', 'b', 'c']; 19 | pather = new Pather({}, undefined, path); 20 | assert.equal(pather.bext, '-a-b-c'); 21 | assert.equal(pather.qb64, '5AACAA-a-b-c'); 22 | assert.deepStrictEqual( 23 | pather.raw, 24 | new Uint8Array([15, 154, 249, 191, 156]) 25 | ); 26 | assert.deepStrictEqual(pather.path, path); 27 | 28 | path = ['0', '1', '2']; 29 | pather = new Pather({}, undefined, path); 30 | assert.equal(pather.bext, '-0-1-2'); 31 | assert.equal(pather.qb64, '5AACAA-0-1-2'); 32 | assert.deepStrictEqual( 33 | pather.raw, 34 | new Uint8Array([15, 180, 251, 95, 182]) 35 | ); 36 | assert.deepStrictEqual(pather.path, path); 37 | 38 | path = ['field0', '1', '0']; 39 | pather = new Pather({}, undefined, path); 40 | assert.equal(pather.bext, '-field0-1-0'); 41 | assert.equal(pather.qb64, '4AADA-field0-1-0'); 42 | assert.deepStrictEqual( 43 | pather.raw, 44 | new Uint8Array([3, 231, 226, 122, 87, 116, 251, 95, 180]) 45 | ); 46 | assert.deepStrictEqual(pather.path, path); 47 | 48 | path = ['Not$Base64', '@moreso', '*again']; 49 | assert.throws(() => { 50 | new Pather({}, undefined, path); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/core/prefixer.test.ts: -------------------------------------------------------------------------------- 1 | import libsodium from 'libsodium-wrappers-sumo'; 2 | import { 3 | Protocols, 4 | Ilks, 5 | Serials, 6 | versify, 7 | Vrsn_1_0, 8 | } from '../../src/keri/core/core.ts'; 9 | import { MtrDex } from '../../src/keri/core/matter.ts'; 10 | import { Prefixer } from '../../src/keri/core/prefixer.ts'; 11 | import { assert, describe, it } from 'vitest'; 12 | 13 | describe('Prefixer', () => { 14 | it('should create autonomic identifier prefix using derivation as determined by code from ked', async () => { 15 | await libsodium.ready; 16 | 17 | // (b'\xacr\xda\xc83~\x99r\xaf\xeb`\xc0\x8cR\xd7\xd7\xf69\xc8E\x1e\xd2\xf0=`\xf7\xbf\x8a\x18\x8a`q') // from keripy 18 | const verkey = new Uint8Array([ 19 | 172, 114, 218, 200, 51, 126, 153, 114, 175, 235, 96, 192, 140, 82, 20 | 215, 215, 246, 57, 200, 69, 30, 210, 240, 61, 96, 247, 191, 138, 24, 21 | 138, 96, 113, 22 | ]); 23 | let prefixer = new Prefixer({ raw: verkey, code: MtrDex.Ed25519 }); 24 | assert.equal(prefixer.code, MtrDex.Ed25519); 25 | assert.equal( 26 | prefixer.qb64, 27 | 'DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx' 28 | ); 29 | 30 | // Test digest derivation from inception ked 31 | const vs = versify(Protocols.KERI, Vrsn_1_0, Serials.JSON, 0); 32 | const sn = 0; 33 | const ilk = Ilks.icp; 34 | const sith = '1'; 35 | const keys = [new Prefixer({ raw: verkey, code: MtrDex.Ed25519 }).qb64]; 36 | const nxt = ''; 37 | const toad = 0; 38 | const wits = new Array(); 39 | const cnfg = new Array(); 40 | 41 | const ked = { 42 | v: vs, // version string 43 | i: '', // qb64 prefix 44 | s: sn.toString(16), // hex string no leading zeros lowercase 45 | t: ilk, 46 | kt: sith, // hex string no leading zeros lowercase 47 | k: keys, // list of qb64 48 | n: nxt, // hash qual Base64 49 | wt: toad.toString(16), // hex string no leading zeros lowercase 50 | w: wits, // list of qb64 may be empty 51 | c: cnfg, // list of config ordered mappings may be empty 52 | }; 53 | 54 | prefixer = new Prefixer({ code: MtrDex.Blake3_256 }, ked); 55 | assert.equal( 56 | prefixer.qb64, 57 | 'ELEjyRTtmfyp4VpTBTkv_b6KONMS1V8-EW-aGJ5P_QMo' 58 | ); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/core/saider.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Protocols, 3 | Serials, 4 | versify, 5 | Vrsn_1_0, 6 | } from '../../src/keri/core/core.ts'; 7 | import { assert, describe, it } from 'vitest'; 8 | import { MtrDex } from '../../src/keri/core/matter.ts'; 9 | import libsodium from 'libsodium-wrappers-sumo'; 10 | import { Saider } from '../../src/keri/core/saider.ts'; 11 | 12 | describe('Saider', () => { 13 | it('should create Saidified dicts', async () => { 14 | await libsodium.ready; 15 | 16 | const kind = Serials.JSON; 17 | const code = MtrDex.Blake3_256; 18 | 19 | const vs = versify(Protocols.KERI, Vrsn_1_0, kind, 0); // vaccuous size == 0 20 | assert.equal(vs, 'KERI10JSON000000_'); 21 | const sad4 = { 22 | v: vs, 23 | t: 'rep', 24 | d: '', // vacuous said 25 | dt: '2020-08-22T17:50:12.988921+00:00', 26 | r: 'logs/processor', 27 | a: { 28 | d: 'EBabiu_JCkE0GbiglDXNB5C4NQq-hiGgxhHKXBxkiojg', 29 | i: 'EB0_D51cTh_q6uOQ-byFiv5oNXZ-cxdqCqBAa4JmBLtb', 30 | name: 'John Jones', 31 | role: 'Founder', 32 | }, 33 | }; 34 | const saider = new Saider({}, sad4); // default version string code, kind, and label 35 | assert.equal(saider.code, code); 36 | assert.equal( 37 | saider.qb64, 38 | 'ELzewBpZHSENRP-sL_G_2Ji4YDdNkns9AzFzufleJqdw' 39 | ); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/core/salter.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it } from 'vitest'; 2 | import libsodium from 'libsodium-wrappers-sumo'; 3 | 4 | import { Salter } from '../../src/keri/core/salter.ts'; 5 | 6 | describe('Salter', () => { 7 | it('should generate salts', async () => { 8 | await libsodium.ready; 9 | let salter = new Salter({}); 10 | 11 | assert.notEqual(salter, null); 12 | assert.equal(salter.qb64.length, 24); 13 | 14 | const salt = new Uint8Array([ 15 | 146, 78, 142, 186, 189, 77, 130, 3, 232, 248, 186, 197, 8, 0, 73, 16 | 182, 17 | ]); 18 | salter = new Salter({ raw: salt }); 19 | assert.notEqual(salter, null); 20 | assert.equal(salter.qb64, '0ACSTo66vU2CA-j4usUIAEm2'); 21 | 22 | salter = new Salter({ qb64: '0ACSTo66vU2CA-j4usUIAEm2' }); 23 | let raw = new Uint8Array([ 24 | 146, 78, 142, 186, 189, 77, 130, 3, 232, 248, 186, 197, 8, 0, 73, 25 | 182, 26 | ]); 27 | assert.deepStrictEqual(salter.raw, raw); 28 | 29 | salter = new Salter({ qb64: '0ABa4cx6f0SdfwFawI0A7mOZ' }); 30 | raw = new Uint8Array([ 31 | 90, 225, 204, 122, 127, 68, 157, 127, 1, 90, 192, 141, 0, 238, 99, 32 | 153, 33 | ]); 34 | assert.deepStrictEqual(salter.raw, raw); 35 | }); 36 | }); 37 | 38 | describe('Salter.signer', () => { 39 | it('should return a signer', async () => { 40 | const salter = new Salter({ qb64: '0ACSTo66vU2CA-j4usUIAEm2' }); 41 | const signer = salter.signer(); 42 | assert.notEqual(signer, null); 43 | assert.equal( 44 | signer.verfer.qb64, 45 | 'DD28x2a4KCZ8f6OAcA856jAD1chNOo4pT8ICxyzJUJhj' 46 | ); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/core/seqner.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it } from 'vitest'; 2 | import libsodium from 'libsodium-wrappers-sumo'; 3 | 4 | import { Seqner } from '../../src/keri/core/seqner.ts'; 5 | 6 | describe('Seqner', () => { 7 | it('should generate Seqner number class', async () => { 8 | await libsodium.ready; 9 | let seqner = new Seqner({}); 10 | assert.equal(seqner.sn, 0); 11 | assert.equal(seqner.snh, '0'); 12 | assert.equal(seqner.qb64, '0AAAAAAAAAAAAAAAAAAAAAAA'); 13 | assert.notStrictEqual( 14 | seqner.qb64b, 15 | new Uint8Array([ 16 | 48, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 17 | 65, 65, 65, 65, 65, 65, 65, 65, 18 | ]) 19 | ); 20 | assert.equal(seqner.raw.length, 16); 21 | assert.notStrictEqual( 22 | seqner.raw, 23 | new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) 24 | ); 25 | 26 | seqner = new Seqner({ snh: '1' }); 27 | assert.equal(seqner.sn, 1); 28 | assert.equal(seqner.snh, '1'); 29 | assert.equal(seqner.qb64, '0AAAAAAAAAAAAAAAAAAAAAAB'); 30 | 31 | seqner = new Seqner({ sn: 1 }); 32 | assert.equal(seqner.sn, 1); 33 | assert.equal(seqner.snh, '1'); 34 | 35 | seqner = new Seqner({ sn: 16 }); 36 | assert.equal(seqner.sn, 16); 37 | assert.equal(seqner.snh, '10'); 38 | assert.equal(seqner.qb64, '0AAAAAAAAAAAAAAAAAAAAAAQ'); 39 | 40 | seqner = new Seqner({ sn: 15 }); 41 | assert.equal(seqner.sn, 15); 42 | assert.equal(seqner.snh, 'f'); 43 | assert.equal(seqner.qb64, '0AAAAAAAAAAAAAAAAAAAAAAP'); 44 | 45 | seqner = new Seqner({ snh: 'f' }); 46 | assert.equal(seqner.sn, 15); 47 | assert.equal(seqner.snh, 'f'); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/core/serder.test.ts: -------------------------------------------------------------------------------- 1 | import { deversify, Ilks, Serials, Version } from '../../src/keri/core/core.ts'; 2 | import { assert, describe, it } from 'vitest'; 3 | import { Salter, Tier } from '../../src/keri/core/salter.ts'; 4 | import { MtrDex } from '../../src/keri/core/matter.ts'; 5 | import { Diger } from '../../src/keri/core/diger.ts'; 6 | import { Serder } from '../../src/keri/core/serder.ts'; 7 | import libsodium from 'libsodium-wrappers-sumo'; 8 | import { Prefixer } from '../../src/keri/core/prefixer.ts'; 9 | 10 | describe('deversify', () => { 11 | it('should parse a KERI event version string', async () => { 12 | const [, kind, version, size] = deversify('KERI10JSON00011c_'); 13 | assert.equal(kind, Serials.JSON); 14 | assert.deepStrictEqual(version, new Version(1, 0)); 15 | assert.equal(size, '00011c'); 16 | }); 17 | }); 18 | 19 | describe('Serder', () => { 20 | it('should create KERI events from dicts', async () => { 21 | await libsodium.ready; 22 | 23 | const sith = 1; 24 | const nsith = 1; 25 | const sn = 0; 26 | const toad = 0; 27 | 28 | const raw = new Uint8Array([ 29 | 5, 170, 143, 45, 83, 154, 233, 250, 85, 156, 2, 156, 155, 8, 72, 30 | 117, 31 | ]); 32 | const salter = new Salter({ raw: raw }); 33 | const skp0 = salter.signer( 34 | MtrDex.Ed25519_Seed, 35 | true, 36 | 'A', 37 | Tier.low, 38 | true 39 | ); 40 | const keys = [skp0.verfer.qb64]; 41 | 42 | const skp1 = salter.signer( 43 | MtrDex.Ed25519_Seed, 44 | true, 45 | 'N', 46 | Tier.low, 47 | true 48 | ); 49 | const ndiger = new Diger({}, skp1.verfer.qb64b); 50 | const nxt = [ndiger.qb64]; 51 | assert.deepStrictEqual(nxt, [ 52 | 'EAKUR-LmLHWMwXTLWQ1QjxHrihBmwwrV2tYaSG7hOrWj', 53 | ]); 54 | 55 | const ked0 = { 56 | v: 'KERI10JSON000000_', 57 | t: Ilks.icp, 58 | d: '', 59 | i: '', 60 | s: sn.toString(16), 61 | kt: sith.toString(16), 62 | k: keys, 63 | nt: nsith.toString(16), 64 | n: nxt, 65 | bt: toad.toString(16), 66 | b: [], 67 | c: [], 68 | a: [], 69 | }; 70 | 71 | const serder = new Serder(ked0); 72 | assert.equal( 73 | serder.raw, 74 | '{"v":"KERI10JSON0000d3_","t":"icp","d":"","i":"","s":"0","kt":"1","k":' + 75 | '["DAUDqkmn-hqlQKD8W-FAEa5JUvJC2I9yarEem-AAEg3e"],"nt":"1",' + 76 | '"n":["EAKUR-LmLHWMwXTLWQ1QjxHrihBmwwrV2tYaSG7hOrWj"],"bt":"0","b":[],"c":[],"a":[]}' 77 | ); 78 | let aid0 = new Prefixer({ code: MtrDex.Ed25519 }, ked0); 79 | assert.equal(aid0.code, MtrDex.Ed25519); 80 | assert.equal(aid0.qb64, skp0.verfer.qb64); 81 | assert.equal( 82 | skp0.verfer.qb64, 83 | 'DAUDqkmn-hqlQKD8W-FAEa5JUvJC2I9yarEem-AAEg3e' 84 | ); 85 | 86 | aid0 = new Prefixer({ code: MtrDex.Blake3_256 }, ked0); 87 | assert.equal(aid0.qb64, 'ECHOi6qRaswNpvytpCtpvEh2cB2aLAwVHBLFinno3YVW'); 88 | 89 | const serder1 = new Serder({ 90 | ...ked0, 91 | a: { 92 | n: 'Lenksjö', 93 | }, 94 | }); 95 | assert.equal(serder1.sad.v, 'KERI10JSON000139_'); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/core/signer.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it } from 'vitest'; 2 | import libsodium from 'libsodium-wrappers-sumo'; 3 | 4 | import { Signer } from '../../src/index.ts'; 5 | import { Matter, MtrDex } from '../../src/index.ts'; 6 | import { b } from '../../src/index.ts'; 7 | 8 | describe('Signer', () => { 9 | it('should sign things', async () => { 10 | await libsodium.ready; 11 | 12 | const signer = new Signer({}); // defaults provide Ed25519 signer Ed25519 verfer 13 | assert.equal(signer.code, MtrDex.Ed25519_Seed); 14 | assert.equal(signer.raw.length, Matter._rawSize(signer.code)); 15 | assert.equal(signer.verfer.code, MtrDex.Ed25519); 16 | assert.equal( 17 | signer.verfer.raw.length, 18 | Matter._rawSize(signer.verfer.code) 19 | ); 20 | 21 | const ser = b('abcdefghijklmnopqrstuvwxyz0123456789'); 22 | 23 | const cigar = signer.sign(ser); 24 | assert.equal(cigar.code, MtrDex.Ed25519_Sig); 25 | assert.equal(cigar.raw.length, Matter._rawSize(cigar.code)); 26 | const result = signer.verfer.verify(cigar.raw, ser); 27 | assert.equal(result, true); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/core/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Protocols, 3 | Saider, 4 | Serder, 5 | Serials, 6 | d, 7 | versify, 8 | } from '../../src/index.ts'; 9 | import { 10 | serializeACDCAttachment, 11 | serializeIssExnAttachment, 12 | } from '../../src/keri/core/utils.ts'; 13 | import { describe, it, assert } from 'vitest'; 14 | 15 | describe(serializeIssExnAttachment.name, () => { 16 | it('serializes iss data', () => { 17 | const [, data] = Saider.saidify({ 18 | d: '', 19 | v: versify(Protocols.KERI, undefined, Serials.JSON, 0), 20 | }); 21 | 22 | const result = serializeIssExnAttachment(new Serder(data)); 23 | 24 | assert.equal( 25 | d(result), 26 | '-VAS-GAB0AAAAAAAAAAAAAAAAAAAAAAAEKZPmzJqhx76bcC2ftPQgeRirmOd8ZBOtGVqHJrSm7F1' 27 | ); 28 | }); 29 | }); 30 | 31 | describe(serializeACDCAttachment.name, () => { 32 | it('serializes acdc data', () => { 33 | const [, data] = Saider.saidify({ 34 | i: 'EP-hA0w9X5FDonCDxQv32OTCAvcxkZxgDLOnDb3Jcn3a', 35 | d: '', 36 | v: versify(Protocols.ACDC, undefined, Serials.JSON, 0), 37 | a: { 38 | LEI: '123', 39 | }, 40 | }); 41 | 42 | const result = serializeACDCAttachment(new Serder(data)); 43 | 44 | assert.equal( 45 | d(result), 46 | '-IABEP-hA0w9X5FDonCDxQv32OTCAvcxkZxgDLOnDb3Jcn3a0AAAAAAAAAAAAAAAAAAAAAAAEHGU7u7cSMjMcJ1UyN8r-MnoZ3cDw4sMQNYxRLjqGVJI' 47 | ); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/core/vdring.test.ts: -------------------------------------------------------------------------------- 1 | import libsodium from 'libsodium-wrappers-sumo'; 2 | import { vdr } from '../../src/keri/core/vdring.ts'; 3 | import { assert, describe, it } from 'vitest'; 4 | 5 | describe('vdr', () => { 6 | it('should create registry inception events ', async () => { 7 | await libsodium.ready; 8 | let actual = vdr.incept({ 9 | pre: 'ECJIoBpEcCWMzvquk861dXP8JJZ-vbmJczlDR-NYcE3g', 10 | toad: 0, 11 | }); 12 | assert.equal(actual.pre.length, 44); 13 | 14 | actual = vdr.incept({ 15 | pre: 'ECJIoBpEcCWMzvquk861dXP8JJZ-vbmJczlDR-NYcE3g', 16 | toad: 0, 17 | nonce: 'AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s', 18 | }); 19 | assert.equal( 20 | actual.pre, 21 | 'EDAsrwU75uoh8sii7w-KN-Txy2d0dhHiUP34TQVBJiPS' 22 | ); 23 | assert.equal(actual.code, 'E'); 24 | assert.equal( 25 | actual.raw, 26 | '{"v":"KERI10JSON00010f_","t":"vcp","d":"EDAsrwU75uoh8sii7w-KN-Txy2d0dhHiUP34TQVBJiPS","i":"EDAsrwU75uoh8sii7w-KN-Txy2d0dhHiUP34TQVBJiPS","ii":"ECJIoBpEcCWMzvquk861dXP8JJZ-vbmJczlDR-NYcE3g","s":"0","c":[],"bt":"0","b":[],"n":"AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s"}' 27 | ); 28 | assert.equal(actual.size, 271); 29 | }); 30 | 31 | it('should fail on NB config with backers', async () => { 32 | await libsodium.ready; 33 | const cnfg = ['NB']; 34 | assert.throws(() => { 35 | vdr.incept({ 36 | pre: 'ECJIoBpEcCWMzvquk861dXP8JJZ-vbmJczlDR-NYcE3g', 37 | toad: 0, 38 | nonce: 'AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s', 39 | cnfg: cnfg, 40 | baks: ['a backer'], 41 | }); 42 | }, '1 backers specified for NB vcp, 0 allowed'); 43 | }); 44 | 45 | it('should fail with duplicate backers', async () => { 46 | await libsodium.ready; 47 | assert.throws(() => { 48 | vdr.incept({ 49 | pre: 'ECJIoBpEcCWMzvquk861dXP8JJZ-vbmJczlDR-NYcE3g', 50 | toad: 0, 51 | nonce: 'AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s', 52 | baks: ['a backer', 'a backer'], 53 | }); 54 | }, 'Invalid baks a backer,a backer has duplicates'); 55 | }); 56 | 57 | it('should fail with invalid toad config for backers', async () => { 58 | await libsodium.ready; 59 | assert.throws(() => { 60 | vdr.incept({ 61 | pre: 'ECJIoBpEcCWMzvquk861dXP8JJZ-vbmJczlDR-NYcE3g', 62 | toad: 0, 63 | nonce: 'AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s', 64 | baks: ['a backer'], 65 | }); 66 | }, 'Invalid toad 0 for baks in a backer'); 67 | }); 68 | 69 | it('should fail with invalid toad for no backers', async () => { 70 | await libsodium.ready; 71 | assert.throws(() => { 72 | vdr.incept({ 73 | pre: 'ECJIoBpEcCWMzvquk861dXP8JJZ-vbmJczlDR-NYcE3g', 74 | toad: 1, 75 | nonce: 'AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s', 76 | }); 77 | }, 'Invalid toad 1 for no baks'); 78 | }); 79 | 80 | it('should allow optional toad and no backers', async () => { 81 | await libsodium.ready; 82 | const actual = vdr.incept({ 83 | pre: 'ECJIoBpEcCWMzvquk861dXP8JJZ-vbmJczlDR-NYcE3g', 84 | nonce: 'AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s', 85 | }); 86 | 87 | assert.equal( 88 | actual.pre, 89 | 'EDAsrwU75uoh8sii7w-KN-Txy2d0dhHiUP34TQVBJiPS' 90 | ); 91 | assert.equal(actual.code, 'E'); 92 | assert.equal( 93 | actual.raw, 94 | '{"v":"KERI10JSON00010f_","t":"vcp","d":"EDAsrwU75uoh8sii7w-KN-Txy2d0dhHiUP34TQVBJiPS","i":"EDAsrwU75uoh8sii7w-KN-Txy2d0dhHiUP34TQVBJiPS","ii":"ECJIoBpEcCWMzvquk861dXP8JJZ-vbmJczlDR-NYcE3g","s":"0","c":[],"bt":"0","b":[],"n":"AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s"}' 95 | ); 96 | assert.equal(actual.size, 271); 97 | }); 98 | 99 | it('should allow optional toad and backers', async () => { 100 | await libsodium.ready; 101 | const actual = vdr.incept({ 102 | pre: 'ECJIoBpEcCWMzvquk861dXP8JJZ-vbmJczlDR-NYcE3g', 103 | nonce: 'AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s', 104 | baks: ['a backer'], 105 | toad: 1, 106 | }); 107 | 108 | const expectedPrefix = 'ENlghG6_krj9YMzy5-E3j5sEjsd6FR1nskBtbtSQGOFL'; 109 | assert.equal(actual.pre, expectedPrefix); 110 | assert.equal(actual.code, 'E'); 111 | assert.equal( 112 | actual.raw, 113 | JSON.stringify({ 114 | v: 'KERI10JSON000119_', 115 | t: 'vcp', 116 | d: expectedPrefix, 117 | i: expectedPrefix, 118 | ii: 'ECJIoBpEcCWMzvquk861dXP8JJZ-vbmJczlDR-NYcE3g', 119 | s: '0', 120 | c: [], 121 | bt: '1', 122 | b: ['a backer'], 123 | n: 'AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s', 124 | }) 125 | ); 126 | assert.equal(actual.size, 281); 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /test/core/verfer.test.ts: -------------------------------------------------------------------------------- 1 | import { MtrDex } from '../../src/keri/core/matter.ts'; 2 | import libsodium from 'libsodium-wrappers-sumo'; 3 | import { assert, describe, it, expect, beforeAll } from 'vitest'; 4 | import { Verfer } from '../../src/keri/core/verfer.ts'; 5 | import { p256 } from '@noble/curves/p256'; 6 | import { b } from 'signify-ts'; 7 | 8 | beforeAll(async () => { 9 | await libsodium.ready; 10 | }); 11 | 12 | describe('Verfer', () => { 13 | describe('Ed25519', () => { 14 | const seed = libsodium.randombytes_buf(libsodium.crypto_sign_SEEDBYTES); 15 | const keypair = libsodium.crypto_sign_seed_keypair(seed); 16 | 17 | it('should create verfer', async () => { 18 | const verkey = keypair.publicKey; 19 | 20 | const verfer = new Verfer({ 21 | raw: keypair.publicKey, 22 | code: MtrDex.Ed25519N, 23 | }); 24 | 25 | assert.deepStrictEqual(verfer.raw, verkey); 26 | assert.deepStrictEqual(verfer.code, MtrDex.Ed25519N); 27 | }); 28 | 29 | it('should verify signature', () => { 30 | const verfer = new Verfer({ 31 | raw: keypair.publicKey, 32 | code: MtrDex.Ed25519N, 33 | }); 34 | const ser = 'abcdefghijklmnopqrstuvwxyz0123456789'; 35 | 36 | const sig = libsodium.crypto_sign_detached(ser, keypair.privateKey); 37 | 38 | assert.equal(verfer.verify(sig, ser), true); 39 | }); 40 | 41 | it('should create verfer from qb64', async () => { 42 | const verfer = new Verfer({ 43 | qb64: 'BGgVB5Aar1pOr70nRpJmRA_RP68HErflNovoEMP7b7mJ', 44 | }); 45 | 46 | assert.deepStrictEqual( 47 | verfer.raw, 48 | new Uint8Array([ 49 | 104, 21, 7, 144, 26, 175, 90, 78, 175, 189, 39, 70, 146, 50 | 102, 68, 15, 209, 63, 175, 7, 18, 183, 229, 54, 139, 232, 51 | 16, 195, 251, 111, 185, 137, 52 | ]) 53 | ); 54 | }); 55 | }); 56 | 57 | describe('secp256r1', () => { 58 | const privateKey = new Uint8Array([ 59 | 138, 17, 14, 173, 86, 68, 80, 39, 61, 52, 208, 154, 211, 190, 21, 60 | 99, 156, 134, 184, 90, 166, 171, 226, 251, 239, 132, 127, 221, 144, 61 | 51, 245, 71, 62 | ]); 63 | 64 | const publicKey = p256.getPublicKey(privateKey); 65 | 66 | it('should create verfer', async () => { 67 | const verfer = new Verfer({ 68 | raw: publicKey, 69 | code: MtrDex.ECDSA_256r1, 70 | }); 71 | 72 | assert.deepStrictEqual(verfer.raw, publicKey); 73 | assert.deepStrictEqual(verfer.code, MtrDex.ECDSA_256r1); 74 | assert.equal( 75 | verfer.qb64, 76 | '1AAJA-blKBTkTkEEOX_Yq3i3KxZJvcHarPfu_crKVwcfEwvQ' 77 | ); 78 | }); 79 | 80 | it('should verify secp256r1', async () => { 81 | const verfer = new Verfer({ 82 | raw: publicKey, 83 | code: MtrDex.ECDSA_256r1, 84 | }); 85 | 86 | const ser = 'abcdefghijklmnopqrstuvwxyz0123456789'; 87 | 88 | const sig = p256.sign(b(ser), privateKey).toCompactRawBytes(); 89 | 90 | assert.deepEqual( 91 | sig, 92 | new Uint8Array([ 93 | 56, 81, 180, 93, 192, 44, 174, 128, 161, 173, 226, 227, 149, 94 | 26, 203, 255, 36, 189, 144, 110, 163, 51, 67, 138, 99, 130, 95 | 38, 189, 16, 170, 164, 77, 167, 254, 123, 220, 149, 72, 71, 96 | 28, 32, 66, 213, 177, 197, 158, 195, 234, 167, 109, 207, 97 | 174, 15, 221, 245, 85, 120, 226, 224, 33, 94, 89, 115, 49, 98 | ]) 99 | ); 100 | 101 | assert.equal(verfer.verify(sig, ser), true); 102 | }); 103 | 104 | it('should parse qb64', () => { 105 | const verfer = new Verfer({ 106 | qb64: '1AAJAwf0oSqmdjPud5gnK6bAPKkBLrXUMQZiOW4Vpc4XpOPf', 107 | }); 108 | 109 | assert.deepStrictEqual( 110 | verfer.raw, 111 | new Uint8Array([ 112 | 3, 7, 244, 161, 42, 166, 118, 51, 238, 119, 152, 39, 43, 113 | 166, 192, 60, 169, 1, 46, 181, 212, 49, 6, 98, 57, 110, 21, 114 | 165, 206, 23, 164, 227, 223, 115 | ]) 116 | ); 117 | }); 118 | }); 119 | 120 | it('should not verify secp256k1', async () => { 121 | const publicKey = new Uint8Array([ 122 | 2, 79, 93, 30, 107, 249, 254, 237, 205, 87, 8, 149, 203, 214, 36, 123 | 187, 162, 251, 58, 206, 241, 203, 27, 76, 236, 37, 189, 148, 240, 124 | 178, 204, 133, 31, 125 | ]); 126 | 127 | expect(() => { 128 | new Verfer({ raw: publicKey, code: MtrDex.ECDSA_256k1 }); 129 | }).toThrow(new Error(`Unsupported code = 1AAB for verifier.`)); 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types.d.ts"], 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "rootDir": "src", 6 | "outDir": "dist", 7 | "noEmit": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "test", "test-integration"], 3 | "exclude": ["node_modules", "dist"], 4 | "compilerOptions": { 5 | "noEmit": true, 6 | "allowImportingTsExtensions": true, 7 | "rewriteRelativeImportExtensions": true, 8 | "declaration": true, 9 | "target": "ES2017", 10 | "module": "Node16", 11 | "lib": ["dom", "esnext"], 12 | "moduleResolution": "node16", 13 | "strict": true, 14 | "skipLibCheck": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "allowSyntheticDefaultImports": true, 17 | "esModuleInterop": true, 18 | "paths": { 19 | "signify-ts": ["./src/index.ts"] 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | name: 'Unit tests', 6 | root: 'test', 7 | testTimeout: 10000, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /vitest.integration.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | import tsconfig from 'vite-tsconfig-paths'; 3 | 4 | export default defineConfig({ 5 | plugins: [tsconfig()], 6 | test: { 7 | fileParallelism: false, 8 | name: 'Integration tests', 9 | root: 'test-integration', 10 | bail: 1, 11 | testTimeout: 60000, 12 | watch: false, 13 | }, 14 | }); 15 | --------------------------------------------------------------------------------