From d0dea3884b1f1fa8d7c850e4024db9f1edc8b3aa Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Fri, 25 Mar 2022 11:02:40 +1100 Subject: [PATCH] Replace Base 64 buggy conversion functions with `uint8arrays` --- CHANGELOG.md | 4 ++++ src/lib/discovery/enrtree.ts | 8 +++++-- src/lib/enr/enr.spec.ts | 8 +++---- src/lib/enr/enr.ts | 10 ++++---- src/lib/utils.ts | 46 ------------------------------------ 5 files changed, 20 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6037082cb2..4aa1bcd93d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Examples: Add Relay JavaScript example. +### Fixed + +- Replace Base 64 buggy conversion functions with `uint8arrays`. + ## [0.19.2] - 2022-03-21 ### Fixed diff --git a/src/lib/discovery/enrtree.ts b/src/lib/discovery/enrtree.ts index f5b3d97797..0626ca339a 100644 --- a/src/lib/discovery/enrtree.ts +++ b/src/lib/discovery/enrtree.ts @@ -2,10 +2,11 @@ import assert from "assert"; import * as base32 from "hi-base32"; import { ecdsaVerify } from "secp256k1"; +import { fromString } from "uint8arrays/from-string"; import { ENR } from "../enr"; import { utf8ToBytes } from "../utf8"; -import { base64ToBytes, keccak256Buf } from "../utils"; +import { keccak256Buf } from "../utils"; export type ENRRootValues = { eRoot: string; @@ -43,7 +44,10 @@ export class ENRTree { // (Trailing recovery bit must be trimmed to pass `ecdsaVerify` method) const signedComponent = root.split(" sig")[0]; const signedComponentBuffer = utf8ToBytes(signedComponent); - const signatureBuffer = base64ToBytes(rootValues.signature).slice(0, 64); + const signatureBuffer = fromString(rootValues.signature, "base64url").slice( + 0, + 64 + ); const isVerified = ecdsaVerify( signatureBuffer, diff --git a/src/lib/enr/enr.spec.ts b/src/lib/enr/enr.spec.ts index c7d8d30d16..d18bb0be8f 100644 --- a/src/lib/enr/enr.spec.ts +++ b/src/lib/enr/enr.spec.ts @@ -29,7 +29,7 @@ describe("ENR", function () { "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234/wss" ), ]; - const txt = await enr.encodeTxt(keypair.privateKey); + const txt = enr.encodeTxt(keypair.privateKey); const enr2 = ENR.decodeTxt(txt); if (!enr.signature) throw "enr.signature is undefined"; @@ -102,7 +102,7 @@ describe("ENR", function () { enr.setLocationMultiaddr(new Multiaddr("/ip4/18.223.219.100/udp/9000")); enr.set("id", new Uint8Array([0])); - const txt = await enr.encodeTxt(keypair.privateKey); + const txt = enr.encodeTxt(keypair.privateKey); ENR.decodeTxt(txt); assert.fail("Expect error here"); @@ -203,7 +203,7 @@ describe("ENR", function () { expect(decoded).to.deep.equal(record); }); - it("should encode/decode to text encoding", async () => { + it("should encode/decode to text encoding", () => { // spec enr https://eips.ethereum.org/EIPS/eip-778 const testTxt = "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8"; @@ -211,7 +211,7 @@ describe("ENR", function () { expect(decoded.udp).to.be.equal(30303); expect(decoded.ip).to.be.equal("127.0.0.1"); expect(decoded).to.deep.equal(record); - const recordTxt = await record.encodeTxt(privateKey); + const recordTxt = record.encodeTxt(privateKey); expect(recordTxt).to.equal(testTxt); }); }); diff --git a/src/lib/enr/enr.ts b/src/lib/enr/enr.ts index c352f07a2e..561457cc98 100644 --- a/src/lib/enr/enr.ts +++ b/src/lib/enr/enr.ts @@ -5,10 +5,12 @@ import { Multiaddr, protocols } from "multiaddr"; // @ts-ignore: No types available import muConvert from "multiaddr/src/convert"; import PeerId from "peer-id"; +import { fromString } from "uint8arrays/from-string"; +import { toString } from "uint8arrays/to-string"; import { encode as varintEncode } from "varint"; import { bytesToUtf8, utf8ToBytes } from "../utf8"; -import { base64ToBytes, bytesToBase64, bytesToHex, hexToBytes } from "../utils"; +import { bytesToHex, hexToBytes } from "../utils"; import { ERR_INVALID_ID, ERR_NO_SIGNATURE, MAX_RECORD_SIZE } from "./constants"; import { @@ -107,7 +109,7 @@ export class ENR extends Map { `"string encoded ENR must start with '${this.RECORD_PREFIX}'` ); } - return ENR.decode(base64ToBytes(encoded.slice(4))); + return ENR.decode(fromString(encoded.slice(4), "base64url")); } set(k: ENRKey, v: ENRValue): this { @@ -465,7 +467,7 @@ export class ENR extends Map { return encoded; } - async encodeTxt(privateKey?: Uint8Array): Promise { - return ENR.RECORD_PREFIX + (await bytesToBase64(this.encode(privateKey))); + encodeTxt(privateKey?: Uint8Array): string { + return ENR.RECORD_PREFIX + toString(this.encode(privateKey), "base64url"); } } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 937ce2db3e..21934486e7 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -81,49 +81,3 @@ export function equalByteArrays( export function keccak256Buf(message: Message): Uint8Array { return new Uint8Array(keccak256.arrayBuffer(message)); } - -/** - * Convert base64 string to byte array. - */ -export function base64ToBytes(base64: string): Uint8Array { - const e = new Map(); - - const len = base64.length; - const res = []; - const A = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; - for (let i = 0; i < 64; i++) { - e.set(A.charAt(i), i); - } - e.set("+", 62); - e.set("/", 63); - - let b = 0, - l = 0, - a; - for (let i = 0; i < len; i++) { - const c = e.get(base64.charAt(i)); - if (c === undefined) - throw new Error(`Invalid base64 character ${base64.charAt(i)}`); - b = (b << 6) + c; - l += 6; - while (l >= 8) { - ((a = (b >>> (l -= 8)) & 0xff) || i < len - 2) && res.push(a); - } - } - return new Uint8Array(res); -} - -/** - * Convert byte array to base64 string. - */ -export async function bytesToBase64(bytes: Uint8Array): Promise { - const base64url: string = await new Promise((r) => { - const reader = new window.FileReader(); - reader.onload = (): void => r(reader.result as string); - reader.readAsDataURL(new Blob([bytes])); - }); - const base64 = base64url.split(",", 2)[1]; - // We want URL and Filename Safe base64: https://datatracker.ietf.org/doc/html/rfc4648#section-5 - // Without trailing padding - return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); -}