diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ce194c3..3649d9a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,11 +8,11 @@ jobs: timeout-minutes: 10 strategy: matrix: - node-version: [20.x] + node-version: [24.x] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install @@ -24,11 +24,11 @@ jobs: timeout-minutes: 10 strategy: matrix: - node-version: [18.x, 20.x] + node-version: [20.x, 22.x, 24.x] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install @@ -40,11 +40,11 @@ jobs: timeout-minutes: 10 strategy: matrix: - node-version: [20.x] + node-version: [24.x] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install @@ -56,18 +56,19 @@ jobs: timeout-minutes: 10 strategy: matrix: - node-version: [20.x] + node-version: [24.x] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install - name: Generate coverage report run: npm run coverage-ci -# - name: Upload coverage to Codecov -# uses: codecov/codecov-action@v2 -# with: -# file: ./coverage/lcov.info -# fail_ci_if_error: true + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + file: ./coverage/lcov.info + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/lib/helpers.js b/lib/helpers.js index d3e110c..4064933 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -1,25 +1,37 @@ /*! - * Copyright (c) 2023 Digital Bazaar, Inc. All rights reserved. + * Copyright (c) 2023-2025 Digital Bazaar, Inc. All rights reserved. */ -import { - X25519KeyAgreementKey2019 -} from '@digitalbazaar/x25519-key-agreement-key-2019'; +import * as base58btc from 'base58-universal'; +import * as Ed25519Multikey from '@digitalbazaar/ed25519-multikey'; import { X25519KeyAgreementKey2020 } from '@digitalbazaar/x25519-key-agreement-key-2020'; +const ED25519_KEY_2018_TYPE = 'Ed25519VerificationKey2018'; const ED25519_KEY_2018_CONTEXT_URL = 'https://w3id.org/security/suites/ed25519-2018/v1'; + +const ED25519_KEY_2020_TYPE = 'Ed25519VerificationKey2020'; const ED25519_KEY_2020_CONTEXT_URL = 'https://w3id.org/security/suites/ed25519-2020/v1'; + +const MULTIKEY_TYPE = 'Multikey'; const MULTIKEY_CONTEXT_V1_URL = 'https://w3id.org/security/multikey/v1'; +const X25519_2019_TYPE = 'X25519KeyAgreementKey2019'; +const X25519_2019_CONTEXT_URL = + 'https://w3id.org/security/suites/x25519-2019/v1'; + +const X25519_2020_TYPE = 'X25519KeyAgreementKey2020'; +const X25519_2020_CONTEXT_URL = + 'https://w3id.org/security/suites/x25519-2020/v1'; + const contextsBySuite = new Map([ - ['Ed25519VerificationKey2020', ED25519_KEY_2020_CONTEXT_URL], - ['Ed25519VerificationKey2018', ED25519_KEY_2018_CONTEXT_URL], - ['Multikey', MULTIKEY_CONTEXT_V1_URL], - [X25519KeyAgreementKey2020.suite, X25519KeyAgreementKey2020.SUITE_CONTEXT], - [X25519KeyAgreementKey2019.suite, X25519KeyAgreementKey2019.SUITE_CONTEXT] + [ED25519_KEY_2020_TYPE, ED25519_KEY_2020_CONTEXT_URL], + [ED25519_KEY_2018_TYPE, ED25519_KEY_2018_CONTEXT_URL], + [MULTIKEY_TYPE, MULTIKEY_CONTEXT_V1_URL], + [X25519_2020_TYPE, X25519_2020_CONTEXT_URL], + [X25519_2019_TYPE, X25519_2019_CONTEXT_URL] ]); /** @@ -61,25 +73,32 @@ export function setKeyPairId({keyPair, did}) { `${did}#${keyPair.publicKeyMultibase}`; } -export function getKeyAgreementKeyPair({contexts, verificationPublicKey}) { +export async function getKeyAgreementKeyPair({ + contexts, verificationPublicKey +}) { // The KAK pair will use the source key's controller, but may generate // its own .id let keyAgreementKeyPair; + // note: a future x25519-multikey lib should handle all key types here + let is2019 = false; + if(verificationPublicKey.type === ED25519_KEY_2018_TYPE) { + is2019 = true; + contexts.push(X25519_2019_CONTEXT_URL); + // convert to `ED25519_KEY_2020_TYPE` for conversion below + verificationPublicKey = await Ed25519Multikey.from(verificationPublicKey); + verificationPublicKey.type = ED25519_KEY_2020_TYPE; + } else if(verificationPublicKey.type === ED25519_KEY_2020_TYPE) { + contexts.push(X25519_2020_CONTEXT_URL); + } + switch(verificationPublicKey.type) { - case 'Ed25519VerificationKey2018': { - keyAgreementKeyPair = X25519KeyAgreementKey2019 - .fromEd25519VerificationKey2018({keyPair: verificationPublicKey}); - contexts.push(X25519KeyAgreementKey2019.SUITE_CONTEXT); - break; - } - case 'Ed25519VerificationKey2020': { + case ED25519_KEY_2020_TYPE: { keyAgreementKeyPair = X25519KeyAgreementKey2020 .fromEd25519VerificationKey2020({keyPair: verificationPublicKey}); - contexts.push(X25519KeyAgreementKey2020.SUITE_CONTEXT); break; } - case 'Multikey': { + case MULTIKEY_TYPE: { // FIXME: Add keyAgreementKeyPair interface for Multikey. break; } @@ -89,6 +108,29 @@ export function getKeyAgreementKeyPair({contexts, verificationPublicKey}) { "${verificationPublicKey.type}".`); } } + + if(is2019) { + // modify 2020 x25519 key pair for 2019 legacy use... + + // update `type` and add `publicKeyBase58` + keyAgreementKeyPair.type = X25519_2019_TYPE; + const {publicKeyMultibase} = keyAgreementKeyPair; + const multikey = base58btc.decode(publicKeyMultibase.slice(1)); + keyAgreementKeyPair.publicKeyBase58 = base58btc.encode(multikey.slice(2)); + + // update `export` to output 2019 legacy version + const previousExport = keyAgreementKeyPair.export; + keyAgreementKeyPair.export = (...args) => { + const exported = previousExport.apply(keyAgreementKeyPair, args); + if(exported['@context']) { + exported['@context'] = X25519_2019_CONTEXT_URL; + } + delete exported.publicKeyMultibase; + exported.publicKeyBase58 = keyAgreementKeyPair.publicKeyBase58; + return exported; + }; + } + return {keyAgreementKeyPair}; } @@ -102,15 +144,15 @@ export function getMultibaseMultikeyHeader({value}) { export function addKeyAgreementKeyContext({contexts, keyAgreementKeyPair}) { const {type} = keyAgreementKeyPair; switch(type) { - case 'X25519KeyAgreementKey2019': { - if(!contexts.includes(X25519KeyAgreementKey2019.SUITE_CONTEXT)) { - contexts.push(X25519KeyAgreementKey2019.SUITE_CONTEXT); + case X25519_2019_TYPE: { + if(!contexts.includes(X25519_2019_CONTEXT_URL)) { + contexts.push(X25519_2019_CONTEXT_URL); } break; } - case 'X25519KeyAgreementKey2020': { - if(!contexts.includes(X25519KeyAgreementKey2020.SUITE_CONTEXT)) { - contexts.push(X25519KeyAgreementKey2020.SUITE_CONTEXT); + case X25519_2020_TYPE: { + if(!contexts.includes(X25519_2020_CONTEXT_URL)) { + contexts.push(X25519_2020_CONTEXT_URL); } break; } @@ -131,8 +173,7 @@ export async function getKeyPair({ } const {type} = keyPair; let keyAgreementKeyPair; - if(type === 'X25519KeyAgreementKey2020' || - type === 'X25519KeyAgreementKey2019') { + if(type === X25519_2020_TYPE || type === X25519_2019_TYPE) { keyAgreementKeyPair = keyPair; keyPair = null; } diff --git a/package.json b/package.json index e604115..87fc82b 100644 --- a/package.json +++ b/package.json @@ -24,21 +24,22 @@ ], "dependencies": { "@digitalbazaar/did-io": "^2.0.0", - "@digitalbazaar/x25519-key-agreement-key-2019": "^6.0.0", + "@digitalbazaar/ed25519-multikey": "^1.3.1", "@digitalbazaar/x25519-key-agreement-key-2020": "^3.0.0" }, "devDependencies": { - "@digitalbazaar/bls12-381-multikey": "^1.1.1", + "@digitalbazaar/bls12-381-multikey": "^2.1.0", "@digitalbazaar/ecdsa-multikey": "^1.1.1", "@digitalbazaar/ed25519-verification-key-2018": "^4.0.0", "@digitalbazaar/ed25519-verification-key-2020": "^4.0.0", + "@digitalbazaar/x25519-key-agreement-key-2019": "^6.0.0", "c8": "^7.11.3", "chai": "^4.3.6", "cross-env": "^7.0.3", "eslint": "^8.37.0", - "eslint-config-digitalbazaar": "^4.2.0", - "eslint-plugin-jsdoc": "^40.1.1", - "eslint-plugin-unicorn": "^46.0.0", + "eslint-config-digitalbazaar": "^5.2.0", + "eslint-plugin-jsdoc": "^50.8.0", + "eslint-plugin-unicorn": "^56.0.1", "karma": "^6.3.20", "karma-babel-preprocessor": "^8.0.2", "karma-chai": "^0.1.0",