From d534ee3126d5f96eb1870a38bd63dfd7145a1411 Mon Sep 17 00:00:00 2001 From: lenkan Date: Thu, 15 Feb 2024 11:35:56 +0100 Subject: [PATCH] fix: remove urlsafe-base64 dependency --- package-lock.json | 8 +------- package.json | 3 +-- src/keri/core/base64.ts | 28 ++++++++++++++++++++++++++++ src/keri/core/bexter.ts | 6 +++--- src/keri/core/httping.ts | 4 ++-- src/keri/core/indexer.ts | 8 ++++---- src/keri/core/matter.ts | 10 +++++----- test/core/base64.test.ts | 35 +++++++++++++++++++++++++++++++++++ test/core/indexer.test.ts | 8 ++++---- 9 files changed, 83 insertions(+), 27 deletions(-) create mode 100644 src/keri/core/base64.ts create mode 100644 test/core/base64.test.ts diff --git a/package-lock.json b/package-lock.json index ca3d4cd6..e4f9dcd9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,8 +17,7 @@ "ecdsa-secp256r1": "^1.3.3", "libsodium-wrappers-sumo": "^0.7.9", "mathjs": "^12.4.0", - "structured-headers": "^0.5.0", - "urlsafe-base64": "^1.0.0" + "structured-headers": "^0.5.0" }, "devDependencies": { "@mermaid-js/mermaid-cli": "^10.3.0", @@ -7797,11 +7796,6 @@ "punycode": "^2.1.0" } }, - "node_modules/urlsafe-base64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/urlsafe-base64/-/urlsafe-base64-1.0.0.tgz", - "integrity": "sha512-RtuPeMy7c1UrHwproMZN9gN6kiZ0SvJwRaEzwZY0j9MypEkFqyBaKv176jvlPtg58Zh36bOkS0NFABXMHvvGCA==" - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index b1bddadf..1c5e7644 100644 --- a/package.json +++ b/package.json @@ -59,8 +59,7 @@ "ecdsa-secp256r1": "^1.3.3", "libsodium-wrappers-sumo": "^0.7.9", "mathjs": "^12.4.0", - "structured-headers": "^0.5.0", - "urlsafe-base64": "^1.0.0" + "structured-headers": "^0.5.0" }, "workspaces": [ "examples/*" diff --git a/src/keri/core/base64.ts b/src/keri/core/base64.ts new file mode 100644 index 00000000..ff81a475 --- /dev/null +++ b/src/keri/core/base64.ts @@ -0,0 +1,28 @@ +import { Buffer } from 'buffer'; +// base64url is supported by node Buffer, but not in buffer package for browser compatibility +// https://github.com/feross/buffer/issues/309 + +// Instead of using a node.js-only module and forcing us to polyfill the Buffer global, +// we insert code from https://gitlab.com/seangenabe/safe-base64 here + +export function encodeBase64Url(buffer: Buffer) { + if (!Buffer.isBuffer(buffer)) { + throw new TypeError('`buffer` must be a buffer.'); + } + return buffer + .toString('base64') + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+/, ''); +} + +export function decodeBase64Url(input: string) { + if (!(typeof input === 'string')) { + throw new TypeError('`input` must be a string.'); + } + + const n = input.length % 4; + const padded = input + '='.repeat(n > 0 ? 4 - n : n); + const base64String = padded.replace(/-/g, '+').replace(/_/g, '/'); + return Buffer.from(base64String, 'base64'); +} diff --git a/src/keri/core/bexter.ts b/src/keri/core/bexter.ts index 9be0983f..c484fbb5 100644 --- a/src/keri/core/bexter.ts +++ b/src/keri/core/bexter.ts @@ -1,7 +1,7 @@ import { BexDex, Matter, MatterArgs, MtrDex } from './matter'; import { EmptyMaterialError } from './kering'; -import Base64 from 'urlsafe-base64'; import { Buffer } from 'buffer'; +import { decodeBase64Url, encodeBase64Url } from './base64'; const B64REX = '^[A-Za-z0-9\\-_]*$'; export const Reb64 = new RegExp(B64REX); @@ -115,7 +115,7 @@ export class Bexter extends Matter { const wad = new Array(ws); wad.fill('A'); const base = wad.join('') + bext; // pre pad with wad of zeros in Base64 == 'A' - const raw = Base64.decode(base); // [ls:] // convert and remove leader + const raw = decodeBase64Url(base); // [ls:] // convert and remove leader return Uint8Array.from(raw).subarray(ls); // raw binary equivalent of text } @@ -123,7 +123,7 @@ export class Bexter extends Matter { get bext(): string { const sizage = Matter.Sizes.get(this.code); const wad = Uint8Array.from(new Array(sizage?.ls).fill(0)); - const bext = Base64.encode(Buffer.from([...wad, ...this.raw])); + const bext = encodeBase64Url(Buffer.from([...wad, ...this.raw])); let ws = 0; if (sizage?.ls === 0 && bext !== undefined) { diff --git a/src/keri/core/httping.ts b/src/keri/core/httping.ts index 70b59dd5..d83694ff 100644 --- a/src/keri/core/httping.ts +++ b/src/keri/core/httping.ts @@ -10,8 +10,8 @@ import { b } from './core'; import { Cigar } from './cigar'; import { nowUTC } from './utils'; import { Siger } from './siger'; -import Base64 from 'urlsafe-base64'; import { Buffer } from 'buffer'; +import { encodeBase64Url } from './base64'; export function normalize(header: string) { return header.trim(); @@ -121,7 +121,7 @@ export class Unqualified { } get qb64(): string { - return Base64.encode(Buffer.from(this._raw)); + return encodeBase64Url(Buffer.from(this._raw)); } get qb64b(): Uint8Array { diff --git a/src/keri/core/indexer.ts b/src/keri/core/indexer.ts index 8649ca55..09b7f0ae 100644 --- a/src/keri/core/indexer.ts +++ b/src/keri/core/indexer.ts @@ -1,7 +1,7 @@ import { EmptyMaterialError } from './kering'; import { b, b64ToInt, d, intToB64, readInt } from './core'; -import Base64 from 'urlsafe-base64'; import { Buffer } from 'buffer'; +import { decodeBase64Url, encodeBase64Url } from './base64'; export class IndexerCodex { Ed25519_Sig: string = 'A'; // Ed25519 sig appears same in both lists if any. @@ -399,7 +399,7 @@ export class Indexer { } const full = - both + Base64.encode(Buffer.from(bytes)).slice(ps - xizage.ls); + both + encodeBase64Url(Buffer.from(bytes)).slice(ps - xizage.ls); if (full.length != xizage.fs) { throw new Error(`Invalid code=${both} for raw size=${raw.length}.`); } @@ -474,7 +474,7 @@ export class Indexer { let raw; if (ps != 0) { const base = new Array(ps + 1).join('A') + qb64.slice(cs); - const paw = Base64.decode(base); // decode base to leave prepadded raw + const paw = decodeBase64Url(base); // decode base to leave prepadded raw const pi = readInt(paw.slice(0, ps)); // prepad as int if (pi & (2 ** pbs - 1)) { // masked pad bits non-zero @@ -485,7 +485,7 @@ export class Indexer { raw = paw.slice(ps); // strip off ps prepad paw bytes } else { const base = qb64.slice(cs); - const paw = Base64.decode(base); + const paw = decodeBase64Url(base); const li = readInt(paw.slice(0, xizage!.ls)); if (li != 0) { if (li == 1) { diff --git a/src/keri/core/matter.ts b/src/keri/core/matter.ts index 8abaedf7..cbbc0f85 100644 --- a/src/keri/core/matter.ts +++ b/src/keri/core/matter.ts @@ -1,9 +1,9 @@ import { EmptyMaterialError } from './kering'; import { intToB64, readInt } from './core'; -import Base64 from 'urlsafe-base64'; import { b, d } from './core'; import { Buffer } from 'buffer'; +import { decodeBase64Url, encodeBase64Url } from './base64'; export class Codex { has(prop: string): boolean { @@ -421,7 +421,7 @@ export class Matter { bytes[odx] = raw[i]; } - return both + Base64.encode(Buffer.from(bytes)); + return both + encodeBase64Url(Buffer.from(bytes)); } else { const both = code; const cs = both.length; @@ -443,7 +443,7 @@ export class Matter { bytes[odx] = raw[i]; } - return both + Base64.encode(Buffer.from(bytes)).slice(cs % 4); + return both + encodeBase64Url(Buffer.from(bytes)).slice(cs % 4); } } @@ -487,7 +487,7 @@ export class Matter { let raw; if (ps != 0) { const base = new Array(ps + 1).join('A') + qb64.slice(cs); - const paw = Base64.decode(base); // decode base to leave prepadded raw + const paw = decodeBase64Url(base); // decode base to leave prepadded raw const pi = readInt(paw.subarray(0, ps)); // prepad as int if (pi & (2 ** pbs - 1)) { // masked pad bits non-zero @@ -498,7 +498,7 @@ export class Matter { raw = paw.subarray(ps); // strip off ps prepad paw bytes } else { const base = qb64.slice(cs); - const paw = Base64.decode(base); + const paw = decodeBase64Url(base); const li = readInt(paw.subarray(0, sizage!.ls)); if (li != 0) { if (li == 1) { diff --git a/test/core/base64.test.ts b/test/core/base64.test.ts new file mode 100644 index 00000000..cc4f9488 --- /dev/null +++ b/test/core/base64.test.ts @@ -0,0 +1,35 @@ +import assert from 'node:assert'; +import { decodeBase64Url, encodeBase64Url } from '../../src/keri/core/base64'; + +test('encode', () => { + assert.equal(encodeBase64Url(Buffer.from('f')), 'Zg'); + assert.equal(encodeBase64Url(Buffer.from('fi')), 'Zmk'); + assert.equal(encodeBase64Url(Buffer.from('fis')), 'Zmlz'); + assert.equal(encodeBase64Url(Buffer.from('fish')), 'ZmlzaA'); + assert.equal(encodeBase64Url(Buffer.from([248])), '-A'); + assert.equal(encodeBase64Url(Buffer.from([252])), '_A'); +}); + +test('decode', () => { + assert.equal(decodeBase64Url('Zg').toString(), 'f'); + assert.equal(decodeBase64Url('Zmk').toString(), 'fi'); + assert.equal(decodeBase64Url('Zmlz').toString(), 'fis'); + assert.equal(decodeBase64Url('ZmlzaA').toString(), 'fish'); + assert.equal(Buffer.from([248]).buffer, decodeBase64Url('-A').buffer); + assert.equal(Buffer.from([252]).buffer, decodeBase64Url('_A').buffer); +}); + +test('Test encode / decode compare with built in node Buffer', () => { + const text = '🏳️🏳️'; + const b64url = '8J-Ps--4j_Cfj7PvuI8'; + + assert.equal( + Buffer.from(text).toString('base64url'), + encodeBase64Url(Buffer.from(text)) + ); + + assert.equal( + Buffer.from(b64url, 'base64url').buffer, + decodeBase64Url(b64url).buffer + ); +}); diff --git a/test/core/indexer.test.ts b/test/core/indexer.test.ts index e0e53864..9186e82e 100644 --- a/test/core/indexer.test.ts +++ b/test/core/indexer.test.ts @@ -2,8 +2,8 @@ import libsodium from 'libsodium-wrappers-sumo'; import { strict as assert } from 'assert'; import { IdrDex, Indexer } from '../../src/keri/core/indexer'; import { b, intToB64 } from '../../src/keri/core/core'; -import Base64 from 'urlsafe-base64'; import { Buffer } from 'buffer'; +import { decodeBase64Url, encodeBase64Url } from '../../src/keri/core/base64'; describe('Indexer', () => { it('should encode and decode dual indexed signatures', async () => { @@ -68,7 +68,7 @@ describe('Indexer', () => { const odx = i + ps; bytes[odx] = sig[i]; } - const sig64 = Base64.encode(Buffer.from(bytes)); + const sig64 = encodeBase64Url(Buffer.from(bytes)); assert.equal(sig64.length, 88); assert.equal( sig64, @@ -85,7 +85,7 @@ describe('Indexer', () => { assert.equal(qsig64.length, 88); let qsig64b = b(qsig64); - let qsig2b = Base64.decode(qsig64); + let qsig2b = decodeBase64Url(qsig64); assert.equal(qsig2b.length, 66); // assert qsig2b == (b"\x00\x00\x99\xd2<9$$0\x9fk\xfb\x18\xa0\x8c@r\x122.k\xb2\xc7\x1fp\x0e'm" // b'\x8f@\xaa\xa5\x8c\xc8n\x85\xc8!\xf6q\x91p\xa9\xec\xcf\x92\xaf)' @@ -166,7 +166,7 @@ describe('Indexer', () => { qsig64 = 'AFCZ0jw5JCQwn2v7GKCMQHISMi5rsscfcA4nbY9AqqWMyG6FyCH2cZFwqezPkq8p3sr8f37Xb3wXgh3UPG8igSYJ'; qsig64b = b(qsig64); - qsig2b = Base64.decode(qsig64); + qsig2b = decodeBase64Url(qsig64); assert.equal(qsig2b.length, 66); indexer = new Indexer({ raw: sig, code: IdrDex.Ed25519_Sig, index: 5 });