Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multikey and loosen key expression requirements. #32

Merged
merged 3 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
node-version: [16.x]
node-version: [20.x]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm install
Expand All @@ -24,11 +24,11 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
node-version: [14.x, 16.x, 18.x]
node-version: [18.x, 20.x]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm install
Expand All @@ -40,11 +40,11 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
node-version: [16.x]
node-version: [20.x]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm install
Expand All @@ -56,18 +56,19 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
node-version: [20.x]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v3
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
uses: codecov/codecov-action@v4
with:
file: ./coverage/lcov.info
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# @digitalbazaar/ed25519-signature-2020 Changelog

## 5.3.0 - 2024-mm-dd

### Added
- Add support for `Multikey` verification methods.

### Changed
- Loosen restrictions on verification methods that do not have
contexts, allowing processing of well-known types in those cases.
- Allow `publiKeyJwk` to be used to express key material.

## 5.2.0 - 2023-02-13

### Removed
Expand Down
38 changes: 19 additions & 19 deletions lib/Ed25519Signature2020.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
/*!
* Copyright (c) 2020-2021 Digital Bazaar, Inc. All rights reserved.
* Copyright (c) 2020-2024 Digital Bazaar, Inc. All rights reserved.
*/
import jsigs from 'jsonld-signatures';
const {suites: {LinkedDataSignature}} = jsigs;
import * as base58btc from 'base58-universal';
import * as Ed25519Multikey from '@digitalbazaar/ed25519-multikey';
import {
Ed25519VerificationKey2020
} from '@digitalbazaar/ed25519-verification-key-2020';
import suiteContext2018 from 'ed25519-signature-2018-context';
import jsigs from 'jsonld-signatures';
const {suites: {LinkedDataSignature}} = jsigs;
import suiteContext2020 from 'ed25519-signature-2020-context';

// 'https://w3id.org/security/suites/ed25519-2020/v1'
const SUITE_CONTEXT_URL = suiteContext2020.constants.CONTEXT_URL;
// 'https://w3id.org/security/suites/ed25519-2018/v1'
const SUITE_CONTEXT_URL_2018 = suiteContext2018.constants.CONTEXT_URL;

// multibase base58-btc header
const MULTIBASE_BASE58BTC_HEADER = 'z';

Expand Down Expand Up @@ -56,8 +55,9 @@ export class Ed25519Signature2020 extends LinkedDataSignature {
key, signer, verifier, proof, date, useNativeCanonize,
canonizeOptions
});
// Some operations may be performed with Ed25519VerificationKey2018.
// So, Ed25519VerificationKey2020 is recommended, but not strictly required.
// some operations may be performed with `Ed25519VerificationKey2018` or
// `Multikey`; so, `Ed25519VerificationKey2020` is recommended, but not
// strictly required
this.requiredKeyType = 'Ed25519VerificationKey2020';
}

Expand Down Expand Up @@ -109,7 +109,7 @@ export class Ed25519Signature2020 extends LinkedDataSignature {

let {verifier} = this;
if(!verifier) {
const key = await this.LDKeyClass.from(verificationMethod);
const key = await Ed25519Multikey.from(verificationMethod);
verifier = key.verifier();
}
return verifier.verify({data: verifyData, signature: signatureBytes});
Expand All @@ -119,14 +119,10 @@ export class Ed25519Signature2020 extends LinkedDataSignature {
let contextUrl;
if(verificationMethod.type === 'Ed25519VerificationKey2020') {
contextUrl = SUITE_CONTEXT_URL;
} else if(verificationMethod.type === 'Ed25519VerificationKey2018') {
contextUrl = SUITE_CONTEXT_URL_2018;
} else {
throw new Error(`Unsupported key type "${verificationMethod.type}".`);
}
if(!_includesContext({
document: verificationMethod, contextUrl
})) {
if(!_includesContext({document: verificationMethod, contextUrl})) {
// For DID Documents, since keys do not have their own contexts,
// the suite context is usually provided by the documentLoader logic
throw new TypeError(
Expand Down Expand Up @@ -163,12 +159,16 @@ export class Ed25519Signature2020 extends LinkedDataSignature {
verificationMethod = typeof document === 'string' ?
JSON.parse(document) : document;

// for maximum compatibility, import using multikey library and convert to
// type `Ed25519Signature2020`
const key = await Ed25519Multikey.from(verificationMethod);
verificationMethod = {
...await key.export({publicKey: true, includeContext: true}),
'@context': SUITE_CONTEXT_URL,
type: 'Ed25519VerificationKey2020'
};
await this.assertVerificationMethod({verificationMethod});
if(verificationMethod.type === 'Ed25519VerificationKey2018') {
verificationMethod = (await Ed25519VerificationKey2020
.fromEd25519VerificationKey2018({keyPair: verificationMethod}))
.export({publicKey: true, includeContext: true});
}

return verificationMethod;
}

Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
"lib/**/*.js"
],
"dependencies": {
"@digitalbazaar/ed25519-multikey": "^1.1.0",
"@digitalbazaar/ed25519-verification-key-2020": "^4.1.0",
"base58-universal": "^2.0.0",
"ed25519-signature-2018-context": "^1.1.0",
"ed25519-signature-2020-context": "^1.1.0",
"jsonld-signatures": "^11.1.0"
},
Expand All @@ -26,6 +26,7 @@
"c8": "^7.12.0",
"chai": "^4.3.7",
"cross-env": "^7.0.3",
"ed25519-signature-2018-context": "^1.1.0",
"eslint": "^8.34.0",
"eslint-config-digitalbazaar": "^4.2.0",
"eslint-plugin-jsdoc": "^40.0.0",
Expand All @@ -42,7 +43,7 @@
"webpack": "^5.75.0"
},
"engines": {
"node": ">=14"
"node": ">=18"
},
"scripts": {
"test": "npm run test-node",
Expand Down
28 changes: 3 additions & 25 deletions test/Ed25519Signature2020.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*!
* Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved.
* Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved.
*/
import {expect} from 'chai';

Expand Down Expand Up @@ -416,27 +416,6 @@ describe('Ed25519Signature2020', () => {
});
expect(result.verified).to.be.true;
});
it('should throw error when verification method does not have' +
'2018 context', async () => {
const mockPublicKey2018WithoutContext = {...mockPublicKey2018};
// intentionally delete the context
delete mockPublicKey2018WithoutContext['@context'];
loader.addStatic(mockKeyPair2018.controller, controllerDoc2018);
loader.addStatic(mockPublicKey2018WithoutContext.id,
mockPublicKey2018WithoutContext);
const documentLoader = loader.build();
const suite = new Ed25519Signature2020();
const result = await jsigs.verify(signedCredential, {
suite,
purpose: new AssertionProofPurpose(),
documentLoader
});
expect(result.verified).to.be.false;
expect(result.results[0].error.name).equal('TypeError');
expect(result.results[0].error.message).equal(
'The verification method (key) must contain ' +
'\"https://w3id.org/security/suites/ed25519-2018/v1\" context.');
});
it('should throw error when verification method contains 2018 key ' +
'with (not-matching) 2020 context', async () => {
const mockPublicKey2018With2020Context = {...mockPublicKey2018};
Expand All @@ -454,10 +433,9 @@ describe('Ed25519Signature2020', () => {
documentLoader
});
expect(result.verified).to.be.false;
expect(result.results[0].error.name).equal('TypeError');
expect(result.results[0].error.message).equal(
'The verification method (key) must contain ' +
'\"https://w3id.org/security/suites/ed25519-2018/v1\" context.');
'Context not supported ' +
'"https://w3id.org/security/suites/ed25519-2020/v1".');
});
});
});
Loading