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

Add RFC9285 base45 support and more options. #15

Merged
merged 2 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# vpqr Changelog

## 4.2.0 - 2024-xx-xx

### Added
- Add RFC9285 base45 support for more efficient encoding than base32.
- Defaulting to base32 until the next major release.
- More `toQrCode` options available:
- `qrMultibaseEncoding`: Encoding for QR data. `B` for base32, `R` for
base45. base45 is more efficient. Defaults to base32.
- `qrErrorCorrectionLevel`: Error correction level used in the QR code. 'L'
Low 7%, 'M' Medium 15%, 'Q' Qartile 25%, 'H' High 30%. Defaults to 'L'.
- `qrVersion`: QR version. 1-40 or 0 for auto. Defaults to auto.

## 4.1.0 - 2023-12-19

### Added
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ const {vp} = await vqpr.fromQrCode({text: qrCodeText, documentLoader});
// becomes a JSON object with the VP in the example above.
```

### Encoding Options

- `qrMultibaseEncoding`: Encoding for QR data. `B` for [RFC4648][] base32, `R`
for [RFC9285][] base45. base45 is more efficient. Defaults to base32.
- `qrErrorCorrectionLevel`: Error correction level used in the QR code. 'L' Low
7%, 'M' Medium 15%, 'Q' Qartile 25%, 'H' High 30%. Defaults to 'L'.
- `qrVersion`: QR version. 1-40 or 0 for auto. Defaults to auto.
Comment on lines +116 to +120
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way to do this would be under the options.qr namespace (e.g., options.qr.multibaseEncoding, ...). I'm ok with either.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suffering analysis paralysis with this and differences in libraries and use cases. Going to go with something for now and will adjust in major releases later as needed and experience is gained.


## Contribute

See [the contribute file](https://github.com/digitalbazaar/bedrock/blob/master/CONTRIBUTING.md)!
Expand All @@ -128,3 +136,6 @@ Digital Bazaar: [email protected]
## License

[New BSD License (3-clause)](LICENSE) © Digital Bazaar

[RFC4648]: https://datatracker.ietf.org/doc/rfc4648/
[RFC9285]: https://datatracker.ietf.org/doc/rfc9285/
68 changes: 56 additions & 12 deletions lib/util.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,46 @@
/*!
* Copyright (c) 2021-2023 Digital Bazaar, Inc. All rights reserved.
* Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved.
*/
import * as cborld from '@digitalbazaar/cborld';
import {
decode as base45Decode,
encode as base45Encode
} from '@digitalbazaar/base45';
import {
Encoder,
ErrorCorrectionLevel,
// QRByte,
QRAlphanumeric,
// ErrorCorrectionLevel
QRAlphanumeric
} from '@nuintun/qrcode';
import base32Decode from 'base32-decode';
import base32Encode from 'base32-encode';

const BASE_32_UPPERCASE_MULTIBASE_PREFIX = 'B';
const BASE_45_MULTIBASE_PREFIX = 'R';

/**
* Encode to a QR code.
*
* @param {object} options - Encoding options.
* @param {object} options.header - Header for QR data such as "VP1-".
* @param {object} [options.jsonldDocument] - Document to encode.
* @param {object} [options.cborldBytes] - CBOR-LD bytes to encode.
* @param {object} [options.documentLoader] - Loader for jsonldDocument.
* @param {object} [options.appContextMap] - CBOR-LD app context map.
* @param {object} [options.size] - Output image module size.
* @param {object} [options.diagnose] - Diagnostic function.
* @param {string} [options.qrMultibaseEncoding='B'] - Multibase encoding for
* QR data. 'R' for base45, 'B' for base32.
* @param {string} [options.qrErrorCorrectionLevel='L'] - QR error correction
* level. 'L' Low 7%, 'M' Medium 15%, 'Q' Qartile 25%, 'H' High 30%.
* @param {string} [options.qrVersion=0] - QR version. 1-40 or 0 for auto.
*
* @returns {object} The QR code properties.
*/
export async function toQrCode({
header, jsonldDocument, cborldBytes, documentLoader, appContextMap, size,
diagnose
qrMultibaseEncoding = BASE_32_UPPERCASE_MULTIBASE_PREFIX,
qrErrorCorrectionLevel = 'L', qrVersion = 0, diagnose
} = {}) {
if(jsonldDocument && cborldBytes) {
throw new Error(
Expand All @@ -38,7 +63,8 @@ export async function toQrCode({
encodedCborld,
version
} = _bytesToQrCodeDataUrl({
header, bytes: cborldBytes, size
header, bytes: cborldBytes, size, qrMultibaseEncoding,
qrErrorCorrectionLevel, qrVersion
});

return {
Expand All @@ -60,11 +86,18 @@ export async function fromQrCode({

const multibasePayload = text.slice(expectedHeader.length);

if(!multibasePayload.startsWith(BASE_32_UPPERCASE_MULTIBASE_PREFIX)) {
throw TypeError('Payload must be multibase base32 (RFC4648) encoded.');
let cborldArrayBuffer;

if(multibasePayload.startsWith(BASE_45_MULTIBASE_PREFIX)) {
// base45 RFC9285 encoded
cborldArrayBuffer = base45Decode(multibasePayload.slice(1));
} else if(multibasePayload.startsWith(BASE_32_UPPERCASE_MULTIBASE_PREFIX)) {
// base32 RFC4648 encoded
cborldArrayBuffer = base32Decode(multibasePayload.slice(1), 'RFC4648');
} else {
throw TypeError('Payload multibase type not supported.');
}

const cborldArrayBuffer = base32Decode(multibasePayload.slice(1), 'RFC4648');
const cborldBytes = new Uint8Array(cborldArrayBuffer);
if(!decodeCborld) {
return {cborldBytes};
Expand All @@ -80,13 +113,24 @@ export async function fromQrCode({
return {jsonldDocument};
}

function _bytesToQrCodeDataUrl({header = '', bytes, size}) {
const encoded = base32Encode(bytes, 'RFC4648', {padding: false});
const vpPayload = `${header}${BASE_32_UPPERCASE_MULTIBASE_PREFIX}${encoded}`;
function _bytesToQrCodeDataUrl({
header = '', bytes, size, qrMultibaseEncoding, qrErrorCorrectionLevel,
qrVersion
}) {
let encoded;
let vpPayload;
if(qrMultibaseEncoding === BASE_45_MULTIBASE_PREFIX) {
encoded = base45Encode(bytes);
vpPayload = `${header}${BASE_45_MULTIBASE_PREFIX}${encoded}`;
} else if(qrMultibaseEncoding === BASE_32_UPPERCASE_MULTIBASE_PREFIX) {
encoded = base32Encode(bytes, 'RFC4648', {padding: false});
vpPayload = `${header}${BASE_32_UPPERCASE_MULTIBASE_PREFIX}${encoded}`;
}

const qrcode = new Encoder();
qrcode.setEncodingHint(true);
// qrcode.setErrorCorrectionLevel(ErrorCorrectionLevel.M);
qrcode.setErrorCorrectionLevel(ErrorCorrectionLevel[qrErrorCorrectionLevel]);
qrcode.setVersion(qrVersion);
qrcode.write(new QRAlphanumeric(vpPayload));
qrcode.make();

Expand Down
9 changes: 5 additions & 4 deletions lib/vpqr.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
/*!
* Copyright (c) 2021-2023 Digital Bazaar, Inc. All rights reserved.
* Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved.
*/
import * as util from './util.js';

const VP_QR_VERSION = 'VP1';
const HEADER = VP_QR_VERSION + '-';

export async function toQrCode({
vp, documentLoader, appContextMap, size, diagnose
vp, documentLoader, appContextMap, size, qrMultibaseEncoding,
qrErrorCorrectionLevel, qrVersion, diagnose
} = {}) {
return util.toQrCode({
header: HEADER, jsonldDocument: vp, documentLoader, appContextMap, size,
diagnose
header: HEADER, jsonldDocument: vp, documentLoader, appContextMap,
qrMultibaseEncoding, qrErrorCorrectionLevel, qrVersion, size, diagnose
});
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"lib/**/*.js"
],
"dependencies": {
"@digitalbazaar/base45": "^1.0.0",
"@digitalbazaar/cborld": "^6.0.1",
"@nuintun/qrcode": "^3.3.0",
"base32-decode": "^1.0.0",
Expand Down
11 changes: 9 additions & 2 deletions test/mock-data.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading