Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,8 @@ services:
report-receiver:
build: scripts/
command: ["serve-report-receiver"]
volumes:
- "config:/config"
ports:
- "127.0.0.1:9081:8080"

Expand Down
76 changes: 72 additions & 4 deletions scripts/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,7 @@ function computeAddressHash(address: string, salt: string): string {
const normalizedAddress = address.toLowerCase().replace('0x', '');
const saltBytes = Buffer.from(salt.replace(/-/g, ''), 'hex');
const addrBytes = Buffer.from(normalizedAddress, 'hex');
return crypto.createHash('sha256').update(Buffer.concat([saltBytes, addrBytes])).digest('hex');
return crypto.createHash('sha256').update(Buffer.concat([saltBytes, addrBytes] as Uint8Array[]) as Uint8Array).digest('hex');
}

async function uploadFilteredAddressesToMinio() {
Expand Down Expand Up @@ -1112,6 +1112,9 @@ function writeFilteringReportConfig() {
"external-endpoint": {
"url": "http://report-receiver:8080",
"timeout": "10s"
},
"signer": {
"pem-file": consts.filteringReportSignerPemPath
}
}
};
Expand All @@ -1138,17 +1141,82 @@ function applyFilteringReportConfig(config: any) {
};
}

// Self-signed Ed25519 cert (testnode skips the production CA chain): combined PEM for the forwarder, cert-only for the receiver to pin.
async function initFilteringReportSigner() {
require("reflect-metadata"); // @peculiar/x509 uses decorator metadata internally; must load first
const x509 = require("@peculiar/x509");
const webcrypto = require("crypto").webcrypto;
x509.cryptoProvider.set(webcrypto);

const keys = await webcrypto.subtle.generateKey({ name: "Ed25519" }, true, ["sign", "verify"]);
const cert = await x509.X509CertificateGenerator.createSelfSigned({
serialNumber: "01",
name: "CN=filtering-report",
notBefore: new Date(Date.now() - 24 * 60 * 60 * 1000), // backdated to absorb clock skew
notAfter: new Date(Date.now() + 100 * 365 * 24 * 60 * 60 * 1000), // far future so it never expires
keys,
});

const pkcs8 = await webcrypto.subtle.exportKey("pkcs8", keys.privateKey);
const keyPem = x509.PemConverter.encode(pkcs8, "PRIVATE KEY");
fs.writeFileSync(consts.filteringReportSignerPemPath, keyPem + "\n" + cert.toString("pem") + "\n");
fs.writeFileSync(consts.filteringReportSignerPubPath, cert.toString("pem") + "\n");
console.log("Generated filtering-report signing cert in", consts.configpath);
}

export const initFilteringReportSignerCommand = {
command: "init-filtering-report-signer",
describe: "generates the Ed25519 signing cert for filtering-report report forwarding",
handler: async () => {
await initFilteringReportSigner();
}
}

const REPORT_SIGNATURE_SKEW_MS = 5 * 60 * 1000;

function verifyReportSignature(req: any, rawBody: Buffer, signerKey: crypto.KeyObject) {
const sigHeader = req.headers['x-signature'];
const tsHeader = req.headers['x-signature-timestamp'];
if (typeof sigHeader !== 'string' || typeof tsHeader !== 'string') {
throw new Error('missing signature headers');
}

const tsSeconds = Number(tsHeader);
if (!Number.isFinite(tsSeconds) || Math.abs(Date.now() - tsSeconds * 1000) > REPORT_SIGNATURE_SKEW_MS) {
throw new Error('timestamp outside tolerance');
}

const payload = Buffer.concat([Buffer.from(`${tsHeader}.`), rawBody] as Uint8Array[]);
if (!crypto.verify(null, payload as Uint8Array, signerKey, Buffer.from(sigHeader, 'base64') as Uint8Array)) {
throw new Error('signature verification failed');
}
}

export const serveReportReceiverCommand = {
command: "serve-report-receiver",
describe: "starts an HTTP server that receives and logs filtering reports",
describe: "starts an HTTP server that verifies signatures on and logs filtering reports",
handler: async () => {
const http = require('http');
if (!fs.existsSync(consts.filteringReportSignerPubPath)) {
throw new Error(`signing cert not found at ${consts.filteringReportSignerPubPath}; run init-filtering-report-signer first`);
}
const signerKey = new crypto.X509Certificate(fs.readFileSync(consts.filteringReportSignerPubPath) as Uint8Array).publicKey;
const reports: any[] = [];
const server = http.createServer((req: any, res: any) => {
if (req.method === 'POST') {
let body = '';
req.on('data', (chunk: string) => body += chunk);
const chunks: Buffer[] = [];
req.on('data', (chunk: Buffer) => chunks.push(chunk));
req.on('end', () => {
const rawBody = Buffer.concat(chunks as Uint8Array[]);
try {
verifyReportSignature(req, rawBody, signerKey);
} catch (err: any) {
console.error('Rejected report with invalid signature:', err.message);
res.writeHead(401, {'Content-Type': 'application/json'});
res.end(JSON.stringify({status: 'error', message: 'signature verification failed'}));
return;
}
const body = rawBody.toString();
console.log('Received report:', body);
try {
reports.push(JSON.parse(body));
Expand Down
5 changes: 4 additions & 1 deletion scripts/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ export const tokenbridgedatapath = "/tokenbridge-data";
export const l1mnemonic =
"indoor dish desk flag debris potato excuse depart ticket judge file exit";

export const ARB_OWNER = "0x0000000000000000000000000000000000000070";
export const ARB_OWNER = "0x0000000000000000000000000000000000000070";

export const filteringReportSignerPemPath = configpath + "/filtering_report_signer.pem";
export const filteringReportSignerPubPath = configpath + "/filtering_report_signer_pub.pem";
2 changes: 2 additions & 0 deletions scripts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
removeFilteredAddressCommand,
writeElasticMQConfigCommand,
writeFilteringReportConfigCommand,
initFilteringReportSignerCommand,
serveReportReceiverCommand,
} from "./config";
import {
Expand Down Expand Up @@ -93,6 +94,7 @@ async function main() {
.command(removeFilteredAddressCommand)
.command(writeElasticMQConfigCommand)
.command(writeFilteringReportConfigCommand)
.command(initFilteringReportSignerCommand)
.command(serveReportReceiverCommand)
.command(grantFiltererRoleCommand)
.command(printAddressCommand)
Expand Down
2 changes: 2 additions & 0 deletions scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
"@aws-sdk/client-s3": "3.1050.0",
"@node-redis/client": "^1.0.4",
"@openzeppelin/contracts": "^4.9.3",
"@peculiar/x509": "2.0.0",
"@types/node": "^17.0.22",
"@types/yargs": "^17.0.10",
"ethers": "^5.6.1",
"path": "^0.12.7",
"reflect-metadata": "0.2.2",
"typescript": "^4.6.2",
"yargs": "^17.4.0"
},
Expand Down
169 changes: 168 additions & 1 deletion scripts/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,135 @@
proper-lockfile "^4.1.1"
solidity-ast "^0.4.51"

"@peculiar/asn1-cms@^2.6.0", "@peculiar/asn1-cms@^2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@peculiar/asn1-cms/-/asn1-cms-2.7.0.tgz#8e0eb656f4fc85f7c621dd442fa2d298faa84984"
integrity sha512-hew63shtzzvBcSHbhm+cyAmKe6AIfinT9hzEqSPjDC6opTTMKmTkQ0gHuN2KsWlvqiKw1S/fS94fhag/FJkioQ==
dependencies:
"@peculiar/asn1-schema" "^2.7.0"
"@peculiar/asn1-x509" "^2.7.0"
"@peculiar/asn1-x509-attr" "^2.7.0"
asn1js "^3.0.6"
tslib "^2.8.1"

"@peculiar/asn1-csr@^2.6.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@peculiar/asn1-csr/-/asn1-csr-2.7.0.tgz#1a03ac03f7571ea981f5d8377c6f4510c5d43411"
integrity sha512-VVsAyGqErT9D1SY4aEqozThXMVI+ssVRiv2DDeYuvpBKLIgZ3hYs3Ay3u/VSoKq6ESFi9cf6rf3IOOzfwh7oMA==
dependencies:
"@peculiar/asn1-schema" "^2.7.0"
"@peculiar/asn1-x509" "^2.7.0"
asn1js "^3.0.6"
tslib "^2.8.1"

"@peculiar/asn1-ecc@^2.6.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@peculiar/asn1-ecc/-/asn1-ecc-2.7.0.tgz#c35b57859812ecd0c2ae7b2144855e8208c2cfee"
integrity sha512-n7KEs/Q/wrB415cxy4fHOBhegp4NdJ15fkJPwcB/3/8iNBQC2L/N7SChJPKDJPZGYH0jD4Tg4/0vnHmwghnbKw==
dependencies:
"@peculiar/asn1-schema" "^2.7.0"
"@peculiar/asn1-x509" "^2.7.0"
asn1js "^3.0.6"
tslib "^2.8.1"

"@peculiar/asn1-pfx@^2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@peculiar/asn1-pfx/-/asn1-pfx-2.7.0.tgz#d00766b13ff49785684a604248e6aadd184b291f"
integrity sha512-V/nrlQVmhg7lYAsM7E13UDL5erAwFv6kCIVFqNaMIHSVi7dngcT839JkRTkQBqznMG98l2XjxYk74ZztAohZzA==
dependencies:
"@peculiar/asn1-cms" "^2.7.0"
"@peculiar/asn1-pkcs8" "^2.7.0"
"@peculiar/asn1-rsa" "^2.7.0"
"@peculiar/asn1-schema" "^2.7.0"
asn1js "^3.0.6"
tslib "^2.8.1"

"@peculiar/asn1-pkcs8@^2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.7.0.tgz#5ee602d8a9a3e0a3f09f7b008ff644a657378692"
integrity sha512-9GTl1nE8Mx1kTZ+7QyYatDyKsm34QcWRBFkY1iPvWC3X4Dona5s/tlLiQsx5WzVdZqiMBZNYT0buyw4/vbhnjw==
dependencies:
"@peculiar/asn1-schema" "^2.7.0"
"@peculiar/asn1-x509" "^2.7.0"
asn1js "^3.0.6"
tslib "^2.8.1"

"@peculiar/asn1-pkcs9@^2.6.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.7.0.tgz#23b4eae41c2feb8df258aa69c502a70092026b0a"
integrity sha512-Bh7m+OuIaSEllPQcSd9OSp93F4ROWH7sbITWV8MI+8dwsjE5111/87VxiWVvYFKyww3vp39geLv9ENqhwWHcew==
dependencies:
"@peculiar/asn1-cms" "^2.7.0"
"@peculiar/asn1-pfx" "^2.7.0"
"@peculiar/asn1-pkcs8" "^2.7.0"
"@peculiar/asn1-schema" "^2.7.0"
"@peculiar/asn1-x509" "^2.7.0"
"@peculiar/asn1-x509-attr" "^2.7.0"
asn1js "^3.0.6"
tslib "^2.8.1"

"@peculiar/asn1-rsa@^2.6.0", "@peculiar/asn1-rsa@^2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@peculiar/asn1-rsa/-/asn1-rsa-2.7.0.tgz#6dc5c78c643264dd5a251a66f1dd9a38fcbba385"
integrity sha512-/qvENQrXyTZURjMqSeofHul0JJt2sNSzSwk36pl2olkHbaioMQgrASDZAlHXl0xUlnVbHj0uGgOrBMTb5x2aJQ==
dependencies:
"@peculiar/asn1-schema" "^2.7.0"
"@peculiar/asn1-x509" "^2.7.0"
asn1js "^3.0.6"
tslib "^2.8.1"

"@peculiar/asn1-schema@^2.6.0", "@peculiar/asn1-schema@^2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.7.0.tgz#f2dcb25995ce7cac8687ba1039f043e5eff43820"
integrity sha512-W8ZfWzLmQnrcky+eh3tni4IozMdqBDiHWU0N+vve/UGjMaUs8c0L7A2oEdkBXS8rTpWDpK/aoI3DG/L/hxmxPg==
dependencies:
"@peculiar/utils" "^2.0.2"
asn1js "^3.0.6"
tslib "^2.8.1"

"@peculiar/asn1-x509-attr@^2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.7.0.tgz#5ef2a10d3a78d4763b848a2cb56db4bb6b1e082a"
integrity sha512-NS8e7SOgXipkzUPLF/sce7ukpMpWjhxYsH0n6Y+bHYo4TTxOb95Zv7hqwSuL212mj5YxovjdOKQOgH1As3E94w==
dependencies:
"@peculiar/asn1-schema" "^2.7.0"
"@peculiar/asn1-x509" "^2.7.0"
asn1js "^3.0.6"
tslib "^2.8.1"

"@peculiar/asn1-x509@^2.6.0", "@peculiar/asn1-x509@^2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@peculiar/asn1-x509/-/asn1-x509-2.7.0.tgz#84793efb7819dbc9526fd6b0a4ccd86f90464b96"
integrity sha512-mUn9RRrkGDnG4ALfunDmzyRW5dg+sWCj/pfnCCqEHYbkGxEpvUt6iVJv8Yw1cyp6SWZ26ZE5oSmI5SqEaen15g==
dependencies:
"@peculiar/asn1-schema" "^2.7.0"
"@peculiar/utils" "^2.0.2"
asn1js "^3.0.6"
tslib "^2.8.1"

"@peculiar/utils@^2.0.2":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@peculiar/utils/-/utils-2.0.3.tgz#a27ca4c4b73652e110f19a7d16d664f458a5528e"
integrity sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==
dependencies:
tslib "^2.8.1"

"@peculiar/x509@2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@peculiar/x509/-/x509-2.0.0.tgz#237e782b114160c71c87ecbd15d907d08d818416"
integrity sha512-r10lkuy6BNfRmyYdRAfgu6dq0HOmyIV2OLhXWE3gDEPBdX1b8miztJVyX/UxWhLwemNyDP3CLZHpDxDwSY0xaA==
dependencies:
"@peculiar/asn1-cms" "^2.6.0"
"@peculiar/asn1-csr" "^2.6.0"
"@peculiar/asn1-ecc" "^2.6.0"
"@peculiar/asn1-pkcs9" "^2.6.0"
"@peculiar/asn1-rsa" "^2.6.0"
"@peculiar/asn1-schema" "^2.6.0"
"@peculiar/asn1-x509" "^2.6.0"
pvtsutils "^1.3.6"
tslib "^2.8.1"
tsyringe "^4.10.0"

"@smithy/core@^3.24.2", "@smithy/core@^3.24.6":
version "3.24.6"
resolved "https://registry.yarnpkg.com/@smithy/core/-/core-3.24.6.tgz#72891bad85d577b2e43f30a8fc67adf36d577798"
Expand Down Expand Up @@ -1343,6 +1472,15 @@ arraybuffer.prototype.slice@^1.0.2:
is-array-buffer "^3.0.2"
is-shared-array-buffer "^1.0.2"

asn1js@^3.0.6:
version "3.0.10"
resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.10.tgz#df26c874c8a8b41ca605efea47b2ad07551013dd"
integrity sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==
dependencies:
pvtsutils "^1.3.6"
pvutils "^1.1.5"
tslib "^2.8.1"

asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
Expand Down Expand Up @@ -2885,6 +3023,18 @@ puppeteer@^13.7.0:
unbzip2-stream "1.4.3"
ws "8.5.0"

pvtsutils@^1.3.6:
version "1.3.6"
resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.3.6.tgz#ec46e34db7422b9e4fdc5490578c1883657d6001"
integrity sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==
dependencies:
tslib "^2.8.1"

pvutils@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.1.5.tgz#84b0dea4a5d670249aa9800511804ee0b7c2809c"
integrity sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==

randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
Expand Down Expand Up @@ -2913,6 +3063,11 @@ redis-parser@3.0.0:
dependencies:
redis-errors "^1.0.0"

reflect-metadata@0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b"
integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==

regexp.prototype.flags@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e"
Expand Down Expand Up @@ -3200,11 +3355,23 @@ tr46@~0.0.3:
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==

tslib@^2.6.2:
tslib@^1.9.3:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==

tslib@^2.6.2, tslib@^2.8.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==

tsyringe@^4.10.0:
version "4.10.0"
resolved "https://registry.yarnpkg.com/tsyringe/-/tsyringe-4.10.0.tgz#d0c95815d584464214060285eaaadd94aa03299c"
integrity sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==
dependencies:
tslib "^1.9.3"

typed-array-buffer@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60"
Expand Down
3 changes: 3 additions & 0 deletions test-node.bash
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,9 @@ if $force_init; then
fi

if $l2filteringreport; then
echo == Generating filtering-report signing PKI
run_script init-filtering-report-signer

echo == Starting report receiver
docker compose up --wait report-receiver

Expand Down
Loading