mirror of https://github.com/waku-org/js-waku.git
Remove secp256k1 usage from enr.ts
This commit is contained in:
parent
8e6f9e320e
commit
ae6cb98d5d
|
@ -190,11 +190,11 @@ describe("ENR", function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Static tests", () => {
|
describe("Static tests", function () {
|
||||||
let privateKey: Uint8Array;
|
let privateKey: Uint8Array;
|
||||||
let record: ENR;
|
let record: ENR;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async function () {
|
||||||
const seq = BigInt(1);
|
const seq = BigInt(1);
|
||||||
privateKey = hexToBytes(
|
privateKey = hexToBytes(
|
||||||
"b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
|
"b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
|
||||||
|
@ -202,8 +202,7 @@ describe("ENR", function () {
|
||||||
record = ENR.createV4(v4.publicKey(privateKey));
|
record = ENR.createV4(v4.publicKey(privateKey));
|
||||||
record.setLocationMultiaddr(new Multiaddr("/ip4/127.0.0.1/udp/30303"));
|
record.setLocationMultiaddr(new Multiaddr("/ip4/127.0.0.1/udp/30303"));
|
||||||
record.seq = seq;
|
record.seq = seq;
|
||||||
// To set signature
|
await record.encodeTxt(privateKey);
|
||||||
record.encode(privateKey);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should properly compute the node id", () => {
|
it("should properly compute the node id", () => {
|
||||||
|
@ -212,21 +211,24 @@ describe("ENR", function () {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should encode/decode to RLP encoding", () => {
|
it("should encode/decode to RLP encoding", async function () {
|
||||||
const decoded = ENR.decode(record.encode(privateKey));
|
const decoded = ENR.decode(await record.encode(privateKey));
|
||||||
expect(decoded).to.deep.equal(record);
|
expect(decoded).to.deep.equal(record);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should encode/decode to text encoding", () => {
|
it("should encode/decode to text encoding", async function () {
|
||||||
// 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";
|
||||||
const decoded = ENR.decodeTxt(testTxt);
|
const decoded = ENR.decodeTxt(testTxt);
|
||||||
expect(decoded.udp).to.be.equal(30303);
|
// Note: Signatures are different due to the extra entropy added
|
||||||
expect(decoded.ip).to.be.equal("127.0.0.1");
|
// by @noble/secp256k1:
|
||||||
expect(decoded).to.deep.equal(record);
|
// https://github.com/paulmillr/noble-secp256k1#signmsghash-privatekey
|
||||||
const recordTxt = record.encodeTxt(privateKey);
|
expect(decoded.udp).to.deep.equal(record.udp);
|
||||||
expect(recordTxt).to.equal(testTxt);
|
expect(decoded.ip).to.deep.equal(record.ip);
|
||||||
|
expect(decoded.id).to.deep.equal(record.id);
|
||||||
|
expect(decoded.seq).to.equal(record.seq);
|
||||||
|
expect(decoded.get("secp256k1")).to.deep.equal(record.get("secp256k1"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
import { decodeMultiaddrs, encodeMultiaddrs } from "./multiaddrs_codec";
|
import { decodeMultiaddrs, encodeMultiaddrs } from "./multiaddrs_codec";
|
||||||
import { ENRKey, ENRValue, NodeId, SequenceNumber } from "./types";
|
import { ENRKey, ENRValue, NodeId, SequenceNumber } from "./types";
|
||||||
import * as v4 from "./v4";
|
import * as v4 from "./v4";
|
||||||
|
import { compressPublicKey } from "./v4";
|
||||||
import { decodeWaku2, encodeWaku2, Waku2 } from "./waku2_codec";
|
import { decodeWaku2, encodeWaku2, Waku2 } from "./waku2_codec";
|
||||||
|
|
||||||
const dbg = debug("waku:enr");
|
const dbg = debug("waku:enr");
|
||||||
|
@ -45,6 +46,10 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
publicKey: Uint8Array,
|
publicKey: Uint8Array,
|
||||||
kvs: Record<ENRKey, ENRValue> = {}
|
kvs: Record<ENRKey, ENRValue> = {}
|
||||||
): ENR {
|
): ENR {
|
||||||
|
// EIP-778 specifies that the key must be in compressed format, 33 bytes
|
||||||
|
if (publicKey.length !== 33) {
|
||||||
|
publicKey = compressPublicKey(publicKey);
|
||||||
|
}
|
||||||
return new ENR({
|
return new ENR({
|
||||||
...kvs,
|
...kvs,
|
||||||
id: utf8ToBytes("v4"),
|
id: utf8ToBytes("v4"),
|
||||||
|
@ -453,10 +458,10 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
return v4.verify(this.publicKey, data, signature);
|
return v4.verify(this.publicKey, data, signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
sign(data: Uint8Array, privateKey: Uint8Array): Uint8Array {
|
async sign(data: Uint8Array, privateKey: Uint8Array): Promise<Uint8Array> {
|
||||||
switch (this.id) {
|
switch (this.id) {
|
||||||
case "v4":
|
case "v4":
|
||||||
this.signature = v4.sign(privateKey, data);
|
this.signature = await v4.sign(privateKey, data);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(ERR_INVALID_ID);
|
throw new Error(ERR_INVALID_ID);
|
||||||
|
@ -464,7 +469,9 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
return this.signature;
|
return this.signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
encodeToValues(privateKey?: Uint8Array): (ENRKey | ENRValue | number[])[] {
|
async encodeToValues(
|
||||||
|
privateKey?: Uint8Array
|
||||||
|
): Promise<(ENRKey | ENRValue | number[])[]> {
|
||||||
// sort keys and flatten into [k, v, k, v, ...]
|
// sort keys and flatten into [k, v, k, v, ...]
|
||||||
const content: Array<ENRKey | ENRValue | number[]> = Array.from(this.keys())
|
const content: Array<ENRKey | ENRValue | number[]> = Array.from(this.keys())
|
||||||
.sort((a, b) => a.localeCompare(b))
|
.sort((a, b) => a.localeCompare(b))
|
||||||
|
@ -473,7 +480,9 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
.flat();
|
.flat();
|
||||||
content.unshift(new Uint8Array([Number(this.seq)]));
|
content.unshift(new Uint8Array([Number(this.seq)]));
|
||||||
if (privateKey) {
|
if (privateKey) {
|
||||||
content.unshift(this.sign(hexToBytes(RLP.encode(content)), privateKey));
|
content.unshift(
|
||||||
|
await this.sign(hexToBytes(RLP.encode(content)), privateKey)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
if (!this.signature) {
|
if (!this.signature) {
|
||||||
throw new Error(ERR_NO_SIGNATURE);
|
throw new Error(ERR_NO_SIGNATURE);
|
||||||
|
@ -483,8 +492,10 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
encode(privateKey?: Uint8Array): Uint8Array {
|
async encode(privateKey?: Uint8Array): Promise<Uint8Array> {
|
||||||
const encoded = hexToBytes(RLP.encode(this.encodeToValues(privateKey)));
|
const encoded = hexToBytes(
|
||||||
|
RLP.encode(await this.encodeToValues(privateKey))
|
||||||
|
);
|
||||||
if (encoded.length >= MAX_RECORD_SIZE) {
|
if (encoded.length >= MAX_RECORD_SIZE) {
|
||||||
throw new Error("ENR must be less than 300 bytes");
|
throw new Error("ENR must be less than 300 bytes");
|
||||||
}
|
}
|
||||||
|
@ -492,6 +503,8 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
}
|
}
|
||||||
|
|
||||||
encodeTxt(privateKey?: Uint8Array): string {
|
encodeTxt(privateKey?: Uint8Array): string {
|
||||||
return ENR.RECORD_PREFIX + toString(this.encode(privateKey), "base64url");
|
return (
|
||||||
|
ENR.RECORD_PREFIX + toString(await this.encode(privateKey), "base64url")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
import * as secp from "@noble/secp256k1";
|
||||||
import { keccak256 } from "js-sha3";
|
import { keccak256 } from "js-sha3";
|
||||||
import * as secp256k1 from "secp256k1";
|
|
||||||
|
|
||||||
import { randomBytes } from "../crypto";
|
import { randomBytes } from "../crypto";
|
||||||
|
import { bytesToHex } from "../utils";
|
||||||
|
|
||||||
import { createNodeId } from "./create";
|
import { createNodeId } from "./create";
|
||||||
import { NodeId } from "./types";
|
import { NodeId } from "./types";
|
||||||
|
@ -15,12 +16,21 @@ export function createPrivateKey(): Uint8Array {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function publicKey(privKey: Uint8Array): Uint8Array {
|
export function publicKey(privKey: Uint8Array): Uint8Array {
|
||||||
return secp256k1.publicKeyCreate(privKey);
|
return secp.getPublicKey(privKey, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sign(privKey: Uint8Array, msg: Uint8Array): Uint8Array {
|
export function compressPublicKey(publicKey: Uint8Array): Uint8Array {
|
||||||
const { signature } = secp256k1.ecdsaSign(hash(msg), privKey);
|
const point = secp.Point.fromHex(bytesToHex(publicKey));
|
||||||
return signature;
|
return point.toRawBytes(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sign(
|
||||||
|
privKey: Uint8Array,
|
||||||
|
msg: Uint8Array
|
||||||
|
): Promise<Uint8Array> {
|
||||||
|
return secp.sign(hash(msg), privKey, {
|
||||||
|
der: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verify(
|
export function verify(
|
||||||
|
@ -28,12 +38,17 @@ export function verify(
|
||||||
msg: Uint8Array,
|
msg: Uint8Array,
|
||||||
sig: Uint8Array
|
sig: Uint8Array
|
||||||
): boolean {
|
): boolean {
|
||||||
// Remove the recovery id if present (byte #65)
|
try {
|
||||||
return secp256k1.ecdsaVerify(sig.slice(0, 64), hash(msg), pubKey);
|
const _sig = secp.Signature.fromCompact(sig.slice(0, 64));
|
||||||
|
return secp.verify(_sig, hash(msg), pubKey);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function nodeId(pubKey: Uint8Array): NodeId {
|
export function nodeId(pubKey: Uint8Array): NodeId {
|
||||||
const uncompressedPubkey = secp256k1.publicKeyConvert(pubKey, false);
|
const publicKey = secp.Point.fromHex(pubKey);
|
||||||
|
const uncompressedPubkey = publicKey.toRawBytes(false);
|
||||||
|
|
||||||
return createNodeId(hash(uncompressedPubkey.slice(1)));
|
return createNodeId(hash(uncompressedPubkey.slice(1)));
|
||||||
}
|
}
|
||||||
|
@ -47,7 +62,7 @@ export class ENRKeyPair {
|
||||||
|
|
||||||
public static create(privateKey?: Uint8Array): ENRKeyPair {
|
public static create(privateKey?: Uint8Array): ENRKeyPair {
|
||||||
if (privateKey) {
|
if (privateKey) {
|
||||||
if (!secp256k1.privateKeyVerify(privateKey)) {
|
if (!secp.utils.isValidPrivateKey(privateKey)) {
|
||||||
throw new Error("Invalid private key");
|
throw new Error("Invalid private key");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +73,7 @@ export class ENRKeyPair {
|
||||||
return new ENRKeyPair(_nodeId, _privateKey, _publicKey);
|
return new ENRKeyPair(_nodeId, _privateKey, _publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sign(msg: Uint8Array): Uint8Array {
|
public async sign(msg: Uint8Array): Promise<Uint8Array> {
|
||||||
return sign(this.privateKey, msg);
|
return sign(this.privateKey, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue