Replace Base 64 buggy conversion functions with `uint8arrays`

This commit is contained in:
Franck Royer 2022-03-25 11:02:40 +11:00
parent a67df28ae8
commit d0dea3884b
No known key found for this signature in database
GPG Key ID: A82ED75A8DFC50A4
5 changed files with 20 additions and 56 deletions

View File

@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Examples: Add Relay JavaScript example. - Examples: Add Relay JavaScript example.
### Fixed
- Replace Base 64 buggy conversion functions with `uint8arrays`.
## [0.19.2] - 2022-03-21 ## [0.19.2] - 2022-03-21
### Fixed ### Fixed

View File

@ -2,10 +2,11 @@ import assert from "assert";
import * as base32 from "hi-base32"; import * as base32 from "hi-base32";
import { ecdsaVerify } from "secp256k1"; import { ecdsaVerify } from "secp256k1";
import { fromString } from "uint8arrays/from-string";
import { ENR } from "../enr"; import { ENR } from "../enr";
import { utf8ToBytes } from "../utf8"; import { utf8ToBytes } from "../utf8";
import { base64ToBytes, keccak256Buf } from "../utils"; import { keccak256Buf } from "../utils";
export type ENRRootValues = { export type ENRRootValues = {
eRoot: string; eRoot: string;
@ -43,7 +44,10 @@ export class ENRTree {
// (Trailing recovery bit must be trimmed to pass `ecdsaVerify` method) // (Trailing recovery bit must be trimmed to pass `ecdsaVerify` method)
const signedComponent = root.split(" sig")[0]; const signedComponent = root.split(" sig")[0];
const signedComponentBuffer = utf8ToBytes(signedComponent); const signedComponentBuffer = utf8ToBytes(signedComponent);
const signatureBuffer = base64ToBytes(rootValues.signature).slice(0, 64); const signatureBuffer = fromString(rootValues.signature, "base64url").slice(
0,
64
);
const isVerified = ecdsaVerify( const isVerified = ecdsaVerify(
signatureBuffer, signatureBuffer,

View File

@ -29,7 +29,7 @@ describe("ENR", function () {
"/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234/wss" "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234/wss"
), ),
]; ];
const txt = await enr.encodeTxt(keypair.privateKey); const txt = enr.encodeTxt(keypair.privateKey);
const enr2 = ENR.decodeTxt(txt); const enr2 = ENR.decodeTxt(txt);
if (!enr.signature) throw "enr.signature is undefined"; 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.setLocationMultiaddr(new Multiaddr("/ip4/18.223.219.100/udp/9000"));
enr.set("id", new Uint8Array([0])); enr.set("id", new Uint8Array([0]));
const txt = await enr.encodeTxt(keypair.privateKey); const txt = enr.encodeTxt(keypair.privateKey);
ENR.decodeTxt(txt); ENR.decodeTxt(txt);
assert.fail("Expect error here"); assert.fail("Expect error here");
@ -203,7 +203,7 @@ describe("ENR", function () {
expect(decoded).to.deep.equal(record); 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 // spec enr https://eips.ethereum.org/EIPS/eip-778
const testTxt = const testTxt =
"enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8"; "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8";
@ -211,7 +211,7 @@ describe("ENR", function () {
expect(decoded.udp).to.be.equal(30303); expect(decoded.udp).to.be.equal(30303);
expect(decoded.ip).to.be.equal("127.0.0.1"); expect(decoded.ip).to.be.equal("127.0.0.1");
expect(decoded).to.deep.equal(record); expect(decoded).to.deep.equal(record);
const recordTxt = await record.encodeTxt(privateKey); const recordTxt = record.encodeTxt(privateKey);
expect(recordTxt).to.equal(testTxt); expect(recordTxt).to.equal(testTxt);
}); });
}); });

View File

@ -5,10 +5,12 @@ import { Multiaddr, protocols } from "multiaddr";
// @ts-ignore: No types available // @ts-ignore: No types available
import muConvert from "multiaddr/src/convert"; import muConvert from "multiaddr/src/convert";
import PeerId from "peer-id"; import PeerId from "peer-id";
import { fromString } from "uint8arrays/from-string";
import { toString } from "uint8arrays/to-string";
import { encode as varintEncode } from "varint"; import { encode as varintEncode } from "varint";
import { bytesToUtf8, utf8ToBytes } from "../utf8"; 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 { ERR_INVALID_ID, ERR_NO_SIGNATURE, MAX_RECORD_SIZE } from "./constants";
import { import {
@ -107,7 +109,7 @@ export class ENR extends Map<ENRKey, ENRValue> {
`"string encoded ENR must start with '${this.RECORD_PREFIX}'` `"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 { set(k: ENRKey, v: ENRValue): this {
@ -465,7 +467,7 @@ export class ENR extends Map<ENRKey, ENRValue> {
return encoded; return encoded;
} }
async encodeTxt(privateKey?: Uint8Array): Promise<string> { encodeTxt(privateKey?: Uint8Array): string {
return ENR.RECORD_PREFIX + (await bytesToBase64(this.encode(privateKey))); return ENR.RECORD_PREFIX + toString(this.encode(privateKey), "base64url");
} }
} }

View File

@ -81,49 +81,3 @@ export function equalByteArrays(
export function keccak256Buf(message: Message): Uint8Array { export function keccak256Buf(message: Message): Uint8Array {
return new Uint8Array(keccak256.arrayBuffer(message)); return new Uint8Array(keccak256.arrayBuffer(message));
} }
/**
* Convert base64 string to byte array.
*/
export function base64ToBytes(base64: string): Uint8Array {
const e = new Map<string, number>();
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<string> {
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, "");
}