diff --git a/.gitattributes b/.gitattributes index 24e0b39b6a..43c43ce94a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ nym-validator-rewarder/.sqlx/** diff=nodiff +nym-node-status-api/nym-node-status-api/.sqlx/** diff=nodiff diff --git a/.github/actions/nym-hash-releases/src/package-lock.json b/.github/actions/nym-hash-releases/src/package-lock.json index 1c9a90fe9e..2560602d49 100644 --- a/.github/actions/nym-hash-releases/src/package-lock.json +++ b/.github/actions/nym-hash-releases/src/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "dependencies": { "@actions/core": "^1.10.1", - "@actions/github": "^5.1.1", + "@actions/github": "^6.0.0", "@octokit/auth-action": "^4.0.1", "@octokit/rest": "^20.0.2", "hasha": "^5.2.0", @@ -29,22 +29,34 @@ } }, "node_modules/@actions/github": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz", - "integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.0.tgz", + "integrity": "sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==", + "license": "MIT", "dependencies": { - "@actions/http-client": "^2.0.1", - "@octokit/core": "^3.6.0", - "@octokit/plugin-paginate-rest": "^2.17.0", - "@octokit/plugin-rest-endpoint-methods": "^5.13.0" + "@actions/http-client": "^2.2.0", + "@octokit/core": "^5.0.1", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/plugin-rest-endpoint-methods": "^10.0.0" } }, "node_modules/@actions/http-client": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.1.tgz", - "integrity": "sha512-qhrkRMB40bbbLo7gF+0vu+X+UawOvQQqNAA/5Unx774RS8poaOhThDOG6BGmxvAnxhQnDp2BG/ZUm65xZILTpw==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", + "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", + "license": "MIT", "dependencies": { - "tunnel": "^0.0.6" + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", + "engines": { + "node": ">=14" } }, "node_modules/@octokit/auth-action": { @@ -59,14 +71,6 @@ "node": ">= 18" } }, - "node_modules/@octokit/auth-action/node_modules/@octokit/auth-token": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", - "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", - "engines": { - "node": ">= 18" - } - }, "node_modules/@octokit/auth-action/node_modules/@octokit/openapi-types": { "version": "20.0.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", @@ -81,115 +85,152 @@ } }, "node_modules/@octokit/auth-token": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", - "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", - "dependencies": { - "@octokit/types": "^6.0.3" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "license": "MIT", + "engines": { + "node": ">= 18" } }, "node_modules/@octokit/core": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", - "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz", + "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", + "license": "MIT", "dependencies": { - "@octokit/auth-token": "^2.4.4", - "@octokit/graphql": "^4.5.8", - "@octokit/request": "^5.6.3", - "@octokit/request-error": "^2.0.5", - "@octokit/types": "^6.0.3", + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" } }, "node_modules/@octokit/endpoint": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", - "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", + "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", + "license": "MIT", "dependencies": { - "@octokit/types": "^6.0.3", - "is-plain-object": "^5.0.0", + "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" } }, "node_modules/@octokit/graphql": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", - "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.0.tgz", + "integrity": "sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==", + "license": "MIT", "dependencies": { - "@octokit/request": "^5.6.0", - "@octokit/types": "^6.0.3", + "@octokit/request": "^8.3.0", + "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" } }, "node_modules/@octokit/openapi-types": { - "version": "12.11.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", - "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==" + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz", + "integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==", + "license": "MIT" }, "node_modules/@octokit/plugin-paginate-rest": { - "version": "2.21.3", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz", - "integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz", + "integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==", + "license": "MIT", "dependencies": { - "@octokit/types": "^6.40.0" + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" }, "peerDependencies": { - "@octokit/core": ">=2" + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" } }, "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "5.16.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz", - "integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", + "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", + "license": "MIT", "dependencies": { - "@octokit/types": "^6.39.0", - "deprecation": "^2.3.1" + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" }, "peerDependencies": { - "@octokit/core": ">=3" + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" } }, "node_modules/@octokit/request": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", - "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", + "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "license": "MIT", "dependencies": { - "@octokit/endpoint": "^6.0.1", - "@octokit/request-error": "^2.1.0", - "@octokit/types": "^6.16.1", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.7", + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" } }, "node_modules/@octokit/request-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", - "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", + "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "license": "MIT", "dependencies": { - "@octokit/types": "^6.0.3", + "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" - } - }, - "node_modules/@octokit/request/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": ">= 18" } }, "node_modules/@octokit/rest": { @@ -206,89 +247,6 @@ "node": ">= 18" } }, - "node_modules/@octokit/rest/node_modules/@octokit/auth-token": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", - "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/core": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.0.0.tgz", - "integrity": "sha512-YbAtMWIrbZ9FCXbLwT9wWB8TyLjq9mxpKdgB3dUNxQcIVTf9hJ70gRPwAcqGZdY6WdJPZ0I7jLaaNDCiloGN2A==", - "dependencies": { - "@octokit/auth-token": "^4.0.0", - "@octokit/graphql": "^7.0.0", - "@octokit/request": "^8.0.2", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^11.0.0", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/endpoint": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.0.tgz", - "integrity": "sha512-szrQhiqJ88gghWY2Htt8MqUDO6++E/EIXqJ2ZEp5ma3uGS46o7LZAzSLt49myB7rT+Hfw5Y6gO3LmOxGzHijAQ==", - "dependencies": { - "@octokit/types": "^11.0.0", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/graphql": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.1.tgz", - "integrity": "sha512-T5S3oZ1JOE58gom6MIcrgwZXzTaxRnxBso58xhozxHpOqSTgDS6YNeEUvZ/kRvXgPrRz/KHnZhtb7jUMRi9E6w==", - "dependencies": { - "@octokit/request": "^8.0.1", - "@octokit/types": "^11.0.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/openapi-types": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.0.0.tgz", - "integrity": "sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw==" - }, - "node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.1.tgz", - "integrity": "sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==", - "dependencies": { - "@octokit/types": "^12.6.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": "5" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", - "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" - }, - "node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", - "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", - "dependencies": { - "@octokit/openapi-types": "^20.0.0" - } - }, "node_modules/@octokit/rest/node_modules/@octokit/plugin-request-log": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.0.tgz", @@ -300,75 +258,13 @@ "@octokit/core": ">=5" } }, - "node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", - "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", - "dependencies": { - "@octokit/types": "^12.6.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": "5" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", - "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" - }, - "node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", - "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", - "dependencies": { - "@octokit/openapi-types": "^20.0.0" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/request": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.1.tgz", - "integrity": "sha512-8N+tdUz4aCqQmXl8FpHYfKG9GelDFd7XGVzyN8rc6WxVlYcfpHECnuRkgquzz+WzvHTK62co5di8gSXnzASZPQ==", - "dependencies": { - "@octokit/endpoint": "^9.0.0", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^11.1.0", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/request-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.0.tgz", - "integrity": "sha512-1ue0DH0Lif5iEqT52+Rf/hf0RmGO9NWFjrzmrkArpG9trFfDM/efx00BJHdLGuro4BR/gECxCU2Twf5OKrRFsQ==", - "dependencies": { - "@octokit/types": "^11.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/types": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-11.1.0.tgz", - "integrity": "sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ==", - "dependencies": { - "@octokit/openapi-types": "^18.0.0" - } - }, "node_modules/@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", + "version": "13.8.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz", + "integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==", + "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^12.11.0" + "@octokit/openapi-types": "^23.0.1" } }, "node_modules/@vercel/ncc": { @@ -396,7 +292,8 @@ "node_modules/deprecation": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "license": "ISC" }, "node_modules/fetch-blob": { "version": "3.2.0", @@ -446,14 +343,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -504,15 +393,11 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "node_modules/tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", @@ -529,6 +414,18 @@ "node": ">=8" } }, + "node_modules/undici": { + "version": "5.28.5", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz", + "integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", @@ -550,24 +447,11 @@ "node": ">= 8" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" } } } diff --git a/.github/actions/nym-hash-releases/src/package.json b/.github/actions/nym-hash-releases/src/package.json index 78d73da4d6..d8f5047b24 100644 --- a/.github/actions/nym-hash-releases/src/package.json +++ b/.github/actions/nym-hash-releases/src/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@actions/core": "^1.10.1", - "@actions/github": "^5.1.1", + "@actions/github": "^6.0.0", "@octokit/auth-action": "^4.0.1", "@octokit/rest": "^20.0.2", "hasha": "^5.2.0", diff --git a/.github/workflows/ci-build-upload-binaries.yml b/.github/workflows/ci-build-upload-binaries.yml index 928cd92a52..5e693bbe69 100644 --- a/.github/workflows/ci-build-upload-binaries.yml +++ b/.github/workflows/ci-build-upload-binaries.yml @@ -26,6 +26,7 @@ jobs: runs-on: ${{ matrix.platform }} env: CARGO_TERM_COLOR: always + RUSTUP_PERMIT_COPY_RENAME: 1 steps: - uses: actions/checkout@v4 @@ -99,7 +100,6 @@ jobs: cp target/release/nymvisor $OUTPUT_DIR cp target/release/nym-node $OUTPUT_DIR cp target/release/nym-cli $OUTPUT_DIR - cp target/release/explorer-api $OUTPUT_DIR if [ ${{ github.event_name == 'workflow_dispatch' && inputs.enable_deb == true }} = true ]; then cp target/debian/*.deb $OUTPUT_DIR fi diff --git a/.github/workflows/ci-build-vpn-api-wasm.yml b/.github/workflows/ci-build-vpn-api-wasm.yml index a4abd8fa3e..76694ecca0 100644 --- a/.github/workflows/ci-build-vpn-api-wasm.yml +++ b/.github/workflows/ci-build-vpn-api-wasm.yml @@ -12,6 +12,7 @@ jobs: runs-on: arc-ubuntu-22.04 env: CARGO_TERM_COLOR: always + RUSTUP_PERMIT_COPY_RENAME: 1 steps: - name: Check out repository code uses: actions/checkout@v4 diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 72ee5560ec..52ad69314c 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -27,6 +27,12 @@ on: - '.github/workflows/ci-build.yml' workflow_dispatch: +concurrency: + # only 1 concurrent `ci-build` allowed per branch + # https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#example-using-concurrency-and-the-default-behavior + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: strategy: @@ -37,6 +43,7 @@ jobs: env: CARGO_TERM_COLOR: always IPINFO_API_TOKEN: ${{ secrets.IPINFO_API_TOKEN }} + RUSTUP_PERMIT_COPY_RENAME: 1 steps: - name: Install Dependencies (Linux) run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler diff --git a/.github/workflows/ci-check-ns-api-version.yml b/.github/workflows/ci-check-ns-api-version.yml new file mode 100644 index 0000000000..7ea704d703 --- /dev/null +++ b/.github/workflows/ci-check-ns-api-version.yml @@ -0,0 +1,57 @@ +name: ci-check-ns-api-version + +on: + pull_request: + paths: + - "nym-node-status-api/**" + +env: + WORKING_DIRECTORY: "nym-node-status-api/nym-node-status-api" + +jobs: + check-if-tag-exists: + runs-on: arc-ubuntu-22.04-dind + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Get version from cargo.toml + uses: mikefarah/yq@v4.45.1 + id: get_version + with: + cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml + + - name: Check if git tag exists + run: | + TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} + if [[ -z "$TAG" ]]; then + echo "Tag is empty" + exit 1 + fi + git ls-remote --tags origin | awk '{print $2}' + if git ls-remote --tags origin | awk '{print $2}' | grep -q "refs/tags/$TAG$" ; then + echo "Tag '$TAG' ALREADY EXISTS on the remote" + exit 1 + else + echo "Tag '$TAG' does not exist on the remote" + fi + - name: Check if harbor tag exists + run: | + TAG=${{ steps.get_version.outputs.result }} + registry=https://harbor.nymte.ch + repo_name=nym/node-status-api + if [[ -z $TAG ]]; then + echo "Tag is empty" + exit 1 + fi + curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq + exists=$(curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq --arg tag $TAG '.tags | contains([$tag])' ) + if [[ $exists = "true" ]]; then + echo "Version '$TAG' defined in Cargo.toml ALREADY EXISTS as tag in harbor repo" + exit 1 + elif [[ $exists = "false" ]]; then + echo "Version '$TAG' doesn't exist on the remote" + else + echo "Unknown output '$exists'" + exit 1 + fi diff --git a/.github/workflows/ci-docs.yml b/.github/workflows/ci-docs.yml index 307df47733..36f2191f24 100644 --- a/.github/workflows/ci-docs.yml +++ b/.github/workflows/ci-docs.yml @@ -11,6 +11,8 @@ on: jobs: build: runs-on: arc-ubuntu-20.04 + env: + RUSTUP_PERMIT_COPY_RENAME: 1 defaults: run: working-directory: documentation/docs diff --git a/.github/workflows/ci-lint-typescript.yml b/.github/workflows/ci-lint-typescript.yml index 0f3aa78fa0..aab3a4105f 100644 --- a/.github/workflows/ci-lint-typescript.yml +++ b/.github/workflows/ci-lint-typescript.yml @@ -42,7 +42,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.20' + go-version: "1.23.7" - name: Install run: yarn diff --git a/.github/workflows/ci-nym-wallet-rust.yml b/.github/workflows/ci-nym-wallet-rust.yml index 0d556165f9..bfa0ac1962 100644 --- a/.github/workflows/ci-nym-wallet-rust.yml +++ b/.github/workflows/ci-nym-wallet-rust.yml @@ -14,6 +14,7 @@ jobs: runs-on: arc-ubuntu-20.04 env: CARGO_TERM_COLOR: always + RUSTUP_PERMIT_COPY_RENAME: 1 steps: - name: Install Dependencies (Linux) run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools diff --git a/.github/workflows/ci-sdk-wasm.yml b/.github/workflows/ci-sdk-wasm.yml index 3164ba6483..8f9eaeeeba 100644 --- a/.github/workflows/ci-sdk-wasm.yml +++ b/.github/workflows/ci-sdk-wasm.yml @@ -14,6 +14,7 @@ jobs: runs-on: arc-ubuntu-20.04 env: CARGO_TERM_COLOR: always + RUSTUP_PERMIT_COPY_RENAME: 1 steps: - uses: actions/checkout@v4 @@ -32,7 +33,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.20' + go-version: "1.23.7" - name: Install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh diff --git a/.github/workflows/publish-nyms5-android-apk.yml b/.github/workflows/publish-nyms5-android-apk.yml index b4518d57c3..b1e6d1ad64 100644 --- a/.github/workflows/publish-nyms5-android-apk.yml +++ b/.github/workflows/publish-nyms5-android-apk.yml @@ -49,7 +49,7 @@ jobs: "build-tools;$SDK_BUILDTOOLS_VERSION" - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@1.90.0 + uses: dtolnay/rust-toolchain@1.100.0 - name: Install rust android targets run: | diff --git a/.github/workflows/publish-sdk-npm.yml b/.github/workflows/publish-sdk-npm.yml index 405cbcd022..c3d4ef3fc7 100644 --- a/.github/workflows/publish-sdk-npm.yml +++ b/.github/workflows/publish-sdk-npm.yml @@ -31,12 +31,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.20" - - - name: Install TinyGo - uses: acifani/setup-tinygo@v2 - with: - tinygo-version: "0.27.0" + go-version: "1.23.7" - name: Install dependencies run: yarn diff --git a/CHANGELOG.md b/CHANGELOG.md index 293f70002b..d7715b1763 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,216 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +## [2025.5-chokito] (2025-03-18) + +- build(deps): bump braces from 3.0.2 to 3.0.3 in /sdk/typescript/packages/nodejs-client ([#5611]) +- build(deps-dev): bump webpack-dev-middleware from 5.3.3 to 5.3.4 in /wasm/client/internal-dev ([#5610]) +- Export lane queue lengths in sdk ([#5609]) +- Chore/more payment watcher debug endpoints ([#5608]) +- build(deps): bump @babel/helpers from 7.24.4 to 7.26.10 ([#5606]) +- Chore/update bls12 381 fork ([#5605]) +- chore: change auth v2 timestamp skew and allow values from the future ([#5604]) +- Chore/payment watcher debug endpoints ([#5601]) +- Allow resetting all SURB sender tags ([#5600]) +- introduce internal tool for checking signer status ([#5598]) +- build(deps-dev): bump webpack from 5.77.0 to 5.98.0 in /wasm/mix-fetch/internal-dev ([#5597]) +- build(deps): bump body-parser and express in /wasm/mix-fetch/internal-dev ([#5596]) +- build(deps): bump serve-static and express in /wasm/mix-fetch/internal-dev ([#5594]) +- build(deps-dev): bump ws from 8.13.0 to 8.18.1 in /wasm/mix-fetch/internal-dev ([#5593]) +- build(deps): bump cookie and express in /wasm/client/internal-dev ([#5592]) +- build(deps): bump cookie and express in /wasm/mix-fetch/internal-dev ([#5591]) +- build(deps): bump braces from 3.0.2 to 3.0.3 in /wasm/zknym-lib/internal-dev ([#5590]) +- build(deps): bump webpack-dev-middleware from 5.3.3 to 5.3.4 in /wasm/zknym-lib/internal-dev ([#5589]) +- build(deps): bump tempfile from 3.17.1 to 3.18.0 ([#5588]) +- build(deps): bump tokio from 1.43.0 to 1.44.0 ([#5587]) +- build(deps): bump the patch-updates group with 8 updates ([#5585]) +- build(deps): bump ring from 0.17.9 to 0.17.13 ([#5583]) +- delete double memo field in send modal ([#5578]) +- Server Side internal DoT/DoH opt out ([#5577]) +- Rust SDK SURB example: change hardcoded file to tempdir ([#5576]) +- Add /v3/nym-nodes ([#5569]) +- chore: start sending v2 sphinx packets ([#5554]) +- build(deps): bump the patch-updates group across 1 directory with 14 updates ([#5549]) +- build(deps): bump uuid from 1.13.2 to 1.15.1 ([#5542]) +- build(deps): bump rs_merkle from 1.4.2 to 1.5.0 ([#5541]) +- feature: v2 authentication request ([#5537]) +- Set RUSTUP_PERMIT_COPY_RENAME ([#5533]) +- feature: disallow routing mix packets to nodes not present in the topology ([#5526]) +- Make "Memo" visible per default on send NYM ([#5524]) +- feat: make sure any terminated task kills the watcher and write run info to db ([#5517]) +- Another total_stake SQL fix ([#5516]) +- Fix total_stake on SQL update ([#5514]) +- build(deps): bump flate2 from 1.0.35 to 1.1.0 ([#5510]) +- build(deps): bump itertools from 0.13.0 to 0.14.0 ([#5509]) +- build(deps): bump the patch-updates group with 2 updates ([#5505]) +- Treat gateways as Nym Nodes ([#5504]) +- Update version in Cargo.toml ([#5503]) +- feat: use ct_eq for checking bearer token ([#5501]) +- Add extra args for the probe ([#5499]) +- Fix stats bug & remove HM caching ([#5495]) +- fix: Cargo.lock for contracts ([#5489]) +- Display error messages if IPv4 or IPv6 address not found on nymtun0 ([#5465]) + +[#5611]: https://github.com/nymtech/nym/pull/5611 +[#5610]: https://github.com/nymtech/nym/pull/5610 +[#5609]: https://github.com/nymtech/nym/pull/5609 +[#5608]: https://github.com/nymtech/nym/pull/5608 +[#5606]: https://github.com/nymtech/nym/pull/5606 +[#5605]: https://github.com/nymtech/nym/pull/5605 +[#5604]: https://github.com/nymtech/nym/pull/5604 +[#5601]: https://github.com/nymtech/nym/pull/5601 +[#5600]: https://github.com/nymtech/nym/pull/5600 +[#5598]: https://github.com/nymtech/nym/pull/5598 +[#5597]: https://github.com/nymtech/nym/pull/5597 +[#5596]: https://github.com/nymtech/nym/pull/5596 +[#5594]: https://github.com/nymtech/nym/pull/5594 +[#5593]: https://github.com/nymtech/nym/pull/5593 +[#5592]: https://github.com/nymtech/nym/pull/5592 +[#5591]: https://github.com/nymtech/nym/pull/5591 +[#5590]: https://github.com/nymtech/nym/pull/5590 +[#5589]: https://github.com/nymtech/nym/pull/5589 +[#5588]: https://github.com/nymtech/nym/pull/5588 +[#5587]: https://github.com/nymtech/nym/pull/5587 +[#5585]: https://github.com/nymtech/nym/pull/5585 +[#5583]: https://github.com/nymtech/nym/pull/5583 +[#5578]: https://github.com/nymtech/nym/pull/5578 +[#5577]: https://github.com/nymtech/nym/pull/5577 +[#5576]: https://github.com/nymtech/nym/pull/5576 +[#5569]: https://github.com/nymtech/nym/pull/5569 +[#5554]: https://github.com/nymtech/nym/pull/5554 +[#5549]: https://github.com/nymtech/nym/pull/5549 +[#5542]: https://github.com/nymtech/nym/pull/5542 +[#5541]: https://github.com/nymtech/nym/pull/5541 +[#5537]: https://github.com/nymtech/nym/pull/5537 +[#5533]: https://github.com/nymtech/nym/pull/5533 +[#5526]: https://github.com/nymtech/nym/pull/5526 +[#5524]: https://github.com/nymtech/nym/pull/5524 +[#5517]: https://github.com/nymtech/nym/pull/5517 +[#5516]: https://github.com/nymtech/nym/pull/5516 +[#5514]: https://github.com/nymtech/nym/pull/5514 +[#5510]: https://github.com/nymtech/nym/pull/5510 +[#5509]: https://github.com/nymtech/nym/pull/5509 +[#5505]: https://github.com/nymtech/nym/pull/5505 +[#5504]: https://github.com/nymtech/nym/pull/5504 +[#5503]: https://github.com/nymtech/nym/pull/5503 +[#5501]: https://github.com/nymtech/nym/pull/5501 +[#5499]: https://github.com/nymtech/nym/pull/5499 +[#5495]: https://github.com/nymtech/nym/pull/5495 +[#5489]: https://github.com/nymtech/nym/pull/5489 +[#5465]: https://github.com/nymtech/nym/pull/5465 + +## [2025.4-dorina-patched] (2025-03-06) + +- use legacy crypto for constructing SURB headers ([#5579]) +- bugfix: make sure to correctly decode response content when putting it into error message ([#5571]) +- Tweak surb management to be more conservative ([#5570]) +- Deserialize v5 authenticator requests ([#5568]) +- chore: additional logs when attempting to load ecash keys ([#5567]) +- add full response body to error message upon decoding failure ([#5566]) +- hotfix: ensure we bail on merkle leaves insertion upon missing data ([#5565]) +- feature: v2 authentication request (#5537) ([#5563]) +- Create authenticator v5 request/response types ([#5561]) + +[#5579]: https://github.com/nymtech/nym/pull/5579 +[#5571]: https://github.com/nymtech/nym/pull/5571 +[#5570]: https://github.com/nymtech/nym/pull/5570 +[#5568]: https://github.com/nymtech/nym/pull/5568 +[#5567]: https://github.com/nymtech/nym/pull/5567 +[#5566]: https://github.com/nymtech/nym/pull/5566 +[#5565]: https://github.com/nymtech/nym/pull/5565 +[#5563]: https://github.com/nymtech/nym/pull/5563 +[#5561]: https://github.com/nymtech/nym/pull/5561 + +## [2025.4-dorina] (2025-03-04) + +- fixed sphinx version metrics registration ([#5546]) +- Feature/chain status api ([#5539]) +- Add SURBs soft threshold ([#5535]) +- Simplify IPR v8 ([#5532]) +- Shared instance for DNS AsyncResolver ([#5523]) +- merge #5512 again after reverting due to incorrect rebase ([#5520]) +- cherry-pick 17d3ff2d775f61aee381d90a304ed416c08f33fc onto dorina ([#5519]) +- cherry-pick 6e5d0dac1b75413c5f09122b0d953f8ec6ef48df onto dorina ([#5518]) +- chore: workspace global panic preventing lints ([#5512]) +- bugfix: dont query for ecash apis unless necessary when spending ticketbooks ([#5508]) +- bugfix: bound check when recovering a reply SURB ([#5502]) +- chore: removed all old coconut code ([#5500]) +- IPR request types v8 ([#5498]) +- Support static routes for HTTP requests ([#5487]) +- build(deps): bump the patch-updates group across 1 directory with 3 updates ([#5482]) +- added missing import to doctest ([#5480]) +- adjusted TestSetup::new_complex to ensure bonded node's existence ([#5478]) +- Trigger contracts CI on main workspace Cargo changes ([#5477]) +- build(deps): bump http from 1.1.0 to 1.2.0 ([#5472]) +- build(deps): bump utoipa-swagger-ui from 8.0.3 to 8.1.0 ([#5471]) +- build(deps): bump colored from 2.1.0 to 2.2.0 ([#5470]) +- build(deps): bump celes from 2.4.0 to 2.5.0 ([#5469]) +- build(deps): bump the patch-updates group with 2 updates ([#5467]) +- build(deps): bump elliptic from 6.5.4 to 6.6.1 in /docker/typescript_client/upload_contract ([#5463]) +- Run cargo autoinherit ([#5460]) +- Fix clippy::precedence ([#5457]) +- Provide Interval context with node descriptor endpoints ([#5456]) +- fix: update fx average rate calcs to ignore 0 values ([#5454]) +- Feature/add gbp currency ([#5453]) +- Add helper to extract a list of sqlite files with journal files wal/shm ([#5452]) +- Add a middleware layer to the nym api allowing for data compression ([#5451]) +- Condense core API functionalities and enable gzip decompression for reqwest payloads ([#5450]) +- build(deps): bump uniffi_build from 0.25.3 to 0.29.0 ([#5448]) +- Upgrade tower to 0.5.2 ([#5446]) +- build(deps): bump hickory-proto from 0.24.2 to 0.24.3 ([#5444]) +- Seedable clients ([#5440]) +- build(deps): bump the patch-updates group across 1 directory with 10 updates ([#5439]) +- Remove all recv_with_delay and add shutdown condition to loops in client-core ([#5435]) +- Disable the test for checking the remaining bandwidth in nym-node-status-api ([#5425]) +- Dz nym node stats ([#5418]) +- build(deps): bump hyper from 1.4.1 to 1.6.0 ([#5416]) +- build(deps): bump publicsuffix from 2.2.3 to 2.3.0 ([#5367]) +- Nymnode entrypoint docker ([#5300]) + +[#5546]: https://github.com/nymtech/nym/pull/5546 +[#5539]: https://github.com/nymtech/nym/pull/5539 +[#5535]: https://github.com/nymtech/nym/pull/5535 +[#5532]: https://github.com/nymtech/nym/pull/5532 +[#5523]: https://github.com/nymtech/nym/pull/5523 +[#5520]: https://github.com/nymtech/nym/pull/5520 +[#5519]: https://github.com/nymtech/nym/pull/5519 +[#5518]: https://github.com/nymtech/nym/pull/5518 +[#5512]: https://github.com/nymtech/nym/pull/5512 +[#5508]: https://github.com/nymtech/nym/pull/5508 +[#5502]: https://github.com/nymtech/nym/pull/5502 +[#5500]: https://github.com/nymtech/nym/pull/5500 +[#5498]: https://github.com/nymtech/nym/pull/5498 +[#5487]: https://github.com/nymtech/nym/pull/5487 +[#5482]: https://github.com/nymtech/nym/pull/5482 +[#5480]: https://github.com/nymtech/nym/pull/5480 +[#5478]: https://github.com/nymtech/nym/pull/5478 +[#5477]: https://github.com/nymtech/nym/pull/5477 +[#5472]: https://github.com/nymtech/nym/pull/5472 +[#5471]: https://github.com/nymtech/nym/pull/5471 +[#5470]: https://github.com/nymtech/nym/pull/5470 +[#5469]: https://github.com/nymtech/nym/pull/5469 +[#5467]: https://github.com/nymtech/nym/pull/5467 +[#5463]: https://github.com/nymtech/nym/pull/5463 +[#5460]: https://github.com/nymtech/nym/pull/5460 +[#5457]: https://github.com/nymtech/nym/pull/5457 +[#5456]: https://github.com/nymtech/nym/pull/5456 +[#5454]: https://github.com/nymtech/nym/pull/5454 +[#5453]: https://github.com/nymtech/nym/pull/5453 +[#5452]: https://github.com/nymtech/nym/pull/5452 +[#5451]: https://github.com/nymtech/nym/pull/5451 +[#5450]: https://github.com/nymtech/nym/pull/5450 +[#5448]: https://github.com/nymtech/nym/pull/5448 +[#5446]: https://github.com/nymtech/nym/pull/5446 +[#5444]: https://github.com/nymtech/nym/pull/5444 +[#5440]: https://github.com/nymtech/nym/pull/5440 +[#5439]: https://github.com/nymtech/nym/pull/5439 +[#5435]: https://github.com/nymtech/nym/pull/5435 +[#5425]: https://github.com/nymtech/nym/pull/5425 +[#5418]: https://github.com/nymtech/nym/pull/5418 +[#5416]: https://github.com/nymtech/nym/pull/5416 +[#5367]: https://github.com/nymtech/nym/pull/5367 +[#5300]: https://github.com/nymtech/nym/pull/5300 + ## [2025.3-ruta] (2025-02-10) - Push down forget me to client configs ([#5431]) @@ -48,7 +258,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// - Downgrade harmless log message from info to debug ([#5403]) - Redirect from mixnode page to nodes page ([#5397]) - chore :update version of chain watcher and validator rewarder ([#5394]) -- bugfix: correctly handle ingore epoch roles flag ([#5390]) +- bugfix: correctly handle ignore epoch roles flag ([#5390]) - bugfix: terminate mixnet socket listener on shutdown ([#5389]) - feat: make client ignore dual mode nodes by default ([#5388]) - Handle ecash network errors differently ([#5378]) @@ -69,7 +279,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// - Use expect in geodata test to give error message on failure ([#5314]) - feature: periodically remove stale gateway messages ([#5312]) - build(deps): bump the patch-updates group across 1 directory with 35 updates ([#5310]) -- Add dependabot assignes for the root cargo ecosystem ([#5297]) +- Add dependabot assigns for the root cargo ecosystem ([#5297]) - Move tun constants to network defaults ([#5286]) - Include IPINFO_API_TOKEN in nightly CI ([#5285]) - Nyx Chain Watcher ([#5274]) @@ -122,7 +332,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [2025.1-reeses] (2025-01-15) -- Feture/legacy alert ([#5346]) +- Feature, Future/legacy alert ([#5346]) - chore: readjusted --mode behaviour to fix the regression ([#5331]) - chore: apply 1.84 linter suggestions ([#5330]) - bugfix: make sure refresh data key matches bond info ([#5329]) @@ -202,7 +412,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [2024.14-crunch-patched] (2024-12-17) -- Fixes an issue to allow previously registred clients to connect to latest nym-nodes +- Fixes an issue to allow previously registered clients to connect to latest nym-nodes - Fixes compatibility issues between nym-nodes and older clients ## [2024.14-crunch] (2024-12-11) @@ -210,7 +420,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// - Merge/release/2024.14-crunch ([#5242]) - bugfix: added explicit openapi servers to account for route prefixes ([#5237]) - Further config score adjustments ([#5225]) -- feature: remve any filtering on node semver ([#5224]) +- feature: remove any filtering on node semver ([#5224]) - Backport #5218 ([#5220]) - Derive serialize for UserAgent (#5210) ([#5217]) - dont consider legacy nodes for rewarded set selection ([#5215]) @@ -389,7 +599,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// - bugfix/feature: added NymApiClient method to get all skimmed nodes ([#5062]) - Merge1/release/2024.13 magura ([#5061]) - added hacky routes to return nymnodes alongside legacy nodes ([#5051]) -- bugfix: mark migrated gateways as rewarded in the previous epoch in case theyre in the rewarded set ([#5049]) +- bugfix: mark migrated gateways as rewarded in the previous epoch in case they're, their, there in the rewarded set ([#5049]) - bugfix: adjust runtime storage migration ([#5047]) - bugfix: supersede 'cb13be27f8f61d9ae74d924e85d2e6787895eb14' by using… ([#5046]) - bugfix: restore default http port for nym-api ([#5045]) @@ -450,7 +660,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// - Fix broken build after merge ([#4937]) - bugfix: correctly paginate through 'search_tx' endpoint ([#4936]) - Add more conversions for responses of authenticator messages ([#4929]) -- Directory Sevices v2.1 ([#4903]) +- Directory Services, Devices v2.1 ([#4903]) - Migrate Legacy Node (Frontend) ([#4826]) - Fix critical issues SI84 and SI85 from Cure53 ([#4758]) @@ -834,7 +1044,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// - Remove stale peers ([#4640]) - Add generic wg private network routing ([#4636]) - Feature/new node endpoints ([#4635]) -- standarised ContractBuildInformation and added it to all contracts ([#4631]) +- standardised ContractBuildInformation and added it to all contracts ([#4631]) - validate nym-node public ips on startup ([#4630]) - Bump defguard wg ([#4625]) - Fix cargo warnings ([#4624]) @@ -1455,7 +1665,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// - clean-up nym-api startup arguments/flags to use clap 3 and its macro-derived arguments ([#2772]) - renamed all references to validator_api to nym_api - renamed all references to nymd to nyxd ([#2696]) -- all-binaries: standarised argument names (note: old names should still be accepted) ([#2762] +- all-binaries: standardised argument names (note: old names should still be accepted) ([#2762] ### Fixed @@ -1960,7 +2170,7 @@ The release also include some additional work for distributed key generation in - Explorer UI tests missing data-testid [\#903](https://github.com/nymtech/nym/pull/903) ([tommyv1987](https://github.com/tommyv1987)) - Fix up Nym-Wallet README.md [\#899](https://github.com/nymtech/nym/pull/899) ([tommyv1987](https://github.com/tommyv1987)) - Feature/batch delegator rewarding [\#898](https://github.com/nymtech/nym/pull/898) ([jstuczyn](https://github.com/jstuczyn)) -- Bug mapp nodemap [\#897](https://github.com/nymtech/nym/pull/897) ([Aid19801](https://github.com/Aid19801)) +- Bug map nodemap [\#897](https://github.com/nymtech/nym/pull/897) ([Aid19801](https://github.com/Aid19801)) - Bug fix/macos keyboard shortcuts [\#896](https://github.com/nymtech/nym/pull/896) ([fmtabbara](https://github.com/fmtabbara)) - Add a Mobile Nav to the Network Explorer [\#895](https://github.com/nymtech/nym/pull/895) ([Aid19801](https://github.com/Aid19801)) - Only use ts-rs in tests [\#894](https://github.com/nymtech/nym/pull/894) ([durch](https://github.com/durch)) diff --git a/Cargo.lock b/Cargo.lock index 654fb80097..ea648f713f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,9 +229,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "arbitrary" @@ -344,9 +344,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.86" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", @@ -379,21 +379,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "atomic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" - -[[package]] -name = "atomic" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" -dependencies = [ - "bytemuck", -] - [[package]] name = "atty" version = "0.2.14" @@ -421,7 +406,7 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" name = "autodoc" version = "0.1.0" dependencies = [ - "env_logger 0.11.6", + "env_logger 0.11.7", "log", ] @@ -464,7 +449,7 @@ dependencies = [ "axum-macros", "bytes", "futures-util", - "http 1.2.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "hyper 1.6.0", @@ -525,7 +510,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.2.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "mime", @@ -549,11 +534,11 @@ dependencies = [ "fastrand 2.3.0", "futures-util", "headers", - "http 1.2.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "mime", - "multer 3.1.0", + "multer", "pin-project-lite", "serde", "tower 0.5.2", @@ -585,7 +570,7 @@ dependencies = [ "bytes", "bytesize", "cookie", - "http 1.2.0", + "http 1.3.1", "http-body-util", "hyper 1.6.0", "hyper-util", @@ -662,12 +647,6 @@ dependencies = [ "serde", ] -[[package]] -name = "binascii" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" - [[package]] name = "bincode" version = "1.3.3" @@ -774,9 +753,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1230237285e3e10cde447185e8975408ae24deaa67205ce684805c25bc0c7937" +checksum = "675f87afced0413c9bb02843499dbbd3882a237645883f71a2b59644a6d2f753" dependencies = [ "arrayref", "arrayvec", @@ -784,7 +763,6 @@ dependencies = [ "cfg-if", "constant_time_eq", "digest 0.10.7", - "memmap2", ] [[package]] @@ -817,9 +795,9 @@ dependencies = [ [[package]] name = "bls12_381" version = "0.8.0" -source = "git+https://github.com/jstuczyn/bls12_381?branch=temp/experimental-serdect#22cd0a16b674af1629110a2dc8b6cf6c73ea4cd9" +source = "git+https://github.com/jstuczyn/bls12_381?branch=temp/experimental-serdect-updated#9bf520059cb28323fc51469cae86868ef4fa6fbd" dependencies = [ - "digest 0.9.0", + "digest 0.10.7", "ff", "group", "pairing", @@ -889,12 +867,6 @@ dependencies = [ "trackable 0.2.24", ] -[[package]] -name = "bytemuck" -version = "1.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" - [[package]] name = "byteorder" version = "1.5.0" @@ -903,9 +875,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" dependencies = [ "serde", ] @@ -942,7 +914,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.25", + "semver 1.0.26", "serde", "serde_json", "thiserror 1.0.69", @@ -956,7 +928,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.25", + "semver 1.0.26", "serde", "serde_json", "thiserror 1.0.69", @@ -987,10 +959,11 @@ dependencies = [ [[package]] name = "celes" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54441489dce7026efc8f01d1aa996c23fa39dd615a953d0e934433a42f61dd30" +checksum = "bc3ba3c5408fae183329e0d1ac8f8eed3cb7b647590fd93e6d6288f6b09db0be" dependencies = [ + "phf", "serde", ] @@ -1042,9 +1015,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1052,7 +1025,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -1095,9 +1068,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.30" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" +checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" dependencies = [ "clap_builder", "clap_derive", @@ -1105,21 +1078,21 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.30" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" +checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", ] [[package]] name = "clap_complete" -version = "4.5.45" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e3040c8291884ddf39445dc033c70abc2bc44a42f0a3a00571a0f483a83f0cd" +checksum = "f5c5508ea23c5366f77e53f5a0070e5a84e51687ec3ef9e0464c86dc8d13ce98" dependencies = [ "clap", ] @@ -1136,9 +1109,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -1190,9 +1163,9 @@ dependencies = [ [[package]] name = "console" -version = "0.15.10" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", "libc", @@ -1224,7 +1197,7 @@ dependencies = [ "crossbeam-utils", "futures", "hdrhistogram", - "humantime 2.1.0", + "humantime 2.2.0", "prost-types 0.11.9", "serde", "serde_json", @@ -1291,7 +1264,6 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ - "percent-encoding", "time", "version_check", ] @@ -1588,7 +1560,7 @@ dependencies = [ "bitflags 2.8.0", "crossterm_winapi", "parking_lot", - "rustix", + "rustix 0.38.44", "winapi", ] @@ -1800,7 +1772,7 @@ dependencies = [ "cosmwasm-std", "cw2", "schemars", - "semver 1.0.25", + "semver 1.0.26", "serde", "thiserror 1.0.69", ] @@ -1815,7 +1787,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus", "schemars", - "semver 1.0.25", + "semver 1.0.26", "serde", "thiserror 1.0.69", ] @@ -1861,38 +1833,14 @@ dependencies = [ "serde", ] -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core 0.13.4", - "darling_macro 0.13.4", -] - [[package]] name = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core 0.20.10", - "darling_macro 0.20.10", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn 1.0.109", + "darling_core", + "darling_macro", ] [[package]] @@ -1905,28 +1853,17 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.11.1", + "strsim", "syn 2.0.98", ] -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core 0.13.4", - "quote", - "syn 1.0.109", -] - [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core 0.20.10", + "darling_core", "quote", "syn 2.0.98", ] @@ -2029,18 +1966,18 @@ dependencies = [ [[package]] name = "derive_more" -version = "1.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "1.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", @@ -2048,39 +1985,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "devise" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d90b0c4c777a2cad215e3c7be59ac7c15adf45cf76317009b7d096d46f651d" -dependencies = [ - "devise_codegen", - "devise_core", -] - -[[package]] -name = "devise_codegen" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71b28680d8be17a570a2334922518be6adc3f58ecc880cbb404eaeb8624fd867" -dependencies = [ - "devise_core", - "quote", -] - -[[package]] -name = "devise_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7" -dependencies = [ - "bitflags 2.8.0", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.98", -] - [[package]] name = "diff" version = "0.1.13" @@ -2349,14 +2253,14 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime 2.1.0", + "jiff", "log", ] @@ -2432,41 +2336,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "explorer-api" -version = "1.1.46" -dependencies = [ - "chrono", - "clap", - "dotenvy", - "humantime-serde", - "isocountry", - "itertools 0.13.0", - "log", - "maxminddb", - "nym-bin-common", - "nym-contracts-common", - "nym-explorer-api-requests", - "nym-mixnet-contract-common", - "nym-network-defaults", - "nym-task", - "nym-validator-client", - "okapi", - "pretty_env_logger", - "rand 0.8.5", - "rand_pcg", - "rand_seeder", - "reqwest 0.12.4", - "rocket", - "rocket_cors", - "rocket_okapi", - "schemars", - "serde", - "serde_json", - "thiserror 2.0.11", - "tokio", -] - [[package]] name = "extension-storage" version = "1.3.0-rc.0" @@ -2475,7 +2344,7 @@ dependencies = [ "console_error_panic_hook", "js-sys", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.11", + "thiserror 2.0.12", "wasm-bindgen", "wasm-bindgen-futures", "wasm-storage", @@ -2528,9 +2397,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "ff" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ "rand_core 0.6.4", "subtle 2.6.1", @@ -2542,20 +2411,6 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" -[[package]] -name = "figment" -version = "0.10.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" -dependencies = [ - "atomic 0.6.0", - "pear", - "serde", - "toml 0.8.20", - "uncased", - "version_check", -] - [[package]] name = "filetime" version = "0.2.25" @@ -2576,9 +2431,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.35" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" dependencies = [ "crc32fast", "miniz_oxide", @@ -2791,19 +2646,6 @@ version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" -[[package]] -name = "generator" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "windows 0.48.0", -] - [[package]] name = "generator" version = "0.8.4" @@ -2865,9 +2707,9 @@ dependencies = [ [[package]] name = "getset" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded738faa0e88d3abc9d1a13cb11adc2073c400969eeb8793cf7132589959fc" +checksum = "f3586f256131df87204eb733da72e3d3eb4f343c639f4b7be279ac7c48baeafe" dependencies = [ "proc-macro-error2", "proc-macro2", @@ -2907,7 +2749,7 @@ dependencies = [ "futures-core", "futures-sink", "gloo-utils 0.2.0", - "http 1.2.0", + "http 1.3.1", "js-sys", "pin-project", "serde", @@ -3077,7 +2919,7 @@ dependencies = [ "base64 0.21.7", "bytes", "headers-core", - "http 1.2.0", + "http 1.3.1", "httpdate", "mime", "sha1", @@ -3089,7 +2931,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http 1.2.0", + "http 1.3.1", ] [[package]] @@ -3287,9 +3129,9 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -3314,18 +3156,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.2.0", + "http 1.3.1", ] [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", - "http 1.2.0", + "futures-core", + "http 1.3.1", "http-body 1.0.1", "pin-project-lite", ] @@ -3375,9 +3217,9 @@ dependencies = [ [[package]] name = "humantime" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" [[package]] name = "humantime-serde" @@ -3385,7 +3227,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" dependencies = [ - "humantime 2.1.0", + "humantime 2.2.0", "serde", ] @@ -3422,7 +3264,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.2.0", + "http 1.3.1", "http-body 1.0.1", "httparse", "httpdate", @@ -3454,7 +3296,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", - "http 1.2.0", + "http 1.3.1", "hyper 1.6.0", "hyper-util", "rustls 0.22.4", @@ -3485,7 +3327,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.2.0", + "http 1.3.1", "http-body 1.0.1", "hyper 1.6.0", "pin-project-lite", @@ -3712,9 +3554,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexed_db_futures" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56078e9bfed55e1d3c0cebad5be921d32f062be8f1e4bf3750b487bc6e7d13e" +checksum = "94eebf0199c01a1560770d964efb1fb09183a3afc0b1c1397fac1bfdbfa7d6cf" dependencies = [ "accessory", "cfg-if", @@ -3725,7 +3567,7 @@ dependencies = [ "js-sys", "sealed", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.12", "tokio", "wasm-bindgen", "wasm-bindgen-futures", @@ -3779,12 +3621,6 @@ dependencies = [ "web-time", ] -[[package]] -name = "inlinable_string" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" - [[package]] name = "inotify" version = "0.9.6" @@ -3879,15 +3715,6 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" -[[package]] -name = "ipnetwork" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4088d739b183546b239688ddbc79891831df421773df95e236daf7867866d355" -dependencies = [ - "serde", -] - [[package]] name = "ipnetwork" version = "0.20.0" @@ -3939,16 +3766,6 @@ dependencies = [ "waker-fn", ] -[[package]] -name = "isocountry" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea1dc4bf0fb4904ba83ffdb98af3d9c325274e92e6e295e4151e86c96363e04" -dependencies = [ - "serde", - "thiserror 1.0.69", -] - [[package]] name = "itertools" version = "0.10.5" @@ -3967,15 +3784,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.14.0" @@ -3991,6 +3799,30 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "jiff" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "jobserver" version = "0.1.32" @@ -4154,6 +3986,12 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" + [[package]] name = "lioness" version = "0.1.2" @@ -4194,21 +4032,6 @@ version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" -[[package]] -name = "loom" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" -dependencies = [ - "cfg-if", - "generator 0.7.5", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] - [[package]] name = "loom" version = "0.7.2" @@ -4216,7 +4039,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" dependencies = [ "cfg-if", - "generator 0.8.4", + "generator", "scoped-tls", "tracing", "tracing-subscriber", @@ -4326,18 +4149,6 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" -[[package]] -name = "maxminddb" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe2ba61113f9f7a9f0e87c519682d39c43a6f3f79c2cc42c3ba3dda83b1fa334" -dependencies = [ - "ipnetwork 0.18.0", - "log", - "memchr", - "serde", -] - [[package]] name = "md-5" version = "0.10.6" @@ -4354,15 +4165,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memmap2" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" -dependencies = [ - "libc", -] - [[package]] name = "memoffset" version = "0.9.1" @@ -4451,7 +4253,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tsify", "url", @@ -4488,7 +4290,7 @@ dependencies = [ "crossbeam-utils", "event-listener 5.4.0", "futures-util", - "loom 0.7.2", + "loom", "parking_lot", "portable-atomic", "rustc_version 0.4.1", @@ -4498,26 +4300,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "multer" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http 0.2.12", - "httparse", - "log", - "memchr", - "mime", - "spin", - "tokio", - "tokio-util", - "version_check", -] - [[package]] name = "multer" version = "3.1.0" @@ -4527,7 +4309,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.2.0", + "http 1.3.1", "httparse", "memchr", "mime", @@ -4799,7 +4581,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "nym-api" -version = "1.1.50" +version = "1.1.54" dependencies = [ "anyhow", "async-trait", @@ -4822,12 +4604,11 @@ dependencies = [ "futures", "getset", "humantime-serde", - "itertools 0.13.0", + "itertools 0.14.0", "k256", "nym-api-requests", "nym-bandwidth-controller", "nym-bin-common", - "nym-coconut", "nym-coconut-dkg-common", "nym-compact-ecash", "nym-config", @@ -4861,13 +4642,14 @@ dependencies = [ "rand_chacha 0.3.1", "reqwest 0.12.4", "schemars", - "semver 1.0.25", + "semver 1.0.26", "serde", "serde_json", - "sha2 0.9.9", + "sha2 0.10.8", "sqlx", "tempfile", - "thiserror 2.0.11", + "tendermint 0.40.1", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", @@ -4891,6 +4673,7 @@ dependencies = [ "cosmwasm-std", "ecdsa", "getset", + "humantime-serde", "nym-compact-ecash", "nym-config", "nym-contracts-common", @@ -4908,7 +4691,8 @@ dependencies = [ "serde_json", "sha2 0.10.8", "tendermint 0.40.1", - "thiserror 2.0.11", + "tendermint-rpc", + "thiserror 2.0.12", "time", "ts-rs", "utoipa", @@ -4936,7 +4720,7 @@ dependencies = [ "defguard_wireguard_rs", "fastrand 2.3.0", "futures", - "ipnetwork 0.20.0", + "ipnetwork", "log", "nym-authenticator-requests", "nym-bin-common", @@ -4960,7 +4744,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tokio-stream", "tokio-util", @@ -4983,7 +4767,7 @@ dependencies = [ "rand 0.8.5", "serde", "sha2 0.10.8", - "thiserror 2.0.11", + "thiserror 2.0.12", "x25519-dalek", ] @@ -5003,7 +4787,7 @@ dependencies = [ "nym-task", "nym-validator-client", "rand 0.8.5", - "thiserror 2.0.11", + "thiserror 2.0.12", "url", "zeroize", ] @@ -5042,12 +4826,12 @@ dependencies = [ "nym-validator-client", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] name = "nym-cli" -version = "1.1.48" +version = "1.1.51" dependencies = [ "anyhow", "base64 0.22.1", @@ -5120,7 +4904,7 @@ dependencies = [ "serde_json", "tap", "tempfile", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "toml 0.8.20", @@ -5130,7 +4914,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.48" +version = "1.1.51" dependencies = [ "bs58", "clap", @@ -5157,7 +4941,7 @@ dependencies = [ "serde", "serde_json", "tap", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tokio-tungstenite", @@ -5215,7 +4999,7 @@ dependencies = [ "si-scale", "tap", "tempfile", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", @@ -5240,7 +5024,7 @@ dependencies = [ "nym-sphinx-addressing", "nym-sphinx-params", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "url", ] @@ -5255,7 +5039,7 @@ dependencies = [ "nym-gateway-requests", "serde", "sqlx", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "url", @@ -5273,7 +5057,7 @@ dependencies = [ "nym-sphinx", "nym-task", "sqlx", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", ] @@ -5292,7 +5076,7 @@ dependencies = [ "serde", "serde-wasm-bindgen 0.6.5", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", "tsify", "wasm-bindgen", "wasm-bindgen-futures", @@ -5310,30 +5094,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "nym-coconut" -version = "0.5.0" -dependencies = [ - "bls12_381", - "bs58", - "criterion", - "digest 0.9.0", - "doc-comment", - "ff", - "getrandom 0.2.15", - "group", - "itertools 0.13.0", - "nym-dkg", - "nym-pemstore", - "rand 0.8.5", - "rand_chacha 0.3.1", - "serde", - "serde_derive", - "sha2 0.9.9", - "thiserror 2.0.11", - "zeroize", -] - [[package]] name = "nym-coconut-bandwidth-contract-common" version = "0.1.0" @@ -5366,18 +5126,18 @@ dependencies = [ "bs58", "cfg-if", "criterion", - "digest 0.9.0", + "digest 0.10.7", "ff", "group", - "itertools 0.13.0", + "itertools 0.14.0", "nym-network-defaults", "nym-pemstore", "rand 0.8.5", "rayon", "serde", - "sha2 0.9.9", + "sha2 0.10.8", "subtle 2.6.1", - "thiserror 2.0.11", + "thiserror 2.0.12", "zeroize", ] @@ -5390,7 +5150,7 @@ dependencies = [ "log", "nym-network-defaults", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "toml 0.8.20", "url", ] @@ -5406,7 +5166,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", "utoipa", "vergen", ] @@ -5447,7 +5207,7 @@ dependencies = [ "colored", "dotenvy", "futures", - "humantime 2.1.0", + "humantime 2.2.0", "nym-bin-common", "nym-compact-ecash", "nym-config", @@ -5467,7 +5227,7 @@ dependencies = [ "strum 0.26.3", "strum_macros 0.26.4", "tempfile", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tokio-util", @@ -5515,7 +5275,7 @@ dependencies = [ "nym-ecash-time", "serde", "sqlx", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "zeroize", ] @@ -5533,7 +5293,7 @@ dependencies = [ "nym-credentials-interface", "nym-ecash-time", "nym-validator-client", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", ] @@ -5556,7 +5316,7 @@ dependencies = [ "nym-validator-client", "rand 0.8.5", "si-scale", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -5580,7 +5340,7 @@ dependencies = [ "nym-validator-client", "rand 0.8.5", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "zeroize", ] @@ -5596,7 +5356,7 @@ dependencies = [ "rand 0.8.5", "serde", "strum 0.26.3", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "utoipa", ] @@ -5625,7 +5385,7 @@ dependencies = [ "serde_bytes", "sha2 0.10.8", "subtle-encoding", - "thiserror 2.0.11", + "thiserror 2.0.12", "x25519-dalek", "zeroize", ] @@ -5648,8 +5408,8 @@ dependencies = [ "rand_core 0.6.4", "serde", "serde_derive", - "sha2 0.9.9", - "thiserror 2.0.11", + "sha2 0.10.8", + "thiserror 2.0.12", "zeroize", ] @@ -5664,7 +5424,7 @@ dependencies = [ "cw-utils", "cw2", "nym-multisig-contract-common", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -5690,7 +5450,7 @@ dependencies = [ "reqwest 0.12.4", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", "utoipa", ] @@ -5714,7 +5474,7 @@ dependencies = [ "nym-explorer-api-requests", "reqwest 0.12.4", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tracing", "url", @@ -5746,7 +5506,7 @@ dependencies = [ "dashmap", "defguard_wireguard_rs", "futures", - "ipnetwork 0.20.0", + "ipnetwork", "nym-api-requests", "nym-authenticator", "nym-credential-verification", @@ -5774,7 +5534,7 @@ dependencies = [ "rand 0.8.5", "sha2 0.10.8", "sqlx", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", @@ -5808,7 +5568,7 @@ dependencies = [ "rand 0.8.5", "serde", "si-scale", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", @@ -5835,13 +5595,16 @@ dependencies = [ "nym-credentials-interface", "nym-crypto", "nym-pemstore", + "nym-serde-helpers", "nym-sphinx", "nym-task", "rand 0.8.5", "serde", "serde_json", "strum 0.26.3", - "thiserror 2.0.11", + "subtle 2.6.1", + "thiserror 2.0.12", + "time", "tokio", "tracing", "tungstenite 0.20.1", @@ -5859,7 +5622,7 @@ dependencies = [ "nym-statistics-common", "sqlx", "strum 0.26.3", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -5875,7 +5638,7 @@ dependencies = [ "nym-gateway-requests", "nym-sphinx", "sqlx", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -5891,7 +5654,7 @@ dependencies = [ "nym-ffi-shared", "nym-sdk", "nym-sphinx-anonymous-replies", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "uniffi", "uniffi_build", @@ -5913,14 +5676,17 @@ name = "nym-http-api-client" version = "0.1.0" dependencies = [ "async-trait", + "bytes", + "encoding_rs", "hickory-resolver", - "http 1.2.0", + "http 1.3.1", + "mime", "nym-bin-common", "once_cell", "reqwest 0.12.4", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tracing", "url", @@ -5953,7 +5719,7 @@ version = "0.1.0" dependencies = [ "nym-credential-storage", "nym-credentials", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tracing", "zeroize", @@ -5979,7 +5745,7 @@ version = "0.1.0" dependencies = [ "log", "rand 0.8.5", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -5990,10 +5756,11 @@ dependencies = [ "bytes", "nym-bin-common", "nym-crypto", + "nym-service-provider-requests-common", "nym-sphinx", "rand 0.8.5", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tokio-util", @@ -6004,6 +5771,7 @@ name = "nym-ip-packet-router" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "bincode", "bs58", "bytes", @@ -6021,6 +5789,7 @@ dependencies = [ "nym-network-defaults", "nym-network-requester", "nym-sdk", + "nym-service-provider-requests-common", "nym-service-providers-common", "nym-sphinx", "nym-task", @@ -6032,8 +5801,7 @@ dependencies = [ "reqwest 0.12.4", "serde", "serde_json", - "tap", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tokio-tun", @@ -6049,7 +5817,7 @@ dependencies = [ "k256", "ledger-transport", "ledger-transport-hid", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -6090,11 +5858,11 @@ dependencies = [ "nym-contracts-common", "rand_chacha 0.3.1", "schemars", - "semver 1.0.25", + "semver 1.0.26", "serde", "serde-json-wasm", "serde_repr", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "ts-rs", "utoipa", @@ -6120,7 +5888,7 @@ dependencies = [ "nym-task", "rand 0.8.5", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tokio-util", @@ -6139,7 +5907,7 @@ dependencies = [ "cw4", "schemars", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -6191,7 +5959,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.49" +version = "1.1.52" dependencies = [ "addr", "anyhow", @@ -6201,7 +5969,7 @@ dependencies = [ "dirs", "futures", "humantime-serde", - "ipnetwork 0.20.0", + "ipnetwork", "log", "nym-async-file-watcher", "nym-bin-common", @@ -6232,7 +6000,7 @@ dependencies = [ "sqlx", "tap", "tempfile", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tokio-tungstenite", @@ -6242,7 +6010,7 @@ dependencies = [ [[package]] name = "nym-node" -version = "1.5.0" +version = "1.7.0" dependencies = [ "anyhow", "arc-swap", @@ -6261,7 +6029,7 @@ dependencies = [ "headers", "human-repr", "humantime-serde", - "ipnetwork 0.20.0", + "ipnetwork", "nym-authenticator", "nym-bin-common", "nym-client-core-config-types", @@ -6269,6 +6037,7 @@ dependencies = [ "nym-crypto", "nym-gateway", "nym-gateway-stats-storage", + "nym-http-api-client", "nym-http-api-common", "nym-ip-packet-router", "nym-metrics", @@ -6291,12 +6060,12 @@ dependencies = [ "nym-wireguard", "nym-wireguard-types", "rand 0.8.5", - "semver 1.0.25", + "semver 1.0.26", "serde", "serde_json", "si-scale", "sysinfo", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tokio-util", @@ -6330,7 +6099,7 @@ version = "0.1.0" dependencies = [ "async-trait", "celes", - "humantime 2.1.0", + "humantime 2.2.0", "humantime-serde", "nym-bin-common", "nym-crypto", @@ -6342,7 +6111,7 @@ dependencies = [ "serde", "serde_json", "strum 0.26.3", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "utoipa", @@ -6366,7 +6135,7 @@ dependencies = [ [[package]] name = "nym-node-status-api" -version = "1.0.2" +version = "1.0.3" dependencies = [ "ammonia", "anyhow", @@ -6377,12 +6146,13 @@ dependencies = [ "cosmwasm-std", "envy", "futures-util", - "itertools 0.13.0", + "itertools 0.14.0", "moka", "nym-bin-common", "nym-contracts-common", "nym-crypto", "nym-explorer-client", + "nym-http-api-client", "nym-network-defaults", "nym-node-metrics", "nym-node-requests", @@ -6401,7 +6171,7 @@ dependencies = [ "sqlx", "strum 0.26.3", "strum_macros 0.26.4", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tokio-util", @@ -6444,7 +6214,7 @@ dependencies = [ "rand_chacha 0.3.1", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "wasm-utils", ] @@ -6459,7 +6229,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tsify", "wasm-bindgen", @@ -6500,7 +6270,7 @@ name = "nym-ordered-buffer" version = "0.1.0" dependencies = [ "log", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -6517,7 +6287,7 @@ dependencies = [ "rand 0.8.5", "rayon", "sphinx-packet", - "thiserror 2.0.11", + "thiserror 2.0.12", "x25519-dalek", "zeroize", ] @@ -6527,6 +6297,7 @@ name = "nym-pemstore" version = "0.3.0" dependencies = [ "pem", + "tracing", ] [[package]] @@ -6539,12 +6310,13 @@ dependencies = [ "bip39", "bytecodec", "bytes", + "clap", "dashmap", "dirs", "dotenvy", "futures", "hex", - "http 1.2.0", + "http 1.3.1", "httpcodec", "log", "nym-bandwidth-controller", @@ -6562,6 +6334,7 @@ dependencies = [ "nym-socks5-client-core", "nym-socks5-requests", "nym-sphinx", + "nym-sphinx-addressing", "nym-statistics-common", "nym-task", "nym-topology", @@ -6572,7 +6345,8 @@ dependencies = [ "reqwest 0.12.4", "serde", "tap", - "thiserror 2.0.11", + "tempfile", + "thiserror 2.0.12", "tokio", "tokio-stream", "tokio-util", @@ -6599,7 +6373,9 @@ dependencies = [ name = "nym-service-provider-requests-common" version = "0.1.0" dependencies = [ + "bincode", "serde", + "thiserror 2.0.12", ] [[package]] @@ -6615,13 +6391,13 @@ dependencies = [ "nym-sphinx-anonymous-replies", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", ] [[package]] name = "nym-socks5-client" -version = "1.1.48" +version = "1.1.51" dependencies = [ "bs58", "clap", @@ -6645,7 +6421,7 @@ dependencies = [ "serde", "serde_json", "tap", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "url", @@ -6679,7 +6455,7 @@ dependencies = [ "schemars", "serde", "tap", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "url", ] @@ -6711,7 +6487,7 @@ dependencies = [ "serde", "serde_json", "tap", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -6736,7 +6512,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rand_distr", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", ] @@ -6754,7 +6530,7 @@ dependencies = [ "nym-topology", "rand 0.8.5", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "zeroize", ] @@ -6766,7 +6542,7 @@ dependencies = [ "nym-sphinx-types", "rand 0.8.5", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -6783,7 +6559,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "wasm-bindgen", ] @@ -6800,7 +6576,7 @@ dependencies = [ "nym-sphinx-types", "rand 0.8.5", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "utoipa", ] @@ -6818,7 +6594,7 @@ dependencies = [ "nym-sphinx-types", "nym-topology", "rand 0.8.5", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -6829,7 +6605,7 @@ dependencies = [ "nym-sphinx-addressing", "nym-sphinx-params", "nym-sphinx-types", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -6844,7 +6620,7 @@ dependencies = [ "nym-sphinx-forwarding", "nym-sphinx-params", "nym-sphinx-types", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tokio-util", ] @@ -6856,7 +6632,7 @@ dependencies = [ "nym-crypto", "nym-sphinx-types", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -6865,7 +6641,7 @@ version = "0.1.0" dependencies = [ "nym-sphinx-addressing", "nym-sphinx-types", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -6874,7 +6650,7 @@ version = "0.2.0" dependencies = [ "nym-outfox", "sphinx-packet", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -6893,7 +6669,7 @@ dependencies = [ "sha2 0.10.8", "si-scale", "sysinfo", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "wasmtimer", @@ -6910,7 +6686,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", "zeroize", ] @@ -6921,7 +6697,7 @@ dependencies = [ "cfg-if", "futures", "log", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tokio-util", "tracing", @@ -6963,7 +6739,7 @@ dependencies = [ "reqwest 0.12.4", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", "tsify", "wasm-bindgen", @@ -6977,7 +6753,7 @@ dependencies = [ "etherparse", "log", "nym-wireguard-types", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tokio-tun", ] @@ -6991,7 +6767,7 @@ dependencies = [ "cosmwasm-std", "eyre", "hmac", - "itertools 0.13.0", + "itertools 0.14.0", "log", "nym-config", "nym-crypto", @@ -7005,7 +6781,7 @@ dependencies = [ "sha2 0.10.8", "strum 0.26.3", "tempfile", - "thiserror 2.0.11", + "thiserror 2.0.12", "ts-rs", "url", "utoipa", @@ -7031,7 +6807,7 @@ dependencies = [ "eyre", "flate2", "futures", - "itertools 0.13.0", + "itertools 0.14.0", "nym-api-requests", "nym-coconut-bandwidth-contract-common", "nym-coconut-dkg-common", @@ -7050,9 +6826,9 @@ dependencies = [ "reqwest 0.12.4", "serde", "serde_json", - "sha2 0.9.9", + "sha2 0.10.8", "tendermint-rpc", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -7071,7 +6847,7 @@ dependencies = [ "clap", "cosmwasm-std", "futures", - "humantime 2.1.0", + "humantime 2.2.0", "humantime-serde", "nym-bin-common", "nym-coconut-bandwidth-contract-common", @@ -7096,7 +6872,7 @@ dependencies = [ "serde_with", "sha2 0.10.8", "sqlx", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -7110,12 +6886,12 @@ version = "0.1.0" dependencies = [ "bytes", "futures", - "humantime 2.1.0", + "humantime 2.2.0", "nym-crypto", "nym-task", "nym-validator-client", "rand 0.8.5", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tokio-util", @@ -7133,7 +6909,7 @@ dependencies = [ "nym-contracts-common", "nym-mixnet-contract-common", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "ts-rs", ] @@ -7154,7 +6930,7 @@ dependencies = [ "serde", "serde-wasm-bindgen 0.6.5", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tsify", "wasm-bindgen", @@ -7201,7 +6977,7 @@ dependencies = [ "nym-node-metrics", "nym-task", "nym-wireguard-types", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", @@ -7220,13 +6996,13 @@ dependencies = [ "nym-network-defaults", "rand 0.8.5", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "x25519-dalek", ] [[package]] name = "nymvisor" -version = "0.1.13" +version = "0.1.16" dependencies = [ "anyhow", "bytes", @@ -7235,7 +7011,7 @@ dependencies = [ "flate2", "futures", "hex", - "humantime 2.1.0", + "humantime 2.2.0", "humantime-serde", "nix 0.27.1", "nym-async-file-watcher", @@ -7247,7 +7023,7 @@ dependencies = [ "serde_json", "sha2 0.10.8", "tar", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -7256,7 +7032,7 @@ dependencies = [ [[package]] name = "nyx-chain-watcher" -version = "0.1.11" +version = "0.1.14" dependencies = [ "anyhow", "async-trait", @@ -7266,17 +7042,14 @@ dependencies = [ "nym-bin-common", "nym-config", "nym-network-defaults", - "nym-node-requests", "nym-task", "nym-validator-client", "nyxd-scraper", "reqwest 0.12.4", - "rocket", "schemars", "serde", - "serde_json", "sqlx", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tokio-util", @@ -7297,13 +7070,13 @@ dependencies = [ "cosmrs 0.21.1", "eyre", "futures", - "humantime 2.1.0", + "humantime 2.2.0", "serde", "sha2 0.10.8", "sqlx", "tendermint 0.40.1", "tendermint-rpc", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", @@ -7321,23 +7094,11 @@ dependencies = [ "memchr", ] -[[package]] -name = "okapi" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64853d7ab065474e87696f7601cee817d200e86c42e04004e005cb3e20c3c5" -dependencies = [ - "log", - "schemars", - "serde", - "serde_json", -] - [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" [[package]] name = "oorandom" @@ -7541,29 +7302,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pear" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" -dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi", -] - -[[package]] -name = "pear_codegen" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.98", -] - [[package]] name = "peg" version = "0.8.4" @@ -7624,7 +7362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 2.0.11", + "thiserror 2.0.12", "ucd-trie", ] @@ -7678,6 +7416,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ + "phf_macros", "phf_shared", ] @@ -7701,6 +7440,19 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "phf_shared" version = "0.11.3" @@ -7712,18 +7464,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", @@ -7848,6 +7600,15 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "postgres-protocol" version = "0.6.8" @@ -7949,19 +7710,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "version_check", - "yansi", -] - [[package]] name = "prometheus" version = "0.13.4" @@ -8109,9 +7857,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -8199,24 +7947,6 @@ dependencies = [ "rand 0.8.5", ] -[[package]] -name = "rand_pcg" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "rand_seeder" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2890aaef0aa82719a50e808de264f9484b74b442e1a3a0e5ee38243ac40bdb" -dependencies = [ - "rand_core 0.6.4", -] - [[package]] name = "rayon" version = "1.10.0" @@ -8257,26 +7987,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "ref-cast" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - [[package]] name = "regex" version = "1.11.1" @@ -8373,7 +8083,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.2.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "hyper 1.6.0", @@ -8414,7 +8124,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "359fc315ed556eb0e42ce74e76f4b1cd807b50fa6307f3de4e51f92dbe86e2d5" dependencies = [ "lazy_static", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -8439,9 +8149,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.9" +version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24" +checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee" dependencies = [ "cc", "cfg-if", @@ -8499,138 +8209,11 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "rocket" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e7bb57ccb26670d73b6a47396c83139447b9e7878cab627fdfe9ea8da489150" -dependencies = [ - "async-stream", - "async-trait", - "atomic 0.5.3", - "binascii", - "bytes", - "either", - "figment", - "futures", - "indexmap 2.7.1", - "log", - "memchr", - "multer 2.1.0", - "num_cpus", - "parking_lot", - "pin-project-lite", - "rand 0.8.5", - "ref-cast", - "rocket_codegen", - "rocket_http", - "serde", - "serde_json", - "state", - "tempfile", - "time", - "tokio", - "tokio-stream", - "tokio-util", - "ubyte", - "version_check", - "yansi", -] - -[[package]] -name = "rocket_codegen" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2238066abf75f21be6cd7dc1a09d5414a671f4246e384e49fe3f8a4936bd04c" -dependencies = [ - "devise", - "glob", - "indexmap 2.7.1", - "proc-macro2", - "quote", - "rocket_http", - "syn 2.0.98", - "unicode-xid", - "version_check", -] - -[[package]] -name = "rocket_cors" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfac3a1df83f8d4fc96aa41dba3b86c786417b7fc0f52ec76295df2ba781aa69" -dependencies = [ - "http 0.2.12", - "log", - "regex", - "rocket", - "serde", - "serde_derive", - "unicase", - "unicase_serde", - "url", -] - -[[package]] -name = "rocket_http" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a1663694d059fe5f943ea5481363e48050acedd241d46deb2e27f71110389e" -dependencies = [ - "cookie", - "either", - "futures", - "http 0.2.12", - "hyper 0.14.32", - "indexmap 2.7.1", - "log", - "memchr", - "pear", - "percent-encoding", - "pin-project-lite", - "ref-cast", - "serde", - "smallvec", - "stable-pattern", - "state", - "time", - "tokio", - "uncased", -] - -[[package]] -name = "rocket_okapi" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e059407ecef9ee2f071fc971e10444fcf942149deb028879d6d8ca61a7ce9edc" -dependencies = [ - "log", - "okapi", - "rocket", - "rocket_okapi_codegen", - "schemars", - "serde", - "serde_json", -] - -[[package]] -name = "rocket_okapi_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfb96114e69e5d7f80bfa0948cbc0120016e9b460954abe9eed37e9a2ad3f999" -dependencies = [ - "darling 0.13.4", - "proc-macro2", - "quote", - "rocket_http", - "syn 1.0.109", -] - [[package]] name = "rs_merkle" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b241d2e59b74ef9e98d94c78c47623d04c8392abaf82014dfd372a16041128f" +checksum = "bb09b49230ba22e8c676e7b75dfe2887dea8121f18b530ae0ba519ce442d2b21" dependencies = [ "sha2 0.10.8", ] @@ -8732,7 +8315,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.25", + "semver 1.0.26", ] [[package]] @@ -8744,7 +8327,20 @@ dependencies = [ "bitflags 2.8.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657" +dependencies = [ + "bitflags 2.8.0", + "errno", + "libc", + "linux-raw-sys 0.9.2", "windows-sys 0.59.0", ] @@ -8876,9 +8472,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" dependencies = [ "dyn-clone", "indexmap 1.9.3", @@ -8890,9 +8486,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" dependencies = [ "proc-macro2", "quote", @@ -9020,9 +8616,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" dependencies = [ "serde", ] @@ -9035,9 +8631,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -9075,18 +8671,18 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.15" +version = "0.11.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -9117,9 +8713,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.139" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -9140,7 +8736,7 @@ dependencies = [ "serde_json", "serde_json_path_core", "serde_json_path_macros", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -9152,7 +8748,7 @@ dependencies = [ "inventory", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -9189,9 +8785,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", @@ -9243,7 +8839,7 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" dependencies = [ - "darling 0.20.10", + "darling", "proc-macro2", "quote", "syn 2.0.98", @@ -9462,9 +9058,9 @@ dependencies = [ [[package]] name = "sphinx-packet" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535f2c430778bf59c22249fcc1ed6d384129eb2f0f694706015d636c688f9ac6" +checksum = "c23047e0cf36ff6904603f499fd13153425cdf5ba47bfbaedbc999da0bd92f4e" dependencies = [ "aes", "arrayref", @@ -9729,30 +9325,12 @@ dependencies = [ "tokio", ] -[[package]] -name = "stable-pattern" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" -dependencies = [ - "memchr", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "state" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8" -dependencies = [ - "loom 0.5.6", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -9795,12 +9373,6 @@ dependencies = [ "unicode-properties", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -9983,15 +9555,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.17.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" +checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600" dependencies = [ - "cfg-if", "fastrand 2.3.0", "getrandom 0.3.1", "once_cell", - "rustix", + "rustix 1.0.1", "windows-sys 0.59.0", ] @@ -10119,7 +9690,7 @@ dependencies = [ "pin-project", "rand 0.8.5", "reqwest 0.11.27", - "semver 1.0.25", + "semver 1.0.26", "serde", "serde_bytes", "serde_json", @@ -10187,7 +9758,7 @@ dependencies = [ "serde_json", "sqlx", "tempfile", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tokio", "toml 0.8.20", @@ -10216,11 +9787,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -10236,9 +9807,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", @@ -10279,9 +9850,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" dependencies = [ "deranged", "itoa", @@ -10297,15 +9868,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" dependencies = [ "num-conv", "time-core", @@ -10348,9 +9919,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.43.0" +version = "1.44.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" dependencies = [ "backtrace", "bytes", @@ -10499,9 +10070,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" dependencies = [ "bytes", "futures-core", @@ -10631,7 +10202,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.2.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "http-range-header", @@ -10810,7 +10381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e640d9b0964e9d39df633548591090ab92f7a4567bc31d3891af23471a3365c6" dependencies = [ "lazy_static", - "thiserror 2.0.11", + "thiserror 2.0.12", "ts-rs-macros", ] @@ -10897,7 +10468,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.2.0", + "http 1.3.1", "httparse", "log", "rand 0.8.5", @@ -10915,47 +10486,18 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" -[[package]] -name = "ubyte" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f720def6ce1ee2fc44d40ac9ed6d3a59c361c80a75a7aa8e75bb9baed31cf2ea" -dependencies = [ - "serde", -] - [[package]] name = "ucd-trie" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" -[[package]] -name = "uncased" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" -dependencies = [ - "serde", - "version_check", -] - [[package]] name = "unicase" version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" -[[package]] -name = "unicase_serde" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef53697679d874d69f3160af80bc28de12730a985d57bdf2b47456ccb8b11f1" -dependencies = [ - "serde", - "unicase", -] - [[package]] name = "unicode-bidi" version = "0.3.18" @@ -11267,14 +10809,32 @@ dependencies = [ [[package]] name = "uuid" -version = "1.13.2" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ "getrandom 0.3.1", "serde", ] +[[package]] +name = "validator-status-check" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "comfy-table", + "nym-bin-common", + "nym-network-defaults", + "nym-validator-client", + "serde", + "serde_json", + "strum 0.26.3", + "time", + "tokio", + "tracing", +] + [[package]] name = "valuable" version = "0.1.1" @@ -11471,7 +11031,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "tsify", "url", @@ -11493,7 +11053,7 @@ dependencies = [ "nym-store-cipher", "serde", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.11", + "thiserror 2.0.12", "wasm-bindgen", "wasm-utils", ] @@ -11642,15 +11202,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows" version = "0.57.0" @@ -11749,6 +11300,12 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "windows-link" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" + [[package]] name = "windows-result" version = "0.1.2" @@ -12069,8 +11626,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" dependencies = [ "libc", - "linux-raw-sys", - "rustix", + "linux-raw-sys 0.4.15", + "rustix 0.38.44", ] [[package]] @@ -12078,9 +11635,6 @@ name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" -dependencies = [ - "is-terminal", -] [[package]] name = "yoke" @@ -12170,9 +11724,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.6.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] @@ -12212,9 +11766,9 @@ dependencies = [ [[package]] name = "zip" -version = "2.2.2" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45" +checksum = "938cc23ac49778ac8340e366ddc422b2227ea176edb447e23fc0627608dddadd" dependencies = [ "arbitrary", "crc32fast", @@ -12223,7 +11777,7 @@ dependencies = [ "flate2", "indexmap 2.7.1", "memchr", - "thiserror 2.0.11", + "thiserror 2.0.12", "zopfli", ] @@ -12237,7 +11791,6 @@ dependencies = [ "getrandom 0.2.15", "js-sys", "nym-bin-common", - "nym-coconut", "nym-compact-ecash", "nym-credentials", "nym-crypto", @@ -12245,7 +11798,7 @@ dependencies = [ "rand 0.8.5", "reqwest 0.12.4", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tsify", "uuid", diff --git a/Cargo.toml b/Cargo.toml index 5713f5888d..cee2652d9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,6 @@ members = [ "common/nym-id", "common/nym-metrics", "common/nym_offline_compact_ecash", - "common/nymcoconut", "common/nymsphinx", "common/nymsphinx/acknowledgements", "common/nymsphinx/addressing", @@ -99,9 +98,9 @@ members = [ "common/wireguard", "common/wireguard-types", "documentation/autodoc", - "explorer-api", - "explorer-api/explorer-api-requests", - "explorer-api/explorer-client", + # "explorer-api", + # "explorer-api/explorer-api-requests", + # "explorer-api/explorer-client", "gateway", "integrations/bity", "nym-api", @@ -138,7 +137,7 @@ members = [ "tools/internal/testnet-manager", "tools/internal/testnet-manager", "tools/internal/testnet-manager/dkg-bypass-contract", - "tools/internal/testnet-manager/dkg-bypass-contract", + "tools/internal/testnet-manager/dkg-bypass-contract", "tools/internal/validator-status-check", "tools/nym-cli", "tools/nym-id-cli", "tools/nym-nr-query", @@ -154,7 +153,6 @@ members = [ default-members = [ "clients/native", "clients/socks5", - "explorer-api", "nym-api", "nym-credential-proxy/nym-credential-proxy", "nym-node", @@ -192,10 +190,10 @@ aes = "0.8.1" aes-gcm = "0.10.1" aes-gcm-siv = "0.11.1" ammonia = "4" -anyhow = "1.0.95" +anyhow = "1.0.97" arc-swap = "1.7.1" argon2 = "0.5.0" -async-trait = "0.1.86" +async-trait = "0.1.88" axum = "0.7.5" axum-client-ip = "0.6.1" axum-extra = "0.9.4" @@ -206,24 +204,24 @@ bincode = "1.3.3" bip39 = { version = "2.0.0", features = ["zeroize"] } bit-vec = "0.7.0" # can we unify those? bitvec = "1.0.0" -blake3 = "1.5.5" +blake3 = "1.6.1" bloomfilter = "1.0.14" bs58 = "0.5.1" bytecodec = "0.4.15" -bytes = "1.7.2" +bytes = "1.10.1" cargo_metadata = "0.18.1" -celes = "2.5.0" +celes = "2.6.0" cfg-if = "1.0.0" chacha20 = "0.9.0" chacha20poly1305 = "0.10.1" -chrono = "0.4.39" +chrono = "0.4.40" cipher = "0.4.3" -clap = "4.5.30" +clap = "4.5.32" clap_complete = "4.5" clap_complete_fig = "4.5" colored = "2.2" comfy-table = "7.1.4" -console = "0.15.10" +console = "0.15.11" console-subscriber = "0.1.1" console_error_panic_hook = "0.1" const-str = "0.5.6" @@ -242,29 +240,30 @@ doc-comment = "0.3" dotenvy = "0.15.6" ecdsa = "0.16" ed25519-dalek = "2.1" -env_logger = "0.11.6" +encoding_rs = "0.8.35" +env_logger = "0.11.7" envy = "0.4" etherparse = "0.13.0" eyre = "0.6.9" fastrand = "2.1.1" -flate2 = "1.0.35" +flate2 = "1.1.0" futures = "0.3.31" futures-util = "0.3" generic-array = "0.14.7" getrandom = "0.2.10" -getset = "0.1.4" +getset = "0.1.5" handlebars = "3.5.5" headers = "0.4.0" hex = "0.4.3" hex-literal = "0.3.3" -hickory-resolver = "0.24.3" +hickory-resolver = "0.24.4" hkdf = "0.12.3" hmac = "0.12.1" http = "1" http-body-util = "0.1" httpcodec = "0.2.3" human-repr = "1.1.0" -humantime = "2.1.0" +humantime = "2.2.0" humantime-serde = "1.1.1" hyper = "1.6.0" hyper-util = "0.1" @@ -273,7 +272,7 @@ inquire = "0.6.2" ip_network = "0.4.1" ipnetwork = "0.20" isocountry = "0.3.2" -itertools = "0.13.0" +itertools = "0.14.0" k256 = "0.13" lazy_static = "1.5.0" ledger-transport = "0.10.0" @@ -285,7 +284,7 @@ moka = { version = "0.12", features = ["future"] } nix = "0.27.1" notify = "5.1.0" okapi = "0.7.0" -once_cell = "1.20.3" +once_cell = "1.21.1" opentelemetry = "0.19.0" opentelemetry-jaeger = "0.18.0" parking_lot = "0.12.3" @@ -308,21 +307,21 @@ reqwest = { version = "0.12.4", default-features = false } rocket = "0.5.0" rocket_cors = "0.6.0" rocket_okapi = "0.8.0" -rs_merkle = "1.4.2" +rs_merkle = "1.5.0" safer-ffi = "0.1.13" -schemars = "0.8.21" -semver = "1.0.25" -serde = "1.0.217" -serde_bytes = "0.11.15" +schemars = "0.8.22" +semver = "1.0.26" +serde = "1.0.219" +serde_bytes = "0.11.17" serde_derive = "1.0" -serde_json = "1.0.138" +serde_json = "1.0.140" serde_json_path = "0.7.2" serde_repr = "0.1" serde_with = "3.9.0" serde_yaml = "0.9.25" sha2 = "0.10.8" si-scale = "0.2.3" -sphinx-packet = "0.3.1" +sphinx-packet = "=0.3.2" sqlx = "0.7.4" strum = "0.26" strum_macros = "0.26" @@ -331,16 +330,16 @@ syn = "1" sysinfo = "0.33.0" tap = "1.0.1" tar = "0.4.44" -tempfile = "3.15" +tempfile = "3.19" thiserror = "2.0" -time = "0.3.37" -tokio = "1.43" +time = "0.3.39" +tokio = "1.44" tokio-postgres = "0.7" tokio-stream = "0.1.17" tokio-test = "0.4.4" tokio-tun = "0.11.5" tokio-tungstenite = { version = "0.20.1" } -tokio-util = "0.7.13" +tokio-util = "0.7.14" toml = "0.8.20" tower = "0.5.2" tower-http = "0.5.2" @@ -362,7 +361,7 @@ vergen = { version = "=8.3.1", default-features = false } walkdir = "2" wasm-bindgen-test = "0.3.49" x25519-dalek = "2.0.0" -zeroize = "1.6.0" +zeroize = "1.8.1" prometheus = { version = "0.13.0" } @@ -370,9 +369,9 @@ prometheus = { version = "0.13.0" } # unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork # as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm # plus to make our live easier we need serde support from https://github.com/zkcrypto/bls12_381/pull/125 -bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", default-features = false, branch = "temp/experimental-serdect" } +bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", default-features = false, branch = "temp/experimental-serdect-updated" } group = { version = "0.13.0", default-features = false } -ff = { version = "0.13.0", default-features = false } +ff = { version = "0.13.1", default-features = false } subtle = "2.5.0" # cosmwasm-related @@ -403,7 +402,7 @@ prost = { version = "0.13", default-features = false } gloo-utils = "0.2.0" gloo-net = "0.6.0" -indexed_db_futures = "0.6.0" +indexed_db_futures = "0.6.1" js-sys = "0.3.76" serde-wasm-bindgen = "0.6.5" tsify = "0.4.5" @@ -438,3 +437,13 @@ opt-level = 'z' [profile.release.package.mix-fetch-wasm] # lto = true opt-level = 'z' + +[workspace.lints.clippy] +unwrap_used = "deny" +expect_used = "deny" +todo = "deny" +dbg_macro = "deny" +exit = "deny" +panic = "deny" +unimplemented = "deny" +unreachable = "deny" diff --git a/README.md b/README.md index 54e8ff0734..3d2b53feea 100644 --- a/README.md +++ b/README.md @@ -67,3 +67,13 @@ As a general approach, licensing is as follows this pattern: - documentation is Apache 2.0 or CC0-1.0 Nym Node Operators and Validators Terms and Conditions can be found [here](https://nym.com/operators-validators-terms). + +## Getting Started + +```bash +yarn install +``` + +```bash +yarn build +``` diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 2b9b59bf4c..3211e70146 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.48" +version = "1.1.51" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index bf40164e8d..b764847432 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.48" +version = "1.1.51" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" diff --git a/clippy.toml b/clippy.toml index 0358cdb508..9b4ca613ab 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,2 +1,3 @@ allow-unwrap-in-tests = true allow-expect-in-tests = true +allow-panic-in-tests = true \ No newline at end of file diff --git a/common/authenticator-requests/src/lib.rs b/common/authenticator-requests/src/lib.rs index ed987a9a50..351a9b443c 100644 --- a/common/authenticator-requests/src/lib.rs +++ b/common/authenticator-requests/src/lib.rs @@ -6,14 +6,15 @@ pub mod v1; pub mod v2; pub mod v3; pub mod v4; +pub mod v5; mod error; mod util; pub use error::Error; -pub use v4 as latest; +pub use v5 as latest; -pub const CURRENT_VERSION: u8 = 4; +pub const CURRENT_VERSION: u8 = 5; fn make_bincode_serializer() -> impl bincode::Options { use bincode::Options; diff --git a/common/authenticator-requests/src/traits.rs b/common/authenticator-requests/src/traits.rs index 7c7064d174..baaaf8b422 100644 --- a/common/authenticator-requests/src/traits.rs +++ b/common/authenticator-requests/src/traits.rs @@ -8,8 +8,8 @@ use nym_sphinx::addressing::clients::Recipient; use nym_wireguard_types::PeerPublicKey; use crate::{ - v1, v2, v3, - v4::{self, registration::IpPair}, + v1, v2, v3, v4, + v5::{self, registration::IpPair}, Error, }; @@ -19,6 +19,7 @@ pub enum AuthenticatorVersion { V2, V3, V4, + V5, UNKNOWN, } @@ -34,6 +35,8 @@ impl From for AuthenticatorVersion { AuthenticatorVersion::V3 } else if value.version == v4::VERSION { AuthenticatorVersion::V4 + } else if value.version == v5::VERSION { + AuthenticatorVersion::V5 } else { AuthenticatorVersion::UNKNOWN } @@ -68,6 +71,12 @@ impl InitMessage for v4::registration::InitMessage { } } +impl InitMessage for v5::registration::InitMessage { + fn pub_key(&self) -> PeerPublicKey { + self.pub_key + } +} + pub trait FinalMessage { fn pub_key(&self) -> PeerPublicKey; fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error>; @@ -138,6 +147,24 @@ impl FinalMessage for v4::registration::FinalMessage { self.gateway_client.verify(private_key, nonce) } + fn private_ips(&self) -> IpPair { + self.gateway_client.private_ips.into() + } + + fn credential(&self) -> Option { + self.credential.clone() + } +} + +impl FinalMessage for v5::registration::FinalMessage { + fn pub_key(&self) -> PeerPublicKey { + self.gateway_client.pub_key + } + + fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> { + self.gateway_client.verify(private_key, nonce) + } + fn private_ips(&self) -> IpPair { self.gateway_client.private_ips } @@ -182,29 +209,39 @@ impl TopUpMessage for v4::topup::TopUpMessage { } } +impl TopUpMessage for v5::topup::TopUpMessage { + fn pub_key(&self) -> PeerPublicKey { + self.pub_key + } + + fn credential(&self) -> CredentialSpendingData { + self.credential.clone() + } +} + pub enum AuthenticatorRequest { Initial { msg: Box, protocol: Protocol, - reply_to: Recipient, + reply_to: Option, request_id: u64, }, Final { msg: Box, protocol: Protocol, - reply_to: Recipient, + reply_to: Option, request_id: u64, }, QueryBandwidth { msg: Box, protocol: Protocol, - reply_to: Recipient, + reply_to: Option, request_id: u64, }, TopUpBandwidth { msg: Box, protocol: Protocol, - reply_to: Recipient, + reply_to: Option, request_id: u64, }, } @@ -218,7 +255,7 @@ impl From for AuthenticatorRequest { version: value.version, service_provider_type: ServiceProviderType::Authenticator, }, - reply_to: value.reply_to, + reply_to: Some(value.reply_to), request_id: value.request_id, }, v1::request::AuthenticatorRequestData::Final(gateway_client) => Self::Final { @@ -227,7 +264,7 @@ impl From for AuthenticatorRequest { version: value.version, service_provider_type: ServiceProviderType::Authenticator, }, - reply_to: value.reply_to, + reply_to: Some(value.reply_to), request_id: value.request_id, }, v1::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => { @@ -237,7 +274,7 @@ impl From for AuthenticatorRequest { version: value.version, service_provider_type: ServiceProviderType::Authenticator, }, - reply_to: value.reply_to, + reply_to: Some(value.reply_to), request_id: value.request_id, } } @@ -251,20 +288,20 @@ impl From for AuthenticatorRequest { v2::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial { msg: Box::new(init_message), protocol: value.protocol, - reply_to: value.reply_to, + reply_to: Some(value.reply_to), request_id: value.request_id, }, v2::request::AuthenticatorRequestData::Final(final_message) => Self::Final { msg: final_message, protocol: value.protocol, - reply_to: value.reply_to, + reply_to: Some(value.reply_to), request_id: value.request_id, }, v2::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => { Self::QueryBandwidth { msg: Box::new(peer_public_key), protocol: value.protocol, - reply_to: value.reply_to, + reply_to: Some(value.reply_to), request_id: value.request_id, } } @@ -278,20 +315,20 @@ impl From for AuthenticatorRequest { v3::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial { msg: Box::new(init_message), protocol: value.protocol, - reply_to: value.reply_to, + reply_to: Some(value.reply_to), request_id: value.request_id, }, v3::request::AuthenticatorRequestData::Final(final_message) => Self::Final { msg: final_message, protocol: value.protocol, - reply_to: value.reply_to, + reply_to: Some(value.reply_to), request_id: value.request_id, }, v3::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => { Self::QueryBandwidth { msg: Box::new(peer_public_key), protocol: value.protocol, - reply_to: value.reply_to, + reply_to: Some(value.reply_to), request_id: value.request_id, } } @@ -299,7 +336,7 @@ impl From for AuthenticatorRequest { Self::TopUpBandwidth { msg: top_up_message, protocol: value.protocol, - reply_to: value.reply_to, + reply_to: Some(value.reply_to), request_id: value.request_id, } } @@ -313,20 +350,20 @@ impl From for AuthenticatorRequest { v4::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial { msg: Box::new(init_message), protocol: value.protocol, - reply_to: value.reply_to, + reply_to: Some(value.reply_to), request_id: value.request_id, }, v4::request::AuthenticatorRequestData::Final(final_message) => Self::Final { msg: final_message, protocol: value.protocol, - reply_to: value.reply_to, + reply_to: Some(value.reply_to), request_id: value.request_id, }, v4::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => { Self::QueryBandwidth { msg: Box::new(peer_public_key), protocol: value.protocol, - reply_to: value.reply_to, + reply_to: Some(value.reply_to), request_id: value.request_id, } } @@ -334,7 +371,42 @@ impl From for AuthenticatorRequest { Self::TopUpBandwidth { msg: top_up_message, protocol: value.protocol, - reply_to: value.reply_to, + reply_to: Some(value.reply_to), + request_id: value.request_id, + } + } + } + } +} + +impl From for AuthenticatorRequest { + fn from(value: v5::request::AuthenticatorRequest) -> Self { + match value.data { + v5::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial { + msg: Box::new(init_message), + protocol: value.protocol, + reply_to: None, + request_id: value.request_id, + }, + v5::request::AuthenticatorRequestData::Final(final_message) => Self::Final { + msg: final_message, + protocol: value.protocol, + reply_to: None, + request_id: value.request_id, + }, + v5::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => { + Self::QueryBandwidth { + msg: Box::new(peer_public_key), + protocol: value.protocol, + reply_to: None, + request_id: value.request_id, + } + } + v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => { + Self::TopUpBandwidth { + msg: top_up_message, + protocol: value.protocol, + reply_to: None, request_id: value.request_id, } } diff --git a/common/authenticator-requests/src/v5/conversion.rs b/common/authenticator-requests/src/v5/conversion.rs new file mode 100644 index 0000000000..e3f7da0830 --- /dev/null +++ b/common/authenticator-requests/src/v5/conversion.rs @@ -0,0 +1,478 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_service_provider_requests_common::{Protocol, ServiceProviderType}; + +use crate::{v4, v5}; + +impl From for v5::request::AuthenticatorRequest { + fn from(authenticator_request: v4::request::AuthenticatorRequest) -> Self { + Self { + protocol: Protocol { + version: 5, + service_provider_type: ServiceProviderType::Authenticator, + }, + data: authenticator_request.data.into(), + request_id: authenticator_request.request_id, + } + } +} + +impl From for v5::request::AuthenticatorRequestData { + fn from(authenticator_request_data: v4::request::AuthenticatorRequestData) -> Self { + match authenticator_request_data { + v4::request::AuthenticatorRequestData::Initial(init_msg) => { + v5::request::AuthenticatorRequestData::Initial(init_msg.into()) + } + v4::request::AuthenticatorRequestData::Final(final_msg) => { + v5::request::AuthenticatorRequestData::Final(Box::new((*final_msg).into())) + } + v4::request::AuthenticatorRequestData::QueryBandwidth(pub_key) => { + v5::request::AuthenticatorRequestData::QueryBandwidth(pub_key) + } + v4::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => { + v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message.into()) + } + } + } +} + +impl From for v5::registration::InitMessage { + fn from(init_msg: v4::registration::InitMessage) -> Self { + Self { + pub_key: init_msg.pub_key, + } + } +} + +impl From for v5::registration::FinalMessage { + fn from(final_msg: v4::registration::FinalMessage) -> Self { + Self { + gateway_client: final_msg.gateway_client.into(), + credential: final_msg.credential, + } + } +} + +impl From for v5::registration::GatewayClient { + fn from(gateway_client: v4::registration::GatewayClient) -> Self { + Self { + pub_key: gateway_client.pub_key, + private_ips: gateway_client.private_ips.into(), + mac: gateway_client.mac.into(), + } + } +} + +impl From for v4::registration::GatewayClient { + fn from(gateway_client: v5::registration::GatewayClient) -> Self { + Self { + pub_key: gateway_client.pub_key, + private_ips: gateway_client.private_ips.into(), + mac: gateway_client.mac.into(), + } + } +} + +impl From for v5::registration::ClientMac { + fn from(client_mac: v4::registration::ClientMac) -> Self { + Self::new((*client_mac).clone()) + } +} + +impl From for v4::registration::ClientMac { + fn from(client_mac: v5::registration::ClientMac) -> Self { + Self::new((*client_mac).clone()) + } +} + +impl From> for Box { + fn from(top_up_message: Box) -> Self { + Box::new(v5::topup::TopUpMessage { + pub_key: top_up_message.pub_key, + credential: top_up_message.credential, + }) + } +} + +impl From for v5::response::AuthenticatorResponse { + fn from(value: v4::response::AuthenticatorResponse) -> Self { + Self { + protocol: Protocol { + version: 5, + service_provider_type: value.protocol.service_provider_type, + }, + data: value.data.into(), + } + } +} + +impl From for v5::response::AuthenticatorResponseData { + fn from(authenticator_response_data: v4::response::AuthenticatorResponseData) -> Self { + match authenticator_response_data { + v4::response::AuthenticatorResponseData::PendingRegistration(pending_response) => { + v5::response::AuthenticatorResponseData::PendingRegistration( + pending_response.into(), + ) + } + v4::response::AuthenticatorResponseData::Registered(registered_response) => { + v5::response::AuthenticatorResponseData::Registered(registered_response.into()) + } + v4::response::AuthenticatorResponseData::RemainingBandwidth( + remaining_bandwidth_response, + ) => v5::response::AuthenticatorResponseData::RemainingBandwidth( + remaining_bandwidth_response.into(), + ), + v4::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response) => { + v5::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response.into()) + } + } + } +} + +impl From for v5::response::RegisteredResponse { + fn from(value: v4::response::RegisteredResponse) -> Self { + Self { + request_id: value.request_id, + reply: value.reply.into(), + } + } +} + +impl From for v5::response::PendingRegistrationResponse { + fn from(value: v4::response::PendingRegistrationResponse) -> Self { + Self { + request_id: value.request_id, + reply: value.reply.into(), + } + } +} + +impl From for v5::registration::RegistrationData { + fn from(value: v4::registration::RegistrationData) -> Self { + Self { + nonce: value.nonce, + gateway_data: value.gateway_data.into(), + wg_port: value.wg_port, + } + } +} + +impl From for v4::registration::RegistrationData { + fn from(value: v5::registration::RegistrationData) -> Self { + Self { + nonce: value.nonce, + gateway_data: value.gateway_data.into(), + wg_port: value.wg_port, + } + } +} + +impl From for v5::response::RemainingBandwidthResponse { + fn from(value: v4::response::RemainingBandwidthResponse) -> Self { + Self { + request_id: value.request_id, + reply: value.reply.map(Into::into), + } + } +} + +impl From for v5::response::TopUpBandwidthResponse { + fn from(value: v4::response::TopUpBandwidthResponse) -> Self { + Self { + request_id: value.request_id, + reply: value.reply.into(), + } + } +} + +impl From for v5::registration::RegistredData { + fn from(value: v4::registration::RegistredData) -> Self { + Self { + pub_key: value.pub_key, + private_ips: value.private_ips.into(), + wg_port: value.wg_port, + } + } +} + +impl From for v5::registration::RemainingBandwidthData { + fn from(value: v4::registration::RemainingBandwidthData) -> Self { + Self { + available_bandwidth: value.available_bandwidth, + } + } +} + +impl From for v5::registration::IpPair { + fn from(value: v4::registration::IpPair) -> Self { + Self { + ipv4: value.ipv4, + ipv6: value.ipv6, + } + } +} + +impl From for v4::registration::IpPair { + fn from(value: v5::registration::IpPair) -> Self { + Self { + ipv4: value.ipv4, + ipv6: value.ipv6, + } + } +} + +#[cfg(test)] +mod tests { + use std::{ + net::{Ipv4Addr, Ipv6Addr}, + str::FromStr, + }; + + use nym_credentials_interface::CredentialSpendingData; + use nym_crypto::asymmetric::encryption::PrivateKey; + use nym_sphinx::addressing::Recipient; + use nym_wireguard_types::PeerPublicKey; + use x25519_dalek::PublicKey; + + use super::*; + use crate::{ + util::tests::{CREDENTIAL_BYTES, RECIPIENT}, + v4, + }; + + #[test] + fn upgrade_initial_req() { + let pub_key = PeerPublicKey::new(PublicKey::from([0; 32])); + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let (msg, _) = v4::request::AuthenticatorRequest::new_initial_request( + v4::registration::InitMessage::new(pub_key), + reply_to, + ); + let upgraded_msg = v5::request::AuthenticatorRequest::from(msg); + + assert_eq!( + upgraded_msg.protocol, + Protocol { + version: 5, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + upgraded_msg.data, + v5::request::AuthenticatorRequestData::Initial(v5::registration::InitMessage { + pub_key + }) + ); + } + + #[test] + fn upgrade_final_req() { + let mut rng = rand::thread_rng(); + + let local_secret = PrivateKey::new(&mut rng); + let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng); + let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap(); + let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap(); + let ips = v4::registration::IpPair::new(ipv4, ipv6); + let nonce = 42; + let gateway_client = v4::registration::GatewayClient::new( + &local_secret, + (&remote_secret).into(), + ips, + nonce, + ); + let credential = Some(CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap()); + let final_message = v4::registration::FinalMessage { + gateway_client: gateway_client.clone(), + credential: credential.clone(), + }; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let (msg, _) = + v4::request::AuthenticatorRequest::new_final_request(final_message, reply_to); + let upgraded_msg = v5::request::AuthenticatorRequest::from(msg); + + assert_eq!( + upgraded_msg.protocol, + Protocol { + version: 5, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + upgraded_msg.data, + v5::request::AuthenticatorRequestData::Final(Box::new( + v5::registration::FinalMessage { + gateway_client: v5::registration::GatewayClient::new( + &local_secret, + (&remote_secret).into(), + v5::registration::IpPair::new(ipv4, ipv6), + nonce + ), + credential + } + )) + ); + } + + #[test] + fn upgrade_query_req() { + let pub_key = PeerPublicKey::new(PublicKey::from([0; 32])); + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let (msg, _) = v4::request::AuthenticatorRequest::new_query_request(pub_key, reply_to); + let upgraded_msg = v5::request::AuthenticatorRequest::from(msg); + + assert_eq!( + upgraded_msg.protocol, + Protocol { + version: 5, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + upgraded_msg.data, + v5::request::AuthenticatorRequestData::QueryBandwidth(pub_key) + ); + } + + #[test] + fn upgrade_pending_reg_resp() { + let mut rng = rand::thread_rng(); + + let local_secret = PrivateKey::new(&mut rng); + let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng); + let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap(); + let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap(); + let ips = v4::registration::IpPair::new(ipv4, ipv6); + let nonce = 42; + let wg_port = 51822; + let gateway_data = v4::registration::GatewayClient::new( + &local_secret, + (&remote_secret).into(), + ips, + nonce, + ); + let registration_data = v4::registration::RegistrationData { + nonce, + gateway_data, + wg_port, + }; + let request_id = 123; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let msg = v4::response::AuthenticatorResponse::new_pending_registration_success( + registration_data, + request_id, + reply_to, + ); + let upgraded_msg = v5::response::AuthenticatorResponse::from(msg); + + assert_eq!( + upgraded_msg.protocol, + Protocol { + version: 5, + service_provider_type: ServiceProviderType::Authenticator + } + ); + + assert_eq!( + upgraded_msg.data, + v5::response::AuthenticatorResponseData::PendingRegistration( + v5::response::PendingRegistrationResponse { + request_id, + reply: v5::registration::RegistrationData { + nonce, + gateway_data: v5::registration::GatewayClient::new( + &local_secret, + (&remote_secret).into(), + v5::registration::IpPair::new(ipv4, ipv6), + nonce + ), + wg_port + } + } + ) + ); + } + + #[test] + fn upgrade_registered_resp() { + let pub_key = PeerPublicKey::new(PublicKey::from([0; 32])); + let ipv4 = Ipv4Addr::from_str("10.1.10.10").unwrap(); + let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap(); + let private_ips = v4::registration::IpPair::new(ipv4, ipv6); + let wg_port = 51822; + let registred_data = v4::registration::RegistredData { + pub_key, + private_ips, + wg_port, + }; + let request_id = 123; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let msg = v4::response::AuthenticatorResponse::new_registered( + registred_data, + reply_to, + request_id, + ); + let upgraded_msg = v5::response::AuthenticatorResponse::from(msg); + + assert_eq!( + upgraded_msg.protocol, + Protocol { + version: 5, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + upgraded_msg.data, + v5::response::AuthenticatorResponseData::Registered(v5::response::RegisteredResponse { + request_id, + reply: v5::registration::RegistredData { + wg_port, + pub_key, + private_ips: v5::registration::IpPair::new(ipv4, ipv6) + } + }) + ); + } + + #[test] + fn upgrade_remaining_bandwidth_resp() { + let available_bandwidth = 42; + let remaining_bandwidth_data = Some(v4::registration::RemainingBandwidthData { + available_bandwidth, + }); + let request_id = 123; + let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap(); + + let msg = v4::response::AuthenticatorResponse::new_remaining_bandwidth( + remaining_bandwidth_data, + reply_to, + request_id, + ); + let upgraded_msg = v5::response::AuthenticatorResponse::from(msg); + + assert_eq!( + upgraded_msg.protocol, + Protocol { + version: 5, + service_provider_type: ServiceProviderType::Authenticator + } + ); + assert_eq!( + upgraded_msg.data, + v5::response::AuthenticatorResponseData::RemainingBandwidth( + v5::response::RemainingBandwidthResponse { + request_id, + reply: Some(v5::registration::RemainingBandwidthData { + available_bandwidth, + }) + } + ) + ); + } +} diff --git a/common/authenticator-requests/src/v5/mod.rs b/common/authenticator-requests/src/v5/mod.rs new file mode 100644 index 0000000000..2f0df82353 --- /dev/null +++ b/common/authenticator-requests/src/v5/mod.rs @@ -0,0 +1,10 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod conversion; +pub mod registration; +pub mod request; +pub mod response; +pub mod topup; + +pub const VERSION: u8 = 5; diff --git a/common/authenticator-requests/src/v5/registration.rs b/common/authenticator-requests/src/v5/registration.rs new file mode 100644 index 0000000000..9bd5c49e44 --- /dev/null +++ b/common/authenticator-requests/src/v5/registration.rs @@ -0,0 +1,287 @@ +// -2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::Error; +use base64::{engine::general_purpose, Engine}; +use nym_credentials_interface::CredentialSpendingData; +use nym_network_defaults::constants::{WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6}; +use nym_wireguard_types::PeerPublicKey; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::time::SystemTime; +use std::{fmt, ops::Deref, str::FromStr}; + +#[cfg(feature = "verify")] +use hmac::{Hmac, Mac}; +#[cfg(feature = "verify")] +use nym_crypto::asymmetric::encryption::PrivateKey; +#[cfg(feature = "verify")] +use sha2::Sha256; + +pub type PendingRegistrations = HashMap; +pub type PrivateIPs = HashMap; + +#[cfg(feature = "verify")] +pub type HmacSha256 = Hmac; + +pub type Nonce = u64; +pub type Taken = Option; + +pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct IpPair { + pub ipv4: Ipv4Addr, + pub ipv6: Ipv6Addr, +} + +impl IpPair { + pub fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self { + IpPair { ipv4, ipv6 } + } +} + +impl From<(Ipv4Addr, Ipv6Addr)> for IpPair { + fn from((ipv4, ipv6): (Ipv4Addr, Ipv6Addr)) -> Self { + IpPair { ipv4, ipv6 } + } +} + +impl fmt::Display for IpPair { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "({}, {})", self.ipv4, self.ipv6) + } +} + +impl From for IpPair { + fn from(value: IpAddr) -> Self { + let (before_last_byte, last_byte) = match value { + std::net::IpAddr::V4(ipv4_addr) => (ipv4_addr.octets()[2], ipv4_addr.octets()[3]), + std::net::IpAddr::V6(ipv6_addr) => (ipv6_addr.octets()[14], ipv6_addr.octets()[15]), + }; + let last_bytes = ((before_last_byte as u16) << 8) | last_byte as u16; + let ipv4 = Ipv4Addr::new( + WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0], + WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1], + before_last_byte, + last_byte, + ); + let ipv6 = Ipv6Addr::new( + WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[0], + WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[1], + WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[2], + WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[3], + WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[4], + WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[5], + WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[6], + last_bytes, + ); + IpPair::new(ipv4, ipv6) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct InitMessage { + /// Base64 encoded x25519 public key + pub pub_key: PeerPublicKey, +} + +impl InitMessage { + pub fn new(pub_key: PeerPublicKey) -> Self { + InitMessage { pub_key } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct FinalMessage { + /// Gateway client data + pub gateway_client: GatewayClient, + + /// Ecash credential + pub credential: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct RegistrationData { + pub nonce: u64, + pub gateway_data: GatewayClient, + pub wg_port: u16, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct RegistredData { + pub pub_key: PeerPublicKey, + pub private_ips: IpPair, + pub wg_port: u16, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct RemainingBandwidthData { + pub available_bandwidth: i64, +} + +/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret. +/// Gateway/Nym node can then verify pub_key payload using the same process +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct GatewayClient { + /// Base64 encoded x25519 public key + pub pub_key: PeerPublicKey, + + /// Assigned private IPs (v4 and v6) + pub private_ips: IpPair, + + /// Sha256 hmac on the data (alongside the prior nonce) + pub mac: ClientMac, +} + +impl GatewayClient { + #[cfg(feature = "verify")] + pub fn new( + local_secret: &PrivateKey, + remote_public: x25519_dalek::PublicKey, + private_ips: IpPair, + nonce: u64, + ) -> Self { + // convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek + #[allow(clippy::expect_used)] + let static_secret = x25519_dalek::StaticSecret::from(local_secret.to_bytes()); + let local_public: x25519_dalek::PublicKey = (&static_secret).into(); + + let dh = static_secret.diffie_hellman(&remote_public); + + // TODO: change that to use our nym_crypto::hmac module instead + #[allow(clippy::expect_used)] + let mut mac = HmacSha256::new_from_slice(dh.as_bytes()) + .expect("x25519 shared secret is always 32 bytes long"); + + mac.update(local_public.as_bytes()); + mac.update(private_ips.to_string().as_bytes()); + mac.update(&nonce.to_le_bytes()); + + GatewayClient { + pub_key: PeerPublicKey::new(local_public), + private_ips, + mac: ClientMac(mac.finalize().into_bytes().to_vec()), + } + } + + // Reusable secret should be gateways Wireguard PK + // Client should perform this step when generating its payload, using its own WG PK + #[cfg(feature = "verify")] + pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> { + // convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek + #[allow(clippy::expect_used)] + let static_secret = x25519_dalek::StaticSecret::from(gateway_key.to_bytes()); + + let dh = static_secret.diffie_hellman(&self.pub_key); + + // TODO: change that to use our nym_crypto::hmac module instead + #[allow(clippy::expect_used)] + let mut mac = HmacSha256::new_from_slice(dh.as_bytes()) + .expect("x25519 shared secret is always 32 bytes long"); + + mac.update(self.pub_key.as_bytes()); + mac.update(self.private_ips.to_string().as_bytes()); + mac.update(&nonce.to_le_bytes()); + + mac.verify_slice(&self.mac) + .map_err(|source| Error::FailedClientMacVerification { + client: self.pub_key.to_string(), + source, + }) + } + + pub fn pub_key(&self) -> PeerPublicKey { + self.pub_key + } +} + +// TODO: change the inner type into generic array of size HmacSha256::OutputSize +// TODO2: rely on our internal crypto/hmac +#[derive(Debug, Clone, PartialEq)] +pub struct ClientMac(Vec); + +impl fmt::Display for ClientMac { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", general_purpose::STANDARD.encode(&self.0)) + } +} + +impl ClientMac { + #[allow(dead_code)] + pub fn new(mac: Vec) -> Self { + ClientMac(mac) + } +} + +impl Deref for ClientMac { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl FromStr for ClientMac { + type Err = Error; + + fn from_str(s: &str) -> Result { + let mac_bytes: Vec = + general_purpose::STANDARD + .decode(s) + .map_err(|source| Error::MalformedClientMac { + mac: s.to_string(), + source, + })?; + + Ok(ClientMac(mac_bytes)) + } +} + +impl Serialize for ClientMac { + fn serialize(&self, serializer: S) -> Result { + let encoded_key = general_purpose::STANDARD.encode(self.0.clone()); + serializer.serialize_str(&encoded_key) + } +} + +impl<'de> Deserialize<'de> for ClientMac { + fn deserialize>(deserializer: D) -> Result { + let encoded_key = String::deserialize(deserializer)?; + ClientMac::from_str(&encoded_key).map_err(serde::de::Error::custom) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use nym_crypto::asymmetric::encryption; + + #[test] + fn create_ip_pair() { + let ipv4: IpAddr = Ipv4Addr::from_str("10.1.10.50").unwrap().into(); + let ipv6: IpAddr = Ipv6Addr::from_str("fc01::0a32").unwrap().into(); + + assert_eq!(IpPair::from(ipv4), IpPair::from(ipv6)); + } + + #[test] + #[cfg(feature = "verify")] + fn client_request_roundtrip() { + let mut rng = rand::thread_rng(); + + let gateway_key_pair = encryption::KeyPair::new(&mut rng); + let client_key_pair = encryption::KeyPair::new(&mut rng); + + let nonce = 1234567890; + + let client = GatewayClient::new( + client_key_pair.private_key(), + x25519_dalek::PublicKey::from(gateway_key_pair.public_key().to_bytes()), + IpPair::new("10.0.0.42".parse().unwrap(), "fc00::42".parse().unwrap()), + nonce, + ); + assert!(client.verify(gateway_key_pair.private_key(), nonce).is_ok()) + } +} diff --git a/common/authenticator-requests/src/v5/request.rs b/common/authenticator-requests/src/v5/request.rs new file mode 100644 index 0000000000..4405ee75e0 --- /dev/null +++ b/common/authenticator-requests/src/v5/request.rs @@ -0,0 +1,132 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use super::{ + registration::{FinalMessage, InitMessage}, + topup::TopUpMessage, +}; +use nym_service_provider_requests_common::{Protocol, ServiceProviderType}; +use nym_wireguard_types::PeerPublicKey; +use serde::{Deserialize, Serialize}; + +use crate::make_bincode_serializer; + +use super::VERSION; + +fn generate_random() -> u64 { + use rand::RngCore; + let mut rng = rand::rngs::OsRng; + rng.next_u64() +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct AuthenticatorRequest { + pub protocol: Protocol, + pub data: AuthenticatorRequestData, + pub request_id: u64, +} + +impl AuthenticatorRequest { + pub fn from_reconstructed_message( + message: &nym_sphinx::receiver::ReconstructedMessage, + ) -> Result { + use bincode::Options; + make_bincode_serializer().deserialize(&message.message) + } + + pub fn new_initial_request(init_message: InitMessage) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorRequestData::Initial(init_message), + request_id, + }, + request_id, + ) + } + + pub fn new_final_request(final_message: FinalMessage) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorRequestData::Final(Box::new(final_message)), + request_id, + }, + request_id, + ) + } + + pub fn new_query_request(peer_public_key: PeerPublicKey) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorRequestData::QueryBandwidth(peer_public_key), + request_id, + }, + request_id, + ) + } + + pub fn new_topup_request(top_up_message: TopUpMessage) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorRequestData::TopUpBandwidth(Box::new(top_up_message)), + request_id, + }, + request_id, + ) + } + + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum AuthenticatorRequestData { + Initial(InitMessage), + Final(Box), + QueryBandwidth(PeerPublicKey), + TopUpBandwidth(Box), +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + #[test] + fn check_first_bytes_protocol() { + let version = 5; + let data = AuthenticatorRequest { + protocol: Protocol { + version, + service_provider_type: ServiceProviderType::Authenticator, + }, + data: AuthenticatorRequestData::Initial(InitMessage::new( + PeerPublicKey::from_str("yvNUDpT5l7W/xDhiu6HkqTHDQwbs/B3J5UrLmORl1EQ=").unwrap(), + )), + request_id: 1, + }; + let bytes = *data.to_bytes().unwrap().first_chunk::<2>().unwrap(); + assert_eq!(bytes, [version, ServiceProviderType::Authenticator as u8]); + } +} diff --git a/common/authenticator-requests/src/v5/response.rs b/common/authenticator-requests/src/v5/response.rs new file mode 100644 index 0000000000..044b803d0d --- /dev/null +++ b/common/authenticator-requests/src/v5/response.rs @@ -0,0 +1,132 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData}; +use nym_service_provider_requests_common::{Protocol, ServiceProviderType}; +use serde::{Deserialize, Serialize}; + +use crate::make_bincode_serializer; + +use super::VERSION; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct AuthenticatorResponse { + pub protocol: Protocol, + pub data: AuthenticatorResponseData, +} + +impl AuthenticatorResponse { + pub fn new_pending_registration_success( + registration_data: RegistrationData, + request_id: u64, + ) -> Self { + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorResponseData::PendingRegistration(PendingRegistrationResponse { + reply: registration_data, + request_id, + }), + } + } + + pub fn new_registered(registred_data: RegistredData, request_id: u64) -> Self { + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorResponseData::Registered(RegisteredResponse { + reply: registred_data, + request_id, + }), + } + } + + pub fn new_remaining_bandwidth( + remaining_bandwidth_data: Option, + request_id: u64, + ) -> Self { + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse { + reply: remaining_bandwidth_data, + request_id, + }), + } + } + + pub fn new_topup_bandwidth( + remaining_bandwidth_data: RemainingBandwidthData, + request_id: u64, + ) -> Self { + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorResponseData::TopUpBandwidth(TopUpBandwidthResponse { + reply: remaining_bandwidth_data, + request_id, + }), + } + } + + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } + + pub fn from_reconstructed_message( + message: &nym_sphinx::receiver::ReconstructedMessage, + ) -> Result { + use bincode::Options; + make_bincode_serializer().deserialize(&message.message) + } + + pub fn id(&self) -> Option { + match &self.data { + AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id), + AuthenticatorResponseData::Registered(response) => Some(response.request_id), + AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id), + AuthenticatorResponseData::TopUpBandwidth(response) => Some(response.request_id), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum AuthenticatorResponseData { + PendingRegistration(PendingRegistrationResponse), + Registered(RegisteredResponse), + RemainingBandwidth(RemainingBandwidthResponse), + TopUpBandwidth(TopUpBandwidthResponse), +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct PendingRegistrationResponse { + pub request_id: u64, + pub reply: RegistrationData, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct RegisteredResponse { + pub request_id: u64, + pub reply: RegistredData, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct RemainingBandwidthResponse { + pub request_id: u64, + pub reply: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct TopUpBandwidthResponse { + pub request_id: u64, + pub reply: RemainingBandwidthData, +} diff --git a/common/authenticator-requests/src/v5/topup.rs b/common/authenticator-requests/src/v5/topup.rs new file mode 100644 index 0000000000..1163d07f12 --- /dev/null +++ b/common/authenticator-requests/src/v5/topup.rs @@ -0,0 +1,15 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_credentials_interface::CredentialSpendingData; +use nym_wireguard_types::PeerPublicKey; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct TopUpMessage { + /// Base64 encoded x25519 public key + pub pub_key: PeerPublicKey, + + /// Ecash credential + pub credential: CredentialSpendingData, +} diff --git a/common/bandwidth-controller/src/lib.rs b/common/bandwidth-controller/src/lib.rs index a9b15db1fb..4d4d4d6952 100644 --- a/common/bandwidth-controller/src/lib.rs +++ b/common/bandwidth-controller/src/lib.rs @@ -105,26 +105,24 @@ impl BandwidthController { async fn get_aggregate_verification_key( &self, epoch_id: EpochId, - apis: &mut ApiClientsWrapper, + ecash_apis: &mut ApiClientsWrapper<'_, C>, ) -> Result where C: DkgQueryClient + Sync + Send, ::StorageError: Send + Sync + 'static, { - let ecash_apis = apis.get_or_init(epoch_id, &self.client).await?; get_aggregate_verification_key(&self.storage, epoch_id, ecash_apis).await } async fn get_coin_index_signatures( &self, epoch_id: EpochId, - apis: &mut ApiClientsWrapper, + ecash_apis: &mut ApiClientsWrapper<'_, C>, ) -> Result, BandwidthControllerError> where C: DkgQueryClient + Sync + Send, ::StorageError: Send + Sync + 'static, { - let ecash_apis = apis.get_or_init(epoch_id, &self.client).await?; get_coin_index_signatures(&self.storage, epoch_id, ecash_apis).await } @@ -132,13 +130,12 @@ impl BandwidthController { &self, epoch_id: EpochId, expiration_date: Date, - apis: &mut ApiClientsWrapper, + ecash_apis: &mut ApiClientsWrapper<'_, C>, ) -> Result, BandwidthControllerError> where C: DkgQueryClient + Sync + Send, ::StorageError: Send + Sync + 'static, { - let ecash_apis = apis.get_or_init(epoch_id, &self.client).await?; get_expiration_date_signatures(&self.storage, epoch_id, expiration_date, ecash_apis).await } @@ -154,7 +151,7 @@ impl BandwidthController { { let epoch_id = retrieved_ticketbook.ticketbook.epoch_id(); let expiration_date = retrieved_ticketbook.ticketbook.expiration_date(); - let mut api_clients = Default::default(); + let mut api_clients = ApiClientsWrapper::new(&self.client, epoch_id); let verification_key = self .get_aggregate_verification_key(epoch_id, &mut api_clients) diff --git a/common/bandwidth-controller/src/utils.rs b/common/bandwidth-controller/src/utils.rs index 9c15f5924b..2016849eb0 100644 --- a/common/bandwidth-controller/src/utils.rs +++ b/common/bandwidth-controller/src/utils.rs @@ -21,30 +21,67 @@ use rand::thread_rng; use std::fmt::Display; use std::future::Future; -// it really doesn't need the RwLock because it's never moved across tasks, -// but we need all the Send/Sync action -#[derive(Default)] -pub(crate) struct ApiClientsWrapper(Option>); - -impl ApiClientsWrapper { - pub(crate) async fn get_or_init( +pub(crate) trait EcashClientsProvider { + async fn try_get_ecash_clients( &mut self, + ) -> Result, BandwidthControllerError>; +} + +impl EcashClientsProvider for Vec { + async fn try_get_ecash_clients( + &mut self, + ) -> Result, BandwidthControllerError> { + Ok(self.clone()) + } +} + +impl EcashClientsProvider for &mut ApiClientsWrapper<'_, C> +where + C: DkgQueryClient + Sync + Send, +{ + async fn try_get_ecash_clients( + &mut self, + ) -> Result, BandwidthControllerError> { + self.clients().await + } +} + +pub(crate) enum ApiClientsWrapper<'a, C> { + Uninitialised { + query_client: &'a C, epoch_id: EpochId, - dkg_client: &C, - ) -> Result, BandwidthControllerError> + }, + Cached { + clients: Vec, + }, +} + +impl<'a, C> ApiClientsWrapper<'a, C> { + pub(crate) fn new(query_client: &'a C, epoch_id: EpochId) -> Self { + ApiClientsWrapper::Uninitialised { + query_client, + epoch_id, + } + } + + async fn clients(&mut self) -> Result, BandwidthControllerError> where C: DkgQueryClient + Sync + Send, { - if let Some(cached) = &self.0 { - return Ok(cached.clone()); + match self { + ApiClientsWrapper::Uninitialised { + query_client, + epoch_id, + } => { + let clients = all_ecash_api_clients(*query_client, *epoch_id).await?; + *self = ApiClientsWrapper::Cached { + clients: clients.clone(), + }; + + Ok(clients) + } + ApiClientsWrapper::Cached { clients } => Ok(clients.clone()), } - - let clients = all_ecash_api_clients(dkg_client, epoch_id).await?; - - // technically we don't have to be cloning all the clients here, but it's way simpler than - // dealing with locking and whatnot given the performance penalty is negligible - self.0 = Some(clients.clone()); - Ok(clients) } } @@ -76,7 +113,7 @@ where pub(crate) async fn get_aggregate_verification_key( storage: &St, epoch_id: EpochId, - ecash_apis: Vec, + mut ecash_apis: impl EcashClientsProvider, ) -> Result where St: Storage, @@ -90,6 +127,8 @@ where return Ok(stored); }; + let ecash_apis = ecash_apis.try_get_ecash_clients().await?; + let master_vk = query_random_apis_until_success( ecash_apis, |api| async move { api.api_client.master_verification_key(Some(epoch_id)).await }, @@ -115,7 +154,7 @@ where pub(crate) async fn get_coin_index_signatures( storage: &St, epoch_id: EpochId, - ecash_apis: Vec, + mut ecash_apis: impl EcashClientsProvider, ) -> Result, BandwidthControllerError> where St: Storage, @@ -129,6 +168,8 @@ where return Ok(stored); }; + let ecash_apis = ecash_apis.try_get_ecash_clients().await?; + let index_sigs = query_random_apis_until_success( ecash_apis, |api| async move { @@ -159,7 +200,7 @@ pub(crate) async fn get_expiration_date_signatures( storage: &St, epoch_id: EpochId, expiration_date: Date, - ecash_apis: Vec, + mut ecash_apis: impl EcashClientsProvider, ) -> Result, BandwidthControllerError> where St: Storage, @@ -173,6 +214,8 @@ where return Ok(stored); }; + let ecash_apis = ecash_apis.try_get_ecash_clients().await?; + let expiration_sigs = query_random_apis_until_success( ecash_apis, |api| async move { diff --git a/common/client-core/config-types/src/lib.rs b/common/client-core/config-types/src/lib.rs index 8347a38c0e..92925dcfdf 100644 --- a/common/client-core/config-types/src/lib.rs +++ b/common/client-core/config-types/src/lib.rs @@ -45,11 +45,12 @@ const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70; // clients/client-core/src/client/replies/reply_storage/surb_storage.rs const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10; const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200; +const DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER: usize = 0; // define how much to request at once // clients/client-core/src/client/replies/reply_controller.rs const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10; -const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100; +const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 50; const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500; @@ -621,6 +622,10 @@ pub struct ReplySurbs { /// Defines the maximum number of reply surbs the client wants to keep in its storage at any times. pub maximum_reply_surb_storage_threshold: usize, + /// Defines the soft threshold ontop of the minimum reply surb storage threshold for when the client + /// should proactively request additional reply surbs. + pub minimum_reply_surb_threshold_buffer: usize, + /// Defines the minimum number of reply surbs the client would request. pub minimum_reply_surb_request_size: u32, @@ -653,6 +658,9 @@ pub struct ReplySurbs { /// Specifies the number of mixnet hops the packet should go through. If not specified, then /// the default value is used. pub surb_mix_hops: Option, + + /// Specifies if we should reset all the sender tags on startup + pub fresh_sender_tags: bool, } impl Default for ReplySurbs { @@ -660,6 +668,7 @@ impl Default for ReplySurbs { ReplySurbs { minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD, maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD, + minimum_reply_surb_threshold_buffer: DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER, minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE, maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE, maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE, @@ -669,6 +678,7 @@ impl Default for ReplySurbs { maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE, maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE, surb_mix_hops: None, + fresh_sender_tags: false, } } } diff --git a/common/client-core/config-types/src/old/v5.rs b/common/client-core/config-types/src/old/v5.rs index df673a0986..872d107cc5 100644 --- a/common/client-core/config-types/src/old/v5.rs +++ b/common/client-core/config-types/src/old/v5.rs @@ -181,6 +181,7 @@ impl From for Config { maximum_reply_surb_age: value.debug.reply_surbs.maximum_reply_surb_age, maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age, surb_mix_hops: value.debug.reply_surbs.surb_mix_hops, + ..Default::default() }, ..Default::default() }, diff --git a/common/client-core/src/client/base_client/non_wasm_helpers.rs b/common/client-core/src/client/base_client/non_wasm_helpers.rs index ce3f05b0f2..e7ef621c9d 100644 --- a/common/client-core/src/client/base_client/non_wasm_helpers.rs +++ b/common/client-core/src/client/base_client/non_wasm_helpers.rs @@ -88,7 +88,7 @@ pub async fn setup_fs_reply_surb_backend>( let db_path = db_path.as_ref(); if db_path.exists() { info!("loading existing surb database"); - match fs_backend::Backend::try_load(db_path).await { + match fs_backend::Backend::try_load(db_path, surb_config.fresh_sender_tags).await { Ok(backend) => Ok(backend), Err(err) => { error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future"); diff --git a/common/client-core/src/client/inbound_messages.rs b/common/client-core/src/client/inbound_messages.rs index b14a4d7ed5..ad5e4b0196 100644 --- a/common/client-core/src/client/inbound_messages.rs +++ b/common/client-core/src/client/inbound_messages.rs @@ -28,6 +28,7 @@ pub enum InputMessage { recipient: Recipient, data: Vec, lane: TransmissionLane, + max_retransmissions: Option, }, /// Creates a message used for a duplex anonymous communication where the recipient @@ -43,6 +44,7 @@ pub enum InputMessage { data: Vec, reply_surbs: u32, lane: TransmissionLane, + max_retransmissions: Option, }, /// Attempt to use our internally received and stored `ReplySurb` to send the message back @@ -53,6 +55,7 @@ pub enum InputMessage { recipient_tag: AnonymousSenderTag, data: Vec, lane: TransmissionLane, + max_retransmissions: Option, }, MessageWrapper { @@ -92,6 +95,7 @@ impl InputMessage { recipient, data, lane, + max_retransmissions: None, }; if let Some(packet_type) = packet_type { InputMessage::new_wrapper(message, packet_type) @@ -112,28 +116,7 @@ impl InputMessage { data, reply_surbs, lane, - }; - if let Some(packet_type) = packet_type { - InputMessage::new_wrapper(message, packet_type) - } else { - message - } - } - - // IMHO `new_anonymous` should take `mix_hops: Option` as an argument instead of creating - // this function, but that would potentially break backwards compatibility with the current API - pub fn new_anonymous_with_custom_hops( - recipient: Recipient, - data: Vec, - reply_surbs: u32, - lane: TransmissionLane, - packet_type: Option, - ) -> Self { - let message = InputMessage::Anonymous { - recipient, - data, - reply_surbs, - lane, + max_retransmissions: None, }; if let Some(packet_type) = packet_type { InputMessage::new_wrapper(message, packet_type) @@ -152,6 +135,7 @@ impl InputMessage { recipient_tag, data, lane, + max_retransmissions: None, }; if let Some(packet_type) = packet_type { InputMessage::new_wrapper(message, packet_type) @@ -169,4 +153,34 @@ impl InputMessage { InputMessage::MessageWrapper { message, .. } => message.lane(), } } + + pub fn set_max_retransmissions(&mut self, max_retransmissions: u32) -> &mut Self { + match self { + InputMessage::Regular { + max_retransmissions: m, + .. + } + | InputMessage::Anonymous { + max_retransmissions: m, + .. + } + | InputMessage::Reply { + max_retransmissions: m, + .. + } => { + *m = Some(max_retransmissions); + } + InputMessage::Premade { .. } => {} + InputMessage::MessageWrapper { message, .. } => { + message.set_max_retransmissions(max_retransmissions); + } + } + + self + } + + pub fn with_max_retransmissions(mut self, max_retransmissions: u32) -> Self { + self.set_max_retransmissions(max_retransmissions); + self + } } diff --git a/common/client-core/src/client/mix_traffic/mod.rs b/common/client-core/src/client/mix_traffic/mod.rs index 24391cb3cb..1114de9d89 100644 --- a/common/client-core/src/client/mix_traffic/mod.rs +++ b/common/client-core/src/client/mix_traffic/mod.rs @@ -52,7 +52,7 @@ impl MixTrafficController { let (message_sender, message_receiver) = tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE); - let (client_sender, client_receiver) = tokio::sync::mpsc::channel(1); + let (client_sender, client_receiver) = tokio::sync::mpsc::channel(8); ( MixTrafficController { @@ -77,7 +77,7 @@ impl MixTrafficController { ) { let (message_sender, message_receiver) = tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE); - let (client_sender, client_receiver) = tokio::sync::mpsc::channel(1); + let (client_sender, client_receiver) = tokio::sync::mpsc::channel(8); ( MixTrafficController { gateway_transceiver, diff --git a/common/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs b/common/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs index 2aca3d58c7..6235b7c477 100644 --- a/common/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs +++ b/common/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs @@ -222,9 +222,6 @@ impl ActionController { // note: when the entry expires it's automatically removed from pending_acks_timers fn handle_expired_ack_timer(&mut self, expired_ack: Expired) { - // I'm honestly not sure how to handle it, because getting it means other things in our - // system are already misbehaving. If we ever see this panic, then I guess we should worry - // about it. Perhaps just reschedule it at later point? let frag_id = expired_ack.into_inner(); trace!("{frag_id} has expired"); diff --git a/common/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs b/common/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs index 566fe87402..49da4bce9c 100644 --- a/common/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs +++ b/common/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs @@ -65,11 +65,12 @@ where recipient_tag: AnonymousSenderTag, data: Vec, lane: TransmissionLane, + max_retransmissions: Option, ) { // offload reply handling to the dedicated task - if let Err(err) = self - .reply_controller_sender - .send_reply(recipient_tag, data, lane) + if let Err(err) = + self.reply_controller_sender + .send_reply(recipient_tag, data, lane, max_retransmissions) { if !self.task_client.is_shutdown_poll() { error!("failed to send a reply - {err}"); @@ -83,10 +84,11 @@ where content: Vec, lane: TransmissionLane, packet_type: PacketType, + max_retransmissions: Option, ) { if let Err(err) = self .message_handler - .try_send_plain_message(recipient, content, lane, packet_type) + .try_send_plain_message(recipient, content, lane, packet_type, max_retransmissions) .await { warn!("failed to send a plain message - {err}") @@ -100,10 +102,18 @@ where reply_surbs: u32, lane: TransmissionLane, packet_type: PacketType, + max_retransmissions: Option, ) { if let Err(err) = self .message_handler - .try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane, packet_type) + .try_send_message_with_reply_surbs( + recipient, + content, + reply_surbs, + lane, + packet_type, + max_retransmissions, + ) .await { warn!("failed to send a repliable message - {err}") @@ -116,25 +126,42 @@ where recipient, data, lane, + max_retransmissions, } => { - self.handle_plain_message(recipient, data, lane, PacketType::Mix) - .await + self.handle_plain_message( + recipient, + data, + lane, + PacketType::Mix, + max_retransmissions, + ) + .await } InputMessage::Anonymous { recipient, data, reply_surbs, lane, + max_retransmissions, } => { - self.handle_repliable_message(recipient, data, reply_surbs, lane, PacketType::Mix) - .await + self.handle_repliable_message( + recipient, + data, + reply_surbs, + lane, + PacketType::Mix, + max_retransmissions, + ) + .await } InputMessage::Reply { recipient_tag, data, lane, + max_retransmissions, } => { - self.handle_reply(recipient_tag, data, lane).await; + self.handle_reply(recipient_tag, data, lane, max_retransmissions) + .await; } InputMessage::Premade { msgs, lane } => self.handle_premade_packets(msgs, lane).await, InputMessage::MessageWrapper { @@ -145,25 +172,42 @@ where recipient, data, lane, + max_retransmissions, } => { - self.handle_plain_message(recipient, data, lane, packet_type) - .await + self.handle_plain_message( + recipient, + data, + lane, + packet_type, + max_retransmissions, + ) + .await } InputMessage::Anonymous { recipient, data, reply_surbs, lane, + max_retransmissions, } => { - self.handle_repliable_message(recipient, data, reply_surbs, lane, packet_type) - .await + self.handle_repliable_message( + recipient, + data, + reply_surbs, + lane, + packet_type, + max_retransmissions, + ) + .await } InputMessage::Reply { recipient_tag, data, lane, + max_retransmissions, } => { - self.handle_reply(recipient_tag, data, lane).await; + self.handle_reply(recipient_tag, data, lane, max_retransmissions) + .await; } InputMessage::Premade { msgs, lane } => { self.handle_premade_packets(msgs, lane).await diff --git a/common/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs b/common/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs index 080b0966d5..c3f63b07ad 100644 --- a/common/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs +++ b/common/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs @@ -72,6 +72,7 @@ pub struct PendingAcknowledgement { delay: SphinxDelay, destination: PacketDestination, retransmissions: u32, + max_retransmissions: Option, } impl PendingAcknowledgement { @@ -80,12 +81,14 @@ impl PendingAcknowledgement { message_chunk: Fragment, delay: SphinxDelay, recipient: Recipient, + max_retransmissions: Option, ) -> Self { PendingAcknowledgement { message_chunk, delay, destination: PacketDestination::KnownRecipient(recipient.into()), retransmissions: 0, + max_retransmissions, } } @@ -94,6 +97,7 @@ impl PendingAcknowledgement { delay: SphinxDelay, recipient_tag: AnonymousSenderTag, extra_surb_request: bool, + max_retransmissions: Option, ) -> Self { PendingAcknowledgement { message_chunk, @@ -103,6 +107,7 @@ impl PendingAcknowledgement { extra_surb_request, }, retransmissions: 0, + max_retransmissions, } } @@ -118,6 +123,18 @@ impl PendingAcknowledgement { self.delay = new_delay; self.retransmissions += 1; } + + pub(crate) fn reached_max_retransmissions( + &self, + global_max_retransmissions: Option, + ) -> bool { + let reached_local_max = self + .max_retransmissions + .is_some_and(|limit| self.retransmissions >= limit); + let reached_global_max = + global_max_retransmissions.is_some_and(|limit| self.retransmissions >= limit); + reached_local_max || reached_global_max + } } /// AcknowledgementControllerConnectors represents set of channels for communication with diff --git a/common/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs b/common/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs index 1c6feabf52..2f7b5d976e 100644 --- a/common/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs +++ b/common/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs @@ -79,17 +79,15 @@ where let frag_id = timed_out_ack.message_chunk.fragment_identifier(); - if let Some(limit) = self.maximum_retransmissions { - if timed_out_ack.retransmissions >= limit { - warn!("reached maximum number of allowed retransmissions for the packet"); - if let Err(err) = self - .action_sender - .unbounded_send(Action::new_remove(frag_id)) - { - error!("Failed to send remove action to the controller: {err}"); - } - return; + if timed_out_ack.reached_max_retransmissions(self.maximum_retransmissions) { + debug!("reached maximum number of allowed retransmissions for the packet"); + if let Err(err) = self + .action_sender + .unbounded_send(Action::new_remove(frag_id)) + { + error!("Failed to send remove action to the controller: {err}"); } + return; } let maybe_prepared_fragment = match &timed_out_ack.destination { diff --git a/common/client-core/src/client/real_messages_control/message_handler.rs b/common/client-core/src/client/real_messages_control/message_handler.rs index b8417cead9..fc74610b60 100644 --- a/common/client-core/src/client/real_messages_control/message_handler.rs +++ b/common/client-core/src/client/real_messages_control/message_handler.rs @@ -6,6 +6,7 @@ use crate::client::real_messages_control::real_traffic_stream::{ BatchRealMessageSender, RealMessage, }; use crate::client::real_messages_control::{AckActionSender, Action}; +use crate::client::replies::reply_controller::MaxRetransmissions; use crate::client::replies::reply_storage::{ReceivedReplySurbsMap, SentReplyKeys, UsedSenderTags}; use crate::client::topology_control::{TopologyAccessor, TopologyReadPermit}; use log::{debug, error, info, trace, warn}; @@ -33,10 +34,12 @@ pub enum PreparationError { #[error(transparent)] NymTopologyError(#[from] NymTopologyError), - #[error("The received message cannot be sent using a single reply surb. It ended up getting split into {fragments} fragments.")] + #[error("message too long for a single SURB, splitting into {fragments} fragments.")] MessageTooLongForSingleSurb { fragments: usize }, - #[error("Not enough reply SURBs to send the message. We have {available} available and require at least {required}.")] + #[error( + "not enough reply SURBs to send the message, available: {available} required: {required}." + )] NotEnoughSurbs { available: usize, required: usize }, } @@ -140,6 +143,12 @@ impl Config { } } +#[derive(Clone)] +pub(crate) struct FragmentWithMaxRetransmissions { + pub(crate) fragment: Fragment, + pub(crate) max_retransmissions: MaxRetransmissions, +} + #[derive(Clone)] pub(crate) struct MessageHandler { config: Config, @@ -196,10 +205,10 @@ where trace!("we already had sender tag for {recipient}"); existing } else { - info!("creating new sender tag for {recipient}"); + debug!("creating new sender tag for {recipient}"); let new_tag = AnonymousSenderTag::new_random(&mut self.rng); self.tag_storage.insert_new(recipient, new_tag); - info!("we'll be using {new_tag} for all anonymous messages sent to {recipient}"); + info!("using {new_tag} for all anonymous messages sent to {recipient}"); new_tag } } @@ -292,8 +301,14 @@ where Some(chunk.fragment_identifier()), ); let delay = prepared_fragment.total_delay; - let pending_ack = - PendingAcknowledgement::new_anonymous(chunk, delay, target, is_extra_surb_request); + let max_retransmissions = None; + let pending_ack = PendingAcknowledgement::new_anonymous( + chunk, + delay, + target, + is_extra_surb_request, + max_retransmissions, + ); let lane = if is_extra_surb_request { TransmissionLane::ReplySurbRequest @@ -348,7 +363,7 @@ where pub(crate) async fn try_send_reply_chunks_on_lane( &mut self, target: AnonymousSenderTag, - fragments: Vec, + fragments: Vec, reply_surbs: Vec, lane: TransmissionLane, ) -> Result<(), SurbWrappedPreparationError> { @@ -365,12 +380,12 @@ where pub(crate) async fn try_send_reply_chunks( &mut self, target: AnonymousSenderTag, - fragments: Vec<(TransmissionLane, Fragment)>, + fragments: Vec<(TransmissionLane, FragmentWithMaxRetransmissions)>, reply_surbs: Vec, ) -> Result<(), SurbWrappedPreparationError> { let prepared_fragments = self .prepare_reply_chunks_for_sending( - fragments.iter().map(|(_, f)| f.clone()).collect(), + fragments.iter().map(|(_, f)| f.fragment.clone()).collect(), reply_surbs, ) .await?; @@ -380,12 +395,21 @@ where for (raw, prepared) in fragments.into_iter().zip(prepared_fragments.into_iter()) { let lane = raw.0; - let fragment = raw.1; + let FragmentWithMaxRetransmissions { + fragment, + max_retransmissions, + } = raw.1; let real_message = RealMessage::new(prepared.mix_packet, Some(prepared.fragment_identifier)); let delay = prepared.total_delay; - let pending_ack = PendingAcknowledgement::new_anonymous(fragment, delay, target, false); + let pending_ack = PendingAcknowledgement::new_anonymous( + fragment, + delay, + target, + false, + max_retransmissions, + ); let entry = to_forward.entry(lane).or_default(); entry.push(real_message); @@ -414,10 +438,17 @@ where message: Vec, lane: TransmissionLane, packet_type: PacketType, + max_retransmissions: Option, ) -> Result<(), PreparationError> { let message = NymMessage::new_plain(message); - self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type) - .await + self.try_split_and_send_non_reply_message( + message, + recipient, + lane, + packet_type, + max_retransmissions, + ) + .await } pub(crate) async fn try_split_and_send_non_reply_message( @@ -426,6 +457,7 @@ where recipient: Recipient, lane: TransmissionLane, packet_type: PacketType, + max_retransmissions: Option, ) -> Result<(), PreparationError> { debug!("Sending non-reply message with packet type {packet_type}"); // TODO: I really dislike existence of this assertion, it implies code has to be re-organised @@ -465,7 +497,8 @@ where Some(fragment.fragment_identifier()), ); let delay = prepared_fragment.total_delay; - let pending_ack = PendingAcknowledgement::new_known(fragment, delay, recipient); + let pending_ack = + PendingAcknowledgement::new_known(fragment, delay, recipient, max_retransmissions); real_messages.push(real_message); pending_acks.push(pending_ack); @@ -493,11 +526,15 @@ where reply_surbs, )); + // When sending SURBs we want to retransmit + let max_retransmissions = None; + self.try_split_and_send_non_reply_message( message, recipient, TransmissionLane::AdditionalReplySurbs, packet_type, + max_retransmissions, ) .await?; @@ -514,6 +551,7 @@ where num_reply_surbs: u32, lane: TransmissionLane, packet_type: PacketType, + max_retransmissions: Option, ) -> Result<(), SurbWrappedPreparationError> { debug!("Sending message with reply SURBs with packet type {packet_type}"); let sender_tag = self.get_or_create_sender_tag(&recipient); @@ -524,8 +562,14 @@ where let message = NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs)); - self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type) - .await?; + self.try_split_and_send_non_reply_message( + message, + recipient, + lane, + packet_type, + max_retransmissions, + ) + .await?; log::trace!("storing {} reply keys", reply_keys.len()); self.reply_key_storage.insert_multiple(reply_keys); diff --git a/common/client-core/src/client/real_messages_control/mod.rs b/common/client-core/src/client/real_messages_control/mod.rs index 4b73990721..8de385e5ee 100644 --- a/common/client-core/src/client/real_messages_control/mod.rs +++ b/common/client-core/src/client/real_messages_control/mod.rs @@ -153,7 +153,7 @@ impl RealMessagesController { let rng = OsRng; // create channels for inter-task communication - let (real_message_sender, real_message_receiver) = tokio::sync::mpsc::channel(1); + let (real_message_sender, real_message_receiver) = tokio::sync::mpsc::channel(8); let (sent_notifier_tx, sent_notifier_rx) = mpsc::unbounded(); let (ack_action_tx, ack_action_rx) = mpsc::unbounded(); let ack_controller_connectors = AcknowledgementControllerConnectors::new( diff --git a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs index 2789228dcf..d20b8040aa 100644 --- a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs +++ b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs @@ -517,17 +517,25 @@ where use crate::error::ClientCoreStatusMessage; let packets = self.transmission_buffer.total_size(); - let backlog = self.transmission_buffer.total_size_in_bytes() as f64 / 1024.0; - let lanes = self.transmission_buffer.num_lanes(); + let lanes = self.transmission_buffer.lanes(); let mult = self.sending_delay_controller.current_multiplier(); let delay = self.current_average_message_sending_delay().as_millis(); + + let lane_status = lanes + .iter() + .map(|lane_name| { + let lane_length = self.transmission_buffer.lane_length(lane_name).unwrap_or(0); + format!("{lane_name:?}: {lane_length}") + }) + .collect::>() + .join(", "); + let status_str = if self.config.traffic.disable_main_poisson_packet_distribution { - format!("Packet backlog: {backlog:.2} kiB ({packets}), {lanes} lanes, no delay") + format!("Packet backlog: {lane_status}, no delay") } else { - format!( - "Packet backlog: {backlog:.2} kiB ({packets}), {lanes} lanes, avg delay: {delay}ms ({mult})" - ) + format!("Packet backlog: {lane_status}, avg delay: {delay}ms ({mult})") }; + if packets > 1000 { log::warn!("{status_str}"); } else if packets > 0 { diff --git a/common/client-core/src/client/received_buffer.rs b/common/client-core/src/client/received_buffer.rs index a5a206dd64..be27383bd7 100644 --- a/common/client-core/src/client/received_buffer.rs +++ b/common/client-core/src/client/received_buffer.rs @@ -23,6 +23,10 @@ use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, C use nym_task::TaskClient; use std::collections::HashSet; use std::sync::Arc; +use std::time::{Duration, Instant}; + +// The interval at which we check for stale buffers +const STALE_BUFFER_CHECK_INTERVAL: Duration = Duration::from_secs(10); // Buffer Requests to say "hey, send any reconstructed messages to this channel" // or to say "hey, I'm going offline, don't send anything more to me. Just buffer them instead" @@ -48,6 +52,9 @@ struct ReceivedMessagesBufferInner { recently_reconstructed: HashSet, stats_tx: ClientStatsSender, + + // Periodically check for stale buffers to clean up + last_stale_check: Instant, } impl ReceivedMessagesBufferInner { @@ -96,9 +103,10 @@ impl ReceivedMessagesBufferInner { } None } - _ => unreachable!( - "no other error kind should have been returned here! If so, it's a bug!" - ), + _ => { + error!("unexpected error occurred during message reconstruction: {err}"); + None + } }, Ok(reconstruction_result) => match reconstruction_result { Some((reconstructed_message, used_sets)) => { @@ -144,6 +152,16 @@ impl ReceivedMessagesBufferInner { self.recover_from_fragment(fragment_data, raw_fragment_size) } + + fn cleanup_stale_buffers(&mut self) { + let now = Instant::now(); + if now - self.last_stale_check > STALE_BUFFER_CHECK_INTERVAL { + self.last_stale_check = now; + self.message_receiver + .reconstructor() + .cleanup_stale_buffers(); + } + } } #[derive(Debug, Clone)] @@ -172,6 +190,7 @@ impl ReceivedMessagesBuffer { message_sender: None, recently_reconstructed: HashSet::new(), stats_tx, + last_stale_check: Instant::now(), })), reply_key_storage, reply_controller_sender, @@ -392,6 +411,11 @@ impl ReceivedMessagesBuffer { } } + // Cleanup stale buffers, if there are any fragments that simply never arrived. + // We do this here as part of handling new received fragments so that we can keep the event + // loop focused on processing new messages. + inner_guard.cleanup_stale_buffers(); + drop(inner_guard); if !completed_messages.is_empty() { diff --git a/common/client-core/src/client/replies/reply_controller/mod.rs b/common/client-core/src/client/replies/reply_controller/mod.rs index 085fbfa6e4..2cf7ac9eaa 100644 --- a/common/client-core/src/client/replies/reply_controller/mod.rs +++ b/common/client-core/src/client/replies/reply_controller/mod.rs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::client::real_messages_control::acknowledgement_control::PendingAcknowledgement; -use crate::client::real_messages_control::message_handler::{MessageHandler, PreparationError}; +use crate::client::real_messages_control::message_handler::{ + FragmentWithMaxRetransmissions, MessageHandler, PreparationError, +}; use crate::client::replies::reply_storage::CombinedReplyStorage; use futures::channel::oneshot; use futures::StreamExt; @@ -10,7 +12,7 @@ use log::{debug, error, info, trace, warn}; use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_sphinx::anonymous_replies::ReplySurb; -use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier}; +use nym_sphinx::chunking::fragment::FragmentIdentifier; use nym_task::connections::{ConnectionId, TransmissionLane}; use nym_task::TaskClient; use rand::{CryptoRng, Rng}; @@ -49,6 +51,8 @@ impl Config { // - replies to "give additional surbs" requests // - will reply to future heartbeats +pub type MaxRetransmissions = Option; + // TODO: this should be split into ingress and egress controllers // because currently its trying to perform two distinct jobs pub struct ReplyController { @@ -59,7 +63,8 @@ pub struct ReplyController { // of surbs required to send the message through // expected_reliability: f32, request_receiver: ReplyControllerReceiver, - pending_replies: HashMap>, + pending_replies: + HashMap>, /// Retransmission packets that have already timed out and are waiting for additional reply SURBs /// so that they could be sent back to the network. Once we receive more SURBs, we should send them ASAP. @@ -96,12 +101,13 @@ where } } - fn insert_pending_replies>( + fn insert_pending_replies>( &mut self, recipient: &AnonymousSenderTag, fragments: I, lane: TransmissionLane, ) { + trace!("buffering pending replies for {recipient}"); self.pending_replies .entry(*recipient) .or_insert_with(TransmissionBuffer::new) @@ -111,8 +117,9 @@ where fn re_insert_pending_replies( &mut self, recipient: &AnonymousSenderTag, - fragments: Vec<(TransmissionLane, Fragment)>, + fragments: Vec<(TransmissionLane, FragmentWithMaxRetransmissions)>, ) { + trace!("re-inserting pending replies for {recipient}"); // the buffer should ALWAYS exist at this point, if it doesn't, it's a bug... self.pending_replies .entry(*recipient) @@ -125,6 +132,7 @@ where recipient: &AnonymousSenderTag, data: Vec>, ) { + trace!("re-inserting pending retransmissions for {recipient}"); // the underlying entry MUST exist as we've just got data from there let map_entry = self .pending_retransmissions @@ -142,7 +150,7 @@ where } fn should_request_more_surbs(&self, target: &AnonymousSenderTag) -> bool { - trace!("checking if we should request more surbs from {:?}", target); + trace!("checking if we should request more surbs from {target}"); let pending_queue_size = self .pending_replies @@ -158,11 +166,6 @@ where let total_queue = pending_queue_size + retransmission_queue; - // simple as that - there's absolutely nothing to retransmit - if total_queue == 0 { - return false; - } - let available_surbs = self .full_reply_storage .surbs_storage_ref() @@ -179,11 +182,27 @@ where .full_reply_storage .surbs_storage_ref() .max_surb_threshold(); + let min_surbs_threshold_buffer = + self.config.reply_surbs.minimum_reply_surb_threshold_buffer; - debug!("total queue size: {total_queue} = pending data {pending_queue_size} + pending retransmission {retransmission_queue}, available surbs: {available_surbs} pending surbs: {pending_surbs} threshold range: {min_surbs_threshold}..{max_surbs_threshold}"); + // After clearing the queue, we want to have at least `min_surbs_threshold` surbs available + // and reserved for requesting additional surbs, and in addition to that we also want to + // have `min_surbs_threshold_buffer` surbs available proactively. + let target_surbs_after_clearing_queue = min_surbs_threshold + min_surbs_threshold_buffer; - (pending_surbs + available_surbs) < max_surbs_threshold - && (pending_surbs + available_surbs) < (total_queue + min_surbs_threshold) + // Check if we have enough surbs to handle the total queue and maintain minimum thresholds + let total_required_surbs = total_queue + target_surbs_after_clearing_queue; + let total_available_surbs = pending_surbs + available_surbs; + + debug!("total queue size: {total_queue} = pending data {pending_queue_size} + pending retransmission {retransmission_queue}, available surbs: {available_surbs} pending surbs: {pending_surbs} threshold range: {min_surbs_threshold}..+{min_surbs_threshold_buffer}..{max_surbs_threshold}"); + + // We should request more surbs if: + // 1. We haven't hit the maximum surb threshold, and + // 2. We don't have enough surbs to handle the queue plus minimum thresholds + let is_below_max_threshold = total_available_surbs < max_surbs_threshold; + let is_below_required_surbs = total_available_surbs < total_required_surbs; + + is_below_max_threshold && is_below_required_surbs } async fn handle_send_reply( @@ -191,6 +210,7 @@ where recipient_tag: AnonymousSenderTag, data: Vec, lane: TransmissionLane, + max_retransmissions: Option, ) { if !self .full_reply_storage @@ -228,7 +248,14 @@ where .get_reply_surbs(&recipient_tag, max_to_send); if let Some(reply_surbs) = surbs { - let to_send = fragments.drain(..max_to_send).collect::>(); + let to_send = fragments + .drain(..max_to_send) + .map(|f| FragmentWithMaxRetransmissions { + fragment: f, + max_retransmissions, + }) + .collect::>(); + if let Err(err) = self .message_handler .try_send_reply_chunks_on_lane( @@ -244,6 +271,10 @@ where &recipient_tag, ); warn!("failed to send reply to {recipient_tag}: {err}"); + info!( + "buffering {no_fragments} fragments for {recipient_tag}", + no_fragments = to_send.len() + ); self.insert_pending_replies(&recipient_tag, to_send, lane); } } @@ -251,6 +282,20 @@ where // if there's leftover data we didn't send because we didn't have enough (or any) surbs - buffer it if !fragments.is_empty() { + // Ideally we should have enough surbs above the minimum threshold to handle sending + // new replies without having to first request more surbs. That's why I'd like to log + // these cases as they might indicate a problem with the surb management. + debug!( + "buffering {no_fragments} fragments for {recipient_tag}", + no_fragments = fragments.len() + ); + let fragments: Vec<_> = fragments + .into_iter() + .map(|fragment| FragmentWithMaxRetransmissions { + fragment, + max_retransmissions, + }) + .collect(); self.insert_pending_replies(&recipient_tag, fragments, lane); } @@ -265,6 +310,7 @@ where target: AnonymousSenderTag, amount: u32, ) -> Result<(), PreparationError> { + debug!("requesting {amount} additional reply surbs for {target}"); let reply_surb = self .full_reply_storage .surbs_storage_ref() @@ -383,7 +429,7 @@ where &mut self, from: &AnonymousSenderTag, amount: usize, - ) -> Option> { + ) -> Option> { // if possible, pop all pending replies, if not, pop only entries for which we'd have a reply surb let total = self.pending_replies.get(from)?.total_size(); trace!("pending queue has {total} elements"); @@ -663,7 +709,11 @@ where recipient, message, lane, - } => self.handle_send_reply(recipient, message, lane).await, + max_retransmissions, + } => { + self.handle_send_reply(recipient, message, lane, max_retransmissions) + .await + } ReplyControllerMessage::AdditionalSurbs { sender_tag, reply_surbs, @@ -686,7 +736,7 @@ where // it should take into consideration the average latency, sending rate and queue size. // it should request as many surbs as it takes to saturate its sending rate before next batch arrives async fn request_reply_surbs_for_queue_clearing(&mut self, target: AnonymousSenderTag) { - trace!("requesting surbs for queues clearing"); + trace!("requesting surbs for queue clearing"); let pending_queue_size = self .pending_replies @@ -700,17 +750,18 @@ where .map(|pending_queue| pending_queue.len()) .unwrap_or_default(); + let min_surbs_buffer = self.config.reply_surbs.minimum_reply_surb_threshold_buffer as u32; + let total_queue = (pending_queue_size + retransmission_queue) as u32; - if total_queue == 0 { - trace!("the pending queues for {:?} are already empty", target); - return; - } + // To proactively request additional surbs, we aim to have a buffer of extra surbs in our + // storage. + let total_queue_with_buffer = total_queue + min_surbs_buffer; let request_size = min( self.config.reply_surbs.maximum_reply_surb_request_size, max( - total_queue, + total_queue_with_buffer, self.config.reply_surbs.minimum_reply_surb_request_size, ), ); @@ -719,7 +770,7 @@ where .request_additional_reply_surbs(target, request_size) .await { - warn!("failed to request additional surbs... - {err}") + info!("{err}") } } diff --git a/common/client-core/src/client/replies/reply_controller/requests.rs b/common/client-core/src/client/replies/reply_controller/requests.rs index a1555329e5..30afc2585a 100644 --- a/common/client-core/src/client/replies/reply_controller/requests.rs +++ b/common/client-core/src/client/replies/reply_controller/requests.rs @@ -66,12 +66,14 @@ impl ReplyControllerSender { recipient: AnonymousSenderTag, message: Vec, lane: TransmissionLane, + max_retransmissions: Option, ) -> Result<(), ReplyControllerSenderError> { self.0 .unbounded_send(ReplyControllerMessage::SendReply { recipient, message, lane, + max_retransmissions, }) .map_err(ReplyControllerSenderError::SendReply) } @@ -160,6 +162,7 @@ pub enum ReplyControllerMessage { recipient: AnonymousSenderTag, message: Vec, lane: TransmissionLane, + max_retransmissions: Option, }, AdditionalSurbs { diff --git a/common/client-core/src/client/transmission_buffer.rs b/common/client-core/src/client/transmission_buffer.rs index d6ee86e861..e6eb7d6891 100644 --- a/common/client-core/src/client/transmission_buffer.rs +++ b/common/client-core/src/client/transmission_buffer.rs @@ -58,8 +58,8 @@ impl TransmissionBuffer { } #[cfg(not(target_arch = "wasm32"))] - pub(crate) fn num_lanes(&self) -> usize { - self.buffer.keys().count() + pub(crate) fn lanes(&self) -> Vec { + self.buffer.keys().cloned().collect() } pub(crate) fn lane_length(&self, lane: &TransmissionLane) -> Option { @@ -83,6 +83,7 @@ impl TransmissionBuffer { } #[cfg(not(target_arch = "wasm32"))] + #[allow(unused)] pub(crate) fn total_size_in_bytes(&self) -> usize where T: SizedData, diff --git a/common/client-core/surb-storage/src/backend/fs_backend/mod.rs b/common/client-core/surb-storage/src/backend/fs_backend/mod.rs index 4914f6621f..4467e17538 100644 --- a/common/client-core/surb-storage/src/backend/fs_backend/mod.rs +++ b/common/client-core/surb-storage/src/backend/fs_backend/mod.rs @@ -10,7 +10,7 @@ use crate::{ CombinedReplyStorage, ReceivedReplySurbsMap, ReplyStorageBackend, SentReplyKeys, UsedSenderTags, }; use async_trait::async_trait; -use log::{error, info, warn}; +use log::{debug, error, info, warn}; use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use std::fs; use std::path::{Path, PathBuf}; @@ -52,7 +52,10 @@ impl Backend { Ok(backend) } - pub async fn try_load>(database_path: P) -> Result { + pub async fn try_load>( + database_path: P, + fresh_sender_tags: bool, + ) -> Result { let owned_path: PathBuf = database_path.as_ref().into(); if owned_path.file_name().is_none() { return Err(StorageError::DatabasePathWithoutFilename { @@ -118,6 +121,9 @@ impl Backend { if days > 2 { info!("it's been over {days} days and {hours} hours since we last used our data store. our used sender tags are already outdated - we're going to purge them now."); manager.delete_all_tags().await?; + } else if fresh_sender_tags { + debug!("starting with fresh sender tags"); + manager.delete_all_tags().await?; } Ok(Backend { diff --git a/common/client-libs/gateway-client/src/client/mod.rs b/common/client-libs/gateway-client/src/client/mod.rs index b72c7a033c..88a55dd850 100644 --- a/common/client-libs/gateway-client/src/client/mod.rs +++ b/common/client-libs/gateway-client/src/client/mod.rs @@ -20,8 +20,8 @@ use nym_credentials_interface::TicketType; use nym_crypto::asymmetric::identity; use nym_gateway_requests::registration::handshake::client_handshake; use nym_gateway_requests::{ - BinaryRequest, ClientControlRequest, ClientRequest, SensitiveServerResponse, ServerResponse, - SharedGatewayKey, SharedSymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION, + BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt, + SensitiveServerResponse, ServerResponse, SharedGatewayKey, SharedSymmetricKey, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION, }; use nym_sphinx::forwarding::packet::MixPacket; @@ -563,28 +563,10 @@ impl GatewayClient { Ok(zeroizing_updated_key) } - async fn authenticate(&mut self) -> Result<(), GatewayClientError> { - let Some(shared_key) = self.shared_key.as_ref() else { - return Err(GatewayClientError::NoSharedKeyAvailable); - }; - - if !self.connection.is_established() { - return Err(GatewayClientError::ConnectionNotEstablished); - } - debug!("authenticating with gateway"); - - let self_address = self - .local_identity - .as_ref() - .public_key() - .derive_destination_address(); - - let msg = ClientControlRequest::new_authenticate( - self_address, - shared_key, - self.cfg.bandwidth.require_tickets, - )?; - + async fn send_authenticate_request_and_handle_response( + &mut self, + msg: ClientControlRequest, + ) -> Result<(), GatewayClientError> { match self.send_websocket_message(msg).await? { ServerResponse::Authenticate { protocol_version, @@ -608,6 +590,51 @@ impl GatewayClient { } } + async fn authenticate_v1(&mut self) -> Result<(), GatewayClientError> { + debug!("using v1 authentication"); + + let Some(shared_key) = self.shared_key.as_ref() else { + return Err(GatewayClientError::NoSharedKeyAvailable); + }; + + let self_address = self + .local_identity + .public_key() + .derive_destination_address(); + + let msg = ClientControlRequest::new_authenticate( + self_address, + shared_key, + self.cfg.bandwidth.require_tickets, + )?; + self.send_authenticate_request_and_handle_response(msg) + .await + } + + async fn authenticate_v2(&mut self) -> Result<(), GatewayClientError> { + debug!("using v2 authentication"); + let Some(shared_key) = self.shared_key.as_ref() else { + return Err(GatewayClientError::NoSharedKeyAvailable); + }; + + let msg = ClientControlRequest::new_authenticate_v2(shared_key, &self.local_identity)?; + self.send_authenticate_request_and_handle_response(msg) + .await + } + + async fn authenticate(&mut self, use_v2: bool) -> Result<(), GatewayClientError> { + if !self.connection.is_established() { + return Err(GatewayClientError::ConnectionNotEstablished); + } + debug!("authenticating with gateway"); + + if use_v2 { + self.authenticate_v2().await + } else { + self.authenticate_v1().await + } + } + /// Helper method to either call register or authenticate based on self.shared_key value #[instrument(skip_all, fields( @@ -623,19 +650,25 @@ impl GatewayClient { } // 1. check gateway's protocol version - let supports_aes_gcm_siv = match self.get_gateway_protocol().await { - Ok(protocol) => protocol >= AES_GCM_SIV_PROTOCOL_VERSION, + let gw_protocol = match self.get_gateway_protocol().await { + Ok(protocol) => Some(protocol), Err(_) => { // if we failed to send the request, it means the gateway is running the old binary, // so it has reset our connection - we have to reconnect self.establish_connection().await?; - false + None } }; + let supports_aes_gcm_siv = gw_protocol.supports_aes256_gcm_siv(); + let supports_auth_v2 = gw_protocol.supports_authenticate_v2(); + if !supports_aes_gcm_siv { warn!("this gateway is on an old version that doesn't support AES256-GCM-SIV"); } + if !supports_auth_v2 { + warn!("this gateway is on an old version that doesn't support authentication v2") + } if self.authenticated { debug!("Already authenticated"); @@ -650,7 +683,7 @@ impl GatewayClient { } if self.shared_key.is_some() { - self.authenticate().await?; + self.authenticate(supports_auth_v2).await?; if self.authenticated { // if we are authenticated it means we MUST have an associated shared_key @@ -983,7 +1016,8 @@ impl GatewayClient { } // if we're reconnecting, because we lost connection, we need to re-authenticate the connection - self.authenticate().await?; + self.authenticate(self.negotiated_protocol.supports_authenticate_v2()) + .await?; // this call is NON-blocking self.start_listening_for_mixnet_messages()?; diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index 5cd86e2dba..3a673f0e15 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -56,7 +56,7 @@ cw4 = { workspace = true } cw-controllers = { workspace = true } prost = { workspace = true, default-features = false } flate2 = { workspace = true } -sha2 = { version = "0.9.5" } +sha2 = { workspace = true } itertools = { workspace = true } zeroize = { workspace = true, features = ["zeroize_derive"] } cosmwasm-std = { workspace = true } diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index e5a9a7ea8a..c41ba519d4 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -23,11 +23,12 @@ use nym_api_requests::models::{ NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse, }; use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated}; -use nym_api_requests::nym_nodes::SkimmedNode; +use nym_api_requests::nym_nodes::{NodesByAddressesResponse, SkimmedNode}; use nym_coconut_dkg_common::types::EpochId; use nym_ecash_contract_common::deposit::DepositId; use nym_http_api_client::UserAgent; use nym_network_defaults::NymNetworkDetails; +use std::net::IpAddr; use time::Date; use url::Url; @@ -710,4 +711,11 @@ impl NymApiClient { .issued_ticketbooks_challenge(expiration_date, deposits) .await?) } + + pub async fn nodes_by_addresses( + &self, + addresses: Vec, + ) -> Result { + Ok(self.nym_api.nodes_by_addresses(addresses).await?) + } } diff --git a/common/client-libs/validator-client/src/coconut/mod.rs b/common/client-libs/validator-client/src/coconut/mod.rs index 95270a25ed..67974c0170 100644 --- a/common/client-libs/validator-client/src/coconut/mod.rs +++ b/common/client-libs/validator-client/src/coconut/mod.rs @@ -83,6 +83,12 @@ impl TryFrom for EcashApiClient { let url_address = Url::parse(&share.announce_address)?; + // The NymApiClient constructed here uses the default (hickory DoT/DoH) resolver because + // this EcashApiClient is used by both client and non-client applications. + // + // In non-client applications this resolver can cause warning logs about H2 connection + // failure. This indicates that the long lived https connection was closed by the remote + // peer and the resolver will have to reconnect. It should not impact actual functionality Ok(EcashApiClient { api_client: NymApiClient::new(url_address), verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?, diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index e1bac67513..a9b1bc54d5 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -12,10 +12,13 @@ use nym_api_requests::ecash::models::{ }; use nym_api_requests::ecash::VerificationKeyResponse; use nym_api_requests::models::{ - AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse, - NodeRefreshBody, NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse, + AnnotationResponse, ApiHealthResponse, BinaryBuildInformationOwned, ChainStatusResponse, + LegacyDescribedMixNode, NodePerformanceResponse, NodeRefreshBody, NymNodeDescription, + PerformanceHistoryResponse, RewardedSetResponse, +}; +use nym_api_requests::nym_nodes::{ + NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponse, }; -use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse; use nym_api_requests::pagination::PaginatedResponse; pub use nym_api_requests::{ ecash::{ @@ -40,6 +43,7 @@ pub use nym_http_api_client::Client; use nym_http_api_client::{ApiClient, NO_PARAMS}; use nym_mixnet_contract_common::mixnode::MixNodeDetails; use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId, NymNodeDetails}; +use std::net::IpAddr; use time::format_description::BorrowedFormatItem; use time::Date; use tracing::instrument; @@ -66,6 +70,19 @@ pub trait NymApiClientExt: ApiClient { .await } + #[instrument(level = "debug", skip(self))] + async fn build_information(&self) -> Result { + self.get_json( + &[ + routes::API_VERSION, + routes::API_STATUS_ROUTES, + routes::BUILD_INFORMATION, + ], + NO_PARAMS, + ) + .await + } + #[deprecated] #[instrument(level = "debug", skip(self))] async fn get_mixnodes(&self) -> Result, NymAPIError> { @@ -1015,6 +1032,23 @@ pub trait NymApiClientExt: ApiClient { .await } + async fn nodes_by_addresses( + &self, + addresses: Vec, + ) -> Result { + self.post_json( + &[ + routes::API_VERSION, + "unstable", + routes::NYM_NODES_ROUTES, + routes::nym_nodes::BY_ADDRESSES, + ], + NO_PARAMS, + &NodesByAddressesRequestBody { addresses }, + ) + .await + } + #[instrument(level = "debug", skip(self))] async fn get_network_details(&self) -> Result { self.get_json( @@ -1023,6 +1057,15 @@ pub trait NymApiClientExt: ApiClient { ) .await } + + #[instrument(level = "debug", skip(self))] + async fn get_chain_status(&self) -> Result { + self.get_json( + &[routes::API_VERSION, routes::NETWORK, routes::CHAIN_STATUS], + NO_PARAMS, + ) + .await + } } #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] diff --git a/common/client-libs/validator-client/src/nym_api/routes.rs b/common/client-libs/validator-client/src/nym_api/routes.rs index 767acc0b8d..8787264c4a 100644 --- a/common/client-libs/validator-client/src/nym_api/routes.rs +++ b/common/client-libs/validator-client/src/nym_api/routes.rs @@ -43,11 +43,14 @@ pub mod nym_nodes { pub const NYM_NODES_BONDED: &str = "bonded"; pub const NYM_NODES_REWARDED_SET: &str = "rewarded-set"; pub const NYM_NODES_REFRESH_DESCRIBED: &str = "refresh-described"; + pub const BY_ADDRESSES: &str = "by-addresses"; } pub const STATUS_ROUTES: &str = "status"; pub const API_STATUS_ROUTES: &str = "api-status"; pub const HEALTH: &str = "health"; +pub const BUILD_INFORMATION: &str = "build-information"; + pub const MIXNODE: &str = "mixnode"; pub const GATEWAY: &str = "gateway"; pub const NYM_NODES: &str = "nym-nodes"; @@ -69,4 +72,5 @@ pub const SUBMIT_NODE: &str = "submit-node-monitoring-results"; pub const SERVICE_PROVIDERS: &str = "services"; pub const DETAILS: &str = "details"; +pub const CHAIN_STATUS: &str = "chain-status"; pub const NETWORK: &str = "network"; diff --git a/common/client-libs/validator-client/src/nyxd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs index 14a91a4459..03746e305e 100644 --- a/common/client-libs/validator-client/src/nyxd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -28,7 +28,6 @@ use nym_network_defaults::{ChainDetails, NymNetworkDetails}; use serde::{de::DeserializeOwned, Serialize}; use std::fmt::Debug; use std::time::SystemTime; -use tendermint_rpc::endpoint::block::Response as BlockResponse; use tendermint_rpc::endpoint::*; use tendermint_rpc::{Error as TendermintRpcError, Order}; use url::Url; @@ -63,6 +62,8 @@ pub use cw3; pub use cw4; pub use cw_controllers; pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment}; +pub use prost::Name; +pub use tendermint_rpc::endpoint::block::Response as BlockResponse; pub use tendermint_rpc::{ endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse}, query::Query, diff --git a/common/config/src/helpers.rs b/common/config/src/helpers.rs index cfed0dc1c7..d1ea90010b 100644 --- a/common/config/src/helpers.rs +++ b/common/config/src/helpers.rs @@ -25,15 +25,6 @@ pub fn in6addr_any_init() -> IpAddr { IpAddr::V6(Ipv6Addr::UNSPECIFIED) } -/// Helper for providing binding warnings if node tries to bind to any of those -pub const SPECIAL_ADDRESSES: &[IpAddr] = &[ - IpAddr::V4(Ipv4Addr::LOCALHOST), - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - IpAddr::V4(Ipv4Addr::BROADCAST), - IpAddr::V6(Ipv6Addr::LOCALHOST), - IpAddr::V6(Ipv6Addr::UNSPECIFIED), -]; - // TODO: is it really part of 'Config'? pub trait OptionalSet { /// If the value is available (i.e. `Some`), the provided closure is applied. diff --git a/common/crypto/Cargo.toml b/common/crypto/Cargo.toml index ee8819585e..6e1f8cfb12 100644 --- a/common/crypto/Cargo.toml +++ b/common/crypto/Cargo.toml @@ -43,4 +43,7 @@ serde = ["dep:serde", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde" asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"] hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array", "sha2"] stream_cipher = ["aes", "ctr", "cipher", "generic-array"] -sphinx = ["nym-sphinx-types/sphinx"] \ No newline at end of file +sphinx = ["nym-sphinx-types/sphinx"] + +[lints] +workspace = true \ No newline at end of file diff --git a/common/crypto/src/hmac.rs b/common/crypto/src/hmac.rs index c2678eb0bd..f878df238c 100644 --- a/common/crypto/src/hmac.rs +++ b/common/crypto/src/hmac.rs @@ -16,8 +16,11 @@ pub fn compute_keyed_hmac(key: &[u8], data: &[u8]) -> HmacOutput where D: Digest + BlockSizeUser, { - let mut hmac = SimpleHmac::::new_from_slice(key) - .expect("HMAC was instantiated with a key of an invalid size!"); + // SAFETY: hmac is fine with keys of any size; if they're smaller than the block size of the underlying + // digest, they're padded with 0. if they're larger they're hashed and padded + // the reason for `Result` return type is due to the trait definition + #[allow(clippy::unwrap_used)] + let mut hmac = SimpleHmac::::new_from_slice(key).unwrap(); hmac.update(data); hmac.finalize() } @@ -27,8 +30,11 @@ pub fn recompute_keyed_hmac_and_verify_tag(key: &[u8], data: &[u8], tag: &[u8 where D: Digest + BlockSizeUser, { - let mut hmac = SimpleHmac::::new_from_slice(key) - .expect("HMAC was instantiated with a key of an invalid size!"); + // SAFETY: hmac is fine with keys of any size; if they're smaller than the block size of the underlying + // digest, they're padded with 0. if they're larger they're hashed and padded + // the reason for `Result` return type is due to the trait definition + #[allow(clippy::unwrap_used)] + let mut hmac = SimpleHmac::::new_from_slice(key).unwrap(); hmac.update(data); let tag_arr = Output::::from_slice(tag); diff --git a/common/crypto/src/shared_key.rs b/common/crypto/src/shared_key.rs index f90e99feb2..def5709339 100644 --- a/common/crypto/src/shared_key.rs +++ b/common/crypto/src/shared_key.rs @@ -27,12 +27,16 @@ where // after performing diffie-hellman we don't care about the private component anymore let dh_result = ephemeral_keypair.private_key().diffie_hellman(remote_key); - // there is no reason for this to fail as our okm is expected to be only C::KeySize bytes + // SAFETY: while this is a relatively weak assumption, it's unlikely that any stream cipher has `C::key_size()` + // larger than 255 * chunk_size of the digest (so for example keys larger than 8160 bytes if sh256 is used) + #[allow(clippy::expect_used)] let okm = hkdf::extract_then_expand::(None, &dh_result, None, C::key_size()) .expect("somehow too long okm was provided"); - let derived_shared_key = - Key::::from_exact_iter(okm).expect("okm was expanded to incorrect length!"); + // SAFETY: the generated okm has exactly `C::key_size()` elements, + // so this call is safe + #[allow(clippy::unwrap_used)] + let derived_shared_key = Key::::from_exact_iter(okm).unwrap(); (ephemeral_keypair, derived_shared_key) } @@ -48,9 +52,14 @@ where { let dh_result = local_key.diffie_hellman(remote_key); - // there is no reason for this to fail as our okm is expected to be only C::KeySize bytes + // SAFETY: while this is a relatively weak assumption, it's unlikely that any stream cipher has `C::key_size()` + // larger than 255 * chunk_size of the digest (so for example keys larger than 8160 bytes if sh256 is used) + #[allow(clippy::expect_used)] let okm = hkdf::extract_then_expand::(None, &dh_result, None, C::key_size()) .expect("somehow too long okm was provided"); - Key::::from_exact_iter(okm).expect("okm was expanded to incorrect length!") + // SAFETY: the generated okm has exactly `C::key_size()` elements, + // so this call is safe + #[allow(clippy::unwrap_used)] + Key::::from_exact_iter(okm).unwrap() } diff --git a/common/crypto/src/symmetric/stream_cipher.rs b/common/crypto/src/symmetric/stream_cipher.rs index 4bd21eb031..758f32617c 100644 --- a/common/crypto/src/symmetric/stream_cipher.rs +++ b/common/crypto/src/symmetric/stream_cipher.rs @@ -60,20 +60,15 @@ where Iv::::default() } -pub fn iv_from_slice(b: &[u8]) -> &IV +pub fn try_iv_from_slice(b: &[u8]) -> Option<&IV> where C: IvSizeUser, { if b.len() != C::iv_size() { - // `from_slice` would have caused a panic about this issue anyway. - // Now we at least have slightly more information - panic!( - "Tried to convert {} bytes to IV. Expected {}", - b.len(), - C::iv_size() - ) + None + } else { + Some(IV::::from_slice(b)) } - IV::::from_slice(b) } // TODO: there's really no way to use more parts of the keystream if it was required at some point. diff --git a/common/dkg/Cargo.toml b/common/dkg/Cargo.toml index 799e38a702..58e899d7b3 100644 --- a/common/dkg/Cargo.toml +++ b/common/dkg/Cargo.toml @@ -21,7 +21,7 @@ lazy_static = { workspace = true } rand = { workspace = true } rand_chacha = { workspace = true } rand_core = { workspace = true } -sha2 = "0.9" +sha2 = { workspace = true } serde = { workspace = true } serde_derive = { workspace = true } thiserror = { workspace = true } diff --git a/common/dkg/src/utils.rs b/common/dkg/src/utils.rs index 28fb99ad5d..e654a83f8c 100644 --- a/common/dkg/src/utils.rs +++ b/common/dkg/src/utils.rs @@ -54,12 +54,12 @@ pub(crate) fn hash_to_scalar>(msg: M, domain: &[u8]) -> Scalar { pub(crate) fn hash_to_scalars>(msg: M, domain: &[u8], n: usize) -> Vec { let mut output = vec![Scalar::zero(); n]; - Scalar::hash_to_field::>(msg.as_ref(), domain, &mut output); + Scalar::hash_to_field::, _>([msg], domain, &mut output); output } pub(crate) fn hash_g2>(msg: M, domain: &[u8]) -> G2Projective { - >>::hash_to_curve(msg, domain) + >>::hash_to_curve([msg], domain) } pub(crate) fn combine_scalar_chunks(chunks: &[Scalar]) -> Scalar { @@ -112,3 +112,97 @@ pub(crate) fn deserialize_g2(b: &[u8]) -> Option { G2Projective::from_bytes(&encoding).into() } } + +#[cfg(test)] +mod tests { + use super::*; + use bls12_381::G2Affine; + + #[test] + fn test_hash_to_scalar() { + let msg1 = "foo"; + let expected1 = Scalar::from_bytes(&[ + 253, 57, 224, 227, 175, 195, 226, 82, 46, 175, 33, 126, 171, 239, 255, 92, 108, 168, 6, + 79, 90, 11, 235, 236, 221, 10, 85, 133, 42, 81, 95, 30, + ]) + .unwrap(); + + let msg2 = "bar"; + let expected2 = Scalar::from_bytes(&[ + 48, 83, 69, 52, 42, 18, 135, 244, 211, 190, 160, 196, 118, 154, 24, 126, 0, 125, 72, + 201, 170, 225, 123, 201, 52, 120, 171, 132, 235, 182, 20, 26, + ]) + .unwrap(); + + let msg3 = [ + 33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123, + 250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27, + 115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101, + 60, 42, 92, 128, 131, 161, 43, + ]; + let expected3 = Scalar::from_bytes(&[ + 128, 189, 8, 43, 186, 55, 52, 61, 171, 196, 159, 177, 162, 100, 27, 143, 85, 83, 218, + 171, 91, 220, 155, 25, 7, 38, 2, 36, 4, 93, 136, 4, + ]) + .unwrap(); + + assert_eq!( + hash_to_scalar(msg1, b"NYMECASH-V01-CS02-with-expander-SHA256"), + expected1 + ); + assert_eq!( + hash_to_scalar(msg2, b"NYMECASH-V01-CS02-with-expander-SHA256"), + expected2 + ); + assert_eq!( + hash_to_scalar(msg3, b"NYMECASH-V01-CS02-with-expander-SHA256"), + expected3 + ); + } + + #[test] + fn test_hash_g2() { + let msg1 = "foo"; + let expected1 = G2Affine::from_compressed(&[ + 175, 187, 62, 7, 29, 17, 42, 93, 28, 93, 234, 253, 101, 166, 158, 187, 153, 82, 93, 18, + 11, 233, 36, 107, 51, 117, 30, 127, 32, 254, 210, 77, 133, 12, 253, 255, 84, 128, 36, + 214, 234, 103, 50, 21, 26, 78, 112, 49, 20, 69, 19, 109, 7, 78, 33, 227, 196, 180, 168, + 219, 73, 251, 192, 221, 41, 138, 160, 131, 191, 186, 156, 117, 179, 179, 191, 235, 171, + 26, 219, 148, 170, 179, 11, 38, 137, 14, 95, 115, 171, 186, 163, 82, 158, 6, 239, 88, + ]) + .unwrap() + .into(); + + let msg2 = "bar"; + let expected2 = G2Affine::from_compressed(&[ + 183, 25, 90, 187, 34, 184, 30, 182, 215, 242, 158, 83, 116, 34, 210, 96, 188, 79, 83, + 255, 100, 122, 90, 188, 196, 93, 164, 253, 20, 106, 205, 33, 48, 140, 60, 149, 66, 246, + 121, 244, 146, 66, 170, 60, 113, 95, 102, 237, 25, 231, 8, 42, 121, 124, 180, 140, 34, + 104, 173, 251, 89, 189, 28, 196, 49, 66, 101, 38, 68, 44, 40, 235, 21, 35, 204, 123, + 218, 238, 216, 92, 134, 217, 212, 246, 176, 77, 187, 0, 245, 134, 132, 73, 31, 44, 137, + 197, + ]) + .unwrap() + .into(); + let msg3 = [ + 33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123, + 250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27, + 115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101, + 60, 42, 92, 128, 131, 161, 43, + ]; + let expected3 = G2Affine::from_compressed(&[ + 151, 185, 8, 123, 223, 150, 192, 192, 115, 10, 3, 129, 49, 179, 31, 108, 0, 17, 46, + 231, 184, 164, 247, 228, 22, 142, 87, 70, 120, 111, 154, 15, 245, 110, 32, 84, 53, 117, + 239, 93, 89, 119, 32, 17, 39, 250, 198, 137, 6, 95, 137, 202, 54, 244, 238, 190, 11, + 217, 237, 95, 72, 59, 140, 56, 3, 42, 61, 195, 192, 101, 46, 204, 207, 75, 70, 176, + 207, 48, 24, 195, 248, 234, 178, 168, 54, 109, 19, 189, 51, 52, 120, 69, 248, 226, 102, + 91, + ]) + .unwrap() + .into(); + + assert_eq!(hash_g2(msg1, b"DUMMY_TEST_DOMAIN"), expected1); + assert_eq!(hash_g2(msg2, b"DUMMY_TEST_DOMAIN"), expected2); + assert_eq!(hash_g2(msg3, b"DUMMY_TEST_DOMAIN"), expected3); + } +} diff --git a/common/gateway-requests/Cargo.toml b/common/gateway-requests/Cargo.toml index b461c428c1..3448184eae 100644 --- a/common/gateway-requests/Cargo.toml +++ b/common/gateway-requests/Cargo.toml @@ -20,11 +20,14 @@ serde_json = { workspace = true } strum = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true, features = ["log"] } +time = { workspace = true } +subtle = { workspace = true } zeroize = { workspace = true } nym-crypto = { path = "../crypto", features = ["aead", "hashing"] } nym-pemstore = { path = "../pemstore" } nym-sphinx = { path = "../nymsphinx" } +nym-serde-helpers = { path = "../serde-helpers", features = ["base64"] } nym-task = { path = "../task" } nym-credentials = { path = "../credentials" } diff --git a/common/gateway-requests/src/authentication/encrypted_address.rs b/common/gateway-requests/src/authentication/encrypted_address.rs index 8b81074454..772b8ca91b 100644 --- a/common/gateway-requests/src/authentication/encrypted_address.rs +++ b/common/gateway-requests/src/authentication/encrypted_address.rs @@ -15,6 +15,12 @@ use thiserror::Error; // this is no longer constant size due to the differences in ciphertext between aes128ctr and aes256gcm-siv (inclusion of tag) pub struct EncryptedAddressBytes(Vec); +impl From> for EncryptedAddressBytes { + fn from(encrypted_address: Vec) -> Self { + EncryptedAddressBytes(encrypted_address) + } +} + #[derive(Debug, Error)] pub enum EncryptedAddressConversionError { #[error("Failed to decode the encrypted address - {0}")] diff --git a/common/gateway-requests/src/lib.rs b/common/gateway-requests/src/lib.rs index b7af197cc5..0d300bc4a4 100644 --- a/common/gateway-requests/src/lib.rs +++ b/common/gateway-requests/src/lib.rs @@ -19,7 +19,7 @@ pub use shared_key::{ SharedGatewayKey, SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey, }; -pub const CURRENT_PROTOCOL_VERSION: u8 = AES_GCM_SIV_PROTOCOL_VERSION; +pub const CURRENT_PROTOCOL_VERSION: u8 = AUTHENTICATE_V2_PROTOCOL_VERSION; /// Defines the current version of the communication protocol between gateway and clients. /// It has to be incremented for any breaking change. @@ -27,10 +27,29 @@ pub const CURRENT_PROTOCOL_VERSION: u8 = AES_GCM_SIV_PROTOCOL_VERSION; // 1 - initial release // 2 - changes to client credentials structure // 3 - change to AES-GCM-SIV and non-zero IVs +// 4 - introduction of v2 authentication protocol to prevent reply attacks pub const INITIAL_PROTOCOL_VERSION: u8 = 1; pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2; pub const AES_GCM_SIV_PROTOCOL_VERSION: u8 = 3; +pub const AUTHENTICATE_V2_PROTOCOL_VERSION: u8 = 4; // TODO: could using `Mac` trait here for OutputSize backfire? // Should hmac itself be exposed, imported and used instead? pub type LegacyGatewayMacSize = ::OutputSize; + +pub trait GatewayProtocolVersionExt { + fn supports_aes256_gcm_siv(&self) -> bool; + fn supports_authenticate_v2(&self) -> bool; +} + +impl GatewayProtocolVersionExt for Option { + fn supports_aes256_gcm_siv(&self) -> bool { + let Some(protocol) = *self else { return false }; + protocol >= AES_GCM_SIV_PROTOCOL_VERSION + } + + fn supports_authenticate_v2(&self) -> bool { + let Some(protocol) = *self else { return false }; + protocol >= AUTHENTICATE_V2_PROTOCOL_VERSION + } +} diff --git a/common/gateway-requests/src/types/error.rs b/common/gateway-requests/src/types/error.rs index 31d94b75b7..edd8e41b22 100644 --- a/common/gateway-requests/src/types/error.rs +++ b/common/gateway-requests/src/types/error.rs @@ -3,12 +3,14 @@ use crate::SharedKeyUsageError; use nym_credentials_interface::CompactEcashError; +use nym_crypto::asymmetric::ed25519::SignatureError; use nym_sphinx::addressing::nodes::NymNodeRoutingAddressError; use nym_sphinx::forwarding::packet::MixPacketFormattingError; use nym_sphinx::params::packet_sizes::PacketSize; use serde::{Deserialize, Serialize}; use std::string::FromUtf8Error; use thiserror::Error; +use time::OffsetDateTime; // specific errors (that should not be nested!!) for clients to match on #[derive(Debug, Copy, Clone, Error, Serialize, Deserialize)] @@ -92,7 +94,34 @@ pub enum GatewayRequestsError { #[error("the provided [v1] credential has invalid number of parameters - {0}")] InvalidNumberOfEmbededParameters(u32), + #[error("failed to authenticate the client: {0}")] + Authentication(#[from] AuthenticationFailure), + // variant to catch legacy errors #[error("{0}")] Other(String), } + +#[derive(Debug, Error)] +pub enum AuthenticationFailure { + #[error(transparent)] + KeyUsageFailure(#[from] SharedKeyUsageError), + + #[error("failed to verify provided address ciphertext")] + MalformedCiphertext, + + #[error("failed to verify request signature")] + InvalidSignature(#[from] SignatureError), + + #[error("the client is not registered")] + NotRegistered, + + #[error("the provided request timestamp is excessively skewed. got {received} whilst the server time is {server}")] + ExcessiveTimestampSkew { + received: OffsetDateTime, + server: OffsetDateTime, + }, + + #[error("the provided request timestamp is smaller or equal to one previously used")] + RequestReuse, +} diff --git a/common/gateway-requests/src/types/text_request/authenticate.rs b/common/gateway-requests/src/types/text_request/authenticate.rs new file mode 100644 index 0000000000..6c4c884957 --- /dev/null +++ b/common/gateway-requests/src/types/text_request/authenticate.rs @@ -0,0 +1,151 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::{AuthenticationFailure, GatewayRequestsError, SharedGatewayKey}; +use nym_crypto::asymmetric::ed25519; +use serde::{Deserialize, Serialize}; +use std::iter; +use std::time::Duration; +use subtle::ConstantTimeEq; +use time::OffsetDateTime; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct AuthenticateRequest { + #[serde(flatten)] + pub content: AuthenticateRequestContent, + + pub request_signature: ed25519::Signature, +} + +impl AuthenticateRequest { + pub fn new( + protocol_version: u8, + shared_key: &SharedGatewayKey, + identity_keys: &ed25519::KeyPair, + ) -> Result { + let content = AuthenticateRequestContent::new( + protocol_version, + shared_key, + *identity_keys.public_key(), + )?; + let plaintext = content.plaintext(); + let request_signature = identity_keys.private_key().sign(&plaintext); + + Ok(AuthenticateRequest { + content, + request_signature, + }) + } + + pub fn verify_timestamp( + &self, + max_request_timestamp_skew: Duration, + ) -> Result<(), AuthenticationFailure> { + let now = OffsetDateTime::now_utc(); + if self.content.request_timestamp() < now - max_request_timestamp_skew { + return Err(AuthenticationFailure::ExcessiveTimestampSkew { + received: self.content.request_timestamp(), + server: now, + }); + } + if self.content.request_timestamp() - max_request_timestamp_skew > now { + return Err(AuthenticationFailure::ExcessiveTimestampSkew { + received: self.content.request_timestamp(), + server: now, + }); + } + Ok(()) + } + + pub fn ensure_timestamp_not_reused( + &self, + previous: OffsetDateTime, + ) -> Result<(), AuthenticationFailure> { + if self.content.request_timestamp() <= previous { + return Err(AuthenticationFailure::RequestReuse); + } + Ok(()) + } + + pub fn verify_ciphertext( + &self, + shared_key: &SharedGatewayKey, + ) -> Result<(), AuthenticationFailure> { + let expected = shared_key.encrypt( + self.content + .client_identity + .derive_destination_address() + .as_bytes_ref(), + Some(&self.content.nonce), + )?; + + if !bool::from(expected.ct_eq(&self.content.address_ciphertext)) { + return Err(AuthenticationFailure::MalformedCiphertext); + } + Ok(()) + } + + pub fn verify_signature(&self) -> Result<(), AuthenticationFailure> { + let plaintext = self.content.plaintext(); + self.content + .client_identity + .verify(plaintext, &self.request_signature) + .map_err(Into::into) + } +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct AuthenticateRequestContent { + pub protocol_version: u8, + + // this is identical to the client's address + pub client_identity: ed25519::PublicKey, + + #[serde(with = "nym_serde_helpers::base64")] + pub address_ciphertext: Vec, + + #[serde(with = "nym_serde_helpers::base64")] + pub nonce: Vec, + + pub request_unix_timestamp: u64, +} + +impl AuthenticateRequestContent { + fn new( + protocol_version: u8, + shared_key: &SharedGatewayKey, + client_identity: ed25519::PublicKey, + ) -> Result { + let nonce = shared_key.random_nonce_or_iv(); + let destination_address = client_identity.derive_destination_address(); + + let address_ciphertext = + shared_key.encrypt(destination_address.as_bytes_ref(), Some(&nonce))?; + let now = OffsetDateTime::now_utc(); + Ok(AuthenticateRequestContent { + protocol_version, + client_identity, + address_ciphertext, + nonce, + request_unix_timestamp: now.unix_timestamp() as u64, // SAFETY: we're running this in post 1970... + }) + } +} + +impl AuthenticateRequestContent { + pub fn plaintext(&self) -> Vec { + iter::once(self.protocol_version) + .chain(self.client_identity.to_bytes()) + .chain(self.address_ciphertext.iter().copied()) + .chain(self.nonce.iter().copied()) + .chain(self.request_unix_timestamp.to_be_bytes()) + .collect() + } + + pub fn request_timestamp(&self) -> OffsetDateTime { + OffsetDateTime::from_unix_timestamp(self.request_unix_timestamp as i64) + .unwrap_or(OffsetDateTime::UNIX_EPOCH) + } +} diff --git a/common/gateway-requests/src/types/text_request.rs b/common/gateway-requests/src/types/text_request/mod.rs similarity index 89% rename from common/gateway-requests/src/types/text_request.rs rename to common/gateway-requests/src/types/text_request/mod.rs index 0fb864a533..7182dd834e 100644 --- a/common/gateway-requests/src/types/text_request.rs +++ b/common/gateway-requests/src/types/text_request/mod.rs @@ -2,16 +2,21 @@ // SPDX-License-Identifier: Apache-2.0 use crate::models::CredentialSpendingRequest; +use crate::text_request::authenticate::AuthenticateRequest; use crate::{ GatewayRequestsError, SharedGatewayKey, SymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION, - CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION, + AUTHENTICATE_V2_PROTOCOL_VERSION, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, + INITIAL_PROTOCOL_VERSION, }; use nym_credentials_interface::CredentialSpendingData; +use nym_crypto::asymmetric::ed25519; use nym_sphinx::DestinationAddressBytes; use serde::{Deserialize, Serialize}; use std::str::FromStr; use tungstenite::Message; +pub mod authenticate; + // wrapper for all encrypted requests for ease of use #[derive(Serialize, Deserialize, Debug, Clone)] #[non_exhaustive] @@ -68,6 +73,9 @@ pub enum ClientControlRequest { enc_address: String, iv: String, }, + + AuthenticateV2(Box), + #[serde(alias = "handshakePayload")] RegisterHandshakeInitRequest { #[serde(default)] @@ -123,9 +131,22 @@ impl ClientControlRequest { }) } + pub fn new_authenticate_v2( + shared_key: &SharedGatewayKey, + identity_keys: &ed25519::KeyPair, + ) -> Result { + // if we're using v2 authentication, we must announce at least that protocol version + let protocol_version = AUTHENTICATE_V2_PROTOCOL_VERSION; + + Ok(ClientControlRequest::AuthenticateV2(Box::new( + AuthenticateRequest::new(protocol_version, shared_key, identity_keys)?, + ))) + } + pub fn name(&self) -> String { match self { ClientControlRequest::Authenticate { .. } => "Authenticate".to_string(), + ClientControlRequest::AuthenticateV2(..) => "AuthenticateV2".to_string(), ClientControlRequest::RegisterHandshakeInitRequest { .. } => { "RegisterHandshakeInitRequest".to_string() } diff --git a/common/gateway-storage/migrations/20250303120000_add_authentication_timestamp.sql b/common/gateway-storage/migrations/20250303120000_add_authentication_timestamp.sql new file mode 100644 index 0000000000..e211803e9e --- /dev/null +++ b/common/gateway-storage/migrations/20250303120000_add_authentication_timestamp.sql @@ -0,0 +1,7 @@ +/* + * Copyright 2025 - Nym Technologies SA + * SPDX-License-Identifier: GPL-3.0-only + */ + +ALTER TABLE shared_keys + ADD COLUMN last_used_authentication TIMESTAMP WITHOUT TIME ZONE; \ No newline at end of file diff --git a/common/gateway-storage/src/lib.rs b/common/gateway-storage/src/lib.rs index 5f289ec106..2d05d43fe7 100644 --- a/common/gateway-storage/src/lib.rs +++ b/common/gateway-storage/src/lib.rs @@ -200,6 +200,20 @@ impl GatewayStorage { Ok(()) } + pub async fn update_last_used_authentication_timestamp( + &self, + client_id: i64, + last_used_authentication_timestamp: OffsetDateTime, + ) -> Result<(), GatewayStorageError> { + self.shared_key_manager + .update_last_used_authentication_timestamp( + client_id, + last_used_authentication_timestamp, + ) + .await?; + Ok(()) + } + pub async fn get_client(&self, client_id: i64) -> Result, GatewayStorageError> { let client = self.client_manager.get_client(client_id).await?; Ok(client) diff --git a/common/gateway-storage/src/models.rs b/common/gateway-storage/src/models.rs index c623f72602..665b8fee47 100644 --- a/common/gateway-storage/src/models.rs +++ b/common/gateway-storage/src/models.rs @@ -14,13 +14,13 @@ pub struct Client { #[derive(FromRow)] pub struct PersistedSharedKeys { - #[allow(dead_code)] pub client_id: i64, #[allow(dead_code)] pub client_address_bs58: String, pub derived_aes128_ctr_blake3_hmac_keys_bs58: Option, pub derived_aes256_gcm_siv_key: Option>, + pub last_used_authentication: Option, } impl TryFrom for SharedGatewayKey { diff --git a/common/gateway-storage/src/shared_keys.rs b/common/gateway-storage/src/shared_keys.rs index 9d17535fb2..a7ae832cac 100644 --- a/common/gateway-storage/src/shared_keys.rs +++ b/common/gateway-storage/src/shared_keys.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::models::PersistedSharedKeys; +use time::OffsetDateTime; #[derive(Clone)] pub(crate) struct SharedKeysManager { @@ -68,6 +69,22 @@ impl SharedKeysManager { Ok(()) } + pub(crate) async fn update_last_used_authentication_timestamp( + &self, + client_id: i64, + last_used: OffsetDateTime, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + "UPDATE shared_keys SET last_used_authentication = ? WHERE client_id = ?;", + last_used, + client_id + ) + .execute(&self.connection_pool) + .await?; + + Ok(()) + } + /// Tries to retrieve shared keys stored for the particular client. /// /// # Arguments @@ -77,13 +94,10 @@ impl SharedKeysManager { &self, client_address_bs58: &str, ) -> Result, sqlx::Error> { - sqlx::query_as!( - PersistedSharedKeys, - "SELECT * FROM shared_keys WHERE client_address_bs58 = ?", - client_address_bs58 - ) - .fetch_optional(&self.connection_pool) - .await + sqlx::query_as("SELECT * FROM shared_keys WHERE client_address_bs58 = ?") + .bind(client_address_bs58) + .fetch_optional(&self.connection_pool) + .await } /// Removes from the database shared keys derived with the particular client. diff --git a/common/http-api-client/Cargo.toml b/common/http-api-client/Cargo.toml index 33ff7bc6f8..79a291e827 100644 --- a/common/http-api-client/Cargo.toml +++ b/common/http-api-client/Cargo.toml @@ -21,6 +21,12 @@ serde_json = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } +# used for decoding text responses (they were already implicitly included) +bytes = { workspace = true } +encoding_rs = { workspace = true } +mime = { workspace = true } + + nym-bin-common = { path = "../bin-common" } [target."cfg(not(target_arch = \"wasm32\"))".dependencies] @@ -32,4 +38,4 @@ workspace = true features = ["tokio"] [dev-dependencies] -tokio = { workspace = true, features=["rt", "macros"] } +tokio = { workspace = true, features = ["rt", "macros"] } diff --git a/common/http-api-client/src/dns.rs b/common/http-api-client/src/dns.rs index d35d72487b..4d4aae82d6 100644 --- a/common/http-api-client/src/dns.rs +++ b/common/http-api-client/src/dns.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + //! DNS resolver configuration for internal lookups. //! //! The resolver itself is the set combination of the google, cloudflare, and quad9 endpoints @@ -9,19 +12,35 @@ //! //! Requires the `dns-over-https-rustls`, `webpki-roots` feature for the //! `hickory-resolver` crate +//! +//! +//! Note: The hickory DoH resolver can cause warning logs about H2 connection failure. This +//! indicates that the long lived https connection was closed by the remote peer and the resolver +//! will have to reconnect. It should not impact actual functionality. +//! +//! code ref: https://github.com/hickory-dns/hickory-dns/blob/06a8b1ce9bd9322d8e6accf857d30257e1274427/crates/proto/src/h2/h2_client_stream.rs#L534 +//! +//! example log: +//! +//! ```txt +//! WARN /home/ubuntu/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/hickory-proto-0.24.3/src/h2/h2_client_stream.rs:493: h2 connection failed: unexpected end of file +//! ``` #![deny(missing_docs)] use crate::ClientBuilder; -use std::{net::SocketAddr, sync::Arc}; +use std::{ + net::SocketAddr, + sync::{Arc, LazyLock}, +}; -use hickory_resolver::lookup_ip::LookupIp; use hickory_resolver::{ config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ResolverOpts}, error::ResolveError, lookup_ip::LookupIpIntoIter, TokioAsyncResolver, }; +use hickory_resolver::{error::ResolveErrorKind, lookup_ip::LookupIp}; use once_cell::sync::OnceCell; use reqwest::dns::{Addrs, Name, Resolve, Resolving}; use tracing::warn; @@ -30,6 +49,13 @@ impl ClientBuilder { /// Override the DNS resolver implementation used by the underlying http client. pub fn dns_resolver(mut self, resolver: Arc) -> Self { self.reqwest_client_builder = self.reqwest_client_builder.dns_resolver(resolver); + self.use_secure_dns = false; + self + } + + /// Override the DNS resolver implementation used by the underlying http client. + pub fn no_hickory_dns(mut self) -> Self { + self.use_secure_dns = false; self } } @@ -38,6 +64,14 @@ struct SocketAddrs { iter: LookupIpIntoIter, } +// n.b. static items do not call [`Drop`] on program termination, so this won't be deallocated. +// this is fine, as the OS can deallocate the terminated program faster than we can free memory +// but tools like valgrind might report "memory leaks" as it isn't obvious this is intentional. +static SHARED_RESOLVER: LazyLock = LazyLock::new(|| { + tracing::debug!("Initializing shared DNS resolver"); + HickoryDnsResolver::default() +}); + #[derive(Debug, thiserror::Error)] #[error("hickory-dns resolver error: {hickory_error}")] /// Error occurring while resolving a hostname into an IP address. @@ -47,29 +81,62 @@ pub struct HickoryDnsError { } /// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait. +/// +/// Typical use involves instantiating using the `Default` implementation and then resolving using +/// methods or trait implementations. +/// +/// The default initialization uses a shared underlying `AsyncResolver`. If a thread local resolver +/// is required use `thread_resolver()` to build a resolver with an independently instantiated +/// internal `AsyncResolver`. #[derive(Debug, Default, Clone)] pub struct HickoryDnsResolver { - /// Since we might not have been called in the context of a - /// Tokio Runtime in initialization, so we must delay the actual - /// construction of the resolver. + // Since we might not have been called in the context of a + // Tokio Runtime in initialization, so we must delay the actual + // construction of the resolver. state: Arc>, fallback: Arc>, + dont_use_shared: bool, } impl Resolve for HickoryDnsResolver { fn resolve(&self, name: Name) -> Resolving { let resolver = self.state.clone(); let fallback = self.fallback.clone(); + let independent = self.dont_use_shared; Box::pin(async move { - let resolver = resolver.get_or_try_init(new_resolver)?; + let resolver = resolver.get_or_try_init(|| { + // using a closure here is slightly gross, but this makes sure that if the + // lazy-init returns an error it can be handled by the client + if independent { + new_resolver() + } else { + Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone()) + } + })?; // try the primary DNS resolver that we set up (DoH or DoT or whatever) let lookup = match resolver.lookup_ip(name.as_str()).await { Ok(res) => res, Err(e) => { // on failure use the fall back system configured DNS resolver - warn!("primary DNS failed w/ error {e}: using system fallback"); - let resolver = fallback.get_or_try_init(new_resolver_system)?; + match e.kind() { + ResolveErrorKind::NoRecordsFound { .. } => {} + _ => { + warn!("primary DNS failed w/ error {e}: using system fallback"); + } + } + let resolver = fallback.get_or_try_init(|| { + // using a closure here is slightly gross, but this makes sure that if the + // lazy-init returns an error it can be handled by the client + if independent { + new_resolver_system() + } else { + Ok(SHARED_RESOLVER + .fallback + .get_or_try_init(new_resolver_system)? + .clone()) + } + })?; resolver.lookup_ip(name.as_str()).await? } }; @@ -93,21 +160,55 @@ impl Iterator for SocketAddrs { impl HickoryDnsResolver { /// Attempt to resolve a domain name to a set of ['IpAddr']s pub async fn resolve_str(&self, name: &str) -> Result { - let resolver = self.state.get_or_try_init(new_resolver)?; + let resolver = self.state.get_or_try_init(|| self.new_resolver())?; // try the primary DNS resolver that we set up (DoH or DoT or whatever) let lookup = match resolver.lookup_ip(name).await { Ok(res) => res, Err(e) => { // on failure use the fall back system configured DNS resolver - warn!("primary DNS failed w/ error {e}: using system fallback"); - let resolver = self.fallback.get_or_try_init(new_resolver_system)?; + match e.kind() { + ResolveErrorKind::NoRecordsFound { .. } => {} + _ => { + warn!("primary DNS failed w/ error {e}: using system fallback"); + } + } + let resolver = self + .fallback + .get_or_try_init(|| self.new_resolver_system())?; resolver.lookup_ip(name).await? } }; Ok(lookup) } + + /// Create a (lazy-initialized) resolver that is not shared across threads. + pub fn thread_resolver() -> Self { + Self { + dont_use_shared: true, + ..Default::default() + } + } + + fn new_resolver(&self) -> Result { + if self.dont_use_shared { + new_resolver() + } else { + Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone()) + } + } + + fn new_resolver_system(&self) -> Result { + if self.dont_use_shared { + new_resolver_system() + } else { + Ok(SHARED_RESOLVER + .fallback + .get_or_try_init(new_resolver_system)? + .clone()) + } + } } /// Create a new resolver with a custom DoT based configuration. The options are overridden to look diff --git a/common/http-api-client/src/lib.rs b/common/http-api-client/src/lib.rs index 08c704897d..5a360f7be1 100644 --- a/common/http-api-client/src/lib.rs +++ b/common/http-api-client/src/lib.rs @@ -147,13 +147,13 @@ use thiserror::Error; use tracing::{instrument, warn}; use url::Url; +use http::HeaderMap; +pub use reqwest::IntoUrl; #[cfg(not(target_arch = "wasm32"))] use std::net::SocketAddr; #[cfg(not(target_arch = "wasm32"))] use std::sync::Arc; -pub use reqwest::IntoUrl; - mod user_agent; pub use user_agent::UserAgent; @@ -210,6 +210,12 @@ pub enum HttpClientError { #[error("failed to resolve request. status: '{status}', additional error message: {error}")] EndpointFailure { status: StatusCode, error: E }, + #[error("failed to decode response body: {source} from {content}")] + ResponseDecodeFailure { + source: serde_json::Error, + content: String, + }, + #[cfg(target_arch = "wasm32")] #[error("the request has timed out")] RequestTimeout, @@ -222,6 +228,8 @@ pub struct ClientBuilder { timeout: Option, custom_user_agent: bool, reqwest_client_builder: reqwest::ClientBuilder, + #[allow(dead_code)] // not dead code, just unused in wasm + use_secure_dns: bool, } impl ClientBuilder { @@ -233,37 +241,46 @@ impl ClientBuilder { U: IntoUrl, E: Display, { - // a naive check: if the provided URL does not start with http(s), add that scheme let str_url = url.as_str(); + // a naive check: if the provided URL does not start with http(s), add that scheme if !str_url.starts_with("http") { let alt = format!("http://{str_url}"); warn!("the provided url ('{str_url}') does not contain scheme information. Changing it to '{alt}' ..."); // TODO: or should we maybe default to https? Self::new(alt) } else { - #[cfg(target_arch = "wasm32")] - let reqwest_client_builder = reqwest::ClientBuilder::new(); + Ok(Self::new_with_url(url.into_url()?)) + } + } - #[cfg(not(target_arch = "wasm32"))] - let reqwest_client_builder = { - let r = reqwest::ClientBuilder::new() - .dns_resolver(Arc::new(HickoryDnsResolver::default())); + /// Constructs a new http `ClientBuilder` from a valid url. + pub fn new_with_url(url: Url) -> Self { + if !url.scheme().starts_with("http") { + warn!("the provided url ('{url}') does not use HTTP / HTTPS scheme"); + } - // Note this is extra as the `gzip` feature for `reqwest` crate should be enabled which - // `"Enable[s] auto gzip decompression by checking the Content-Encoding response header."` - // - // I am going to leave it here anyways so that gzip decompression is attempted even if - // that feature is removed. - r.gzip(true) - }; + #[cfg(target_arch = "wasm32")] + let reqwest_client_builder = reqwest::ClientBuilder::new(); - Ok(ClientBuilder { - url: url.into_url()?, - timeout: None, - custom_user_agent: false, - reqwest_client_builder, - }) + #[cfg(not(target_arch = "wasm32"))] + let reqwest_client_builder = { + let r = reqwest::ClientBuilder::new(); + + // Note this is extra as the `gzip` feature for `reqwest` crate should be enabled which + // `"Enable[s] auto gzip decompression by checking the Content-Encoding response header."` + // + // I am going to leave it here anyways so that gzip decompression is attempted even if + // that feature is removed. + r.gzip(true) + }; + + ClientBuilder { + url, + timeout: None, + custom_user_agent: false, + reqwest_client_builder, + use_secure_dns: true, } } @@ -319,10 +336,18 @@ impl ClientBuilder { let mut builder = self .reqwest_client_builder .timeout(self.timeout.unwrap_or(DEFAULT_TIMEOUT)); + + // if no custom user agent was set, use a default if !self.custom_user_agent { builder = builder.user_agent(format!("nym-http-api-client/{}", env!("CARGO_PKG_VERSION"))) } + + // unless explicitly disabled use the DoT/DoH enabled resolver + if self.use_secure_dns { + builder = builder.dns_resolver(Arc::new(HickoryDnsResolver::default())); + } + builder.build()? }; @@ -349,6 +374,9 @@ pub struct Client { impl Client { /// Create a new http `Client` // no timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed + // + // In order to prevent interference in API requests at the DNS phase we default to a resolver + // that uses DoT and DoH. pub fn new(base_url: Url, timeout: Option) -> Self { Self::new_url::<_, String>(base_url, timeout).expect( "we provided valid url and we were unwrapping previous construction errors anyway", @@ -849,6 +877,26 @@ fn sanitize_url, V: AsRef>( url } +fn decode_as_text(bytes: &bytes::Bytes, headers: HeaderMap) -> String { + use encoding_rs::{Encoding, UTF_8}; + use mime::Mime; + + let content_type = headers + .get(http::header::CONTENT_TYPE) + .and_then(|value| value.to_str().ok()) + .and_then(|value| value.parse::().ok()); + + let encoding_name = content_type + .as_ref() + .and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str())) + .unwrap_or("utf-8"); + + let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8); + + let (text, _, _) = encoding.decode(bytes); + text.into_owned() +} + /// Attempt to parse a json object from an HTTP response #[instrument(level = "debug", skip_all)] pub async fn parse_response(res: Response, allow_empty: bool) -> Result> @@ -864,21 +912,23 @@ where return Err(HttpClientError::EmptyResponse { status }); } } + let headers = res.headers().clone(); + tracing::trace!("headers: {:?}", headers); if res.status().is_success() { - #[cfg(debug_assertions)] - { - let text = res.text().await.inspect_err(|err| { - tracing::error!("Couldn't even get response text: {err}"); - })?; - tracing::trace!("Result:\n{:#?}", text); - - serde_json::from_str(&text) - .map_err(|err| HttpClientError::GenericRequestFailure(err.to_string())) + // internally reqwest is first retrieving bytes and then performing parsing via serde_json + // (and similarly does the same thing for text()) + let full = res.bytes().await?; + match serde_json::from_slice(&full) { + Ok(data) => Ok(data), + Err(err) => { + let content = decode_as_text(&full, headers); + Err(HttpClientError::ResponseDecodeFailure { + source: err, + content, + }) + } } - - #[cfg(not(debug_assertions))] - Ok(res.json().await?) } else if res.status() == StatusCode::NOT_FOUND { Err(HttpClientError::NotFound) } else { diff --git a/common/ip-packet-requests/Cargo.toml b/common/ip-packet-requests/Cargo.toml index 60581a4278..cd9663dc90 100644 --- a/common/ip-packet-requests/Cargo.toml +++ b/common/ip-packet-requests/Cargo.toml @@ -13,6 +13,7 @@ bincode = { workspace = true } bytes = { workspace = true } nym-bin-common = { path = "../bin-common" } nym-crypto = { path = "../crypto" } +nym-service-provider-requests-common = { path = "../service-provider-requests-common" } nym-sphinx = { path = "../nymsphinx" } rand = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/common/ip-packet-requests/src/codec.rs b/common/ip-packet-requests/src/codec.rs index 4047fec8e7..8bd297de8c 100644 --- a/common/ip-packet-requests/src/codec.rs +++ b/common/ip-packet-requests/src/codec.rs @@ -23,14 +23,12 @@ const LENGTH_PREFIX_SIZE: usize = 2; // long for the buffer to fill up, since this kills latency. pub struct MultiIpPacketCodec { buffer: BytesMut, - buffer_timeout: tokio::time::Interval, } impl MultiIpPacketCodec { - pub fn new(buffer_timeout: Duration) -> Self { + pub fn new() -> Self { MultiIpPacketCodec { buffer: BytesMut::new(), - buffer_timeout: tokio::time::interval(buffer_timeout), } } @@ -40,56 +38,81 @@ impl MultiIpPacketCodec { bundled_packets.extend_from_slice(&packet); bundled_packets.freeze() } +} - // Append a packet to the buffer and return the buffer if it's full - pub fn append_packet(&mut self, packet: Bytes) -> Option { - let mut bundled_packets = BytesMut::new(); - self.encode(packet, &mut bundled_packets).unwrap(); - if bundled_packets.is_empty() { - None - } else { - // log::info!("Sphinx packet utilization: {:.2}", self.buffer.len() as f64 / MAX_PACKET_SIZE as f64); - Some(bundled_packets.freeze()) +impl Default for MultiIpPacketCodec { + fn default() -> Self { + Self::new() + } +} + +/// The packet that we encode and decode with the MultiIpPacketCodec into bundled multi-ip packets. +/// The data here is the actual IP packet that we want to send, not the bundled packets. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum IprPacket { + Data(Bytes), + Flush, +} + +impl IprPacket { + pub fn as_bytes(&self) -> &[u8] { + match self { + IprPacket::Data(bytes) => bytes.as_ref(), + IprPacket::Flush => &[], } } - // Flush the current buffer and return it. - pub fn flush_current_buffer(&mut self) -> Bytes { - let mut output_buffer = BytesMut::new(); - std::mem::swap(&mut output_buffer, &mut self.buffer); - output_buffer.freeze() - } - - // Wait for the buffer_timeout to tick and then flush the buffer. - // This is useful when we want to send the buffer even if it's not full. - pub async fn buffer_timeout(&mut self) -> Option { - // Wait for buffer_timeout to tick - let _ = self.buffer_timeout.tick().await; - - // Flush the buffer and return it - let packets = self.flush_current_buffer(); - if packets.is_empty() { - None - } else { - Some(packets) + pub fn into_bytes(self) -> Bytes { + match self { + IprPacket::Data(bytes) => bytes, + IprPacket::Flush => Bytes::new(), } } } -impl Encoder for MultiIpPacketCodec { +impl From for IprPacket { + fn from(bytes: Bytes) -> Self { + IprPacket::Data(bytes) + } +} + +impl From> for IprPacket { + fn from(bytes: Vec) -> Self { + IprPacket::Data(Bytes::from(bytes)) + } +} + +impl Encoder for MultiIpPacketCodec { type Error = Error; - fn encode(&mut self, packet: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> { - if self.buffer.is_empty() { - self.buffer_timeout.reset(); - } + fn encode(&mut self, packet: IprPacket, dst: &mut BytesMut) -> Result<(), Self::Error> { + let packet = match packet { + IprPacket::Flush => { + dst.extend_from_slice(&self.buffer); + self.buffer = BytesMut::new(); + return Ok(()); + } + IprPacket::Data(packet) => packet, + }; + let packet_size = packet.len(); + // If the existing buffer is empty, and the packet is too large, send it directly + if self.buffer.is_empty() && packet_size + LENGTH_PREFIX_SIZE > MAX_PACKET_SIZE { + // Add the packet size + dst.extend_from_slice(&(packet_size as u16).to_be_bytes()); + // Add the packet to the buffer + dst.extend_from_slice(&packet); + return Ok(()); + } + + // If the packet doesn't fit in the existing buffer, send what we have now in the buffer + // and then add it to the next buffer if self.buffer.len() + packet_size + LENGTH_PREFIX_SIZE > MAX_PACKET_SIZE { - // If the packet doesn't fit in the buffer, send the buffer and then add it to the buffer + // Send the existing buffer dst.extend_from_slice(&self.buffer); + // Start a new buffer self.buffer = BytesMut::new(); - self.buffer_timeout.reset(); } // Add the packet size @@ -103,7 +126,7 @@ impl Encoder for MultiIpPacketCodec { } impl Decoder for MultiIpPacketCodec { - type Item = Bytes; + type Item = IprPacket; type Error = Error; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { @@ -125,6 +148,86 @@ impl Decoder for MultiIpPacketCodec { // Read the packet let packet = src.split_to(packet_size); - Ok(Some(packet.freeze())) + Ok(Some(IprPacket::Data(packet.freeze()))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_multi_ip_packet_codec_max_packet_size() { + let mut codec = MultiIpPacketCodec::new(); + let mut buffer = BytesMut::new(); + + // A packet size that is large enough that two packets won't fit in the buffer + const PACKET_SIZE: usize = MAX_PACKET_SIZE - 100; + + let packet1 = IprPacket::from(Bytes::from_static(&[0u8; PACKET_SIZE])); + let packet2 = IprPacket::from(Bytes::from_static(&[0u8; PACKET_SIZE])); + + codec.encode(packet1.clone(), &mut buffer).unwrap(); + assert_eq!(buffer.len(), 0); + + codec.encode(packet2.clone(), &mut buffer).unwrap(); + assert_eq!(buffer.len(), LENGTH_PREFIX_SIZE + PACKET_SIZE); + + // First is the length prefix + assert_eq!(buffer[..2], (PACKET_SIZE as u16).to_be_bytes()); + // Next is the packet + assert_eq!(&buffer[2..], packet1.as_bytes()); + } + + #[test] + fn encode_and_then_decode() { + let mut codec = MultiIpPacketCodec::new(); + let mut buffer = BytesMut::new(); + + let packet = IprPacket::from(Bytes::from_static(&[0u8; 1000])); + codec.encode(packet.clone(), &mut buffer).unwrap(); + codec.encode(packet.clone(), &mut buffer).unwrap(); + + let mut decoded_packets = Vec::new(); + while let Some(decoded_packet) = codec.decode(&mut buffer).unwrap() { + decoded_packets.push(decoded_packet); + } + + assert_eq!(decoded_packets.len(), 1); + assert_eq!(decoded_packets[0].as_bytes(), packet.as_bytes()); + } + + #[test] + fn encode_a_packat_that_is_too_large() { + let mut codec = MultiIpPacketCodec::new(); + let mut buffer = BytesMut::new(); + + let packet = IprPacket::from(Bytes::from_static( + &[0u8; MAX_PACKET_SIZE + MAX_PACKET_SIZE], + )); + codec.encode(packet, &mut buffer).unwrap(); + assert_eq!( + buffer.len(), + MAX_PACKET_SIZE + MAX_PACKET_SIZE + LENGTH_PREFIX_SIZE + ); + codec.encode(IprPacket::Flush, &mut buffer).unwrap(); + assert_eq!( + buffer.len(), + MAX_PACKET_SIZE + MAX_PACKET_SIZE + LENGTH_PREFIX_SIZE + ); + } + + #[test] + fn check_that_max_size_does_not_flush() { + let mut codec = MultiIpPacketCodec::new(); + let mut buffer = BytesMut::new(); + + let packet = IprPacket::from(Bytes::from_static(&[0u8; MAX_PACKET_SIZE - 2])); + codec.encode(packet.clone(), &mut buffer).unwrap(); + assert_eq!(buffer.len(), 0); + + let packet = IprPacket::from(Bytes::from_static(&[0u8; MAX_PACKET_SIZE - 2])); + codec.encode(packet.clone(), &mut buffer).unwrap(); + assert_eq!(buffer.len(), MAX_PACKET_SIZE); } } diff --git a/common/ip-packet-requests/src/lib.rs b/common/ip-packet-requests/src/lib.rs index 1da258c256..977a277446 100644 --- a/common/ip-packet-requests/src/lib.rs +++ b/common/ip-packet-requests/src/lib.rs @@ -2,24 +2,18 @@ use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use std::net::{Ipv4Addr, Ipv6Addr}; -// The current version of the protocol. -// The idea here is that we add new request response types at least one version before we start -// using them. -// Also, depending on the version in the client connect message the IPR could respond with a -// matching older version. -pub use v6::request; -pub use v6::response; - pub mod codec; +pub mod sign; pub mod v6; pub mod v7; +pub mod v8; // version 3: initial version // version 4: IPv6 support // version 5: Add severity level to info response // version 6: Increase the available IPs // version 7: Add signature support (for the future) -pub const CURRENT_VERSION: u8 = 6; +// version 8: Anonymous sends #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct IpPair { @@ -45,3 +39,9 @@ fn make_bincode_serializer() -> impl bincode::Options { .with_big_endian() .with_varint_encoding() } + +fn generate_random() -> u64 { + use rand::RngCore; + let mut rng = rand::rngs::OsRng; + rng.next_u64() +} diff --git a/common/ip-packet-requests/src/v7/signature.rs b/common/ip-packet-requests/src/sign.rs similarity index 62% rename from common/ip-packet-requests/src/v7/signature.rs rename to common/ip-packet-requests/src/sign.rs index e11de693b8..e678a9dc90 100644 --- a/common/ip-packet-requests/src/v7/signature.rs +++ b/common/ip-packet-requests/src/sign.rs @@ -1,6 +1,7 @@ use std::time::Duration; -use nym_crypto::asymmetric::identity; +use nym_crypto::asymmetric::ed25519; +use time::OffsetDateTime; // For reply protection, if a request is older than this, it will be rejected const MAX_REQUEST_AGE: Duration = Duration::from_secs(10); @@ -22,29 +23,37 @@ pub enum SignatureError { #[error("signature verification failed")] VerificationFailed { message: String, - error: identity::SignatureError, + error: ed25519::SignatureError, }, } pub trait SignedRequest { - fn identity(&self) -> &identity::PublicKey; + fn identity(&self) -> Option<&ed25519::PublicKey>; - fn request(&self) -> Result, SignatureError>; + fn request_as_bytes(&self) -> Result, SignatureError>; - fn signature(&self) -> Option<&identity::Signature>; + fn signature(&self) -> Option<&ed25519::Signature>; - fn timestamp(&self) -> time::OffsetDateTime; + fn timestamp(&self) -> OffsetDateTime; fn verify(&self) -> Result<(), SignatureError> { + let identity = match self.identity() { + Some(identity) => identity, + None => { + // If we are not revealing our identity, we don't need to verify anything + return Ok(()); + } + }; + if let Some(signature) = self.signature() { // First check that the request is recent enough - if time::OffsetDateTime::now_utc() - self.timestamp() > MAX_REQUEST_AGE { + if OffsetDateTime::now_utc() - self.timestamp() > MAX_REQUEST_AGE { return Err(SignatureError::RequestOutOfDate); } - let request_as_bytes = self.request()?; + let request_as_bytes = self.request_as_bytes()?; - self.identity() + identity .verify(request_as_bytes, signature) .map_err(|error| SignatureError::VerificationFailed { message: "signature verification failed".to_string(), diff --git a/common/ip-packet-requests/src/v6/conversion.rs b/common/ip-packet-requests/src/v6/conversion.rs deleted file mode 100644 index be2327b2e6..0000000000 --- a/common/ip-packet-requests/src/v6/conversion.rs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::{v6, v7}; - -impl From for v6::response::StaticConnectFailureReason { - fn from(failure: v7::response::StaticConnectFailureReason) -> Self { - match failure { - v7::response::StaticConnectFailureReason::RequestedIpAlreadyInUse => { - v6::response::StaticConnectFailureReason::RequestedIpAlreadyInUse - } - v7::response::StaticConnectFailureReason::RequestedNymAddressAlreadyInUse => { - v6::response::StaticConnectFailureReason::RequestedNymAddressAlreadyInUse - } - v7::response::StaticConnectFailureReason::OutOfDateTimestamp => { - v6::response::StaticConnectFailureReason::Other("out of date timestamp".to_string()) - } - v7::response::StaticConnectFailureReason::Other(reason) => { - v6::response::StaticConnectFailureReason::Other(reason) - } - } - } -} - -impl From for v6::response::DynamicConnectFailureReason { - fn from(failure: v7::response::DynamicConnectFailureReason) -> Self { - match failure { - v7::response::DynamicConnectFailureReason::RequestedNymAddressAlreadyInUse => { - v6::response::DynamicConnectFailureReason::RequestedNymAddressAlreadyInUse - } - v7::response::DynamicConnectFailureReason::NoAvailableIp => { - v6::response::DynamicConnectFailureReason::NoAvailableIp - } - v7::response::DynamicConnectFailureReason::Other(err) => { - v6::response::DynamicConnectFailureReason::Other(err) - } - } - } -} - -impl From for v6::response::InfoResponseReply { - fn from(reply: v7::response::InfoResponseReply) -> Self { - match reply { - v7::response::InfoResponseReply::Generic { msg } => { - v6::response::InfoResponseReply::Generic { msg } - } - v7::response::InfoResponseReply::VersionMismatch { - request_version, - response_version, - } => v6::response::InfoResponseReply::VersionMismatch { - request_version, - response_version, - }, - v7::response::InfoResponseReply::ExitPolicyFilterCheckFailed { dst } => { - v6::response::InfoResponseReply::ExitPolicyFilterCheckFailed { dst } - } - } - } -} - -impl From for v6::response::InfoLevel { - fn from(level: v7::response::InfoLevel) -> Self { - match level { - v7::response::InfoLevel::Info => v6::response::InfoLevel::Info, - v7::response::InfoLevel::Warn => v6::response::InfoLevel::Warn, - v7::response::InfoLevel::Error => v6::response::InfoLevel::Error, - } - } -} diff --git a/common/ip-packet-requests/src/v6/mod.rs b/common/ip-packet-requests/src/v6/mod.rs index ae7394c02d..8f3f1e3955 100644 --- a/common/ip-packet-requests/src/v6/mod.rs +++ b/common/ip-packet-requests/src/v6/mod.rs @@ -1,4 +1,3 @@ -pub mod conversion; pub mod request; pub mod response; diff --git a/common/ip-packet-requests/src/v7/conversion.rs b/common/ip-packet-requests/src/v7/conversion.rs deleted file mode 100644 index 04b0244b05..0000000000 --- a/common/ip-packet-requests/src/v7/conversion.rs +++ /dev/null @@ -1,125 +0,0 @@ -use time::OffsetDateTime; - -use crate::{v6, v7}; - -impl From for v7::request::IpPacketRequest { - fn from(ip_packet_request: v6::request::IpPacketRequest) -> Self { - Self { - version: 7, - data: ip_packet_request.data.into(), - } - } -} - -impl From for v7::request::IpPacketRequestData { - fn from(ip_packet_request_data: v6::request::IpPacketRequestData) -> Self { - match ip_packet_request_data { - v6::request::IpPacketRequestData::StaticConnect(r) => { - v7::request::IpPacketRequestData::StaticConnect( - v7::request::SignedStaticConnectRequest { - request: r.into(), - signature: None, - }, - ) - } - v6::request::IpPacketRequestData::DynamicConnect(r) => { - v7::request::IpPacketRequestData::DynamicConnect( - v7::request::SignedDynamicConnectRequest { - request: r.into(), - signature: None, - }, - ) - } - v6::request::IpPacketRequestData::Disconnect(r) => { - v7::request::IpPacketRequestData::Disconnect(v7::request::SignedDisconnectRequest { - request: r.into(), - signature: None, - }) - } - v6::request::IpPacketRequestData::Data(r) => { - v7::request::IpPacketRequestData::Data(r.into()) - } - v6::request::IpPacketRequestData::Ping(r) => { - v7::request::IpPacketRequestData::Ping(r.into()) - } - v6::request::IpPacketRequestData::Health(r) => { - v7::request::IpPacketRequestData::Health(r.into()) - } - } - } -} - -impl From for v7::request::StaticConnectRequest { - fn from(static_connect_request: v6::request::StaticConnectRequest) -> Self { - Self { - request_id: static_connect_request.request_id, - ips: static_connect_request.ips, - reply_to: static_connect_request.reply_to, - reply_to_hops: static_connect_request.reply_to_hops, - reply_to_avg_mix_delays: static_connect_request.reply_to_avg_mix_delays, - buffer_timeout: static_connect_request.buffer_timeout, - timestamp: OffsetDateTime::now_utc(), - } - } -} - -#[allow(deprecated)] -impl From for v7::request::DynamicConnectRequest { - fn from(dynamic_connect_request: v6::request::DynamicConnectRequest) -> Self { - Self { - request_id: dynamic_connect_request.request_id, - reply_to: dynamic_connect_request.reply_to, - reply_to_hops: dynamic_connect_request.reply_to_hops, - reply_to_avg_mix_delays: dynamic_connect_request.reply_to_avg_mix_delays, - buffer_timeout: dynamic_connect_request.buffer_timeout, - timestamp: OffsetDateTime::now_utc(), - } - } -} - -impl From for v7::request::SignedDisconnectRequest { - fn from(disconnect_request: v6::request::DisconnectRequest) -> Self { - Self { - request: disconnect_request.into(), - signature: None, - } - } -} - -impl From for v7::request::DisconnectRequest { - fn from(disconnect_request: v6::request::DisconnectRequest) -> Self { - Self { - request_id: disconnect_request.request_id, - reply_to: disconnect_request.reply_to, - timestamp: OffsetDateTime::now_utc(), - } - } -} - -impl From for v7::request::DataRequest { - fn from(data_request: v6::request::DataRequest) -> Self { - Self { - ip_packets: data_request.ip_packets, - } - } -} - -impl From for v7::request::PingRequest { - fn from(ping_request: v6::request::PingRequest) -> Self { - Self { - request_id: ping_request.request_id, - reply_to: ping_request.reply_to, - timestamp: OffsetDateTime::now_utc(), - } - } -} - -impl From for v7::request::HealthRequest { - fn from(health_request: v6::request::HealthRequest) -> Self { - Self { - request_id: health_request.request_id, - reply_to: health_request.reply_to, - timestamp: OffsetDateTime::now_utc(), - } - } -} diff --git a/common/ip-packet-requests/src/v7/mod.rs b/common/ip-packet-requests/src/v7/mod.rs index e0e61133f1..f15da6711a 100644 --- a/common/ip-packet-requests/src/v7/mod.rs +++ b/common/ip-packet-requests/src/v7/mod.rs @@ -1,6 +1,4 @@ -pub mod conversion; pub mod request; pub mod response; -pub mod signature; pub const VERSION: u8 = 7; diff --git a/common/ip-packet-requests/src/v7/request.rs b/common/ip-packet-requests/src/v7/request.rs index 43b71bb362..2313c16942 100644 --- a/common/ip-packet-requests/src/v7/request.rs +++ b/common/ip-packet-requests/src/v7/request.rs @@ -1,22 +1,18 @@ +use std::fmt; + use nym_crypto::asymmetric::identity; use nym_sphinx::addressing::clients::Recipient; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; -use crate::{make_bincode_serializer, IpPair}; - -use super::{ - signature::{SignatureError, SignedRequest}, - VERSION, +use crate::{ + sign::{SignatureError, SignedRequest}, + IpPair, }; -fn generate_random() -> u64 { - use rand::RngCore; - let mut rng = rand::rngs::OsRng; - rng.next_u64() -} +use super::VERSION; -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct IpPacketRequest { pub version: u8, pub data: IpPacketRequestData, @@ -30,7 +26,7 @@ impl IpPacketRequest { reply_to_avg_mix_delays: Option, buffer_timeout: Option, ) -> (Self, u64) { - let request_id = generate_random(); + let request_id = crate::generate_random(); ( Self { version: VERSION, @@ -58,7 +54,7 @@ impl IpPacketRequest { reply_to_avg_mix_delays: Option, buffer_timeout: Option, ) -> (Self, u64) { - let request_id = generate_random(); + let request_id = crate::generate_random(); ( Self { version: VERSION, @@ -79,7 +75,7 @@ impl IpPacketRequest { } pub fn new_disconnect_request(reply_to: Recipient) -> (Self, u64) { - let request_id = generate_random(); + let request_id = crate::generate_random(); ( Self { version: VERSION, @@ -104,7 +100,7 @@ impl IpPacketRequest { } pub fn new_ping(reply_to: Recipient) -> (Self, u64) { - let request_id = generate_random(); + let request_id = crate::generate_random(); ( Self { version: VERSION, @@ -119,7 +115,7 @@ impl IpPacketRequest { } pub fn new_health_request(reply_to: Recipient) -> (Self, u64) { - let request_id = generate_random(); + let request_id = crate::generate_random(); ( Self { version: VERSION, @@ -155,16 +151,27 @@ impl IpPacketRequest { } } + pub fn verify(&self) -> Result<(), SignatureError> { + match &self.data { + IpPacketRequestData::StaticConnect(request) => request.verify(), + IpPacketRequestData::DynamicConnect(request) => request.verify(), + IpPacketRequestData::Disconnect(request) => request.verify(), + IpPacketRequestData::Data(_) => Ok(()), + IpPacketRequestData::Ping(_) => Ok(()), + IpPacketRequestData::Health(_) => Ok(()), + } + } + pub fn to_bytes(&self) -> Result, bincode::Error> { use bincode::Options; - make_bincode_serializer().serialize(self) + crate::make_bincode_serializer().serialize(self) } pub fn from_reconstructed_message( message: &nym_sphinx::receiver::ReconstructedMessage, ) -> Result { use bincode::Options; - make_bincode_serializer().deserialize(&message.message) + crate::make_bincode_serializer().deserialize(&message.message) } } @@ -179,6 +186,19 @@ pub enum IpPacketRequestData { Health(HealthRequest), } +impl fmt::Display for IpPacketRequestData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + IpPacketRequestData::StaticConnect(_) => write!(f, "StaticConnect"), + IpPacketRequestData::DynamicConnect(_) => write!(f, "DynamicConnect"), + IpPacketRequestData::Disconnect(_) => write!(f, "Disconnect"), + IpPacketRequestData::Data(_) => write!(f, "Data"), + IpPacketRequestData::Ping(_) => write!(f, "Ping"), + IpPacketRequestData::Health(_) => write!(f, "Health"), + } + } +} + impl IpPacketRequestData { pub fn add_signature(&mut self, signature: identity::Signature) -> Option { match self { @@ -202,9 +222,9 @@ impl IpPacketRequestData { pub fn signable_request(&self) -> Option, SignatureError>> { match self { - IpPacketRequestData::StaticConnect(request) => Some(request.request()), - IpPacketRequestData::DynamicConnect(request) => Some(request.request()), - IpPacketRequestData::Disconnect(request) => Some(request.request()), + IpPacketRequestData::StaticConnect(request) => Some(request.request_as_bytes()), + IpPacketRequestData::DynamicConnect(request) => Some(request.request_as_bytes()), + IpPacketRequestData::Disconnect(request) => Some(request.request_as_bytes()), IpPacketRequestData::Data(_) => None, IpPacketRequestData::Ping(_) => None, IpPacketRequestData::Health(_) => None, @@ -242,7 +262,7 @@ pub struct StaticConnectRequest { impl StaticConnectRequest { pub fn to_bytes(&self) -> Result, bincode::Error> { use bincode::Options; - make_bincode_serializer().serialize(self) + crate::make_bincode_serializer().serialize(self) } } @@ -253,11 +273,11 @@ pub struct SignedStaticConnectRequest { } impl SignedRequest for SignedStaticConnectRequest { - fn identity(&self) -> &identity::PublicKey { - self.request.reply_to.identity() + fn identity(&self) -> Option<&identity::PublicKey> { + Some(self.request.reply_to.identity()) } - fn request(&self) -> Result, SignatureError> { + fn request_as_bytes(&self) -> Result, SignatureError> { self.request .to_bytes() .map_err(|error| SignatureError::RequestSerializationError { @@ -306,7 +326,7 @@ pub struct DynamicConnectRequest { impl DynamicConnectRequest { pub fn to_bytes(&self) -> Result, bincode::Error> { use bincode::Options; - make_bincode_serializer().serialize(self) + crate::make_bincode_serializer().serialize(self) } } @@ -317,11 +337,11 @@ pub struct SignedDynamicConnectRequest { } impl SignedRequest for SignedDynamicConnectRequest { - fn identity(&self) -> &identity::PublicKey { - self.request.reply_to.identity() + fn identity(&self) -> Option<&identity::PublicKey> { + Some(self.request.reply_to.identity()) } - fn request(&self) -> Result, SignatureError> { + fn request_as_bytes(&self) -> Result, SignatureError> { self.request .to_bytes() .map_err(|error| SignatureError::RequestSerializationError { @@ -355,7 +375,7 @@ pub struct DisconnectRequest { impl DisconnectRequest { pub fn to_bytes(&self) -> Result, bincode::Error> { use bincode::Options; - make_bincode_serializer().serialize(self) + crate::make_bincode_serializer().serialize(self) } } @@ -366,11 +386,11 @@ pub struct SignedDisconnectRequest { } impl SignedRequest for SignedDisconnectRequest { - fn identity(&self) -> &identity::PublicKey { - self.request.reply_to.identity() + fn identity(&self) -> Option<&identity::PublicKey> { + Some(self.request.reply_to.identity()) } - fn request(&self) -> Result, SignatureError> { + fn request_as_bytes(&self) -> Result, SignatureError> { self.request .to_bytes() .map_err(|error| SignatureError::RequestSerializationError { diff --git a/common/ip-packet-requests/src/v8/mod.rs b/common/ip-packet-requests/src/v8/mod.rs new file mode 100644 index 0000000000..70d1147b3a --- /dev/null +++ b/common/ip-packet-requests/src/v8/mod.rs @@ -0,0 +1,4 @@ +pub mod request; +pub mod response; + +pub const VERSION: u8 = 8; diff --git a/common/ip-packet-requests/src/v8/request.rs b/common/ip-packet-requests/src/v8/request.rs new file mode 100644 index 0000000000..c5e49e83b7 --- /dev/null +++ b/common/ip-packet-requests/src/v8/request.rs @@ -0,0 +1,304 @@ +use std::fmt; + +use nym_service_provider_requests_common::{Protocol, ServiceProviderType}; +use serde::{Deserialize, Serialize}; +use time::OffsetDateTime; + +use super::VERSION; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct IpPacketRequest { + pub protocol: Protocol, + pub data: IpPacketRequestData, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum IpPacketRequestData { + Data(DataRequest), + Control(Box), +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum ControlRequest { + Connect(ConnectRequest), + Disconnect(DisconnectRequest), + Ping(PingRequest), + Health(HealthRequest), +} + +// A data request is when the client wants to send an IP packet to a destination. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct DataRequest { + pub ip_packets: bytes::Bytes, +} + +// A dynamic connect request is when the client does not provide the internal IP address it will use +// on the ip packet router, and instead requests one to be assigned to it. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct ConnectRequest { + pub request_id: u64, + + // The maximum time in milliseconds the IPR should wait when filling up a mix packet + // with ip packets. + pub buffer_timeout: Option, + + // Timestamp of when the request was sent by the client. + pub timestamp: OffsetDateTime, +} + +// A disconnect request is when the client wants to disconnect from the ip packet router and free +// up the allocated IP address. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct DisconnectRequest { + pub request_id: u64, + + // Timestamp of when the request was sent by the client. + pub timestamp: OffsetDateTime, +} + +// A ping request is when the client wants to check if the ip packet router is still alive. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct PingRequest { + pub request_id: u64, + + // Timestamp of when the request was sent by the client. + pub timestamp: OffsetDateTime, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct HealthRequest { + pub request_id: u64, + + // Timestamp of when the request was sent by the client. + pub timestamp: OffsetDateTime, +} + +impl IpPacketRequest { + pub fn new_connect_request(buffer_timeout: Option) -> (Self, u64) { + let protocol = Protocol { + version: VERSION, + service_provider_type: ServiceProviderType::IpPacketRouter, + }; + let request_id = rand::random(); + let timestamp = OffsetDateTime::now_utc(); + let connect = ConnectRequest { + request_id, + buffer_timeout, + timestamp, + }; + let request = Self { + protocol, + data: IpPacketRequestData::Control(Box::new(ControlRequest::Connect(connect))), + }; + (request, request_id) + } + + pub fn new_disconnect_request() -> (Self, u64) { + let protocol = Protocol { + version: VERSION, + service_provider_type: ServiceProviderType::IpPacketRouter, + }; + let request_id = rand::random(); + let timestamp = OffsetDateTime::now_utc(); + let disconnect = DisconnectRequest { + request_id, + timestamp, + }; + let request = Self { + protocol, + data: IpPacketRequestData::Control(Box::new(ControlRequest::Disconnect(disconnect))), + }; + (request, request_id) + } + + pub fn new_data_request(ip_packets: bytes::Bytes) -> Self { + Self { + protocol: Protocol { + version: VERSION, + service_provider_type: ServiceProviderType::IpPacketRouter, + }, + data: IpPacketRequestData::Data(DataRequest { ip_packets }), + } + } + + pub fn new_ping() -> (Self, u64) { + let protocol = Protocol { + version: VERSION, + service_provider_type: ServiceProviderType::IpPacketRouter, + }; + let request_id = rand::random(); + let timestamp = OffsetDateTime::now_utc(); + let ping_request = PingRequest { + request_id, + timestamp, + }; + let request = Self { + protocol, + data: IpPacketRequestData::Control(Box::new(ControlRequest::Ping(ping_request))), + }; + (request, request_id) + } + + pub fn new_health_request() -> (Self, u64) { + let protocol = Protocol { + version: VERSION, + service_provider_type: ServiceProviderType::IpPacketRouter, + }; + let request_id = rand::random(); + let timestamp = OffsetDateTime::now_utc(); + let health_request = HealthRequest { + request_id, + timestamp, + }; + let request = Self { + protocol, + data: IpPacketRequestData::Control(Box::new(ControlRequest::Health(health_request))), + }; + (request, request_id) + } + + pub fn id(&self) -> Option { + match self.data { + IpPacketRequestData::Control(ref c) => Some(c.id()), + IpPacketRequestData::Data(_) => None, + } + } + + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + crate::make_bincode_serializer().serialize(self) + } + + pub fn from_reconstructed_message( + message: &nym_sphinx::receiver::ReconstructedMessage, + ) -> Result { + use bincode::Options; + crate::make_bincode_serializer().deserialize(&message.message) + } +} + +impl fmt::Display for IpPacketRequest { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "IpPacketRequest {{ version: {}, data: {} }}", + self.protocol.version, self.data + ) + } +} + +impl fmt::Display for IpPacketRequestData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + IpPacketRequestData::Data(_) => write!(f, "Data"), + IpPacketRequestData::Control(c) => write!(f, "Control({})", c), + } + } +} + +impl ControlRequest { + fn id(&self) -> u64 { + match self { + ControlRequest::Connect(request) => request.request_id, + ControlRequest::Disconnect(request) => request.request_id, + ControlRequest::Ping(request) => request.request_id, + ControlRequest::Health(request) => request.request_id, + } + } +} + +impl fmt::Display for ControlRequest { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ControlRequest::Connect(_) => write!(f, "Connect"), + ControlRequest::Disconnect(_) => write!(f, "Disconnect"), + ControlRequest::Ping(_) => write!(f, "Ping"), + ControlRequest::Health(_) => write!(f, "Health"), + } + } +} + +impl ConnectRequest { + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + crate::make_bincode_serializer().serialize(self) + } +} + +impl DisconnectRequest { + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + crate::make_bincode_serializer().serialize(self) + } +} + +#[cfg(test)] +mod tests { + use time::macros::datetime; + + use super::*; + + #[test] + fn check_size_of_request() { + let connect = IpPacketRequest { + protocol: Protocol { + version: 4, + service_provider_type: ServiceProviderType::IpPacketRouter, + }, + data: IpPacketRequestData::Control(Box::new(ControlRequest::Connect(ConnectRequest { + request_id: 123, + buffer_timeout: None, + timestamp: datetime!(2024-01-01 12:59:59.5 UTC), + }))), + }; + assert_eq!(connect.to_bytes().unwrap().len(), 21); + } + + #[test] + fn check_size_of_data() { + let data = IpPacketRequest { + protocol: Protocol { + version: 4, + service_provider_type: ServiceProviderType::IpPacketRouter, + }, + data: IpPacketRequestData::Data(DataRequest { + ip_packets: bytes::Bytes::from(vec![1u8; 32]), + }), + }; + assert_eq!(data.to_bytes().unwrap().len(), 36); + } + + #[test] + fn serialize_and_deserialize_data_request() { + let data = IpPacketRequest { + protocol: Protocol { + version: 4, + service_provider_type: ServiceProviderType::IpPacketRouter, + }, + data: IpPacketRequestData::Data(DataRequest { + ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]), + }), + }; + + let serialized = data.to_bytes().unwrap(); + let deserialized = IpPacketRequest::from_reconstructed_message( + &nym_sphinx::receiver::ReconstructedMessage { + message: serialized, + sender_tag: None, + }, + ) + .unwrap(); + + assert_eq!(deserialized.protocol.version, 4); + assert_eq!( + deserialized.protocol.service_provider_type, + ServiceProviderType::IpPacketRouter + ); + assert_eq!( + deserialized.data, + IpPacketRequestData::Data(DataRequest { + ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]), + }) + ); + } +} diff --git a/common/ip-packet-requests/src/v8/response.rs b/common/ip-packet-requests/src/v8/response.rs new file mode 100644 index 0000000000..946a8621be --- /dev/null +++ b/common/ip-packet-requests/src/v8/response.rs @@ -0,0 +1,219 @@ +use nym_bin_common::build_information::BinaryBuildInformationOwned; +use serde::{Deserialize, Serialize}; + +use crate::{make_bincode_serializer, IpPair}; + +use super::VERSION; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct IpPacketResponse { + pub version: u8, + pub data: IpPacketResponseData, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum IpPacketResponseData { + Data(DataResponse), + Control(Box), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DataResponse { + pub ip_packet: bytes::Bytes, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum ControlResponse { + // Response for a connect request + Connect(ConnectResponse), + + // Response for a disconnect initiqated by the client + Disconnect(DisconnectResponse), + + // Message from the server that the client got disconnected without the client initiating it + UnrequestedDisconnect(UnrequestedDisconnect), + + // Response to ping request + Pong(PongResponse), + + // Response for a health request + Health(Box), + + // Info response. This can be anything from informative messages to errors + Info(InfoResponse), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ConnectResponse { + pub request_id: u64, + pub reply: ConnectResponseReply, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum ConnectResponseReply { + Success(ConnectSuccess), + Failure(ConnectFailureReason), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ConnectSuccess { + pub ips: IpPair, +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +pub enum ConnectFailureReason { + #[error("client is already connected")] + ClientAlreadyConnected, + + #[error("no available ip address")] + NoAvailableIp, + + #[error("{0}")] + Other(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DisconnectResponse { + pub request_id: u64, + pub reply: DisconnectResponseReply, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum DisconnectResponseReply { + Success, + Failure(DisconnectFailureReason), +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +pub enum DisconnectFailureReason { + #[error("client is not connected")] + ClientNotConnected, + + #[error("{0}")] + Other(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct UnrequestedDisconnect { + pub reason: UnrequestedDisconnectReason, +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +pub enum UnrequestedDisconnectReason { + #[error("client mixnet traffic timeout")] + ClientMixnetTrafficTimeout, + + #[error("client tun traffic timeout")] + ClientTunTrafficTimeout, + + #[error("{0}")] + Other(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PongResponse { + pub request_id: u64, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct HealthResponse { + pub request_id: u64, + pub reply: HealthResponseReply, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct HealthResponseReply { + // Return the binary build information of the IPR + pub build_info: BinaryBuildInformationOwned, + + // Return if the IPR has performed a successful routing test. + pub routable: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct InfoResponse { + pub request_id: u64, + pub reply: InfoResponseReply, + pub level: InfoLevel, +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +pub enum InfoResponseReply { + #[error("{msg}")] + Generic { msg: String }, + + #[error( + "version mismatch: response is v{request_version} and response is v{response_version}" + )] + VersionMismatch { + request_version: u8, + response_version: u8, + }, + + #[error("destination failed exit policy filter check: {dst}")] + ExitPolicyFilterCheckFailed { dst: String }, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum InfoLevel { + Info, + Warn, + Error, +} + +impl IpPacketResponse { + pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self { + Self { + version: VERSION, + data: IpPacketResponseData::Data(DataResponse { ip_packet }), + } + } + + pub fn id(&self) -> Option { + match &self.data { + IpPacketResponseData::Data(_) => None, + IpPacketResponseData::Control(response) => response.id(), + } + } + + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } + + pub fn from_reconstructed_message( + message: &nym_sphinx::receiver::ReconstructedMessage, + ) -> Result { + use bincode::Options; + make_bincode_serializer().deserialize(&message.message) + } +} + +impl IpPacketResponseData { + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } +} + +impl ControlResponse { + fn id(&self) -> Option { + match self { + ControlResponse::Connect(response) => Some(response.request_id), + ControlResponse::Disconnect(response) => Some(response.request_id), + ControlResponse::UnrequestedDisconnect(_) => None, + ControlResponse::Pong(response) => Some(response.request_id), + ControlResponse::Health(response) => Some(response.request_id), + ControlResponse::Info(response) => Some(response.request_id), + } + } +} + +impl ConnectResponseReply { + pub fn is_success(&self) -> bool { + match self { + ConnectResponseReply::Success(_) => true, + ConnectResponseReply::Failure(_) => false, + } + } +} diff --git a/common/nym_offline_compact_ecash/Cargo.toml b/common/nym_offline_compact_ecash/Cargo.toml index 9b29d818fe..718787b457 100644 --- a/common/nym_offline_compact_ecash/Cargo.toml +++ b/common/nym_offline_compact_ecash/Cargo.toml @@ -15,10 +15,10 @@ bls12_381 = { workspace = true, features = ["alloc", "pairings", "experimental", bincode.workspace = true cfg-if.workspace = true itertools = { workspace = true } -digest = "0.9" +digest = { workspace = true } rand = { workspace = true } thiserror = { workspace = true } -sha2 = "0.9" +sha2 = { workspace = true } bs58 = { workspace = true } serde = { workspace = true, features = ["derive"] } rayon = { workspace = true, optional = true } diff --git a/common/nym_offline_compact_ecash/src/utils.rs b/common/nym_offline_compact_ecash/src/utils.rs index 57a648fff2..a04f377ddf 100644 --- a/common/nym_offline_compact_ecash/src/utils.rs +++ b/common/nym_offline_compact_ecash/src/utils.rs @@ -113,17 +113,13 @@ const G1_HASH_DOMAIN: &[u8] = b"NYMECASH-V01-CS02-with-BLS12381G1_XMD:SHA-256_SS const SCALAR_HASH_DOMAIN: &[u8] = b"NYMECASH-V01-CS02-with-expander-SHA256"; pub fn hash_g1>(msg: M) -> G1Projective { - >>::hash_to_curve(msg, G1_HASH_DOMAIN) + >>::hash_to_curve([msg], G1_HASH_DOMAIN) } pub fn hash_to_scalar>(msg: M) -> Scalar { let mut output = vec![Scalar::zero()]; - Scalar::hash_to_field::>( - msg.as_ref(), - SCALAR_HASH_DOMAIN, - &mut output, - ); + Scalar::hash_to_field::, _>([msg], SCALAR_HASH_DOMAIN, &mut output); output[0] } @@ -401,4 +397,75 @@ mod tests { assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2)); assert_ne!(hash_to_scalar(msg1), hash_to_scalar(msg2)); } + + #[test] + fn test_hash_to_scalar() { + let msg1 = "foo"; + let expected1 = Scalar::from_bytes(&[ + 253, 57, 224, 227, 175, 195, 226, 82, 46, 175, 33, 126, 171, 239, 255, 92, 108, 168, 6, + 79, 90, 11, 235, 236, 221, 10, 85, 133, 42, 81, 95, 30, + ]) + .unwrap(); + + let msg2 = "bar"; + let expected2 = Scalar::from_bytes(&[ + 48, 83, 69, 52, 42, 18, 135, 244, 211, 190, 160, 196, 118, 154, 24, 126, 0, 125, 72, + 201, 170, 225, 123, 201, 52, 120, 171, 132, 235, 182, 20, 26, + ]) + .unwrap(); + + let msg3 = [ + 33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123, + 250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27, + 115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101, + 60, 42, 92, 128, 131, 161, 43, + ]; + let expected3 = Scalar::from_bytes(&[ + 128, 189, 8, 43, 186, 55, 52, 61, 171, 196, 159, 177, 162, 100, 27, 143, 85, 83, 218, + 171, 91, 220, 155, 25, 7, 38, 2, 36, 4, 93, 136, 4, + ]) + .unwrap(); + + assert_eq!(hash_to_scalar(msg1), expected1); + assert_eq!(hash_to_scalar(msg2), expected2); + assert_eq!(hash_to_scalar(msg3), expected3); + } + + #[test] + fn test_hash_to_g1() { + let msg1 = "foo"; + let expected1 = G1Affine::from_compressed(&[ + 161, 109, 186, 0, 192, 221, 83, 87, 71, 31, 120, 201, 185, 35, 62, 239, 46, 120, 117, + 150, 191, 227, 128, 161, 78, 201, 207, 167, 86, 181, 229, 115, 2, 6, 178, 16, 251, 118, + 219, 115, 184, 96, 2, 10, 31, 63, 150, 70, + ]) + .unwrap() + .into(); + + let msg2 = "bar"; + let expected2 = G1Affine::from_compressed(&[ + 135, 102, 204, 42, 221, 49, 209, 192, 250, 87, 59, 255, 197, 93, 37, 113, 38, 2, 154, + 233, 68, 234, 206, 182, 121, 212, 166, 210, 74, 155, 190, 33, 203, 237, 176, 60, 249, + 241, 53, 170, 18, 168, 49, 35, 1, 151, 205, 174, + ]) + .unwrap() + .into(); + let msg3 = [ + 33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123, + 250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27, + 115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101, + 60, 42, 92, 128, 131, 161, 43, + ]; + let expected3 = G1Affine::from_compressed(&[ + 184, 200, 211, 115, 47, 45, 39, 185, 105, 9, 222, 247, 132, 241, 121, 130, 238, 224, + 155, 109, 105, 201, 137, 154, 132, 149, 214, 233, 136, 69, 77, 132, 174, 30, 46, 123, + 20, 92, 219, 18, 45, 29, 208, 127, 158, 145, 130, 41, + ]) + .unwrap() + .into(); + + assert_eq!(hash_g1(msg1), expected1); + assert_eq!(hash_g1(msg2), expected2); + assert_eq!(hash_g1(msg3), expected3); + } } diff --git a/common/nymcoconut/Cargo.toml b/common/nymcoconut/Cargo.toml deleted file mode 100644 index c4266b3459..0000000000 --- a/common/nymcoconut/Cargo.toml +++ /dev/null @@ -1,48 +0,0 @@ -[package] -name = "nym-coconut" -version = "0.5.0" -authors = ["Jedrzej Stuczynski ", "Ania Piotrowska "] -edition = "2021" -license.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -bls12_381 = { workspace = true, default-features = false, features = ["pairings", "alloc", "experimental"] } -itertools = { workspace = true } -digest = "0.9" -rand = { workspace = true } -thiserror = { workspace = true } -serde = { workspace = true } -serde_derive = { workspace = true } -bs58 = { workspace = true } -sha2 = "0.9" -zeroize = { workspace = true, optional = true } - -nym-dkg = { path = "../dkg" } -nym-pemstore = { path = "../pemstore" } - -[dependencies.ff] -workspace = true -default-features = false - -[dependencies.group] -workspace = true -default-features = false - -[dev-dependencies] -criterion = { workspace = true, features = ["html_reports"] } -doc-comment = { workspace = true } -rand_chacha = { workspace = true } - -[[bench]] -name = "benchmarks" -harness = false - -[features] -key-zeroize = ["zeroize", "bls12_381/zeroize"] -default = [] - - -[target.'cfg(target_env = "wasm32-unknown-unknown")'.dependencies] -getrandom = { version="0.2", features=["js"] } diff --git a/common/nymcoconut/README.md b/common/nymcoconut/README.md deleted file mode 100644 index 2433e80e34..0000000000 --- a/common/nymcoconut/README.md +++ /dev/null @@ -1 +0,0 @@ -This project was partially funded through the NGI0 PET Fund, a fund established by NL.net with financial support from the European Commission's NGI programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 825310. \ No newline at end of file diff --git a/common/nymcoconut/benches/benchmarks.rs b/common/nymcoconut/benches/benchmarks.rs deleted file mode 100644 index 42fb36dc82..0000000000 --- a/common/nymcoconut/benches/benchmarks.rs +++ /dev/null @@ -1,360 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use bls12_381::{multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, Scalar}; -use criterion::{criterion_group, criterion_main, Criterion}; -use ff::Field; -use group::{Curve, Group}; -use nym_coconut::{ - aggregate_signature_shares_and_verify, aggregate_verification_keys, blind_sign, - prepare_blind_sign, prove_bandwidth_credential, random_scalars_refs, setup, ttp_keygen, - verify_credential, verify_partial_blind_signature, Attribute, BlindedSignature, Parameters, - Signature, SignatureShare, VerificationKey, -}; -use rand::seq::SliceRandom; -use std::ops::Neg; -use std::time::Duration; - -#[allow(unused)] -fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) { - let gt1 = bls12_381::pairing(g11, g21); - let gt2 = bls12_381::pairing(g12, g22); - assert_eq!(gt1, gt2) -} - -#[allow(unused)] -fn multi_miller_pairing_affine(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) { - let miller_loop_result = multi_miller_loop(&[ - (g11, &G2Prepared::from(*g21)), - (&g12.neg(), &G2Prepared::from(*g22)), - ]); - assert!(bool::from( - miller_loop_result.final_exponentiation().is_identity() - )) -} - -#[allow(unused)] -fn bench_pairings(c: &mut Criterion) { - let mut rng = rand::thread_rng(); - - let g1 = G1Affine::generator(); - let g2 = G2Affine::generator(); - let r = Scalar::random(&mut rng); - let s = Scalar::random(&mut rng); - - let g11 = (g1 * r).to_affine(); - let g21 = (g2 * s).to_affine(); - let g21_prep = G2Prepared::from(g21); - - let g12 = (g1 * s).to_affine(); - let g22 = (g2 * r).to_affine(); - let g22_prep = G2Prepared::from(g22); - - c.bench_function("double pairing", |b| { - b.iter(|| double_pairing(&g11, &g21, &g12, &g22)) - }); - - c.bench_function("multi miller in affine", |b| { - b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22)) - }); - - c.bench_function("multi miller with prepared g2", |b| { - b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep)) - }); - - c.bench_function("multi miller with semi-prepared g2", |b| { - b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep)) - }); -} - -#[allow(unused)] -fn multi_miller_pairing_with_prepared( - g11: &G1Affine, - g21: &G2Prepared, - g12: &G1Affine, - g22: &G2Prepared, -) { - let miller_loop_result = multi_miller_loop(&[(g11, g21), (&g12.neg(), g22)]); - assert!(bool::from( - miller_loop_result.final_exponentiation().is_identity() - )) -} - -// the case of being able to prepare G2 generator -#[allow(unused)] -fn multi_miller_pairing_with_semi_prepared( - g11: &G1Affine, - g21: &G2Affine, - g12: &G1Affine, - g22: &G2Prepared, -) { - let miller_loop_result = - multi_miller_loop(&[(g11, &G2Prepared::from(*g21)), (&g12.neg(), g22)]); - assert!(bool::from( - miller_loop_result.final_exponentiation().is_identity() - )) -} - -#[allow(clippy::too_many_arguments)] -fn unblind_and_aggregate( - params: &Parameters, - blinded_signatures: &[BlindedSignature], - partial_verification_keys: &[VerificationKey], - private_attributes: &[&Attribute], - public_attributes: &[&Attribute], - commitment_hash: &G1Projective, - pedersen_commitments_openings: &[Scalar], - verification_key: &VerificationKey, -) -> Signature { - // Unblind all partial signatures - let unblinded_signatures: Vec = blinded_signatures - .iter() - .zip(partial_verification_keys.iter()) - .map(|(signature, partial_verification_key)| { - signature - .unblind_and_verify( - params, - partial_verification_key, - private_attributes, - public_attributes, - commitment_hash, - pedersen_commitments_openings, - ) - .unwrap() - }) - .collect(); - - let unblinded_signature_shares: Vec = unblinded_signatures - .iter() - .enumerate() - .map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64)) - .collect(); - - let mut attributes = vec![]; - attributes.extend_from_slice(private_attributes); - attributes.extend_from_slice(public_attributes); - aggregate_signature_shares_and_verify( - params, - verification_key, - &attributes, - &unblinded_signature_shares, - ) - .unwrap() -} - -struct BenchCase { - num_authorities: u64, - threshold_p: f32, - num_public_attrs: u32, - num_private_attrs: u32, -} - -impl BenchCase { - fn threshold(&self) -> u64 { - (self.num_authorities as f32 * self.threshold_p).round() as u64 - } - - fn num_attrs(&self) -> u32 { - self.num_public_attrs + self.num_private_attrs - } -} - -fn bench_coconut(c: &mut Criterion) { - let mut group = c.benchmark_group("benchmark-coconut"); - group.measurement_time(Duration::from_secs(1000)); - let case = BenchCase { - num_authorities: 100, - threshold_p: 0.7, - num_public_attrs: 2, - num_private_attrs: 2, - }; - - let params = setup(case.num_public_attrs + case.num_private_attrs).unwrap(); - - random_scalars_refs!(public_attributes, params, case.num_public_attrs as usize); - let serial_number = params.random_scalar(); - let binding_number = params.random_scalar(); - let private_attributes = vec![&serial_number, &binding_number]; - - // The prepare blind sign is performed by the user - let (pedersen_commitments_openings, blind_sign_request) = - prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); - - // CLIENT BENCHMARK: Data needed to ask for a credential - // Let's benchmark the operations the client has to perform - // to ask for a credential - group.bench_function( - format!( - "[Client] prepare_blind_sign_{}_authorities_{}_attributes_{}_threshold", - case.num_authorities, - case.num_attrs(), - case.threshold_p, - ), - |b| { - b.iter(|| prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap()) - }, - ); - - // keys for the validators - let coconut_keypairs = ttp_keygen(¶ms, case.threshold(), case.num_authorities).unwrap(); - - // VALIDATOR BENCHMARK: Issue partial credential - // we pick only one key pair, as we want to validate how much does it - // take for a single validator to issue a partial credential - let mut rng = rand::thread_rng(); - let keypair = coconut_keypairs.choose(&mut rng).unwrap(); - - group.bench_function( - format!( - "[Validator] compute_single_blind_sign_for_credential_with_{}_attributes", - case.num_attrs(), - ), - |b| { - b.iter(|| { - blind_sign( - ¶ms, - keypair.secret_key(), - &blind_sign_request, - &public_attributes, - ) - .unwrap() - }) - }, - ); - - // computing all partial credentials - // NOTE: in reality, each validator computes only single signature - let mut blinded_signatures = Vec::new(); - for keypair in coconut_keypairs.iter() { - let blinded_signature = blind_sign( - ¶ms, - keypair.secret_key(), - &blind_sign_request, - &public_attributes, - ) - .unwrap(); - blinded_signatures.push(blinded_signature) - } - - let verification_keys: Vec = coconut_keypairs - .iter() - .map(|keypair| keypair.verification_key().clone()) - .collect(); - - // verify a random partial blind signature - let rand_idx = 1; - let random_blind_signature = blinded_signatures.get(rand_idx).unwrap(); - let partial_verification_key = verification_keys.get(rand_idx).unwrap(); - - group.bench_function( - format!( - "verify_partial_blind_signature_{}_private_attributes_{}_public_attributes", - case.num_private_attrs, case.num_public_attrs - ), - |b| { - b.iter(|| { - verify_partial_blind_signature( - ¶ms, - blind_sign_request.get_private_attributes_pedersen_commitments(), - &public_attributes, - random_blind_signature, - partial_verification_key, - ) - }) - }, - ); - - // Lets bench worse case, ie aggregating all - let indices: Vec = (1..=case.num_authorities).collect(); - // aggregate verification keys - let aggr_verification_key = - aggregate_verification_keys(&verification_keys, Some(&indices)).unwrap(); - - // CLIENT OPERATION: Unblind partial singatures and aggregate into single signature - let aggregated_signature = unblind_and_aggregate( - ¶ms, - &blinded_signatures, - &verification_keys, - &private_attributes, - &public_attributes, - &blind_sign_request.get_commitment_hash(), - &pedersen_commitments_openings, - &aggr_verification_key, - ); - - // CLIENT BENCHMARK: aggregate all partial credentials - group.bench_function( - format!( - "[Client] unblind_and_aggregate_partial_credentials_{}_authorities_{}_attributes_{}_threshold", - case.num_authorities, - case.num_attrs(), - case.threshold_p, - ), - |b| { - b.iter(|| { - unblind_and_aggregate( - ¶ms, - &blinded_signatures, - &verification_keys, - &private_attributes, - &public_attributes, - &blind_sign_request.get_commitment_hash(), - &pedersen_commitments_openings, - &aggr_verification_key) - }) - }, - ); - - // CLIENT OPERATION: Randomize credentials and generate any cryptographic material to verify them - let theta = prove_bandwidth_credential( - ¶ms, - &aggr_verification_key, - &aggregated_signature, - &serial_number, - &binding_number, - ) - .unwrap(); - - // CLIENT BENCHMARK - group.bench_function( - format!( - "[Client] randomize_and_prove_credential_{}_authorities_{}_attributes_{}_threshold", - case.num_authorities, - case.num_attrs(), - case.threshold_p, - ), - |b| { - b.iter(|| { - prove_bandwidth_credential( - ¶ms, - &aggr_verification_key, - &aggregated_signature, - &serial_number, - &binding_number, - ) - .unwrap() - }) - }, - ); - - // VERIFIER OPERATION - // Verify credentials - verify_credential(¶ms, &aggr_verification_key, &theta, &public_attributes); - - // VERIFICATION BENCHMARK - group.bench_function( - format!( - "[Verifier] verify_credentials_{}_authorities_{}_attributes_{}_threshold", - case.num_authorities, - case.num_attrs(), - case.threshold_p, - ), - |b| { - b.iter(|| { - verify_credential(¶ms, &aggr_verification_key, &theta, &public_attributes) - }) - }, - ); -} -criterion_group!(benches, bench_coconut); -criterion_main!(benches); diff --git a/common/nymcoconut/src/elgamal.rs b/common/nymcoconut/src/elgamal.rs deleted file mode 100644 index 1b814f0bc5..0000000000 --- a/common/nymcoconut/src/elgamal.rs +++ /dev/null @@ -1,354 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use core::ops::{Deref, Mul}; - -use bls12_381::{G1Projective, Scalar}; -use group::Curve; -use serde_derive::{Deserialize, Serialize}; - -use crate::error::{CoconutError, Result}; -use crate::scheme::setup::Parameters; -use crate::traits::{Base58, Bytable}; -use crate::utils::{try_deserialize_g1_projective, try_deserialize_scalar}; -use crate::Attribute; - -/// Type alias for the ephemeral key generated during ElGamal encryption -pub type EphemeralKey = Scalar; - -/// Two G1 points representing ElGamal ciphertext -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq, Eq))] -pub struct Ciphertext(pub(crate) G1Projective, pub(crate) G1Projective); - -impl TryFrom<&[u8]> for Ciphertext { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - if bytes.len() != 96 { - return Err(CoconutError::Deserialization(format!( - "Ciphertext must be exactly 96 bytes, got {}", - bytes.len() - ))); - } - - // safety: we just checked for the length so the unwraps are fine - #[allow(clippy::unwrap_used)] - let c1_bytes: &[u8; 48] = &bytes[..48].try_into().unwrap(); - #[allow(clippy::unwrap_used)] - let c2_bytes: &[u8; 48] = &bytes[48..].try_into().unwrap(); - - let c1 = try_deserialize_g1_projective( - c1_bytes, - CoconutError::Deserialization("Failed to deserialize compressed c1".to_string()), - )?; - let c2 = try_deserialize_g1_projective( - c2_bytes, - CoconutError::Deserialization("Failed to deserialize compressed c2".to_string()), - )?; - - Ok(Ciphertext(c1, c2)) - } -} - -impl Ciphertext { - pub fn c1(&self) -> &G1Projective { - &self.0 - } - - pub fn c2(&self) -> &G1Projective { - &self.1 - } - - pub fn to_bytes(&self) -> [u8; 96] { - let mut bytes = [0u8; 96]; - bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed()); - bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed()); - bytes - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - Ciphertext::try_from(bytes) - } -} - -/// PrivateKey used in the ElGamal encryption scheme to recover the plaintext -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq, Eq))] -pub struct PrivateKey(pub(crate) Scalar); - -impl PrivateKey { - /// Decrypt takes the ElGamal encryption of a message and returns a point on the G1 curve - /// that represents original h^m. - pub fn decrypt(&self, ciphertext: &Ciphertext) -> G1Projective { - let (c1, c2) = &(ciphertext.0, ciphertext.1); - - // (gamma^k * h^m) / (g1^{d * k}) | note: gamma = g1^d - c2 - c1 * self.0 - } - - pub fn public_key(&self, params: &Parameters) -> PublicKey { - PublicKey(params.gen1() * self.0) - } - - pub fn to_bytes(&self) -> [u8; 32] { - self.0.to_bytes() - } - - pub fn from_bytes(bytes: &[u8; 32]) -> Result { - try_deserialize_scalar( - bytes, - CoconutError::Deserialization( - "Failed to deserialize ElGamal private key - it was not in the canonical form" - .to_string(), - ), - ) - .map(PrivateKey) - } -} - -impl Bytable for PrivateKey { - fn to_byte_vec(&self) -> Vec { - self.to_bytes().to_vec() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - let received = slice.len(); - let Ok(arr) = slice.try_into() else { - return Err(CoconutError::UnexpectedArrayLength { - typ: "elgamal::PrivateKey".to_string(), - received, - expected: 32, - }); - }; - - PrivateKey::from_bytes(arr) - } -} - -impl Base58 for PrivateKey {} - -// TODO: perhaps be more explicit and apart from gamma also store generator and group order? -/// PublicKey used in the ElGamal encryption scheme to produce the ciphertext -#[derive(Debug, Clone)] -#[cfg_attr(test, derive(PartialEq, Eq))] -pub struct PublicKey(G1Projective); - -impl PublicKey { - /// Encrypt encrypts the given message in the form of h^m, - /// where h is a point on the G1 curve using the given public key. - /// The random k is returned alongside the encryption - /// as it is required by the Coconut Scheme to create proofs of knowledge. - pub fn encrypt( - &self, - params: &Parameters, - h: &G1Projective, - msg: &Scalar, - ) -> (Ciphertext, EphemeralKey) { - let k = params.random_scalar(); - // c1 = g1^k - let c1 = params.gen1() * k; - // c2 = gamma^k * h^m - let c2 = self.0 * k + h * msg; - - (Ciphertext(c1, c2), k) - } - - pub fn to_bytes(&self) -> [u8; 48] { - self.0.to_affine().to_compressed() - } - - pub fn from_bytes(bytes: &[u8; 48]) -> Result { - try_deserialize_g1_projective( - bytes, - CoconutError::Deserialization( - "Failed to deserialize compressed ElGamal public key".to_string(), - ), - ) - .map(PublicKey) - } -} - -impl Bytable for PublicKey { - fn to_byte_vec(&self) -> Vec { - self.to_bytes().into() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - let received = slice.len(); - let Ok(arr) = slice.try_into() else { - return Err(CoconutError::UnexpectedArrayLength { - typ: "elgamal::PublicKey".to_string(), - received, - expected: 48, - }); - }; - - PublicKey::from_bytes(arr) - } -} - -impl TryFrom<&[u8]> for PublicKey { - type Error = CoconutError; - - fn try_from(slice: &[u8]) -> Result { - PublicKey::try_from_byte_slice(slice) - } -} - -impl Base58 for PublicKey {} - -impl Deref for PublicKey { - type Target = G1Projective; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'a> Mul<&'a Scalar> for &PublicKey { - type Output = G1Projective; - - fn mul(self, rhs: &'a Scalar) -> Self::Output { - self.0 * rhs - } -} - -#[derive(Serialize, Deserialize)] -/// A convenient wrapper for both keys of the ElGamal keypair -pub struct ElGamalKeyPair { - private_key: PrivateKey, - public_key: PublicKey, -} - -impl ElGamalKeyPair { - pub fn public_key(&self) -> &PublicKey { - &self.public_key - } - - pub fn private_key(&self) -> &PrivateKey { - &self.private_key - } -} - -/// Generate a fresh ElGamal keypair using the group generator specified by the provided [Parameters] -pub fn elgamal_keygen(params: &Parameters) -> ElGamalKeyPair { - let private_key = params.random_scalar(); - let gamma = params.gen1() * private_key; - - ElGamalKeyPair { - private_key: PrivateKey(private_key), - public_key: PublicKey(gamma), - } -} - -pub fn compute_attribute_encryption( - params: &Parameters, - private_attributes: &[&Attribute], - pub_key: &PublicKey, - commitment_hash: G1Projective, -) -> (Vec, Vec) { - private_attributes - .iter() - .map(|m| pub_key.encrypt(params, &commitment_hash, m)) - .unzip() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn keygen() { - let params = Parameters::default(); - let keypair = super::elgamal_keygen(¶ms); - - let expected = params.gen1() * keypair.private_key.0; - let gamma = keypair.public_key.0; - assert_eq!( - expected, gamma, - "Public key, gamma, should be equal to g1^d, where d is the private key" - ); - } - - #[test] - fn encryption() { - let params = Parameters::default(); - let keypair = super::elgamal_keygen(¶ms); - - let r = params.random_scalar(); - let h = params.gen1() * r; - let m = params.random_scalar(); - - let (ciphertext, ephemeral_key) = keypair.public_key.encrypt(¶ms, &h, &m); - - let expected_c1 = params.gen1() * ephemeral_key; - assert_eq!(expected_c1, ciphertext.0, "c1 should be equal to g1^k"); - - let expected_c2 = keypair.public_key.0 * ephemeral_key + h * m; - assert_eq!( - expected_c2, ciphertext.1, - "c2 should be equal to gamma^k * h^m" - ); - } - - #[test] - fn decryption() { - let params = Parameters::default(); - let keypair = super::elgamal_keygen(¶ms); - - let r = params.random_scalar(); - let h = params.gen1() * r; - let m = params.random_scalar(); - - let (ciphertext, _) = keypair.public_key.encrypt(¶ms, &h, &m); - let dec = keypair.private_key.decrypt(&ciphertext); - - let expected = h * m; - assert_eq!( - expected, dec, - "after ElGamal decryption, original h^m should be obtained" - ); - } - - #[test] - fn private_key_bytes_roundtrip() { - let params = Parameters::default(); - let private_key = PrivateKey(params.random_scalar()); - let bytes = private_key.to_bytes(); - - // also make sure it is equivalent to the internal scalar's bytes - assert_eq!(private_key.0.to_bytes(), bytes); - assert_eq!(private_key, PrivateKey::from_bytes(&bytes).unwrap()) - } - - #[test] - fn public_key_bytes_roundtrip() { - let params = Parameters::default(); - let r = params.random_scalar(); - let public_key = PublicKey(params.gen1() * r); - let bytes = public_key.to_bytes(); - - // also make sure it is equivalent to the internal g1 compressed bytes - assert_eq!(public_key.0.to_affine().to_compressed(), bytes); - assert_eq!(public_key, PublicKey::from_bytes(&bytes).unwrap()) - } - - #[test] - fn ciphertext_bytes_roundtrip() { - let params = Parameters::default(); - let r = params.random_scalar(); - let s = params.random_scalar(); - let ciphertext = Ciphertext(params.gen1() * r, params.gen1() * s); - let bytes = ciphertext.to_bytes(); - - // also make sure it is equivalent to the internal g1 compressed bytes concatenated - let expected_bytes = [ - ciphertext.0.to_affine().to_compressed(), - ciphertext.1.to_affine().to_compressed(), - ] - .concat(); - assert_eq!(expected_bytes, bytes); - assert_eq!(ciphertext, Ciphertext::try_from(&bytes[..]).unwrap()) - } -} diff --git a/common/nymcoconut/src/error.rs b/common/nymcoconut/src/error.rs deleted file mode 100644 index d502c76c95..0000000000 --- a/common/nymcoconut/src/error.rs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use thiserror::Error; - -/// A `Result` alias where the `Err` case is `coconut_rs::Error`. -pub type Result = std::result::Result; - -#[derive(Error, Debug)] -pub enum CoconutError { - #[error("Setup error: {0}")] - Setup(String), - - #[error("encountered error during keygen")] - Keygen, - - #[error("Issuance related error: {0}")] - Issuance(String), - - #[error("Tried to prepare blind sign request for higher than specified number of attributes (max: {}, requested: {})", max, requested)] - IssuanceMaxAttributes { max: usize, requested: usize }, - - #[error("Interpolation error: {0}")] - Interpolation(String), - - #[error("Aggregation error: {0}")] - Aggregation(String), - - #[error("Unblind error: {0}")] - Unblind(String), - - #[error("Verification error: {0}")] - Verification(String), - - #[error("Deserialization error: {0}")] - Deserialization(String), - - #[error( - "Deserailization error, expected at least {} bytes, got {}", - min, - actual - )] - DeserializationMinLength { min: usize, actual: usize }, - - #[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {object} or {modulus_target} % {modulus} == 0")] - DeserializationInvalidLength { - actual: usize, - target: usize, - modulus_target: usize, - modulus: usize, - object: String, - }, - - #[error("received an array of unexpected size for deserialization of {typ}. got {received} but expected {expected}")] - UnexpectedArrayLength { - typ: String, - received: usize, - expected: usize, - }, - - #[error("failed to decode the base58 representation: {0}")] - Base58DecodingFailure(#[from] bs58::decode::Error), - - #[error("failed to deserialize scalar from the received bytes - it might not have been canonically encoded")] - ScalarDeserializationFailure, - - #[error("failed to deserialize G1Projective point from the received bytes - it might not have been canonically encoded")] - G1ProjectiveDeserializationFailure, -} diff --git a/common/nymcoconut/src/impls/clone.rs b/common/nymcoconut/src/impls/clone.rs deleted file mode 100644 index 88ee6664c0..0000000000 --- a/common/nymcoconut/src/impls/clone.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::{BlindSignRequest, BlindedSignature, Bytable, VerifyCredentialRequest}; - -macro_rules! impl_clone { - ($struct:ident) => { - impl Clone for $struct { - fn clone(&self) -> Self { - Self::try_from_byte_slice(&self.to_byte_vec()).unwrap() - } - } - }; -} - -impl_clone!(BlindSignRequest); -impl_clone!(BlindedSignature); -impl_clone!(VerifyCredentialRequest); diff --git a/common/nymcoconut/src/impls/mod.rs b/common/nymcoconut/src/impls/mod.rs deleted file mode 100644 index efacad272d..0000000000 --- a/common/nymcoconut/src/impls/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod clone; -mod serde; diff --git a/common/nymcoconut/src/impls/serde.rs b/common/nymcoconut/src/impls/serde.rs deleted file mode 100644 index 7d1e020cb1..0000000000 --- a/common/nymcoconut/src/impls/serde.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::elgamal::PrivateKey; -use crate::scheme::SecretKey; -use crate::{ - Base58, BlindSignRequest, BlindedSignature, PublicKey, Signature, VerificationKey, - VerifyCredentialRequest, -}; -use serde::de::Unexpected; -use serde::{de::Error, de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; -use std::fmt; - -macro_rules! impl_serde { - ($struct:ident, $visitor:ident) => { - pub struct $visitor {} - - impl Serialize for $struct { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.to_bs58()) - } - } - - impl<'de> Visitor<'de> for $visitor { - type Value = $struct; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(formatter, "A base58 encoded struct") - } - - fn visit_str(self, s: &str) -> Result { - match $struct::try_from_bs58(s) { - Ok(x) => Ok(x), - Err(_) => Err(Error::invalid_value(Unexpected::Str(s), &self)), - } - } - } - - impl<'de> Deserialize<'de> for $struct { - fn deserialize(deserializer: D) -> Result<$struct, D::Error> - where - D: Deserializer<'de>, - { - deserializer.deserialize_str($visitor {}) - } - } - }; -} - -impl_serde!(SecretKey, V1); -impl_serde!(VerificationKey, V2); -impl_serde!(PublicKey, V3); -impl_serde!(PrivateKey, V4); -impl_serde!(BlindSignRequest, V5); -impl_serde!(BlindedSignature, V6); -impl_serde!(Signature, V7); -impl_serde!(VerifyCredentialRequest, V8); diff --git a/common/nymcoconut/src/lib.rs b/common/nymcoconut/src/lib.rs deleted file mode 100644 index dbcbda5b39..0000000000 --- a/common/nymcoconut/src/lib.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -#![warn(clippy::expect_used)] -#![warn(clippy::unwrap_used)] - -pub use bls12_381::Scalar; -pub use elgamal::elgamal_keygen; -pub use elgamal::ElGamalKeyPair; -pub use elgamal::PublicKey; -pub use error::CoconutError; -pub use scheme::aggregation::aggregate_key_shares; -pub use scheme::aggregation::aggregate_signature_shares; -pub use scheme::aggregation::aggregate_signature_shares_and_verify; -pub use scheme::aggregation::aggregate_verification_keys; -pub use scheme::issuance::blind_sign; -pub use scheme::issuance::prepare_blind_sign; -pub use scheme::issuance::sign; -pub use scheme::issuance::verify_partial_blind_signature; -pub use scheme::issuance::BlindSignRequest; -pub use scheme::keygen::keygen; -pub use scheme::keygen::ttp_keygen; -pub use scheme::keygen::KeyPair; -pub use scheme::keygen::SecretKey; -pub use scheme::keygen::VerificationKey; -pub use scheme::keygen::VerificationKeyShare; -pub use scheme::setup::setup; -pub use scheme::setup::Parameters; -pub use scheme::verification::check_vk_pairing; -pub use scheme::verification::prove_bandwidth_credential; -pub use scheme::verification::verify; -pub use scheme::verification::verify_credential; -pub use scheme::verification::BlindedSerialNumber; -pub use scheme::verification::VerifyCredentialRequest; -pub use scheme::BlindedSignature; -pub use scheme::Signature; -pub use scheme::SignatureShare; -pub use scheme::SignerIndex; -pub use traits::Base58; -pub use traits::Bytable; -pub use utils::hash_to_scalar; - -pub mod elgamal; -mod error; -mod impls; -mod proofs; -mod scheme; -pub mod tests; -mod traits; -pub mod utils; - -pub type Attribute = bls12_381::Scalar; -pub type PrivateAttribute = Attribute; -pub type PublicAttribute = Attribute; - -pub use bls12_381::G1Projective; diff --git a/common/nymcoconut/src/proofs/mod.rs b/common/nymcoconut/src/proofs/mod.rs deleted file mode 100644 index 17a8416c59..0000000000 --- a/common/nymcoconut/src/proofs/mod.rs +++ /dev/null @@ -1,619 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -// TODO: look at https://crates.io/crates/merlin to perhaps use it instead? - -use std::borrow::Borrow; - -use bls12_381::{G1Projective, G2Projective, Scalar}; -use digest::generic_array::typenum::Unsigned; -use digest::Digest; -use group::GroupEncoding; -use itertools::izip; -use sha2::Sha256; - -use crate::error::{CoconutError, Result}; -use crate::scheme::issuance::compute_hash; -use crate::scheme::setup::Parameters; -use crate::scheme::VerificationKey; -use crate::utils::{try_deserialize_scalar, try_deserialize_scalar_vec}; -use crate::Attribute; - -// as per the reference python implementation -type ChallengeDigest = Sha256; - -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq, Eq))] -pub struct ProofCmCs { - challenge: Scalar, - response_opening: Scalar, - response_openings: Vec, - response_attributes: Vec, -} - -// note: this is slightly different from the reference python implementation -// as we omit the unnecessary string conversion. Instead we concatenate byte -// representations together and hash that. -// note2: G1 and G2 elements are using their compressed representations -// and as per the bls12-381 library all elements are using big-endian form -/// Generates a Scalar [or Fp] challenge by hashing a number of elliptic curve points. -fn compute_challenge(iter: I) -> Scalar -where - D: Digest, - I: Iterator, - B: AsRef<[u8]>, -{ - let mut h = D::new(); - for point_representation in iter { - h.update(point_representation); - } - let digest = h.finalize(); - - // TODO: I don't like the 0 padding here (though it's what we've been using before, - // but we never had a security audit anyway...) - // instead we could maybe use the `from_bytes` variant and adding some suffix - // when computing the digest until we produce a valid scalar. - let mut bytes = [0u8; 64]; - let pad_size = 64usize - .checked_sub(D::OutputSize::to_usize()) - .unwrap_or_default(); - - bytes[pad_size..].copy_from_slice(&digest); - - Scalar::from_bytes_wide(&bytes) -} - -fn produce_response(witness: &Scalar, challenge: &Scalar, secret: &Scalar) -> Scalar { - witness - challenge * secret -} - -// note: it's caller's responsibility to ensure witnesses.len() = secrets.len() -fn produce_responses(witnesses: &[Scalar], challenge: &Scalar, secrets: &[S]) -> Vec -where - S: Borrow, -{ - debug_assert_eq!(witnesses.len(), secrets.len()); - - witnesses - .iter() - .zip(secrets.iter()) - .map(|(w, x)| produce_response(w, challenge, x.borrow())) - .collect() -} - -impl ProofCmCs { - /// Construct non-interactive zero-knowledge proof of correctness of the ciphertexts and the commitment - /// using the Fiat-Shamir heuristic. - pub(crate) fn construct( - params: &Parameters, - commitment: &G1Projective, - commitment_opening: &Scalar, - commitments: &[G1Projective], - pedersen_commitments_openings: &[Scalar], - private_attributes: &[&Attribute], - public_attributes: &[&Attribute], - ) -> Self { - // note: this is only called from `prepare_blind_sign` that already checks - // whether private attributes are non-empty and whether we don't have too many - // attributes in total to sign. - // we also know, due to the single call place, that ephemeral_keys.len() == private_attributes.len() - - // witness creation - let witness_commitment_opening = params.random_scalar(); - let witness_pedersen_commitments_openings = - params.n_random_scalars(pedersen_commitments_openings.len()); - let witness_attributes = params.n_random_scalars(private_attributes.len()); - - // recompute h - let h = compute_hash(*commitment, public_attributes); - let hs_bytes = params - .gen_hs() - .iter() - .map(|h| h.to_bytes()) - .collect::>(); - - let g1 = params.gen1(); - - // compute commitments - - // zkp commitment for the attributes commitment cm - // Ccm = (wr * g1) + (wm[0] * hs[0]) + ... + (wm[i] * hs[i]) - let commitment_attributes = g1 * witness_commitment_opening - + witness_attributes - .iter() - .zip(params.gen_hs().iter()) - .map(|(wm_i, hs_i)| hs_i * wm_i) - .sum::(); - - // zkp commitments for the individual attributes - let commitments_attributes = witness_pedersen_commitments_openings - .iter() - .zip(witness_attributes.iter()) - .map(|(o_j, m_j)| g1 * o_j + h * m_j) - .collect::>(); - - let commitments_bytes = commitments - .iter() - .map(|cm| cm.to_bytes()) - .collect::>(); - - let commitments_attributes_bytes = commitments_attributes - .iter() - .map(|cm| cm.to_bytes()) - .collect::>(); - - // compute challenge - let challenge = compute_challenge::( - std::iter::once(params.gen1().to_bytes().as_ref()) - .chain(hs_bytes.iter().map(|hs| hs.as_ref())) - .chain(std::iter::once(h.to_bytes().as_ref())) - .chain(std::iter::once(commitment.to_bytes().as_ref())) - .chain(commitments_bytes.iter().map(|cm| cm.as_ref())) - .chain(std::iter::once(commitment_attributes.to_bytes().as_ref())) - .chain(commitments_attributes_bytes.iter().map(|cm| cm.as_ref())), - ); - - // Responses - let response_opening = - produce_response(&witness_commitment_opening, &challenge, commitment_opening); - let response_openings = produce_responses( - &witness_pedersen_commitments_openings, - &challenge, - &pedersen_commitments_openings.iter().collect::>(), - ); - let response_attributes = - produce_responses(&witness_attributes, &challenge, private_attributes); - - ProofCmCs { - challenge, - response_opening, - response_openings, - response_attributes, - } - } - - pub(crate) fn verify( - &self, - params: &Parameters, - commitment: &G1Projective, - commitments: &[G1Projective], - public_attributes: &[&Attribute], - ) -> bool { - if self.response_attributes.len() != commitments.len() { - return false; - } - - // recompute h - let h = compute_hash(*commitment, public_attributes); - let g1 = params.gen1(); - - let hs_bytes = params - .gen_hs() - .iter() - .map(|h| h.to_bytes()) - .collect::>(); - - // recompute witnesses commitments - // Cw = (cm * c) + (rr * g1) + (rm[0] * hs[0]) + ... + (rm[n] * hs[n]) - let commitment_attributes = (commitment - - public_attributes - .iter() - .zip(params.gen_hs().iter().skip(self.response_attributes.len())) - .map(|(&pub_attr, hs)| hs * pub_attr) - .sum::()) - * self.challenge - + g1 * self.response_opening - + self - .response_attributes - .iter() - .zip(params.gen_hs().iter()) - .map(|(res_attr, hs)| hs * res_attr) - .sum::(); - - let commitments_attributes = izip!( - commitments.iter(), - self.response_openings.iter(), - self.response_attributes.iter() - ) - .map(|(cm_j, r_o_j, r_m_j)| cm_j * self.challenge + g1 * r_o_j + h * r_m_j) - .collect::>(); - - let commitments_bytes = commitments - .iter() - .map(|cm| cm.to_bytes()) - .collect::>(); - - let commitments_attributes_bytes = commitments_attributes - .iter() - .map(|cm| cm.to_bytes()) - .collect::>(); - - // re-compute the challenge - let challenge = compute_challenge::( - std::iter::once(params.gen1().to_bytes().as_ref()) - .chain(hs_bytes.iter().map(|hs| hs.as_ref())) - .chain(std::iter::once(h.to_bytes().as_ref())) - .chain(std::iter::once(commitment.to_bytes().as_ref())) - .chain(commitments_bytes.iter().map(|cm| cm.as_ref())) - .chain(std::iter::once(commitment_attributes.to_bytes().as_ref())) - .chain(commitments_attributes_bytes.iter().map(|cm| cm.as_ref())), - ); - - challenge == self.challenge - } - - // challenge || response opening || openings len || response openings || attributes len || - // response attributes - pub(crate) fn to_bytes(&self) -> Vec { - let openings_len = self.response_openings.len() as u64; - let attributes_len = self.response_attributes.len() as u64; - - let mut bytes = Vec::with_capacity(16 + (2 + openings_len + attributes_len) as usize * 32); - - bytes.extend_from_slice(&self.challenge.to_bytes()); - bytes.extend_from_slice(&self.response_opening.to_bytes()); - - bytes.extend_from_slice(&openings_len.to_le_bytes()); - for ro in &self.response_openings { - bytes.extend_from_slice(&ro.to_bytes()); - } - - bytes.extend_from_slice(&attributes_len.to_le_bytes()); - for rm in &self.response_attributes { - bytes.extend_from_slice(&rm.to_bytes()); - } - - bytes - } - - pub(crate) fn from_bytes(bytes: &[u8]) -> Result { - // at the very minimum there must be a single attribute being proven - if bytes.len() < 32 * 4 + 16 || (bytes.len() - 16) % 32 != 0 { - return Err(CoconutError::Deserialization( - "tried to deserialize proof of commitments with bytes of invalid length" - .to_string(), - )); - } - - let mut idx = 0; - // safety: bound checked + constant offset - #[allow(clippy::unwrap_used)] - let challenge_bytes = bytes[idx..idx + 32].try_into().unwrap(); - idx += 32; - // safety: bound checked + constant offset - #[allow(clippy::unwrap_used)] - let response_opening_bytes = bytes[idx..idx + 32].try_into().unwrap(); - idx += 32; - - let challenge = try_deserialize_scalar( - &challenge_bytes, - CoconutError::Deserialization("Failed to deserialize challenge".to_string()), - )?; - - let response_opening = try_deserialize_scalar( - &response_opening_bytes, - CoconutError::Deserialization( - "Failed to deserialize the response to the random".to_string(), - ), - )?; - - // safety: bound checked + constant offset - #[allow(clippy::unwrap_used)] - let ro_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap()); - idx += 8; - if bytes[idx..].len() < ro_len as usize * 32 + 8 { - return Err( - CoconutError::Deserialization( - "tried to deserialize proof of ciphertexts and commitment with insufficient number of bytes provided".to_string()), - ); - } - - let ro_end = idx + ro_len as usize * 32; - let response_openings = try_deserialize_scalar_vec( - ro_len, - &bytes[idx..ro_end], - CoconutError::Deserialization("Failed to deserialize openings response".to_string()), - )?; - - // safety: bound checked + constant offset - #[allow(clippy::unwrap_used)] - let rm_len = u64::from_le_bytes(bytes[ro_end..ro_end + 8].try_into().unwrap()); - let response_attributes = try_deserialize_scalar_vec( - rm_len, - &bytes[ro_end + 8..], - CoconutError::Deserialization("Failed to deserialize attributes response".to_string()), - )?; - - Ok(ProofCmCs { - challenge, - response_opening, - response_openings, - response_attributes, - }) - } -} - -#[derive(Debug, PartialEq, Eq)] -pub struct ProofKappaZeta { - // c - challenge: Scalar, - - // responses - response_serial_number: Scalar, - response_binding_number: Scalar, - response_blinder: Scalar, -} - -impl ProofKappaZeta { - pub(crate) fn construct( - params: &Parameters, - verification_key: &VerificationKey, - serial_number: &Attribute, - binding_number: &Attribute, - blinding_factor: &Scalar, - blinded_message: &G2Projective, - blinded_serial_number: &G2Projective, - ) -> Self { - // create the witnesses - let witness_blinder = params.random_scalar(); - let witness_serial_number = params.random_scalar(); - let witness_binding_number = params.random_scalar(); - let witness_attributes = [witness_serial_number, witness_binding_number]; - - let beta_bytes = verification_key - .beta_g2 - .iter() - .map(|beta_i| beta_i.to_bytes()) - .collect::>(); - - // witnesses commitments - // Aw = g2 * wt + alpha + beta[0] * wm[0] + ... + beta[i] * wm[i] - let commitment_kappa = params.gen2() * witness_blinder - + verification_key.alpha - + witness_attributes - .iter() - .zip(verification_key.beta_g2.iter()) - .map(|(wm_i, beta_i)| beta_i * wm_i) - .sum::(); - - // zeta is the public value associated with the serial number - let commitment_zeta = params.gen2() * witness_serial_number; - - let challenge = compute_challenge::( - std::iter::once(params.gen2().to_bytes().as_ref()) - .chain(std::iter::once(blinded_message.to_bytes().as_ref())) - .chain(std::iter::once(blinded_serial_number.to_bytes().as_ref())) - .chain(std::iter::once(verification_key.alpha.to_bytes().as_ref())) - .chain(beta_bytes.iter().map(|b| b.as_ref())) - .chain(std::iter::once(commitment_kappa.to_bytes().as_ref())) - .chain(std::iter::once(commitment_zeta.to_bytes().as_ref())), - ); - - // responses - let response_blinder = produce_response(&witness_blinder, &challenge, blinding_factor); - let response_serial_number = - produce_response(&witness_serial_number, &challenge, serial_number); - let response_binding_number = - produce_response(&witness_binding_number, &challenge, binding_number); - - ProofKappaZeta { - challenge, - response_serial_number, - response_binding_number, - response_blinder, - } - } - - pub(crate) fn private_attributes_len(&self) -> usize { - 2 - } - - pub(crate) fn verify( - &self, - params: &Parameters, - verification_key: &VerificationKey, - kappa: &G2Projective, - zeta: &G2Projective, - ) -> bool { - let beta_bytes = verification_key - .beta_g2 - .iter() - .map(|beta_i| beta_i.to_bytes()) - .collect::>(); - - let response_attributes = [self.response_serial_number, self.response_binding_number]; - // re-compute witnesses commitments - // Aw = (c * kappa) + (rt * g2) + ((1 - c) * alpha) + (rm[0] * beta[0]) + ... + (rm[i] * beta[i]) - let commitment_kappa = kappa * self.challenge - + params.gen2() * self.response_blinder - + verification_key.alpha * (Scalar::one() - self.challenge) - + response_attributes - .iter() - .zip(verification_key.beta_g2.iter()) - .map(|(priv_attr, beta_i)| beta_i * priv_attr) - .sum::(); - - // zeta is the public value associated with the serial number - let commitment_zeta = zeta * self.challenge + params.gen2() * self.response_serial_number; - - // compute the challenge - let challenge = compute_challenge::( - std::iter::once(params.gen2().to_bytes().as_ref()) - .chain(std::iter::once(kappa.to_bytes().as_ref())) - .chain(std::iter::once(zeta.to_bytes().as_ref())) - .chain(std::iter::once(verification_key.alpha.to_bytes().as_ref())) - .chain(beta_bytes.iter().map(|b| b.as_ref())) - .chain(std::iter::once(commitment_kappa.to_bytes().as_ref())) - .chain(std::iter::once(commitment_zeta.to_bytes().as_ref())), - ); - - challenge == self.challenge - } - - // challenge || response serial number || response binding number || repose blinder - pub(crate) fn to_bytes(&self) -> Vec { - let attributes_len = 2; // because we have serial number and the binding number - let mut bytes = Vec::with_capacity((1 + attributes_len + 1) as usize * 32); - - bytes.extend_from_slice(&self.challenge.to_bytes()); - bytes.extend_from_slice(&self.response_serial_number.to_bytes()); - bytes.extend_from_slice(&self.response_binding_number.to_bytes()); - - bytes.extend_from_slice(&self.response_blinder.to_bytes()); - - bytes - } - - pub(crate) fn from_bytes(bytes: &[u8]) -> Result { - // at the very minimum there must be a single attribute being proven - if bytes.len() != 128 { - return Err(CoconutError::DeserializationInvalidLength { - actual: bytes.len(), - modulus_target: bytes.len(), - modulus: 32, - object: "kappa and zeta".to_string(), - target: 32 * 4, - }); - } - - // safety: bound checked + constant offset - #[allow(clippy::unwrap_used)] - let challenge_bytes = bytes[..32].try_into().unwrap(); - let challenge = try_deserialize_scalar( - &challenge_bytes, - CoconutError::Deserialization("Failed to deserialize challenge".to_string()), - )?; - - // safety: bound checked + constant offset - #[allow(clippy::unwrap_used)] - let serial_number_bytes = &bytes[32..64].try_into().unwrap(); - let response_serial_number = try_deserialize_scalar( - serial_number_bytes, - CoconutError::Deserialization("failed to deserialize the serial number".to_string()), - )?; - - // safety: bound checked + constant offset - #[allow(clippy::unwrap_used)] - let binding_number_bytes = &bytes[64..96].try_into().unwrap(); - let response_binding_number = try_deserialize_scalar( - binding_number_bytes, - CoconutError::Deserialization("failed to deserialize the binding number".to_string()), - )?; - - // safety: bound checked + constant offset - #[allow(clippy::unwrap_used)] - let blinder_bytes = bytes[96..].try_into().unwrap(); - let response_blinder = try_deserialize_scalar( - &blinder_bytes, - CoconutError::Deserialization("failed to deserialize the blinder".to_string()), - )?; - - Ok(ProofKappaZeta { - challenge, - response_serial_number, - response_binding_number, - response_blinder, - }) - } -} - -// proof builder: -// - commitment -// - challenge -// - responses - -#[cfg(test)] -mod tests { - use super::*; - use crate::scheme::keygen::keygen; - use crate::scheme::setup::setup; - use crate::scheme::verification::{compute_kappa, compute_zeta}; - use crate::tests::helpers::random_scalars_refs; - use group::Group; - use rand::thread_rng; - - #[test] - fn proof_cm_cs_bytes_roundtrip() { - let mut rng = thread_rng(); - - let params = setup(1).unwrap(); - let cm = G1Projective::random(&mut rng); - let r = params.random_scalar(); - let cms: [G1Projective; 1] = [G1Projective::random(&mut rng)]; - let rs = params.n_random_scalars(1); - random_scalars_refs!(private_attributes, params, 1); - - // 0 public 1 private - let pi_s = ProofCmCs::construct(¶ms, &cm, &r, &cms, &rs, &private_attributes, &[]); - - let bytes = pi_s.to_bytes(); - assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s); - - let params = setup(2).unwrap(); - let cm = G1Projective::random(&mut rng); - let r = params.random_scalar(); - let cms: [G1Projective; 2] = [ - G1Projective::random(&mut rng), - G1Projective::random(&mut rng), - ]; - let rs = params.n_random_scalars(2); - random_scalars_refs!(private_attributes, params, 2); - - // 0 public 2 privates - let pi_s = ProofCmCs::construct(¶ms, &cm, &r, &cms, &rs, &private_attributes, &[]); - - let bytes = pi_s.to_bytes(); - assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s); - } - - #[test] - fn proof_kappa_zeta_bytes_roundtrip() { - let params = setup(4).unwrap(); - - let keypair = keygen(¶ms); - - // we don't care about 'correctness' of the proof. only whether we can correctly recover it from bytes - let serial_number = ¶ms.random_scalar(); - let binding_number = ¶ms.random_scalar(); - let private_attributes = vec![serial_number, binding_number]; - - let r = params.random_scalar(); - let kappa = compute_kappa(¶ms, keypair.verification_key(), &private_attributes, r); - let zeta = compute_zeta(¶ms, serial_number); - - // 0 public 2 private - let pi_v = ProofKappaZeta::construct( - ¶ms, - keypair.verification_key(), - serial_number, - binding_number, - &r, - &kappa, - &zeta, - ); - - let proof_bytes = pi_v.to_bytes(); - - let proof_from_bytes = ProofKappaZeta::from_bytes(&proof_bytes).unwrap(); - assert_eq!(proof_from_bytes, pi_v); - - // 2 public 2 private - let params = setup(4).unwrap(); - let keypair = keygen(¶ms); - - let pi_v = ProofKappaZeta::construct( - ¶ms, - keypair.verification_key(), - serial_number, - binding_number, - &r, - &kappa, - &zeta, - ); - - let proof_bytes = pi_v.to_bytes(); - - let proof_from_bytes = ProofKappaZeta::from_bytes(&proof_bytes).unwrap(); - assert_eq!(proof_from_bytes, pi_v); - } -} diff --git a/common/nymcoconut/src/scheme/aggregation.rs b/common/nymcoconut/src/scheme/aggregation.rs deleted file mode 100644 index e954e5b8b0..0000000000 --- a/common/nymcoconut/src/scheme/aggregation.rs +++ /dev/null @@ -1,432 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use core::iter::Sum; -use core::ops::Mul; - -use bls12_381::{G2Prepared, G2Projective, Scalar}; -use group::Curve; -use itertools::Itertools; - -use crate::error::{CoconutError, Result}; -use crate::scheme::verification::check_bilinear_pairing; -use crate::scheme::{PartialSignature, Signature, SignatureShare, SignerIndex, VerificationKey}; -use crate::utils::perform_lagrangian_interpolation_at_origin; -use crate::{Attribute, Parameters, VerificationKeyShare}; - -pub(crate) trait Aggregatable: Sized { - fn aggregate(aggregatable: &[Self], indices: Option<&[SignerIndex]>) -> Result; - - fn check_unique_indices(indices: &[SignerIndex]) -> bool { - // if aggregation is a threshold one, all indices should be unique - indices.iter().unique_by(|&index| index).count() == indices.len() - } -} - -// includes `VerificationKey` -impl Aggregatable for T -where - T: Sum, - for<'a> T: Sum<&'a T>, - for<'a> &'a T: Mul, -{ - fn aggregate(aggregatable: &[T], indices: Option<&[u64]>) -> Result { - if aggregatable.is_empty() { - return Err(CoconutError::Aggregation("Empty set of values".to_string())); - } - - if let Some(indices) = indices { - if !Self::check_unique_indices(indices) { - return Err(CoconutError::Aggregation("Non-unique indices".to_string())); - } - perform_lagrangian_interpolation_at_origin(indices, aggregatable) - } else { - // non-threshold - Ok(aggregatable.iter().sum()) - } - } -} - -impl Aggregatable for PartialSignature { - fn aggregate(sigs: &[PartialSignature], indices: Option<&[u64]>) -> Result { - let h = sigs - .first() - .ok_or_else(|| CoconutError::Aggregation("Empty set of signatures".to_string()))? - .sig1(); - - // TODO: is it possible to avoid this allocation? - let sigmas = sigs.iter().map(|sig| *sig.sig2()).collect::>(); - let aggr_sigma = Aggregatable::aggregate(&sigmas, indices)?; - - Ok(Signature(*h, aggr_sigma)) - } -} - -/// Ensures all provided verification keys were generated to verify the same number of attributes. -fn check_same_key_size(keys: &[VerificationKey]) -> bool { - keys.iter().map(|vk| vk.beta_g1.len()).all_equal() - && keys.iter().map(|vk| vk.beta_g2.len()).all_equal() -} - -pub fn aggregate_verification_keys( - keys: &[VerificationKey], - indices: Option<&[SignerIndex]>, -) -> Result { - if !check_same_key_size(keys) { - return Err(CoconutError::Aggregation( - "Verification keys are of different sizes".to_string(), - )); - } - Aggregatable::aggregate(keys, indices) -} - -pub fn aggregate_key_shares(shares: &[VerificationKeyShare]) -> Result { - let (keys, indices): (Vec<_>, Vec<_>) = shares - .iter() - .map(|share| (share.key.clone(), share.index)) - .unzip(); - - aggregate_verification_keys(&keys, Some(&indices)) -} - -pub fn aggregate_signatures( - signatures: &[PartialSignature], - indices: Option<&[SignerIndex]>, -) -> Result { - Aggregatable::aggregate(signatures, indices) -} - -pub fn aggregate_signatures_and_verify( - params: &Parameters, - verification_key: &VerificationKey, - attributes: &[&Attribute], - signatures: &[PartialSignature], - indices: Option<&[SignerIndex]>, -) -> Result { - // aggregate the signature - let signature = aggregate_signatures(signatures, indices)?; - - // Verify the signature - let alpha = verification_key.alpha; - - let tmp = attributes - .iter() - .zip(verification_key.beta_g2.iter()) - .map(|(&attr, beta_i)| beta_i * attr) - .sum::(); - - if bool::from(signature.0.is_identity()) { - return Err(CoconutError::Aggregation( - "Verification of the aggregated signature failed - h is an identity point".to_string(), - )); - } - if !check_bilinear_pairing( - &signature.0.to_affine(), - &G2Prepared::from((alpha + tmp).to_affine()), - &signature.1.to_affine(), - params.prepared_miller_g2(), - ) { - return Err(CoconutError::Aggregation( - "Verification of the aggregated signature failed".to_string(), - )); - } - Ok(signature) -} - -pub fn aggregate_signature_shares(shares: &[SignatureShare]) -> Result { - let (signatures, indices): (Vec<_>, Vec<_>) = shares - .iter() - .map(|share| (*share.signature(), share.index())) - .unzip(); - - aggregate_signatures(&signatures, Some(&indices)) -} - -pub fn aggregate_signature_shares_and_verify( - params: &Parameters, - verification_key: &VerificationKey, - attributes: &[&Attribute], - shares: &[SignatureShare], -) -> Result { - let (signatures, indices): (Vec<_>, Vec<_>) = shares - .iter() - .map(|share| (*share.signature(), share.index())) - .unzip(); - - aggregate_signatures_and_verify( - params, - verification_key, - attributes, - &signatures, - Some(&indices), - ) -} - -#[cfg(test)] -mod tests { - use crate::scheme::issuance::sign; - use crate::scheme::keygen::ttp_keygen; - use crate::scheme::verification::verify; - use crate::tests::helpers::random_scalars_refs; - use bls12_381::G1Projective; - use group::Group; - - use super::*; - - #[test] - fn key_aggregation_works_for_any_subset_of_keys() { - let params = Parameters::new(2).unwrap(); - let keypairs = ttp_keygen(¶ms, 3, 5).unwrap(); - - let vks = keypairs - .into_iter() - .map(|keypair| keypair.verification_key().clone()) - .collect::>(); - - let aggr_vk1 = aggregate_verification_keys(&vks[..3], Some(&[1, 2, 3])).unwrap(); - let aggr_vk2 = aggregate_verification_keys(&vks[2..], Some(&[3, 4, 5])).unwrap(); - - assert_eq!(aggr_vk1, aggr_vk2); - - // TODO: should those two actually work or not? - // aggregating threshold+1 - let aggr_more = aggregate_verification_keys(&vks[1..], Some(&[2, 3, 4, 5])).unwrap(); - assert_eq!(aggr_vk1, aggr_more); - - // aggregating all - let aggr_all = aggregate_verification_keys(&vks, Some(&[1, 2, 3, 4, 5])).unwrap(); - assert_eq!(aggr_all, aggr_vk1); - - // not taking enough points (threshold was 3) - let aggr_not_enough = aggregate_verification_keys(&vks[..2], Some(&[1, 2])).unwrap(); - assert_ne!(aggr_not_enough, aggr_vk1); - - // taking wrong index - let aggr_bad = aggregate_verification_keys(&vks[2..], Some(&[42, 123, 100])).unwrap(); - assert_ne!(aggr_vk1, aggr_bad); - } - - #[test] - fn key_aggregation_doesnt_work_for_empty_set_of_keys() { - let keys: Vec = vec![]; - assert!(aggregate_verification_keys(&keys, None).is_err()); - } - - #[test] - fn key_aggregation_doesnt_work_if_indices_have_invalid_length() { - let keys = vec![VerificationKey::identity(3)]; - - assert!(aggregate_verification_keys(&keys, Some(&[])).is_err()); - assert!(aggregate_verification_keys(&keys, Some(&[1, 2])).is_err()); - } - - #[test] - fn key_aggregation_doesnt_work_for_non_unique_indices() { - let keys = vec![VerificationKey::identity(3), VerificationKey::identity(3)]; - - assert!(aggregate_verification_keys(&keys, Some(&[1, 1])).is_err()); - } - - #[test] - fn key_aggregation_doesnt_work_for_keys_of_different_size() { - let keys = vec![VerificationKey::identity(3), VerificationKey::identity(1)]; - - assert!(aggregate_verification_keys(&keys, None).is_err()) - } - - #[test] - fn signature_aggregation_works_for_any_subset_of_signatures() { - let params = Parameters::new(2).unwrap(); - random_scalars_refs!(attributes, params, 2); - - let keypairs = ttp_keygen(¶ms, 3, 5).unwrap(); - - let (sks, vks): (Vec<_>, Vec<_>) = keypairs - .into_iter() - .map(|keypair| { - ( - keypair.secret_key().clone(), - keypair.verification_key().clone(), - ) - }) - .unzip(); - - let sigs = sks - .iter() - .map(|sk| sign(sk, &attributes).unwrap()) - .collect::>(); - - // aggregating (any) threshold works - let aggr_vk_1 = aggregate_verification_keys(&vks[..3], Some(&[1, 2, 3])).unwrap(); - let aggr_sig1 = aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_1, - &attributes, - &sigs[..3], - Some(&[1, 2, 3]), - ) - .unwrap(); - - let aggr_vk_2 = aggregate_verification_keys(&vks[2..], Some(&[3, 4, 5])).unwrap(); - let aggr_sig2 = aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_1, - &attributes, - &sigs[2..], - Some(&[3, 4, 5]), - ) - .unwrap(); - assert_eq!(aggr_sig1, aggr_sig2); - - // verify credential for good measure - assert!(verify(¶ms, &aggr_vk_1, &attributes, &aggr_sig1)); - assert!(verify(¶ms, &aggr_vk_2, &attributes, &aggr_sig2)); - - // aggregating threshold+1 works - let aggr_vk_more = aggregate_verification_keys(&vks[1..], Some(&[2, 3, 4, 5])).unwrap(); - let aggr_more = aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_more, - &attributes, - &sigs[1..], - Some(&[2, 3, 4, 5]), - ) - .unwrap(); - assert_eq!(aggr_sig1, aggr_more); - - // aggregating all - let aggr_vk_all = aggregate_verification_keys(&vks, Some(&[1, 2, 3, 4, 5])).unwrap(); - let aggr_all = aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_all, - &attributes, - &sigs, - Some(&[1, 2, 3, 4, 5]), - ) - .unwrap(); - assert_eq!(aggr_all, aggr_sig1); - - // not taking enough points (threshold was 3) should fail - let aggr_vk_not_enough = aggregate_verification_keys(&vks[..2], Some(&[1, 2])).unwrap(); - let aggr_not_enough = aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_not_enough, - &attributes, - &sigs[..2], - Some(&[1, 2]), - ) - .unwrap(); - assert_ne!(aggr_not_enough, aggr_sig1); - - // taking wrong index should fail - let aggr_vk_bad = aggregate_verification_keys(&vks[2..], Some(&[1, 2, 3])).unwrap(); - assert!(aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_bad, - &attributes, - &sigs[2..], - Some(&[42, 123, 100]), - ) - .is_err()); - } - - fn random_signature() -> Signature { - let mut rng = rand::thread_rng(); - Signature( - G1Projective::random(&mut rng), - G1Projective::random(&mut rng), - ) - } - - #[test] - fn signature_aggregation_doesnt_work_for_empty_set_of_signatures() { - let signatures: Vec = vec![]; - let params = Parameters::new(2).unwrap(); - random_scalars_refs!(attributes, params, 2); - let keypairs = ttp_keygen(¶ms, 3, 5).unwrap(); - - let (_, vks): (Vec<_>, Vec<_>) = keypairs - .into_iter() - .map(|keypair| { - ( - keypair.secret_key().clone(), - keypair.verification_key().clone(), - ) - }) - .unzip(); - - let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap(); - assert!(aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_all, - &attributes, - &signatures, - None - ) - .is_err()); - } - - #[test] - fn signature_aggregation_doesnt_work_if_indices_have_invalid_length() { - let signatures = vec![random_signature()]; - let params = Parameters::new(2).unwrap(); - random_scalars_refs!(attributes, params, 2); - let keypairs = ttp_keygen(¶ms, 3, 5).unwrap(); - let (_, vks): (Vec<_>, Vec<_>) = keypairs - .into_iter() - .map(|keypair| { - ( - keypair.secret_key().clone(), - keypair.verification_key().clone(), - ) - }) - .unzip(); - let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap(); - - assert!(aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_all, - &attributes, - &signatures, - Some(&[]) - ) - .is_err()); - assert!(aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_all, - &attributes, - &signatures, - Some(&[1, 2]), - ) - .is_err()); - } - - #[test] - fn signature_aggregation_doesnt_work_for_non_unique_indices() { - let signatures = vec![random_signature(), random_signature()]; - let params = Parameters::new(2).unwrap(); - random_scalars_refs!(attributes, params, 2); - let keypairs = ttp_keygen(¶ms, 3, 5).unwrap(); - let (_, vks): (Vec<_>, Vec<_>) = keypairs - .into_iter() - .map(|keypair| { - ( - keypair.secret_key().clone(), - keypair.verification_key().clone(), - ) - }) - .unzip(); - let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap(); - - assert!(aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_all, - &attributes, - &signatures, - Some(&[1, 1]), - ) - .is_err()); - } - - // TODO: test for aggregating non-threshold keys -} diff --git a/common/nymcoconut/src/scheme/double_use.rs b/common/nymcoconut/src/scheme/double_use.rs deleted file mode 100644 index f0a6a1eed3..0000000000 --- a/common/nymcoconut/src/scheme/double_use.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2022-2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::error::{CoconutError, Result}; -use crate::traits::{Base58, Bytable}; -use crate::utils::try_deserialize_g2_projective; -use bls12_381::{G2Affine, G2Projective}; -use group::Curve; - -use std::fmt::{Debug, Formatter}; -use std::ops::Deref; - -#[derive(PartialEq, Eq, Clone, Copy)] -pub struct BlindedSerialNumber(G2Projective); - -// use custom Debug implementation to show base58 encoding (rather than raw curve elements) -impl Debug for BlindedSerialNumber { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("BlindedSerialNumber") - .field(&self.to_bs58()) - .finish() - } -} - -impl From for BlindedSerialNumber { - fn from(value: G2Projective) -> Self { - BlindedSerialNumber(value) - } -} - -impl From for BlindedSerialNumber { - fn from(value: G2Affine) -> Self { - BlindedSerialNumber(value.into()) - } -} - -impl Deref for BlindedSerialNumber { - type Target = G2Projective; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl TryFrom<&[u8]> for BlindedSerialNumber { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - if bytes.len() != 96 { - return Err( - CoconutError::Deserialization( - format!("Tried to deserialize blinded serial number with incorrect number of bytes, expected 96, got {}", bytes.len()), - )); - } - - // safety: we've just made a check for 96 bytes - #[allow(clippy::unwrap_used)] - let inner = try_deserialize_g2_projective( - &bytes.try_into().unwrap(), - CoconutError::Deserialization( - "failed to deserialize the blinded serial number (zeta)".to_string(), - ), - )?; - - Ok(BlindedSerialNumber(inner)) - } -} - -impl Bytable for BlindedSerialNumber { - fn to_byte_vec(&self) -> Vec { - self.0.to_affine().to_compressed().to_vec() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - Self::try_from(slice) - } -} - -impl Base58 for BlindedSerialNumber {} diff --git a/common/nymcoconut/src/scheme/issuance.rs b/common/nymcoconut/src/scheme/issuance.rs deleted file mode 100644 index 24a4c85bbf..0000000000 --- a/common/nymcoconut/src/scheme/issuance.rs +++ /dev/null @@ -1,660 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use std::ops::Neg; - -use bls12_381::{multi_miller_loop, G1Affine, G1Projective, G2Prepared, Scalar}; -use group::{Curve, Group, GroupEncoding}; - -use crate::error::{CoconutError, Result}; -use crate::proofs::ProofCmCs; -use crate::scheme::keygen::VerificationKey; -use crate::scheme::setup::Parameters; -use crate::scheme::BlindedSignature; -use crate::scheme::SecretKey; -use crate::Attribute; -use crate::Signature; - -// TODO: possibly completely remove those two functions. -// They only exist to have a simpler and smaller code snippets to test -// basic functionalities. -use crate::traits::{Base58, Bytable}; -use crate::utils::{hash_g1, try_deserialize_g1_projective}; - -// TODO NAMING: double check this one -// Lambda -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq, Eq))] -pub struct BlindSignRequest { - // cm - commitment: G1Projective, - // h - commitment_hash: G1Projective, - // c - private_attributes_commitments: Vec, - // pi_s - pi_s: ProofCmCs, -} - -impl TryFrom<&[u8]> for BlindSignRequest { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - if bytes.len() < 48 + 48 + 8 + 48 { - return Err(CoconutError::DeserializationMinLength { - min: 48 + 48 + 8 + 48, - actual: bytes.len(), - }); - } - - let mut j = 0; - let commitment_bytes_len = 48; - let commitment_hash_bytes_len = 48; - - // safety: we made bound check and we're using constant offest - #[allow(clippy::unwrap_used)] - let cm_bytes = bytes[..j + commitment_bytes_len].try_into().unwrap(); - let commitment = try_deserialize_g1_projective( - &cm_bytes, - CoconutError::Deserialization( - "Failed to deserialize compressed commitment".to_string(), - ), - )?; - j += commitment_bytes_len; - - // safety: we made bound check and we're using constant offest - #[allow(clippy::unwrap_used)] - let cm_hash_bytes = bytes[j..j + commitment_hash_bytes_len].try_into().unwrap(); - let commitment_hash = try_deserialize_g1_projective( - &cm_hash_bytes, - CoconutError::Deserialization( - "Failed to deserialize compressed commitment hash".to_string(), - ), - )?; - j += commitment_hash_bytes_len; - - // safety: we made bound check and we're using constant offest - #[allow(clippy::unwrap_used)] - let c_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap()); - j += 8; - if bytes[j..].len() < c_len as usize * 48 { - return Err(CoconutError::DeserializationMinLength { - min: c_len as usize * 48, - actual: bytes[56..].len(), - }); - } - - let mut private_attributes_commitments = Vec::with_capacity(c_len as usize); - for i in 0..c_len as usize { - let start = j + i * 48; - let end = start + 48; - - if bytes.len() < end { - return Err(CoconutError::Deserialization( - "Failed to deserialize compressed commitment".to_string(), - )); - } - - // safety: we made bound check and we're using constant offest - #[allow(clippy::unwrap_used)] - let private_attributes_commitment_bytes = bytes[start..end].try_into().unwrap(); - let private_attributes_commitment = try_deserialize_g1_projective( - &private_attributes_commitment_bytes, - CoconutError::Deserialization( - "Failed to deserialize compressed commitment".to_string(), - ), - )?; - - private_attributes_commitments.push(private_attributes_commitment) - } - - let pi_s = ProofCmCs::from_bytes(&bytes[j + c_len as usize * 48..])?; - - Ok(BlindSignRequest { - commitment, - commitment_hash, - private_attributes_commitments, - pi_s, - }) - } -} - -impl Bytable for BlindSignRequest { - fn to_byte_vec(&self) -> Vec { - let cm_bytes = self.commitment.to_affine().to_compressed(); - let cm_hash_bytes = self.commitment_hash.to_affine().to_compressed(); - let c_len = self.private_attributes_commitments.len() as u64; - let proof_bytes = self.pi_s.to_bytes(); - - let mut bytes = Vec::with_capacity(48 + 48 + 8 + c_len as usize * 48 + proof_bytes.len()); - - bytes.extend_from_slice(&cm_bytes); - bytes.extend_from_slice(&cm_hash_bytes); - bytes.extend_from_slice(&c_len.to_le_bytes()); - for c in &self.private_attributes_commitments { - bytes.extend_from_slice(&c.to_affine().to_compressed()); - } - - bytes.extend_from_slice(&proof_bytes); - - bytes - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - BlindSignRequest::from_bytes(slice) - } -} - -impl Base58 for BlindSignRequest {} - -impl BlindSignRequest { - fn verify_proof(&self, params: &Parameters, public_attributes: &[&Attribute]) -> bool { - self.pi_s.verify( - params, - &self.commitment, - &self.private_attributes_commitments, - public_attributes, - ) - } - - pub fn verify_commitment_hash(&self, public_attributes: &[&Attribute]) -> bool { - self.commitment_hash == compute_hash(self.commitment, public_attributes) - } - - pub fn get_commitment_hash(&self) -> G1Projective { - self.commitment_hash - } - - pub fn get_private_attributes_pedersen_commitments(&self) -> &[G1Projective] { - &self.private_attributes_commitments - } - - pub fn to_bytes(&self) -> Vec { - self.to_byte_vec() - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - BlindSignRequest::try_from(bytes) - } - - pub fn num_private_attributes(&self) -> usize { - self.private_attributes_commitments.len() - } -} - -pub fn compute_attributes_commitment( - params: &Parameters, - private_attributes: &[&Attribute], - public_attributes: &[&Attribute], - hs: &[G1Affine], -) -> (Scalar, G1Projective) { - let commitment_opening = params.random_scalar(); - - // Produces h0 ^ m0 * h1^m1 * .... * hn^mn - // where m0, m1, ...., mn are attributes - let attr_cm = private_attributes - .iter() - .chain(public_attributes.iter()) - .zip(hs) - .map(|(&m, h)| h * m) - .sum::(); - - // Produces g1^r * h0 ^ m0 * h1^m1 * .... * hn^mn - let commitment = params.gen1() * commitment_opening + attr_cm; - (commitment_opening, commitment) -} - -pub fn compute_pedersen_commitments_for_private_attributes( - params: &Parameters, - private_attributes: &[&Attribute], - h: &G1Projective, -) -> (Vec, Vec) { - // Generate openings for Pedersen commitment for each private attribute - let commitments_openings = params.n_random_scalars(private_attributes.len()); - - // Compute Pedersen commitment for each private attribute - let pedersen_commitments = commitments_openings - .iter() - .zip(private_attributes.iter()) - .map(|(o_j, &m_j)| params.gen1() * o_j + h * m_j) - .collect::>(); - - (commitments_openings, pedersen_commitments) -} - -pub fn compute_hash(commitment: G1Projective, public_attributes: &[&Attribute]) -> G1Projective { - let mut buff = Vec::new(); - buff.extend_from_slice(commitment.to_bytes().as_ref()); - for attr in public_attributes { - buff.extend_from_slice(attr.to_bytes().as_ref()); - } - hash_g1(buff) -} - -/// Builds cryptographic material required for blind sign. -pub fn prepare_blind_sign( - params: &Parameters, - private_attributes: &[&Attribute], - public_attributes: &[&Attribute], -) -> Result<(Vec, BlindSignRequest)> { - if private_attributes.is_empty() { - return Err(CoconutError::Issuance( - "Tried to prepare blind sign request for an empty set of private attributes" - .to_string(), - )); - } - - let hs = params.gen_hs(); - if private_attributes.len() + public_attributes.len() > hs.len() { - return Err(CoconutError::IssuanceMaxAttributes { - max: hs.len(), - requested: private_attributes.len() + public_attributes.len(), - }); - } - - let mut commitment_hash; - let mut commitment; - let mut commitment_opening; - - loop { - // Compute the attributes commitment - let (c_opening, c) = - compute_attributes_commitment(params, private_attributes, public_attributes, hs); - commitment_opening = c_opening; - commitment = c; - - // Compute the commitment hash - commitment_hash = compute_hash(commitment, public_attributes); - - // Check if the commitment hash is not the identity point - if !bool::from(commitment_hash.is_identity()) { - break; - } - } - - let (pedersen_commitments_openings, pedersen_commitments) = - compute_pedersen_commitments_for_private_attributes( - params, - private_attributes, - &commitment_hash, - ); - - let pi_s = ProofCmCs::construct( - params, - &commitment, - &commitment_opening, - &pedersen_commitments, - &pedersen_commitments_openings, - private_attributes, - public_attributes, - ); - - Ok(( - pedersen_commitments_openings, - BlindSignRequest { - commitment, - commitment_hash, - private_attributes_commitments: pedersen_commitments, - pi_s, - }, - )) -} - -pub fn blind_sign( - params: &Parameters, - signing_secret_key: &SecretKey, - blind_sign_request: &BlindSignRequest, - public_attributes: &[&Attribute], -) -> Result { - let num_private = blind_sign_request.private_attributes_commitments.len(); - let hs = params.gen_hs(); - - if num_private + public_attributes.len() > hs.len() { - return Err(CoconutError::IssuanceMaxAttributes { - max: hs.len(), - requested: num_private + public_attributes.len(), - }); - } - - // Verify the commitment hash - let h = compute_hash(blind_sign_request.commitment, public_attributes); - if bool::from(blind_sign_request.commitment_hash.is_identity()) { - return Err(CoconutError::Issuance( - "Commitment hash should not be an identity point".to_string(), - )); - } - if !(h == blind_sign_request.commitment_hash) { - return Err(CoconutError::Issuance( - "Failed to verify the commitment hash".to_string(), - )); - } - - // Verify the ZK proof - if !blind_sign_request.verify_proof(params, public_attributes) { - return Err(CoconutError::Issuance( - "Failed to verify the proof of knowledge".to_string(), - )); - } - - // in python implementation there are n^2 G1 multiplications, let's do it with a single one instead. - // i.e. compute h ^ (pub_m[0] * y[m + 1] + ... + pub_m[n] * y[m + n]) directly (where m is number of PRIVATE attributes) - // rather than ((h ^ pub_m[0]) ^ y[m + 1] , (h ^ pub_m[1]) ^ y[m + 2] , ...).sum() separately - let signed_public = h * public_attributes - .iter() - .zip(signing_secret_key.ys.iter().skip(num_private)) - .map(|(&attr, yi)| attr * yi) - .sum::(); - - // h ^ x + c[0] ^ y[0] + ... c[m] ^ y[m] + h ^ (pub_m[0] * y[m + 1] + ... + pub_m[n] * y[m + n]) - let sig = blind_sign_request - .private_attributes_commitments - .iter() - .zip(signing_secret_key.ys.iter()) - .map(|(c, yi)| c * yi) - .chain(std::iter::once(h * signing_secret_key.x)) - .chain(std::iter::once(signed_public)) - .sum(); - - Ok(BlindedSignature(h, sig)) -} - -/// Verifies a partial blind signature using the provided parameters and validator's verification key. -/// -/// # Arguments -/// -/// * `params` - A reference to the cryptographic parameters. -/// * `blind_sign_request` - A reference to the blind signature request signed by the client. -/// * `public_attributes` - A reference to the public attributes included in the client's request. -/// * `blind_sig` - A reference to the issued partial blinded signature to be verified. -/// * `partial_verification_key` - A reference to the validator's partial verification key. -/// -/// # Returns -/// -/// A boolean indicating whether the partial blind signature is valid (`true`) or not (`false`). -/// -/// # Remarks -/// -/// This function verifies the correctness and validity of a partial blind signature using -/// the provided cryptographic parameters, blind signature request, blinded signature, -/// and partial verification key. -/// It calculates pairings based on the provided values and checks whether the partial blind signature -/// is consistent with the verification key and commitments in the blind signature request. -/// The function returns `true` if the partial blind signature is valid, and `false` otherwise. -pub fn verify_partial_blind_signature( - params: &Parameters, - private_attribute_commitments: &[G1Projective], - public_attributes: &[&Attribute], - blind_sig: &BlindedSignature, - partial_verification_key: &VerificationKey, -) -> bool { - let num_private_attributes = private_attribute_commitments.len(); - if num_private_attributes + public_attributes.len() > partial_verification_key.beta_g2.len() { - return false; - } - if bool::from(blind_sig.0.is_identity()) { - return false; - } - - // TODO: we're losing some memory here due to extra allocation, - // but worst-case scenario (given SANE amount of attributes), it's just few kb at most - let c_neg = blind_sig.1.to_affine().neg(); - let g2_prep = params.prepared_miller_g2(); - - let mut terms = vec![ - // (c^{-1}, g2) - (c_neg, g2_prep.clone()), - // (s, alpha) - ( - blind_sig.0.to_affine(), - G2Prepared::from(partial_verification_key.alpha.to_affine()), - ), - ]; - - // for each private attribute, add (cm_i, beta_i) to the miller terms - for (private_attr_commit, beta_g2) in private_attribute_commitments - .iter() - .zip(&partial_verification_key.beta_g2) - { - // (cm_i, beta_i) - terms.push(( - private_attr_commit.to_affine(), - G2Prepared::from(beta_g2.to_affine()), - )) - } - - // for each public attribute, add (s^pub_j, beta_{priv + j}) to the miller terms - for (&pub_attr, beta_g2) in public_attributes.iter().zip( - partial_verification_key - .beta_g2 - .iter() - .skip(num_private_attributes), - ) { - // (s^pub_j, beta_j) - terms.push(( - (blind_sig.0 * pub_attr).to_affine(), - G2Prepared::from(beta_g2.to_affine()), - )) - } - - // get the references to all the terms to get the arguments the miller loop expects - #[allow(clippy::map_identity)] - let terms_refs = terms.iter().map(|(g1, g2)| (g1, g2)).collect::>(); - - // since checking whether e(a, b) == e(c, d) - // is equivalent to checking e(a, b) • e(c, d)^{-1} == id - // and thus to e(a, b) • e(c^{-1}, d) == id - // - // compute e(c^{-1}, g2) • e(s, alpha) • e(cm_0, beta_0) • e(cm_i, beta_i) • (s^pub_0, beta_{i+1}) (s^pub_j, beta_{i + j}) - multi_miller_loop(&terms_refs) - .final_exponentiation() - .is_identity() - .into() -} - -/// Creates a Coconut Signature under a given secret key on a set of public attributes only. -pub fn sign(secret_key: &SecretKey, public_attributes: &[&Attribute]) -> Result { - if public_attributes.len() > secret_key.ys.len() { - return Err(CoconutError::IssuanceMaxAttributes { - max: secret_key.ys.len(), - requested: public_attributes.len(), - }); - } - - //Serialize the array structure of the public attributes into a byte array - let mut serialized_attributes = Vec::new(); - //Prepend the length of the entire array (in bytes) - let array_len = public_attributes.len() as u64; - serialized_attributes.extend_from_slice(&array_len.to_le_bytes()); - //Serialize each attribute with its length - for &attribute in public_attributes.iter() { - let attr_bytes = attribute.to_bytes(); - let attr_len = attr_bytes.len() as u64; - - // Prefix the attribute with its length - serialized_attributes.extend_from_slice(&attr_len.to_le_bytes()); - serialized_attributes.extend_from_slice(&attr_bytes); - } - - //Hash the resulting byte array to derive the point H - let h = hash_g1(serialized_attributes); - - // x + m0 * y0 + m1 * y1 + ... mn * yn - let exponent = secret_key.x - + public_attributes - .iter() - .zip(secret_key.ys.iter()) - .map(|(&m_i, y_i)| m_i * y_i) - .sum::(); - - let sig2 = h * exponent; - Ok(Signature(h, sig2)) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::scheme::keygen::keygen; - use crate::tests::helpers::random_scalars_refs; - - #[test] - fn blind_sign_request_bytes_roundtrip() { - // 0 public and 1 private attribute - let params = Parameters::new(1).unwrap(); - random_scalars_refs!(private_attributes, params, 1); - random_scalars_refs!(public_attributes, params, 0); - - let (_commitments_openings, lambda) = - prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); - - let bytes = lambda.to_bytes(); - assert_eq!( - BlindSignRequest::try_from(bytes.as_slice()).unwrap(), - lambda - ); - - // 2 public and 2 private attributes - let params = Parameters::new(4).unwrap(); - random_scalars_refs!(private_attributes, params, 2); - random_scalars_refs!(public_attributes, params, 2); - - let (_commitments_openings, lambda) = - prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); - - let bytes = lambda.to_bytes(); - assert_eq!( - BlindSignRequest::try_from(bytes.as_slice()).unwrap(), - lambda - ); - } - - #[test] - fn test_prepare_blind_sign_non_identity_commitment_hash() { - let params = Parameters::new(1).unwrap(); - random_scalars_refs!(private_attributes, params, 1); - random_scalars_refs!(public_attributes, params, 0); - - // Call the function to prepare the blind sign - let result = prepare_blind_sign(¶ms, &private_attributes, &public_attributes); - - // Ensure the result is Ok - assert!(result.is_ok(), "prepare_blind_sign should succeed"); - - let (_, blind_sign_request) = result.unwrap(); - - // Ensure the commitment_hash is not the identity point - assert!( - !bool::from(blind_sign_request.commitment_hash.is_identity()), - "commitment_hash should not be the identity point" - ); - } - - #[test] - fn test_blind_sign_with_identity_commitment_hash() { - let params = Parameters::new(1).unwrap(); - random_scalars_refs!(private_attributes, params, 1); - random_scalars_refs!(public_attributes, params, 0); - - // Call the function to prepare the blind sign - let (_commitments_openings, blind_sign_request) = - prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); - let blind_sign_request = BlindSignRequest { - commitment_hash: G1Projective::identity(), - ..blind_sign_request // This copies the other fields from the existing instance - }; - - let signing_secret_key = SecretKey { - x: params.random_scalar(), - ys: vec![params.random_scalar()], - }; - - // Call blind_sign and ensure it returns an error due to identity commitment hash - let result = blind_sign( - ¶ms, - &signing_secret_key, - &blind_sign_request, - &public_attributes, - ); - - // The result should be an error - assert!( - result.is_err(), - "blind_sign should return an error when commitment_hash is the identity point" - ); - } - - #[test] - fn successful_verify_partial_blind_signature() { - let params = Parameters::new(4).unwrap(); - random_scalars_refs!(private_attributes, params, 2); - random_scalars_refs!(public_attributes, params, 2); - - let (_commitments_openings, request) = - prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); - - let validator_keypair = keygen(¶ms); - let blind_sig = blind_sign( - ¶ms, - validator_keypair.secret_key(), - &request, - &public_attributes, - ) - .unwrap(); - - assert!(verify_partial_blind_signature( - ¶ms, - &request.private_attributes_commitments, - &public_attributes, - &blind_sig, - validator_keypair.verification_key() - )); - } - - #[test] - fn successful_verify_partial_blind_signature_no_public_attributes() { - let params = Parameters::new(4).unwrap(); - random_scalars_refs!(private_attributes, params, 2); - - let (_commitments_openings, request) = - prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap(); - - let validator_keypair = keygen(¶ms); - let blind_sig = blind_sign(¶ms, validator_keypair.secret_key(), &request, &[]).unwrap(); - - assert!(verify_partial_blind_signature( - ¶ms, - &request.private_attributes_commitments, - &[], - &blind_sig, - validator_keypair.verification_key() - )); - } - - #[test] - fn fail_verify_partial_blind_signature_with_wrong_key() { - let params = Parameters::new(4).unwrap(); - random_scalars_refs!(private_attributes, params, 2); - random_scalars_refs!(public_attributes, params, 2); - - let (_commitments_openings, request) = - prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); - - let validator_keypair = keygen(¶ms); - let validator2_keypair = keygen(¶ms); - let blind_sig = blind_sign( - ¶ms, - validator_keypair.secret_key(), - &request, - &public_attributes, - ) - .unwrap(); - - // this assertion should fail, as we try to verify with a wrong validator key - assert!(!verify_partial_blind_signature( - ¶ms, - &request.private_attributes_commitments, - &public_attributes, - &blind_sig, - validator2_keypair.verification_key() - ),); - } -} diff --git a/common/nymcoconut/src/scheme/keygen.rs b/common/nymcoconut/src/scheme/keygen.rs deleted file mode 100644 index f80c131143..0000000000 --- a/common/nymcoconut/src/scheme/keygen.rs +++ /dev/null @@ -1,722 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use core::borrow::Borrow; -use core::iter::Sum; -use core::ops::{Add, Mul}; - -use bls12_381::{G1Projective, G2Projective, Scalar}; -use group::Curve; -use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; -use serde_derive::{Deserialize, Serialize}; - -use crate::error::{CoconutError, Result}; -use crate::scheme::aggregation::aggregate_verification_keys; -use crate::scheme::setup::Parameters; -use crate::scheme::SignerIndex; -use crate::traits::Bytable; -use crate::utils::{ - try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar, - try_deserialize_scalar_vec, Polynomial, -}; -use crate::Base58; - -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq, Eq, Clone))] -#[cfg_attr( - feature = "key-zeroize", - derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop) -)] -pub struct SecretKey { - pub(crate) x: Scalar, - pub(crate) ys: Vec, -} - -impl PemStorableKey for SecretKey { - type Error = CoconutError; - - fn pem_type() -> &'static str { - "COCONUT SECRET KEY" - } - - fn to_bytes(&self) -> Vec { - self.to_bytes() - } - - fn from_bytes(bytes: &[u8]) -> std::result::Result { - Self::from_bytes(bytes) - } -} - -impl TryFrom<&[u8]> for SecretKey { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - // There should be x and at least one y - if bytes.len() < 32 * 2 + 8 || (bytes.len() - 8) % 32 != 0 { - return Err(CoconutError::DeserializationInvalidLength { - actual: bytes.len(), - modulus_target: bytes.len() - 8, - target: 32 * 2 + 8, - modulus: 32, - object: "secret key".to_string(), - }); - } - - // this conversion will not fail as we are taking the same length of data - #[allow(clippy::unwrap_used)] - let x_bytes: [u8; 32] = bytes[..32].try_into().unwrap(); - #[allow(clippy::unwrap_used)] - let ys_len = u64::from_le_bytes(bytes[32..40].try_into().unwrap()); - let actual_ys_len = (bytes.len() - 40) / 32; - - if ys_len as usize != actual_ys_len { - return Err(CoconutError::Deserialization(format!( - "Tried to deserialize secret key with inconsistent ys len (expected {ys_len}, got {actual_ys_len})" - ))); - } - - let x = try_deserialize_scalar( - &x_bytes, - CoconutError::Deserialization("Failed to deserialize secret key scalar".to_string()), - )?; - let ys = try_deserialize_scalar_vec( - ys_len, - &bytes[40..], - CoconutError::Deserialization("Failed to deserialize secret key scalars".to_string()), - )?; - - Ok(SecretKey { x, ys }) - } -} - -impl SecretKey { - /// Following a (distributed) key generation process, scalar values can be obtained - /// outside of the normal key generation process. - pub fn create_from_raw(x: Scalar, ys: Vec) -> Self { - Self { x, ys } - } - - /// Extract the Scalar copy of the underlying secrets. - /// The caller of this function must exercise extreme care to not misuse the data and ensuring it gets zeroized - pub fn hazmat_to_raw(&self) -> (Scalar, Vec) { - (self.x, self.ys.clone()) - } - - pub fn size(&self) -> usize { - self.ys.len() - } - - /// Derive verification key using this secret key. - pub fn verification_key(&self, params: &Parameters) -> VerificationKey { - let g1 = params.gen1(); - let g2 = params.gen2(); - VerificationKey { - alpha: g2 * self.x, - beta_g1: self.ys.iter().map(|y| g1 * y).collect(), - beta_g2: self.ys.iter().map(|y| g2 * y).collect(), - } - } - - // x || ys.len() || ys - pub fn to_bytes(&self) -> Vec { - let ys_len = self.ys.len(); - let mut bytes = Vec::with_capacity(8 + (ys_len + 1) * 32); - - bytes.extend_from_slice(&self.x.to_bytes()); - bytes.extend_from_slice(&ys_len.to_le_bytes()); - for y in self.ys.iter() { - bytes.extend_from_slice(&y.to_bytes()) - } - bytes - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - SecretKey::try_from(bytes) - } -} - -impl Bytable for SecretKey { - fn to_byte_vec(&self) -> Vec { - self.to_bytes() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - SecretKey::try_from(slice) - } -} - -impl Base58 for SecretKey {} - -// TODO: perhaps change points to affine representation -// to make verification slightly more efficient? -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct VerificationKey { - // TODO add gen2 as per the paper or imply it from the fact library is using bls381? - pub(crate) alpha: G2Projective, - pub(crate) beta_g1: Vec, - pub(crate) beta_g2: Vec, -} - -impl PemStorableKey for VerificationKey { - type Error = CoconutError; - - fn pem_type() -> &'static str { - "COCONUT VERIFICATION KEY" - } - - fn to_bytes(&self) -> Vec { - self.to_bytes() - } - - fn from_bytes(bytes: &[u8]) -> std::result::Result { - Self::from_bytes(bytes) - } -} - -impl TryFrom<&[u8]> for VerificationKey { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - // There should be at least alpha, one betaG1 and one betaG2 and their length - if bytes.len() < 96 * 2 + 48 + 8 || (bytes.len() - 8 - 96) % (96 + 48) != 0 { - return Err(CoconutError::DeserializationInvalidLength { - actual: bytes.len(), - modulus_target: bytes.len() - 8 - 96, - target: 96 * 2 + 48 + 8, - modulus: 96 + 48, - object: "verification key".to_string(), - }); - } - - // this conversion will not fail as we are taking the same length of data - #[allow(clippy::unwrap_used)] - let alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap(); - #[allow(clippy::unwrap_used)] - let betas_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap()); - - let actual_betas_len = (bytes.len() - 104) / (96 + 48); - - if betas_len as usize != actual_betas_len { - return Err( - CoconutError::Deserialization( - format!("Tried to deserialize verification key with inconsistent betas len (expected {betas_len}, got {actual_betas_len})" - ))); - } - - let alpha = try_deserialize_g2_projective( - &alpha_bytes, - CoconutError::Deserialization( - "Failed to deserialize verification key G2 point (alpha)".to_string(), - ), - )?; - - let mut beta_g1 = Vec::with_capacity(betas_len as usize); - let mut beta_g1_end: u64 = 0; - for i in 0..betas_len { - let start = (104 + i * 48) as usize; - let end = start + 48; - // we're using a constant 48 byte offset (which is the size of G1 compressed) so unwrap is fine - #[allow(clippy::unwrap_used)] - let beta_i_bytes = bytes[start..end].try_into().unwrap(); - let beta_i = try_deserialize_g1_projective( - &beta_i_bytes, - CoconutError::Deserialization( - "Failed to deserialize verification key G1 point (beta)".to_string(), - ), - )?; - - beta_g1_end = end as u64; - beta_g1.push(beta_i) - } - - let mut beta_g2 = Vec::with_capacity(betas_len as usize); - for i in 0..betas_len { - let start = (beta_g1_end + i * 96) as usize; - let end = start + 96; - // we're using a constant 96 byte offset (which is the size of G2 compressed) so unwrap is fine - #[allow(clippy::unwrap_used)] - let beta_i_bytes = bytes[start..end].try_into().unwrap(); - let beta_i = try_deserialize_g2_projective( - &beta_i_bytes, - CoconutError::Deserialization( - "Failed to deserialize verification key G2 point (beta)".to_string(), - ), - )?; - - beta_g2.push(beta_i) - } - - Ok(VerificationKey { - alpha, - beta_g1, - beta_g2, - }) - } -} - -impl<'b> Add<&'b VerificationKey> for VerificationKey { - type Output = VerificationKey; - - #[inline] - fn add(self, rhs: &'b VerificationKey) -> VerificationKey { - // If you're trying to add two keys together that were created - // for different number of attributes, just panic as it's a - // nonsense operation. - assert_eq!( - self.beta_g1.len(), - rhs.beta_g1.len(), - "trying to add verification keys generated for different number of attributes [G1]" - ); - - assert_eq!( - self.beta_g2.len(), - rhs.beta_g2.len(), - "trying to add verification keys generated for different number of attributes [G2]" - ); - - assert_eq!( - self.beta_g1.len(), - self.beta_g2.len(), - "this key is incorrect - the number of elements G1 and G2 does not match" - ); - - assert_eq!( - rhs.beta_g1.len(), - rhs.beta_g2.len(), - "they key you want to add is incorrect - the number of elements G1 and G2 does not match" - ); - - VerificationKey { - alpha: self.alpha + rhs.alpha, - beta_g1: self - .beta_g1 - .iter() - .zip(rhs.beta_g1.iter()) - .map(|(self_beta_g1, rhs_beta_g1)| self_beta_g1 + rhs_beta_g1) - .collect(), - beta_g2: self - .beta_g2 - .iter() - .zip(rhs.beta_g2.iter()) - .map(|(self_beta_g2, rhs_beta_g2)| self_beta_g2 + rhs_beta_g2) - .collect(), - } - } -} - -impl Mul for &VerificationKey { - type Output = VerificationKey; - - #[inline] - fn mul(self, rhs: Scalar) -> Self::Output { - VerificationKey { - alpha: self.alpha * rhs, - beta_g1: self.beta_g1.iter().map(|b_i| b_i * rhs).collect(), - beta_g2: self.beta_g2.iter().map(|b_i| b_i * rhs).collect(), - } - } -} - -impl Sum for VerificationKey -where - T: Borrow, -{ - #[inline] - fn sum(iter: I) -> Self - where - I: Iterator, - { - let mut peekable = iter.peekable(); - let head_attributes = match peekable.peek() { - Some(head) => head.borrow().beta_g2.len(), - None => { - // TODO: this is a really weird edge case. You're trying to sum an EMPTY iterator - // of VerificationKey. So should it panic here or just return some nonsense value? - return VerificationKey::identity(0); - } - }; - - peekable.fold(VerificationKey::identity(head_attributes), |acc, item| { - acc + item.borrow() - }) - } -} - -impl VerificationKey { - /// Create a (kinda) identity verification key using specified - /// number of 'beta' elements - pub(crate) fn identity(beta_size: usize) -> Self { - VerificationKey { - alpha: G2Projective::identity(), - beta_g1: vec![G1Projective::identity(); beta_size], - beta_g2: vec![G2Projective::identity(); beta_size], - } - } - - pub fn aggregate(sigs: &[Self], indices: Option<&[SignerIndex]>) -> Result { - aggregate_verification_keys(sigs, indices) - } - - pub fn alpha(&self) -> &G2Projective { - &self.alpha - } - - pub fn beta_g1(&self) -> &Vec { - &self.beta_g1 - } - - pub fn beta_g2(&self) -> &Vec { - &self.beta_g2 - } - - pub fn to_bytes(&self) -> Vec { - let beta_g1_len = self.beta_g1.len(); - let beta_g2_len = self.beta_g2.len(); - let mut bytes = Vec::with_capacity(96 + 8 + beta_g1_len * 48 + beta_g2_len * 96); - - bytes.extend_from_slice(&self.alpha.to_affine().to_compressed()); - - bytes.extend_from_slice(&beta_g1_len.to_le_bytes()); - - for beta_g1 in self.beta_g1.iter() { - bytes.extend_from_slice(&beta_g1.to_affine().to_compressed()) - } - - for beta_g2 in self.beta_g2.iter() { - bytes.extend_from_slice(&beta_g2.to_affine().to_compressed()) - } - - bytes - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - VerificationKey::try_from(bytes) - } -} - -impl Bytable for VerificationKey { - fn to_byte_vec(&self) -> Vec { - self.to_bytes() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - VerificationKey::try_from(slice) - } -} - -impl Base58 for VerificationKey {} - -#[derive(Debug, Clone)] -pub struct VerificationKeyShare { - pub key: VerificationKey, - pub index: SignerIndex, -} - -impl From<(VerificationKey, SignerIndex)> for VerificationKeyShare { - fn from(value: (VerificationKey, SignerIndex)) -> Self { - VerificationKeyShare { - key: value.0, - index: value.1, - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -#[cfg_attr(test, derive(PartialEq, Eq, Clone))] -pub struct KeyPair { - secret_key: SecretKey, - verification_key: VerificationKey, - - /// Optional index value specifying polynomial point used during threshold key generation. - pub index: Option, -} - -impl From for (SecretKey, VerificationKey) { - fn from(value: KeyPair) -> Self { - (value.secret_key, value.verification_key) - } -} - -impl PemStorableKeyPair for KeyPair { - type PrivatePemKey = SecretKey; - type PublicPemKey = VerificationKey; - - fn private_key(&self) -> &Self::PrivatePemKey { - &self.secret_key - } - - fn public_key(&self) -> &Self::PublicPemKey { - &self.verification_key - } - - fn from_keys(secret_key: Self::PrivatePemKey, verification_key: Self::PublicPemKey) -> Self { - Self::from_keys(secret_key, verification_key) - } -} - -impl KeyPair { - const MARKER_BYTES: &'static [u8] = b"coconutkeypair"; - - pub fn from_keys(secret_key: SecretKey, verification_key: VerificationKey) -> Self { - Self { - secret_key, - verification_key, - index: None, - } - } - - pub fn secret_key(&self) -> &SecretKey { - &self.secret_key - } - - pub fn verification_key(&self) -> &VerificationKey { - &self.verification_key - } - - pub fn to_verification_key_share(&self) -> Option { - self.index.map(|index| VerificationKeyShare { - key: self.verification_key.clone(), - index, - }) - } - - pub fn to_bytes(&self) -> Vec { - // Schema is coconutkeypair[14]|secret_key_len[8]|secret_key[secret_key_len]|verification_key_len[8]|verification_key[verification_key_len]|signer_index[8] - optional - self.to_byte_vec() - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - KeyPair::try_from_byte_slice(bytes) - } -} - -impl Bytable for KeyPair { - fn to_byte_vec(&self) -> Vec { - // Schema is coconutkeypair[14]|secret_key_len[8]|secret_key[secret_key_len]|verification_key_len[8]|verification_key[verification_key_len]|signer_index[8] - optional - let mut byts = vec![]; - let secret_key_bytes = self.secret_key.to_bytes(); - let secret_key_len = (secret_key_bytes.len() as u64).to_le_bytes(); - let verification_key_bytes = self.verification_key.to_bytes(); - let verification_key_len = (verification_key_bytes.len() as u64).to_le_bytes(); - byts.extend_from_slice(Self::MARKER_BYTES); - byts.extend_from_slice(&secret_key_len); - byts.extend_from_slice(&secret_key_bytes); - byts.extend_from_slice(&verification_key_len); - byts.extend_from_slice(&verification_key_bytes); - if let Some(index) = self.index { - byts.extend_from_slice(&index.to_le_bytes()) - } - byts - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - KeyPair::try_from(slice) - } -} - -impl Base58 for KeyPair {} - -impl TryFrom<&[u8]> for KeyPair { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - let header_len = Self::MARKER_BYTES.len(); - - // we must be able to at the very least read the length of secret key which is past the header - // and is 8 bytes long - if bytes.len() < header_len + 8 { - return Err(CoconutError::DeserializationMinLength { - min: header_len + 8, - actual: bytes.len(), - }); - } - - // safety: we made bound check and we're using constant offest - #[allow(clippy::unwrap_used)] - let secret_key_len = - u64::from_le_bytes(bytes[header_len..header_len + 8].try_into().unwrap()) as usize; - let secret_key_start = header_len + 8; - - let secret_key = - SecretKey::try_from(&bytes[secret_key_start..secret_key_start + secret_key_len])?; - - // we must be able to read the length of verification key - if bytes.len() < secret_key_start + secret_key_len + 8 { - return Err(CoconutError::DeserializationMinLength { - min: secret_key_start + secret_key_len + 8, - actual: bytes.len(), - }); - } - - // safety: we made bound check - #[allow(clippy::unwrap_used)] - let verification_key_len = u64::from_le_bytes( - bytes[secret_key_start + secret_key_len..secret_key_start + secret_key_len + 8] - .try_into() - .unwrap(), - ) as usize; - let verification_key_start = secret_key_start + secret_key_len + 8; - - let verification_key = VerificationKey::try_from( - &bytes[verification_key_start..verification_key_start + verification_key_len], - )?; - let consumed_bytes = verification_key_start + verification_key_len; - let index = if consumed_bytes < bytes.len() && [consumed_bytes..].len() == 8 { - #[allow(clippy::unwrap_used)] - Some(u64::from_le_bytes( - bytes[consumed_bytes..consumed_bytes + 8] - .try_into() - .unwrap(), - )) - } else { - None - }; - Ok(KeyPair { - secret_key, - verification_key, - index, - }) - } -} - -/// Generate a single Coconut keypair ((x, y0, y1...), (g2^x, g2^y0, ...)). -/// -/// It is not suitable for threshold credentials as all subsequent calls to `keygen` generate keys -/// that are independent of each other. -pub fn keygen(params: &Parameters) -> KeyPair { - let attributes = params.gen_hs().len(); - - let x = params.random_scalar(); - let ys = params.n_random_scalars(attributes); - - let secret_key = SecretKey { x, ys }; - let verification_key = secret_key.verification_key(params); - - KeyPair { - secret_key, - verification_key, - index: None, - } -} - -/// Generate Coconut keypairs. -/// -/// Generate a set of n Coconut keypairs [((x, y0, y1...), (g2^x, g2^y0, ...)), ...], -/// such that they support threshold aggregation by `threshold` number of parties. -/// It is expected that this procedure is executed by a Trusted Third Party. -pub fn ttp_keygen( - params: &Parameters, - threshold: u64, - num_authorities: u64, -) -> Result> { - if threshold == 0 { - return Err(CoconutError::Setup( - "Tried to generate threshold keys with a 0 threshold value".to_string(), - )); - } - - if threshold > num_authorities { - return Err( - CoconutError::Setup( - "Tried to generate threshold keys for threshold value being higher than number of the signing authorities".to_string(), - )); - } - - let attributes = params.gen_hs().len(); - - // generate polynomials - let v = Polynomial::new_random(params, threshold - 1); - let ws = (0..attributes) - .map(|_| Polynomial::new_random(params, threshold - 1)) - .collect::>(); - - // TODO: potentially if we had some known authority identifier we could use that instead - // of the increasing (1,2,3,...) sequence - let polynomial_indices = (1..=num_authorities).collect::>(); - - // generate polynomial shares - let x = polynomial_indices - .iter() - .map(|&id| v.evaluate(&Scalar::from(id))); - let ys = polynomial_indices.iter().map(|&id| { - ws.iter() - .map(|w| w.evaluate(&Scalar::from(id))) - .collect::>() - }); - - // finally set the keys - let secret_keys = x.zip(ys).map(|(x, ys)| SecretKey { x, ys }); - - let keypairs = secret_keys - .zip(polynomial_indices.iter()) - .map(|(secret_key, index)| { - let verification_key = secret_key.verification_key(params); - KeyPair { - secret_key, - verification_key, - index: Some(*index), - } - }) - .collect(); - - Ok(keypairs) -} - -#[cfg(test)] -mod tests { - use crate::scheme::setup::setup; - - use super::*; - - #[test] - fn keypair_bytes_roundtrip() { - let params1 = setup(1).unwrap(); - let params5 = setup(5).unwrap(); - - let keypair1 = keygen(¶ms1); - let keypair5 = keygen(¶ms5); - - let bytes1 = keypair1.to_bytes(); - let bytes5 = keypair5.to_bytes(); - - assert_eq!(KeyPair::from_bytes(&bytes1).unwrap(), keypair1); - assert_eq!(KeyPair::from_bytes(&bytes5).unwrap(), keypair5); - } - - #[test] - fn secret_key_bytes_roundtrip() { - let params1 = setup(1).unwrap(); - let params5 = setup(5).unwrap(); - - let keypair1 = keygen(¶ms1); - let keypair5 = keygen(¶ms5); - - let bytes1 = keypair1.secret_key.to_bytes(); - let bytes5 = keypair5.secret_key.to_bytes(); - - assert_eq!(SecretKey::from_bytes(&bytes1).unwrap(), keypair1.secret_key); - assert_eq!(SecretKey::from_bytes(&bytes5).unwrap(), keypair5.secret_key); - } - - #[test] - fn verification_key_bytes_roundtrip() { - let params1 = setup(1).unwrap(); - let params5 = setup(5).unwrap(); - - let keypair1 = &keygen(¶ms1); - let keypair5 = &keygen(¶ms5); - - let bytes1: Vec = keypair1.verification_key.to_bytes(); - let bytes5: Vec = keypair5.verification_key.to_bytes(); - - assert_eq!( - VerificationKey::try_from(bytes1.as_slice()).unwrap(), - keypair1.verification_key - ); - assert_eq!( - VerificationKey::try_from(bytes5.as_slice()).unwrap(), - keypair5.verification_key - ); - } -} diff --git a/common/nymcoconut/src/scheme/mod.rs b/common/nymcoconut/src/scheme/mod.rs deleted file mode 100644 index b811b6e250..0000000000 --- a/common/nymcoconut/src/scheme/mod.rs +++ /dev/null @@ -1,672 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -// TODO: implement https://crates.io/crates/signature traits? - -use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar}; -use group::Curve; - -pub use keygen::{SecretKey, VerificationKey}; - -use crate::error::{CoconutError, Result}; -use crate::scheme::setup::Parameters; -use crate::scheme::verification::check_bilinear_pairing; -use crate::traits::{Base58, Bytable}; -use crate::utils::try_deserialize_g1_projective; -use crate::Attribute; - -pub mod aggregation; -pub mod double_use; -pub mod issuance; -pub mod keygen; -pub mod setup; -pub mod verification; - -pub type SignerIndex = u64; - -// (h, s) -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Signature(pub(crate) G1Projective, pub(crate) G1Projective); - -pub type PartialSignature = Signature; - -impl TryFrom<&[u8]> for Signature { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - if bytes.len() != 96 { - return Err(CoconutError::Deserialization(format!( - "Signature must be exactly 96 bytes, got {}", - bytes.len() - ))); - } - - // safety: we just checked for the length so the unwraps are fine - #[allow(clippy::expect_used)] - let sig1_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48"); - #[allow(clippy::expect_used)] - let sig2_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48"); - - let sig1 = try_deserialize_g1_projective( - sig1_bytes, - CoconutError::Deserialization("Failed to deserialize compressed sig1".to_string()), - )?; - - let sig2 = try_deserialize_g1_projective( - sig2_bytes, - CoconutError::Deserialization("Failed to deserialize compressed sig2".to_string()), - )?; - - Ok(Signature(sig1, sig2)) - } -} - -impl Signature { - pub(crate) fn sig1(&self) -> &G1Projective { - &self.0 - } - - pub(crate) fn sig2(&self) -> &G1Projective { - &self.1 - } - - pub fn randomise_simple(&self, params: &Parameters) -> Signature { - let r = params.random_scalar(); - Signature(self.0 * r, self.1 * r) - } - - pub fn randomise(&self, params: &Parameters) -> (Signature, Scalar) { - let r = params.random_scalar(); - let r_prime = params.random_scalar(); - let h_prime = self.0 * r_prime; - let s_prime = (self.1 * r_prime) + (h_prime * r); - (Signature(h_prime, s_prime), r) - } - - pub fn to_bytes(self) -> [u8; 96] { - let mut bytes = [0u8; 96]; - bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed()); - bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed()); - bytes - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - Signature::try_from(bytes) - } - - pub fn verify( - &self, - params: &Parameters, - partial_verification_key: &VerificationKey, - private_attributes: &[&Attribute], - public_attributes: &[&Attribute], - commitment_hash: &G1Projective, - ) -> Result<()> { - // Verify the commitment hash - if bool::from(self.0.is_identity()) { - return Err(CoconutError::Verification( - "Commitment hash should not be an identity point".to_string(), - )); - } - if !(commitment_hash == &self.0) { - return Err(CoconutError::Verification( - "Verification of commitment hash from signature failed".to_string(), - )); - } - - let alpha = partial_verification_key.alpha; - - let signed_attributes = private_attributes - .iter() - .chain(public_attributes.iter()) - .zip(partial_verification_key.beta_g2.iter()) - .map(|(&attr, beta_i)| beta_i * attr) - .sum::(); - - // Verify the signature share - if !check_bilinear_pairing( - &self.0.to_affine(), - &G2Prepared::from((alpha + signed_attributes).to_affine()), - &self.1.to_affine(), - params.prepared_miller_g2(), - ) { - return Err(CoconutError::Unblind( - "Verification of signature share failed".to_string(), - )); - } - - Ok(()) - } -} - -impl Bytable for Signature { - fn to_byte_vec(&self) -> Vec { - self.to_bytes().to_vec() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - Signature::from_bytes(slice) - } -} - -impl Base58 for Signature {} - -#[derive(Debug, PartialEq, Eq)] -pub struct BlindedSignature(G1Projective, G1Projective); - -impl Bytable for BlindedSignature { - fn to_byte_vec(&self) -> Vec { - self.to_bytes().to_vec() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - Self::from_bytes(slice) - } -} - -impl Base58 for BlindedSignature {} - -impl TryFrom<&[u8]> for BlindedSignature { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - if bytes.len() != 96 { - return Err(CoconutError::Deserialization(format!( - "BlindedSignature must be exactly 96 bytes, got {}", - bytes.len() - ))); - } - - // safety: we just checked for the length so the unwraps are fine - #[allow(clippy::expect_used)] - let h_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48"); - #[allow(clippy::expect_used)] - let sig_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48"); - - let h = try_deserialize_g1_projective( - h_bytes, - CoconutError::Deserialization("Failed to deserialize compressed h".to_string()), - )?; - let sig = try_deserialize_g1_projective( - sig_bytes, - CoconutError::Deserialization("Failed to deserialize compressed sig".to_string()), - )?; - - Ok(BlindedSignature(h, sig)) - } -} - -impl BlindedSignature { - pub fn unblind( - &self, - partial_verification_key: &VerificationKey, - pedersen_commitments_openings: &[Scalar], - ) -> Signature { - // parse the signature - let h = &self.0; - let c = &self.1; - let blinding_removers = partial_verification_key - .beta_g1 - .iter() - .zip(pedersen_commitments_openings.iter()) - .map(|(beta, opening)| beta * opening) - .sum::(); - - let unblinded_c = c - blinding_removers; - - Signature(*h, unblinded_c) - } - - pub fn unblind_and_verify( - &self, - params: &Parameters, - partial_verification_key: &VerificationKey, - private_attributes: &[&Attribute], - public_attributes: &[&Attribute], - commitment_hash: &G1Projective, - pedersen_commitments_openings: &[Scalar], - ) -> Result { - let unblinded = self.unblind(partial_verification_key, pedersen_commitments_openings); - unblinded.verify( - params, - partial_verification_key, - private_attributes, - public_attributes, - commitment_hash, - )?; - Ok(unblinded) - } - - pub fn to_bytes(&self) -> [u8; 96] { - let mut bytes = [0u8; 96]; - bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed()); - bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed()); - bytes - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - BlindedSignature::try_from(bytes) - } -} - -// perhaps this should take signature by reference? we'll see how it goes -#[derive(Clone, Copy)] -pub struct SignatureShare { - signature: Signature, - index: SignerIndex, -} - -impl From<(Signature, SignerIndex)> for SignatureShare { - fn from(value: (Signature, SignerIndex)) -> Self { - SignatureShare { - signature: value.0, - index: value.1, - } - } -} - -impl SignatureShare { - pub fn new(signature: Signature, index: SignerIndex) -> Self { - SignatureShare { signature, index } - } - - pub fn signature(&self) -> &Signature { - &self.signature - } - - pub fn index(&self) -> SignerIndex { - self.index - } - - // pub fn aggregate(shares: &[Self]) -> Result { - // aggregate_signature_shares(shares) - // } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::hash_to_scalar; - use crate::scheme::aggregation::{ - aggregate_signatures_and_verify, aggregate_verification_keys, - }; - use crate::scheme::issuance::{blind_sign, compute_hash, prepare_blind_sign, sign}; - use crate::scheme::keygen::{keygen, ttp_keygen}; - use crate::scheme::verification::{prove_bandwidth_credential, verify, verify_credential}; - use crate::tests::helpers::random_scalars_refs; - - #[test] - fn unblind_returns_error_if_integrity_check_on_commitment_hash_fails() { - let params = Parameters::new(2).unwrap(); - random_scalars_refs!(private_attributes, params, 2); - - let (_commitments_openings, lambda) = - prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap(); - - let keypair1 = keygen(¶ms); - - let sig1 = blind_sign(¶ms, keypair1.secret_key(), &lambda, &[]).unwrap(); - - let wrong_commitment_opening = params.random_scalar(); - let wrong_commitment = params.gen1() * wrong_commitment_opening; - let fake_commitment_hash = compute_hash(wrong_commitment, &[]); - let wrong_commitments_openings = params.n_random_scalars(private_attributes.len()); - - assert!(sig1 - .unblind_and_verify( - ¶ms, - keypair1.verification_key(), - &private_attributes, - &[], - &fake_commitment_hash, - &wrong_commitments_openings, - ) - .is_err()); - } - - #[test] - fn unblind_returns_error_if_signature_verification_fails() { - let params = Parameters::new(2).unwrap(); - let p = [hash_to_scalar("Attribute1"), hash_to_scalar("Attribute2")]; - let private_attributes = vec![&p[0], &p[1]]; - - let p2 = [hash_to_scalar("Attribute3"), hash_to_scalar("Attribute4")]; - let private_attributes2 = vec![&p2[0], &p2[1]]; - - let (commitments_openings, lambda) = - prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap(); - - let keypair1 = keygen(¶ms); - - let sig1 = blind_sign(¶ms, keypair1.secret_key(), &lambda, &[]).unwrap(); - - assert!(sig1 - .unblind_and_verify( - ¶ms, - keypair1.verification_key(), - &private_attributes2, - &[], - &lambda.get_commitment_hash(), - &commitments_openings, - ) - .is_err()); - } - - #[test] - fn verification_on_two_private_attributes() { - let params = Parameters::new(2).unwrap(); - let serial_number = params.random_scalar(); - let binding_number = params.random_scalar(); - let private_attributes = vec![&serial_number, &binding_number]; - - let keypair1 = keygen(¶ms); - let keypair2 = keygen(¶ms); - - let (commitments_openings, lambda) = - prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap(); - - let sig1 = blind_sign(¶ms, keypair1.secret_key(), &lambda, &[]) - .unwrap() - .unblind_and_verify( - ¶ms, - keypair1.verification_key(), - &private_attributes, - &[], - &lambda.get_commitment_hash(), - &commitments_openings, - ) - .unwrap(); - - let sig2 = blind_sign(¶ms, keypair2.secret_key(), &lambda, &[]) - .unwrap() - .unblind_and_verify( - ¶ms, - keypair2.verification_key(), - &private_attributes, - &[], - &lambda.get_commitment_hash(), - &commitments_openings, - ) - .unwrap(); - - let theta1 = prove_bandwidth_credential( - ¶ms, - keypair1.verification_key(), - &sig1, - &serial_number, - &binding_number, - ) - .unwrap(); - - let theta2 = prove_bandwidth_credential( - ¶ms, - keypair2.verification_key(), - &sig2, - &serial_number, - &binding_number, - ) - .unwrap(); - - assert!(verify_credential( - ¶ms, - keypair1.verification_key(), - &theta1, - &[], - )); - - assert!(verify_credential( - ¶ms, - keypair2.verification_key(), - &theta2, - &[], - )); - - assert!(!verify_credential( - ¶ms, - keypair1.verification_key(), - &theta2, - &[], - )); - } - - #[test] - fn verification_on_two_public_attributes() { - let params = Parameters::new(2).unwrap(); - random_scalars_refs!(attributes, params, 2); - - let keypair1 = keygen(¶ms); - let keypair2 = keygen(¶ms); - let sig1 = sign(keypair1.secret_key(), &attributes).unwrap(); - let sig2 = sign(keypair2.secret_key(), &attributes).unwrap(); - - assert!(verify( - ¶ms, - keypair1.verification_key(), - &attributes, - &sig1, - )); - - assert!(!verify( - ¶ms, - keypair2.verification_key(), - &attributes, - &sig1, - )); - - assert!(!verify( - ¶ms, - keypair1.verification_key(), - &attributes, - &sig2, - )); - } - - #[test] - fn verification_on_two_public_and_two_private_attributes() { - let params = Parameters::new(4).unwrap(); - random_scalars_refs!(public_attributes, params, 2); - - let serial_number = params.random_scalar(); - let binding_number = params.random_scalar(); - let private_attributes = vec![&serial_number, &binding_number]; - - let keypair1 = keygen(¶ms); - let keypair2 = keygen(¶ms); - - let (commitments_openings, lambda) = - prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); - - let sig1 = blind_sign(¶ms, keypair1.secret_key(), &lambda, &public_attributes) - .unwrap() - .unblind_and_verify( - ¶ms, - keypair1.verification_key(), - &private_attributes, - &public_attributes, - &lambda.get_commitment_hash(), - &commitments_openings, - ) - .unwrap(); - - let sig2 = blind_sign(¶ms, keypair2.secret_key(), &lambda, &public_attributes) - .unwrap() - .unblind_and_verify( - ¶ms, - keypair2.verification_key(), - &private_attributes, - &public_attributes, - &lambda.get_commitment_hash(), - &commitments_openings, - ) - .unwrap(); - - let theta1 = prove_bandwidth_credential( - ¶ms, - keypair1.verification_key(), - &sig1, - &serial_number, - &binding_number, - ) - .unwrap(); - - let theta2 = prove_bandwidth_credential( - ¶ms, - keypair2.verification_key(), - &sig2, - &serial_number, - &binding_number, - ) - .unwrap(); - - assert!(verify_credential( - ¶ms, - keypair1.verification_key(), - &theta1, - &public_attributes, - )); - - assert!(verify_credential( - ¶ms, - keypair2.verification_key(), - &theta2, - &public_attributes, - )); - - assert!(!verify_credential( - ¶ms, - keypair1.verification_key(), - &theta2, - &public_attributes, - )); - } - - #[test] - fn verification_on_two_public_and_two_private_attributes_from_two_signers() { - let params = Parameters::new(4).unwrap(); - random_scalars_refs!(public_attributes, params, 2); - - let serial_number = params.random_scalar(); - let binding_number = params.random_scalar(); - let private_attributes = vec![&serial_number, &binding_number]; - - let keypairs = ttp_keygen(¶ms, 2, 3).unwrap(); - - let (commitments_openings, lambda) = - prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); - - let sigs = keypairs - .iter() - .map(|keypair| { - blind_sign(¶ms, keypair.secret_key(), &lambda, &public_attributes) - .unwrap() - .unblind_and_verify( - ¶ms, - keypair.verification_key(), - &private_attributes, - &public_attributes, - &lambda.get_commitment_hash(), - &commitments_openings, - ) - .unwrap() - }) - .collect::>(); - - let vks = keypairs - .into_iter() - .map(|keypair| keypair.verification_key().clone()) - .collect::>(); - - let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len()); - attributes.extend_from_slice(&private_attributes); - attributes.extend_from_slice(&public_attributes); - - let aggr_vk = aggregate_verification_keys(&vks[..2], Some(&[1, 2])).unwrap(); - let aggr_sig = aggregate_signatures_and_verify( - ¶ms, - &aggr_vk, - &attributes, - &sigs[..2], - Some(&[1, 2]), - ) - .unwrap(); - - let theta = prove_bandwidth_credential( - ¶ms, - &aggr_vk, - &aggr_sig, - &serial_number, - &binding_number, - ) - .unwrap(); - - assert!(verify_credential( - ¶ms, - &aggr_vk, - &theta, - &public_attributes, - )); - - // taking different subset of keys and credentials - let aggr_vk = aggregate_verification_keys(&vks[1..], Some(&[2, 3])).unwrap(); - let aggr_sig = aggregate_signatures_and_verify( - ¶ms, - &aggr_vk, - &attributes, - &sigs[1..], - Some(&[2, 3]), - ) - .unwrap(); - - let theta = prove_bandwidth_credential( - ¶ms, - &aggr_vk, - &aggr_sig, - &serial_number, - &binding_number, - ) - .unwrap(); - - assert!(verify_credential( - ¶ms, - &aggr_vk, - &theta, - &public_attributes, - )); - } - - #[test] - fn signature_bytes_roundtrip() { - let params = Parameters::default(); - let r = params.random_scalar(); - let s = params.random_scalar(); - let signature = Signature(params.gen1() * r, params.gen1() * s); - let bytes = signature.to_bytes(); - - // also make sure it is equivalent to the internal g1 compressed bytes concatenated - let expected_bytes = [ - signature.0.to_affine().to_compressed(), - signature.1.to_affine().to_compressed(), - ] - .concat(); - assert_eq!(expected_bytes, bytes); - assert_eq!(signature, Signature::try_from(&bytes[..]).unwrap()) - } - - #[test] - fn blinded_signature_bytes_roundtrip() { - let params = Parameters::default(); - let r = params.random_scalar(); - let s = params.random_scalar(); - let blinded_sig = BlindedSignature(params.gen1() * r, params.gen1() * s); - let bytes = blinded_sig.to_bytes(); - - // also make sure it is equivalent to the internal g1 compressed bytes concatenated - let expected_bytes = [ - blinded_sig.0.to_affine().to_compressed(), - blinded_sig.1.to_affine().to_compressed(), - ] - .concat(); - assert_eq!(expected_bytes, bytes); - assert_eq!(blinded_sig, BlindedSignature::try_from(&bytes[..]).unwrap()) - } -} diff --git a/common/nymcoconut/src/scheme/setup.rs b/common/nymcoconut/src/scheme/setup.rs deleted file mode 100644 index 65c994ec8f..0000000000 --- a/common/nymcoconut/src/scheme/setup.rs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use bls12_381::{G1Affine, G2Affine, G2Prepared, Scalar}; -use ff::Field; -use group::Curve; -use rand::thread_rng; - -use crate::error::{CoconutError, Result}; -use crate::utils::hash_g1; - -/// System-wide parameters used for the protocol -#[derive(Clone)] -pub struct Parameters { - /// Generator of the G1 group - g1: G1Affine, - - /// Additional generators of the G1 group - hs: Vec, - - /// Generator of the G2 group - g2: G2Affine, - - /// Precomputed G2 generator used for the miller loop - _g2_prepared_miller: G2Prepared, -} - -impl Parameters { - pub fn new(num_attributes: u32) -> Result { - if num_attributes == 0 { - return Err(CoconutError::Setup( - "Tried to setup the scheme for 0 attributes".to_string(), - )); - } - - let hs = (1..=num_attributes) - .map(|i| hash_g1(format!("h{i}")).to_affine()) - .collect(); - - Ok(Parameters { - g1: G1Affine::generator(), - hs, - g2: G2Affine::generator(), - _g2_prepared_miller: G2Prepared::from(G2Affine::generator()), - }) - } - - pub fn gen1(&self) -> &G1Affine { - &self.g1 - } - - pub fn gen2(&self) -> &G2Affine { - &self.g2 - } - - pub(crate) fn prepared_miller_g2(&self) -> &G2Prepared { - &self._g2_prepared_miller - } - - pub fn gen_hs(&self) -> &[G1Affine] { - &self.hs - } - - pub fn random_scalar(&self) -> Scalar { - // lazily-initialized thread-local random number generator, seeded by the system - let mut rng = thread_rng(); - Scalar::random(&mut rng) - } - - pub fn n_random_scalars(&self, n: usize) -> Vec { - (0..n).map(|_| self.random_scalar()).collect() - } -} - -pub fn setup(num_attributes: u32) -> Result { - Parameters::new(num_attributes) -} - -// for ease of use in tests requiring params -// TODO: not sure if this will have to go away when tests require some specific number of generators -#[cfg(test)] -impl Default for Parameters { - fn default() -> Self { - Parameters { - g1: G1Affine::generator(), - hs: Vec::new(), - g2: G2Affine::generator(), - _g2_prepared_miller: G2Prepared::from(G2Affine::generator()), - } - } -} diff --git a/common/nymcoconut/src/scheme/verification.rs b/common/nymcoconut/src/scheme/verification.rs deleted file mode 100644 index 149c60d3e3..0000000000 --- a/common/nymcoconut/src/scheme/verification.rs +++ /dev/null @@ -1,432 +0,0 @@ -// Copyright 2021-2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::error::{CoconutError, Result}; -use crate::proofs::ProofKappaZeta; -use crate::scheme::setup::Parameters; -use crate::scheme::Signature; -use crate::scheme::VerificationKey; -use crate::traits::{Base58, Bytable}; -use crate::utils::try_deserialize_g2_projective; -use crate::Attribute; -use bls12_381::{multi_miller_loop, G1Affine, G2Prepared, G2Projective, Scalar}; -use core::ops::Neg; -use group::{Curve, Group}; - -pub use crate::scheme::double_use::BlindedSerialNumber; - -// TODO NAMING: this whole thing -// Theta -#[derive(Debug, PartialEq, Eq)] -pub struct VerifyCredentialRequest { - // blinded_message (kappa) - pub blinded_message: G2Projective, - // blinded serial number (zeta) - pub blinded_serial_number: BlindedSerialNumber, - // sigma - pub credential: Signature, - // pi_v - pub pi_v: ProofKappaZeta, -} - -impl TryFrom<&[u8]> for VerifyCredentialRequest { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - if bytes.len() < 288 { - return Err( - CoconutError::Deserialization( - format!("Tried to deserialize theta with insufficient number of bytes, expected >= 288, got {}", bytes.len()), - )); - } - - // safety: we just checked for the length so the unwraps are fine - #[allow(clippy::unwrap_used)] - let blinded_message_bytes = bytes[..96].try_into().unwrap(); - let blinded_message = try_deserialize_g2_projective( - &blinded_message_bytes, - CoconutError::Deserialization( - "failed to deserialize the blinded message (kappa)".to_string(), - ), - )?; - - let blinded_serial_number_bytes = &bytes[96..192]; - let blinded_serial_number = - BlindedSerialNumber::try_from_byte_slice(blinded_serial_number_bytes)?; - - let credential = Signature::try_from(&bytes[192..288])?; - - let pi_v = ProofKappaZeta::from_bytes(&bytes[288..])?; - - Ok(VerifyCredentialRequest { - blinded_message, - blinded_serial_number, - credential, - pi_v, - }) - } -} - -impl VerifyCredentialRequest { - fn verify_proof(&self, params: &Parameters, verification_key: &VerificationKey) -> bool { - self.pi_v.verify( - params, - verification_key, - &self.blinded_message, - &self.blinded_serial_number, - ) - } - - pub fn has_blinded_serial_number(&self, blinded_serial_number_bs58: &str) -> Result { - let blinded_serial_number = BlindedSerialNumber::try_from_bs58(blinded_serial_number_bs58)?; - let ret = self.blinded_serial_number.eq(&blinded_serial_number); - Ok(ret) - } - - // blinded message (kappa) || blinded serial number (zeta) || credential || pi_v - pub fn to_bytes(&self) -> Vec { - let blinded_message_bytes = self.blinded_message.to_affine().to_compressed(); - let blinded_serial_number_bytes = self.blinded_serial_number.to_affine().to_compressed(); - let credential_bytes = self.credential.to_bytes(); - let proof_bytes = self.pi_v.to_bytes(); - - let mut bytes = Vec::with_capacity(288 + proof_bytes.len()); - bytes.extend_from_slice(&blinded_message_bytes); - bytes.extend_from_slice(&blinded_serial_number_bytes); - bytes.extend_from_slice(&credential_bytes); - bytes.extend_from_slice(&proof_bytes); - - bytes - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - VerifyCredentialRequest::try_from(bytes) - } - - pub fn blinded_serial_number(&self) -> BlindedSerialNumber { - self.blinded_serial_number - } - - pub fn blinded_serial_number_bs58(&self) -> String { - self.blinded_serial_number.to_bs58() - } -} - -impl Bytable for VerifyCredentialRequest { - fn to_byte_vec(&self) -> Vec { - self.to_bytes() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - VerifyCredentialRequest::try_from(slice) - } -} - -impl Base58 for VerifyCredentialRequest {} - -pub fn compute_kappa( - params: &Parameters, - verification_key: &VerificationKey, - private_attributes: &[&Attribute], - blinding_factor: Scalar, -) -> G2Projective { - params.gen2() * blinding_factor - + verification_key.alpha - + private_attributes - .iter() - .zip(verification_key.beta_g2.iter()) - .map(|(&priv_attr, beta_i)| beta_i * priv_attr) - .sum::() -} - -pub fn compute_zeta(params: &Parameters, serial_number: &Attribute) -> G2Projective { - params.gen2() * serial_number -} - -pub fn prove_bandwidth_credential( - params: &Parameters, - verification_key: &VerificationKey, - signature: &Signature, - serial_number: &Attribute, - binding_number: &Attribute, -) -> Result { - if verification_key.beta_g2.len() < 2 { - return Err( - CoconutError::Verification( - format!("Tried to prove a credential for higher than supported by the provided verification key number of attributes (max: {}, requested: 2)", - verification_key.beta_g2.len() - ))); - } - - // Randomize the signature - let (signature_prime, sign_blinding_factor) = signature.randomise(params); - - // blinded_message : kappa in the paper. - // Value kappa is needed since we want to show a signature sigma'. - // In order to verify sigma' we need both the verification key vk and the message m. - // However, we do not want to reveal m to whomever we are showing the signature. - // Thus, we need kappa which allows us to verify sigma'. In particular, - // kappa is computed on m as input, but thanks to the use or random value r, - // it does not reveal any information about m. - let private_attributes = [serial_number, binding_number]; - let blinded_message = compute_kappa( - params, - verification_key, - &private_attributes, - sign_blinding_factor, - ); - - // zeta is a commitment to the serial number (i.e., a public value associated with the serial number) - let blinded_serial_number = compute_zeta(params, serial_number); - - let pi_v = ProofKappaZeta::construct( - params, - verification_key, - serial_number, - binding_number, - &sign_blinding_factor, - &blinded_message, - &blinded_serial_number, - ); - - Ok(VerifyCredentialRequest { - blinded_message, - blinded_serial_number: blinded_serial_number.into(), - credential: signature_prime, - pi_v, - }) -} - -/// Checks whether e(P, Q) * e(-R, S) == id -pub fn check_bilinear_pairing(p: &G1Affine, q: &G2Prepared, r: &G1Affine, s: &G2Prepared) -> bool { - // checking e(P, Q) * e(-R, S) == id - // is equivalent to checking e(P, Q) == e(R, S) - // but requires only a single final exponentiation rather than two of them - // and therefore, as seen via benchmarks.rs, is almost 50% faster - // (1.47ms vs 2.45ms, tested on R9 5900X) - - let multi_miller = multi_miller_loop(&[(p, q), (&r.neg(), s)]); - multi_miller.final_exponentiation().is_identity().into() -} - -pub fn check_vk_pairing( - params: &Parameters, - dkg_values: &[G2Projective], - vk: &VerificationKey, -) -> bool { - let values_len = dkg_values.len(); - if values_len == 0 || values_len - 1 != vk.beta_g1.len() || values_len - 1 != vk.beta_g2.len() { - return false; - } - - // safety: we made an explicit check for if the length of the slice is 0, thus unwrap here is fine - #[allow(clippy::unwrap_used)] - if &vk.alpha != *dkg_values.first().as_ref().unwrap() { - return false; - } - let dkg_betas = &dkg_values[1..]; - if dkg_betas - .iter() - .zip(vk.beta_g2.iter()) - .any(|(dkg_beta, vk_beta)| dkg_beta != vk_beta) - { - return false; - } - if vk.beta_g1.iter().zip(vk.beta_g2.iter()).any(|(g1, g2)| { - !check_bilinear_pairing( - params.gen1(), - &G2Prepared::from(g2.to_affine()), - &g1.to_affine(), - params.prepared_miller_g2(), - ) - }) { - return false; - } - - true -} - -pub fn verify_credential( - params: &Parameters, - verification_key: &VerificationKey, - theta: &VerifyCredentialRequest, - public_attributes: &[&Attribute], -) -> bool { - if public_attributes.len() + theta.pi_v.private_attributes_len() - > verification_key.beta_g2.len() - { - return false; - } - - if !theta.verify_proof(params, verification_key) { - return false; - } - - let kappa = if public_attributes.is_empty() { - theta.blinded_message - } else { - let signed_public_attributes = public_attributes - .iter() - .zip( - verification_key - .beta_g2 - .iter() - .skip(theta.pi_v.private_attributes_len()), - ) - .map(|(&pub_attr, beta_i)| beta_i * pub_attr) - .sum::(); - - theta.blinded_message + signed_public_attributes - }; - - check_bilinear_pairing( - &theta.credential.0.to_affine(), - &G2Prepared::from(kappa.to_affine()), - &(theta.credential.1).to_affine(), - params.prepared_miller_g2(), - ) && !bool::from(theta.credential.0.is_identity()) -} - -// Used in tests only -pub fn verify( - params: &Parameters, - verification_key: &VerificationKey, - public_attributes: &[&Attribute], - sig: &Signature, -) -> bool { - let kappa = (verification_key.alpha - + public_attributes - .iter() - .zip(verification_key.beta_g2.iter()) - .map(|(&m_i, b_i)| b_i * m_i) - .sum::()) - .to_affine(); - - check_bilinear_pairing( - &sig.0.to_affine(), - &G2Prepared::from(kappa), - &sig.1.to_affine(), - params.prepared_miller_g2(), - ) && !bool::from(sig.0.is_identity()) -} - -#[cfg(test)] -mod tests { - use crate::scheme::issuance::sign; - use crate::scheme::keygen::keygen; - use crate::scheme::setup::setup; - - use super::*; - - #[test] - fn vk_pairing() { - let params = setup(2).unwrap(); - let keypair = keygen(¶ms); - let vk = keypair.verification_key(); - - let mut dkg_values = vec![vk.alpha]; - dkg_values.append(&mut vk.beta_g2.clone()); - assert!(check_vk_pairing(¶ms, &dkg_values, vk)); - } - - #[test] - fn theta_bytes_roundtrip() { - let params = setup(2).unwrap(); - - let keypair = keygen(¶ms); - let r = params.random_scalar(); - let s = params.random_scalar(); - - let signature = Signature(params.gen1() * r, params.gen1() * s); - let serial_number = params.random_scalar(); - let binding_number = params.random_scalar(); - - let theta = prove_bandwidth_credential( - ¶ms, - keypair.verification_key(), - &signature, - &serial_number, - &binding_number, - ) - .unwrap(); - - let bytes = theta.to_bytes(); - assert_eq!( - VerifyCredentialRequest::try_from(bytes.as_slice()).unwrap(), - theta - ); - } - - #[test] - fn reject_forged_signature_via_linear_combination() { - // This test checks if the protocol correctly rejects forged signatures created - // by linear combinations of valid signatures. The verification for forged - // signatures should fail. - let params = Parameters::new(4).unwrap(); - - let scalar_2 = Scalar::one() + Scalar::one(); - let scalar_2_inv = Scalar::invert(&scalar_2).unwrap(); - - //#1 - let a = params.random_scalar(); - let zero = Scalar::zero(); - let a_zero = vec![&a, &zero]; - let zero_a = vec![&zero, &a]; - - let validator_keypair = keygen(¶ms); - - //#2 - let sig_a_zero = sign(validator_keypair.secret_key(), &a_zero).unwrap(); - let sig_zero_a = sign(validator_keypair.secret_key(), &zero_a).unwrap(); - - assert!(verify( - ¶ms, - validator_keypair.verification_key(), - &a_zero, - &sig_a_zero - )); - assert!(verify( - ¶ms, - validator_keypair.verification_key(), - &zero_a, - &sig_zero_a - )); - - //#3 - let h0 = sig_a_zero.0; - // Removed unnecessary references - let h1 = scalar_2_inv * sig_a_zero.1 + scalar_2_inv * sig_zero_a.1; - let forged_signature = Signature(h0, h1); - let a_half = a * scalar_2_inv; - let new_plaintext = vec![&a_half, &a_half]; - - // The forged signature should not pass verification - assert!(!verify( - ¶ms, - validator_keypair.verification_key(), - &new_plaintext, - &forged_signature - )); - - //#4 - let scalar_3 = Scalar::one() + Scalar::one() + Scalar::one(); - let scalar_4 = Scalar::one() + Scalar::one() + Scalar::one() + Scalar::one(); - let scalar_4_inv = Scalar::invert(&scalar_4).unwrap(); - let scalar_3_over_4 = scalar_3 * scalar_4_inv; - - // Removed unnecessary references - let h1_2 = scalar_4_inv * sig_a_zero.1 + scalar_3_over_4 * sig_zero_a.1; - let forged_signature_2 = Signature(h0, h1_2); - let a_quarter = a * scalar_4_inv; - let a_3_over_4 = a * scalar_3_over_4; - let new_plaintext_2 = vec![&a_quarter, &a_3_over_4]; - - // The second forged signature should also not pass verification - assert!(!verify( - ¶ms, - validator_keypair.verification_key(), - &new_plaintext_2, - &forged_signature_2 - )); - } -} diff --git a/common/nymcoconut/src/tests/e2e.rs b/common/nymcoconut/src/tests/e2e.rs deleted file mode 100644 index b9cc397845..0000000000 --- a/common/nymcoconut/src/tests/e2e.rs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::random_scalars_refs; -use crate::tests::helpers::tests::generate_dkg_keys; -use crate::{ - aggregate_verification_keys, setup, tests::helpers::*, ttp_keygen, verify_credential, - CoconutError, VerificationKey, -}; - -#[test] -fn keygen() -> Result<(), CoconutError> { - let params = setup(5)?; - let node_indices = vec![15u64, 248, 33521]; - - random_scalars_refs!(public_attributes, params, 2); - - // generate_keys - let coconut_keypairs = ttp_keygen(¶ms, 2, 3)?; - - let verification_keys: Vec = coconut_keypairs - .iter() - .map(|keypair| keypair.verification_key().clone()) - .collect(); - - // aggregate verification keys - let verification_key = aggregate_verification_keys(&verification_keys, Some(&node_indices))?; - - // Generate cryptographic material to verify them - let theta = theta_from_keys_and_attributes( - ¶ms, - &coconut_keypairs, - &node_indices, - &public_attributes, - )?; - - // Verify credentials - assert!(verify_credential( - ¶ms, - &verification_key, - &theta, - &public_attributes, - )); - - Ok(()) -} - -#[test] -#[ignore] // expensive test -fn dkg() -> Result<(), CoconutError> { - let params = setup(5)?; - let node_indices = vec![15u64, 248, 33521]; - - random_scalars_refs!(public_attributes, params, 2); - - // generate_keys - let coconut_keypairs = generate_dkg_keys(5, &node_indices); - - let verification_keys: Vec = coconut_keypairs - .iter() - .map(|keypair| keypair.verification_key().clone()) - .collect(); - - // aggregate verification keys - let verification_key = aggregate_verification_keys(&verification_keys, Some(&node_indices))?; - - // Generate cryptographic material to verify them - let theta = theta_from_keys_and_attributes( - ¶ms, - &coconut_keypairs, - &node_indices, - &public_attributes, - )?; - - // Verify credentials - assert!(verify_credential( - ¶ms, - &verification_key, - &theta, - &public_attributes, - )); - - Ok(()) -} diff --git a/common/nymcoconut/src/tests/helpers.rs b/common/nymcoconut/src/tests/helpers.rs deleted file mode 100644 index f01081bbf1..0000000000 --- a/common/nymcoconut/src/tests/helpers.rs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::*; -use itertools::izip; -use std::fmt::Debug; - -// unwraps are fine in the test code -#[allow(clippy::unwrap_used)] -pub fn theta_from_keys_and_attributes( - params: &Parameters, - coconut_keypairs: &Vec, - indices: &[scheme::SignerIndex], - public_attributes: &[&PublicAttribute], -) -> Result { - let serial_number = params.random_scalar(); - let binding_number = params.random_scalar(); - let private_attributes = vec![&serial_number, &binding_number]; - - // generate commitment - let (commitments_openings, blind_sign_request) = - prepare_blind_sign(params, &private_attributes, public_attributes)?; - - let verification_keys: Vec = coconut_keypairs - .iter() - .map(|keypair| keypair.verification_key().clone()) - .collect(); - - // aggregate verification keys - let verification_key = aggregate_verification_keys(&verification_keys, Some(indices))?; - - // generate blinded signatures - let mut blinded_signatures = Vec::new(); - - for keypair in coconut_keypairs { - let blinded_signature = blind_sign( - params, - keypair.secret_key(), - &blind_sign_request, - public_attributes, - )?; - blinded_signatures.push(blinded_signature) - } - - // Unblind - let unblinded_signatures: Vec<(scheme::SignerIndex, Signature)> = izip!( - indices.iter(), - blinded_signatures.iter(), - verification_keys.iter() - ) - .map(|(idx, s, vk)| { - ( - *idx, - s.unblind_and_verify( - params, - vk, - &private_attributes, - public_attributes, - &blind_sign_request.get_commitment_hash(), - &commitments_openings, - ) - .unwrap(), - ) - }) - .collect(); - - // Aggregate signatures - let signature_shares: Vec = unblinded_signatures - .iter() - .map(|(idx, signature)| SignatureShare::new(*signature, *idx)) - .collect(); - - let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len()); - attributes.extend_from_slice(&private_attributes); - attributes.extend_from_slice(public_attributes); - - // Randomize credentials and generate any cryptographic material to verify them - let signature = aggregate_signature_shares_and_verify( - params, - &verification_key, - &attributes, - &signature_shares, - )?; - - // Generate cryptographic material to verify them - let theta = prove_bandwidth_credential( - params, - &verification_key, - &signature, - &serial_number, - &binding_number, - )?; - - Ok(theta) -} - -// unwraps are fine in the test code -#[allow(clippy::unwrap_used)] -pub fn transpose_matrix(matrix: Vec>) -> Vec> { - if matrix.is_empty() { - return vec![]; - } - let len = matrix[0].len(); - let mut iters: Vec<_> = matrix.into_iter().map(|d| d.into_iter()).collect(); - (0..len) - .map(|_| { - iters - .iter_mut() - .map(|it| it.next().unwrap()) - .collect::>() - }) - .collect::>() -} - -#[macro_export] -macro_rules! random_scalars_refs { - ( $x: ident, $params: expr, $n: expr ) => { - let _vec = $params.n_random_scalars($n); - #[allow(clippy::map_identity)] - let $x = _vec.iter().collect::>(); - }; -} - -pub use random_scalars_refs; - -#[cfg(test)] -pub mod tests { - use super::*; - use bls12_381::Scalar; - use nym_dkg::{bte::decrypt_share, combine_shares, Dealing, NodeIndex}; - use rand_chacha::rand_core::SeedableRng; - - pub fn generate_dkg_secrets(node_indices: &[NodeIndex]) -> Vec { - let dummy_seed = [42u8; 32]; - let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed); - let params = nym_dkg::bte::setup(); - - // the simplest possible case - let threshold = 2; - - let mut receivers = std::collections::BTreeMap::new(); - let mut full_keys = Vec::new(); - for index in node_indices { - let (dk, pk) = nym_dkg::bte::keygen(¶ms, &mut rng); - receivers.insert(*index, *pk.public_key()); - full_keys.push((dk, pk)) - } - let dealings = node_indices - .iter() - .map(|&dealer_index| { - Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 - }) - .collect::>(); - let mut derived_secrets = Vec::new(); - for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { - let shares = dealings - .iter() - .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) - .collect(); - - let recovered_secret = - combine_shares(shares, &receivers.keys().copied().collect::>()).unwrap(); - - derived_secrets.push(recovered_secret) - } - derived_secrets - } - pub fn generate_dkg_keys(num_attributes: u32, node_indices: &[NodeIndex]) -> Vec { - let params = Parameters::new(num_attributes).unwrap(); - let mut all_secrets = vec![]; - for _ in 0..num_attributes { - let secrets = generate_dkg_secrets(node_indices); - all_secrets.push(secrets); - } - let signers = transpose_matrix(all_secrets); - signers - .into_iter() - .map(|mut secrets| { - let x = secrets.pop().unwrap(); - let sk = SecretKey::create_from_raw(x, secrets); - let vk = sk.verification_key(¶ms); - KeyPair::from_keys(sk, vk) - }) - .collect() - } -} diff --git a/common/nymcoconut/src/tests/mod.rs b/common/nymcoconut/src/tests/mod.rs deleted file mode 100644 index c13b7a3b20..0000000000 --- a/common/nymcoconut/src/tests/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -#[cfg(test)] -mod e2e; -pub mod helpers; diff --git a/common/nymcoconut/src/traits.rs b/common/nymcoconut/src/traits.rs deleted file mode 100644 index b2aa13944c..0000000000 --- a/common/nymcoconut/src/traits.rs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -#![warn(clippy::expect_used)] -#![warn(clippy::unwrap_used)] - -use crate::CoconutError; -use bls12_381::{G1Affine, G1Projective, Scalar}; -use group::GroupEncoding; - -pub trait Bytable -where - Self: Sized, -{ - fn to_byte_vec(&self) -> Vec; - - fn try_from_byte_slice(slice: &[u8]) -> Result; -} - -pub trait Base58 -where - Self: Bytable, -{ - fn try_from_bs58>(x: S) -> Result { - let bs58_decoded = &bs58::decode(x.as_ref()).into_vec()?; - Self::try_from_byte_slice(bs58_decoded) - } - fn to_bs58(&self) -> String { - bs58::encode(self.to_byte_vec()).into_string() - } -} - -impl Bytable for Scalar { - fn to_byte_vec(&self) -> Vec { - self.to_bytes().to_vec() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - let received = slice.len(); - let Ok(arr) = slice.try_into() else { - return Err(CoconutError::UnexpectedArrayLength { - typ: "Scalar".to_string(), - received, - expected: 32, - }); - }; - - let maybe_scalar = Scalar::from_bytes(arr); - if maybe_scalar.is_none().into() { - Err(CoconutError::ScalarDeserializationFailure) - } else { - // safety: this unwrap is fine as we've just checked the element is not none - #[allow(clippy::unwrap_used)] - Ok(maybe_scalar.unwrap()) - } - } -} - -impl Base58 for Scalar {} - -impl Bytable for G1Projective { - fn to_byte_vec(&self) -> Vec { - self.to_bytes().as_ref().to_vec() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - let received = slice.len(); - let arr: Result<[u8; 48], _> = slice.try_into(); - let Ok(bytes) = arr else { - return Err(CoconutError::UnexpectedArrayLength { - typ: "G1Projective".to_string(), - received, - expected: 48, - }); - }; - - let maybe_g1 = G1Affine::from_compressed(&bytes); - if maybe_g1.is_none().into() { - Err(CoconutError::G1ProjectiveDeserializationFailure) - } else { - // safety: this unwrap is fine as we've just checked the element is not none - #[allow(clippy::unwrap_used)] - Ok(maybe_g1.unwrap().into()) - } - } -} - -impl Base58 for G1Projective {} diff --git a/common/nymcoconut/src/utils.rs b/common/nymcoconut/src/utils.rs deleted file mode 100644 index d12707408c..0000000000 --- a/common/nymcoconut/src/utils.rs +++ /dev/null @@ -1,382 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use core::iter::Sum; -use core::ops::Mul; - -use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; -use bls12_381::{G1Affine, G1Projective, G2Affine, G2Projective, Scalar}; -use ff::Field; - -use crate::error::{CoconutError, Result}; -use crate::scheme::setup::Parameters; -use crate::scheme::SignerIndex; - -pub struct Polynomial { - coefficients: Vec, -} - -impl Polynomial { - // for polynomial of degree n, we generate n+1 values - // (for example for degree 1, like y = x + 2, we need [2,1]) - pub fn new_random(params: &Parameters, degree: u64) -> Self { - Polynomial { - coefficients: params.n_random_scalars((degree + 1) as usize), - } - } - - /// Evaluates the polynomial at point x. - pub fn evaluate(&self, x: &Scalar) -> Scalar { - if self.coefficients.is_empty() { - Scalar::zero() - // if x is zero then we can ignore most of the expensive computation and - // just return the last term of the polynomial - } else if x.is_zero().into() { - // we checked that coefficients are not empty so unwrap here is fine - #[allow(clippy::unwrap_used)] - *self.coefficients.first().unwrap() - } else { - self.coefficients - .iter() - .enumerate() - // coefficient[n] * x ^ n - .map(|(i, coefficient)| coefficient * x.pow(&[i as u64, 0, 0, 0])) - .sum() - } - } -} - -#[inline] -fn generate_lagrangian_coefficients_at_origin(points: &[u64]) -> Vec { - let x = Scalar::zero(); - - points - .iter() - .enumerate() - .map(|(i, point_i)| { - let mut numerator = Scalar::one(); - let mut denominator = Scalar::one(); - let xi = Scalar::from(*point_i); - - for (j, point_j) in points.iter().enumerate() { - if j != i { - let xj = Scalar::from(*point_j); - - // numerator = (x - xs[0]) * ... * (x - xs[j]), j != i - numerator *= x - xj; - - // denominator = (xs[i] - x[0]) * ... * (xs[i] - x[j]), j != i - denominator *= xi - xj; - } - } - // numerator / denominator - numerator * denominator.invert().unwrap() - }) - .collect() -} - -/// Performs a Lagrange interpolation at the origin for a polynomial defined by `points` and `values`. -/// It can be used for Scalars, G1 and G2 points. -pub(crate) fn perform_lagrangian_interpolation_at_origin( - points: &[SignerIndex], - values: &[T], -) -> Result -where - T: Sum, - for<'a> &'a T: Mul, -{ - if points.is_empty() || values.is_empty() { - return Err(CoconutError::Interpolation( - "Tried to perform lagrangian interpolation for an empty set of coordinates".to_string(), - )); - } - - if points.len() != values.len() { - return Err(CoconutError::Interpolation( - "Tried to perform lagrangian interpolation for an incomplete set of coordinates" - .to_string(), - )); - } - - let coefficients = generate_lagrangian_coefficients_at_origin(points); - - Ok(coefficients - .into_iter() - .zip(values.iter()) - .map(|(coeff, val)| val * coeff) - .sum()) -} - -// A temporary way of hashing particular message into G1. -// Implementation idea was taken from `threshold_crypto`: -// https://github.com/poanetwork/threshold_crypto/blob/7709462f2df487ada3bb3243060504b5881f2628/src/lib.rs#L691 -// Eventually it should get replaced by, most likely, the osswu map -// method once ideally it's implemented inside the pairing crate. - -// note: I have absolutely no idea what are the correct domains for those. I just used whatever -// was given in the test vectors of `Hashing to Elliptic Curves draft-irtf-cfrg-hash-to-curve-11` - -// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-J.9.1 -const G1_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_"; - -// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-K.1 -const SCALAR_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-expander"; - -pub fn hash_g1>(msg: M) -> G1Projective { - >>::hash_to_curve(msg, G1_HASH_DOMAIN) -} - -pub fn hash_to_scalar>(msg: M) -> Scalar { - let mut output = vec![Scalar::zero()]; - - Scalar::hash_to_field::>( - msg.as_ref(), - SCALAR_HASH_DOMAIN, - &mut output, - ); - output[0] -} - -pub fn try_deserialize_scalar_vec( - expected_len: u64, - bytes: &[u8], - err: CoconutError, -) -> Result> { - if bytes.len() != expected_len as usize * 32 { - return Err(err); - } - - let mut out = Vec::with_capacity(expected_len as usize); - for i in 0..expected_len as usize { - // we just checked we have exactly the amount of bytes we need and thus the unwrap is fine - #[allow(clippy::unwrap_used)] - let s_bytes = bytes[i * 32..(i + 1) * 32].try_into().unwrap(); - let s = match Into::>::into(Scalar::from_bytes(&s_bytes)) { - None => return Err(err), - Some(scalar) => scalar, - }; - out.push(s) - } - - Ok(out) -} - -pub fn try_deserialize_scalar(bytes: &[u8; 32], err: CoconutError) -> Result { - Into::>::into(Scalar::from_bytes(bytes)).ok_or(err) -} - -pub fn try_deserialize_g1_projective(bytes: &[u8; 48], err: CoconutError) -> Result { - Into::>::into(G1Affine::from_compressed(bytes)) - .ok_or(err) - .map(G1Projective::from) -} - -pub fn try_deserialize_g2_projective(bytes: &[u8; 96], err: CoconutError) -> Result { - Into::>::into(G2Affine::from_compressed(bytes)) - .ok_or(err) - .map(G2Projective::from) -} - -// use core::fmt; -// #[cfg(feature = "serde")] -// use serde::de::Visitor; -// #[cfg(feature = "serde")] -// use serde::{self, Deserialize, Deserializer, Serialize, Serializer}; -// -// // #[cfg(feature = "serde")] -// #[serde(remote = "Scalar")] -// pub(crate) struct ScalarDef(pub Scalar); -// -// // #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -// -// impl Serialize for ScalarDef { -// fn serialize(&self, serializer: S) -> core::result::Result -// where -// S: Serializer, -// { -// use serde::ser::SerializeTuple; -// let mut tup = serializer.serialize_tuple(32)?; -// for byte in self.0.to_bytes().iter() { -// tup.serialize_element(byte)?; -// } -// tup.end() -// } -// } -// -// impl<'de> Deserialize<'de> for ScalarDef { -// fn deserialize(deserializer: D) -> core::result::Result -// where -// D: Deserializer<'de>, -// { -// struct ScalarVisitor; -// -// impl<'de> Visitor<'de> for ScalarVisitor { -// type Value = ScalarDef; -// -// fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { -// formatter.write_str("a 32-byte canonical bls12_381 scalar") -// } -// -// fn visit_seq(self, mut seq: A) -> core::result::Result -// where -// A: serde::de::SeqAccess<'de>, -// { -// let mut bytes = [0u8; 32]; -// for i in 0..32 { -// bytes[i] = seq -// .next_element()? -// .ok_or_else(|| serde::de::Error::invalid_length(i, &"expected 32 bytes"))?; -// } -// -// let res = Scalar::from_bytes(&bytes); -// if res.is_some().into() { -// Ok(ScalarDef(res.unwrap())) -// } else { -// Err(serde::de::Error::custom( -// &"scalar was not canonically encoded", -// )) -// } -// } -// } -// -// deserializer.deserialize_tuple(32, ScalarVisitor) -// } -// } -// -// #[cfg(feature = "serde")] -// pub(crate) struct G1ProjectiveSerdeHelper(Scalar); -// -// #[cfg(feature = "serde")] -// pub(crate) struct G2ProjectiveSerdeHelper(Scalar); - -#[cfg(test)] -mod tests { - use rand::RngCore; - - use super::*; - - #[test] - fn polynomial_evaluation() { - // y = 42 (it should be 42 regardless of x) - let poly = Polynomial { - coefficients: vec![Scalar::from(42)], - }; - - assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(1))); - assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(0))); - assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(10))); - - // y = x + 10, at x = 2 (exp: 12) - let poly = Polynomial { - coefficients: vec![Scalar::from(10), Scalar::from(1)], - }; - - assert_eq!(Scalar::from(12), poly.evaluate(&Scalar::from(2))); - - // y = x^4 - 5x^2 + 2x - 3, at x = 3 (exp: 39) - let poly = Polynomial { - coefficients: vec![ - (-Scalar::from(3)), - Scalar::from(2), - (-Scalar::from(5)), - Scalar::zero(), - Scalar::from(1), - ], - }; - - assert_eq!(Scalar::from(39), poly.evaluate(&Scalar::from(3))); - - // empty polynomial - let poly = Polynomial { - coefficients: vec![], - }; - - // should always be 0 - assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(1))); - assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(0))); - assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(10))); - } - - #[test] - fn performing_lagrangian_scalar_interpolation_at_origin() { - // x^2 + 3 - // x, f(x): - // 1, 4, - // 2, 7, - // 3, 12, - let points = vec![1, 2, 3]; - let values = vec![Scalar::from(4), Scalar::from(7), Scalar::from(12)]; - - assert_eq!( - Scalar::from(3), - perform_lagrangian_interpolation_at_origin(&points, &values).unwrap() - ); - - // x^3 + 3x^2 - 5x + 11 - // x, f(x): - // 1, 10 - // 2, 21 - // 3, 50 - // 4, 103 - let points = vec![1, 2, 3, 4]; - let values = vec![ - Scalar::from(10), - Scalar::from(21), - Scalar::from(50), - Scalar::from(103), - ]; - - assert_eq!( - Scalar::from(11), - perform_lagrangian_interpolation_at_origin(&points, &values).unwrap() - ); - - // more points than it is required - // x^2 + x + 10 - // x, f(x) - // 1, 12 - // 2, 16 - // 3, 22 - // 4, 30 - // 5, 40 - let points = vec![1, 2, 3, 4, 5]; - let values = vec![ - Scalar::from(12), - Scalar::from(16), - Scalar::from(22), - Scalar::from(30), - Scalar::from(40), - ]; - - assert_eq!( - Scalar::from(10), - perform_lagrangian_interpolation_at_origin(&points, &values).unwrap() - ); - } - - #[test] - fn hash_g1_sanity_check() { - let mut rng = rand::thread_rng(); - let mut msg1 = [0u8; 1024]; - rng.fill_bytes(&mut msg1); - let mut msg2 = [0u8; 1024]; - rng.fill_bytes(&mut msg2); - - assert_eq!(hash_g1(msg1), hash_g1(msg1)); - assert_eq!(hash_g1(msg2), hash_g1(msg2)); - assert_ne!(hash_g1(msg1), hash_g1(msg2)); - } - - #[test] - fn hash_scalar_sanity_check() { - let mut rng = rand::thread_rng(); - let mut msg1 = [0u8; 1024]; - rng.fill_bytes(&mut msg1); - let mut msg2 = [0u8; 1024]; - rng.fill_bytes(&mut msg2); - - assert_eq!(hash_to_scalar(msg1), hash_to_scalar(msg1)); - assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2)); - assert_ne!(hash_to_scalar(msg1), hash_to_scalar(msg2)); - } -} diff --git a/common/nymsphinx/acknowledgements/src/identifier.rs b/common/nymsphinx/acknowledgements/src/identifier.rs index abe1d58368..544e9be959 100644 --- a/common/nymsphinx/acknowledgements/src/identifier.rs +++ b/common/nymsphinx/acknowledgements/src/identifier.rs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::AckKey; -use nym_crypto::symmetric::stream_cipher::{self, encrypt, iv_from_slice, random_iv, IvSizeUser}; +use nym_crypto::symmetric::stream_cipher::{ + self, encrypt, random_iv, try_iv_from_slice, IvSizeUser, +}; use nym_sphinx_params::{AckEncryptionAlgorithm, SerializedFragmentIdentifier, FRAG_ID_LEN}; use rand::{CryptoRng, RngCore}; @@ -25,7 +27,11 @@ pub fn recover_identifier( iv_id_ciphertext: &[u8], ) -> Option { let iv_size = AckEncryptionAlgorithm::iv_size(); - let iv = iv_from_slice::(&iv_id_ciphertext[..iv_size]); + if iv_id_ciphertext.len() < FRAG_ID_LEN + iv_size { + return None; + } + + let iv = try_iv_from_slice::(&iv_id_ciphertext[..iv_size])?; let id = stream_cipher::decrypt::( key.inner(), diff --git a/common/nymsphinx/addressing/src/nodes.rs b/common/nymsphinx/addressing/src/nodes.rs index c748a72dcd..4171410b6c 100644 --- a/common/nymsphinx/addressing/src/nodes.rs +++ b/common/nymsphinx/addressing/src/nodes.rs @@ -161,6 +161,12 @@ impl From for SocketAddr { } } +impl AsRef for NymNodeRoutingAddress { + fn as_ref(&self) -> &SocketAddr { + &self.0 + } +} + impl TryInto for NymNodeRoutingAddress { type Error = NymNodeRoutingAddressError; diff --git a/common/nymsphinx/anonymous-replies/src/reply_surb.rs b/common/nymsphinx/anonymous-replies/src/reply_surb.rs index 3ac7af8571..90bd892d78 100644 --- a/common/nymsphinx/anonymous-replies/src/reply_surb.rs +++ b/common/nymsphinx/anonymous-replies/src/reply_surb.rs @@ -19,6 +19,9 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum ReplySurbError { + #[error("did not receive enough data to recover a reply SURB")] + TooShort, + #[error("tried to use reply SURB with an unpadded message")] UnpaddedMessageError, @@ -131,7 +134,9 @@ impl ReplySurb { } pub fn from_bytes(bytes: &[u8]) -> Result { - // TODO: introduce bound checks to guard us against out of bound reads + if bytes.len() <= SurbEncryptionKeySize::USIZE { + return Err(ReplySurbError::TooShort); + } let encryption_key = SurbEncryptionKey::try_from_bytes(&bytes[..SurbEncryptionKeySize::USIZE])?; diff --git a/common/nymsphinx/chunking/src/reconstruction.rs b/common/nymsphinx/chunking/src/reconstruction.rs index db098c3222..000b0ca6dc 100644 --- a/common/nymsphinx/chunking/src/reconstruction.rs +++ b/common/nymsphinx/chunking/src/reconstruction.rs @@ -4,6 +4,7 @@ use crate::fragment::Fragment; use crate::{monitoring, ChunkingError}; use log::*; use std::collections::HashMap; +use std::time::{Duration, Instant}; // TODO: perhaps a more sophisticated approach with writing to disk periodically in case // we're receiving fast & furious in uncompressed 4K - we don't want to keep that in memory; @@ -31,6 +32,9 @@ struct ReconstructionBuffer { /// appropriately resized and all missing fragments are set to a `None`, thus keeping /// everything in order the whole time, allowing for O(1) insertions and O(n) reconstruction. fragments: Vec>, + + /// The timestamp of the last received fragment. Used for cleaning up stale buffers. + last_fragment_timestamp: Instant, } /// Type alias representing fully reconstructed message - its original data and list of all @@ -56,6 +60,7 @@ impl ReconstructionBuffer { previous_fragments_set_id: None, next_fragments_set_id: None, fragments: fragments_buffer, + last_fragment_timestamp: Instant::now(), } } @@ -99,6 +104,8 @@ impl ReconstructionBuffer { /// `previous_fragments_set_id` and `next_fragments_set_id` are set for the ease /// of access. fn insert_fragment(&mut self, fragment: Fragment) { + self.last_fragment_timestamp = Instant::now(); + // all fragments in the buffer should always have the same id as before inserting an element, // the correct buffer instance is looked up based on the fragment to be inserted. debug_assert!({ @@ -158,6 +165,10 @@ pub struct MessageReconstructor { } impl MessageReconstructor { + // We want to be very conservative here. We remove these to avoid memory leaks, but it's okay + // to wait for a long time before we do so. + const INCOMPLETE_MESSAGE_TIMEOUT: Duration = Duration::from_secs(30 * 60); + /// Creates an empty `MessageReconstructor`. pub fn new() -> Self { Default::default() @@ -296,6 +307,25 @@ impl MessageReconstructor { pub fn recover_fragment(&self, fragment_data: Vec) -> Result { Fragment::try_from_bytes(&fragment_data) } + + pub fn cleanup_stale_buffers(&mut self) { + trace!("Cleaning up stale buffers"); + let now = Instant::now(); + self.reconstructed_sets.retain(|_, set_buf| { + let keep = now.duration_since(set_buf.last_fragment_timestamp) + < Self::INCOMPLETE_MESSAGE_TIMEOUT; + if !keep { + debug!( + "Removing stale buffer for set id {:?}", + set_buf + .fragments + .first() + .and_then(|f| f.as_ref().map(|f| f.id())) + ); + } + keep + }); + } } #[cfg(test)] @@ -508,6 +538,8 @@ mod reconstruction_buffer { #[cfg(test)] mod message_reconstructor { + use std::time::Instant; + use super::*; use crate::fragment::unlinked_fragment_payload_max_len; use crate::set::{max_one_way_linked_set_payload_length, two_way_linked_set_payload_length}; @@ -869,6 +901,7 @@ mod message_reconstructor { previous_fragments_set_id: None, next_fragments_set_id: None, fragments: vec![], + last_fragment_timestamp: Instant::now(), }, ); @@ -879,6 +912,7 @@ mod message_reconstructor { previous_fragments_set_id: None, next_fragments_set_id: None, fragments: vec![], + last_fragment_timestamp: Instant::now(), }, ); @@ -1006,6 +1040,7 @@ mod message_reconstructor { previous_fragments_set_id: None, next_fragments_set_id: Some(1234), fragments: vec![], + last_fragment_timestamp: Instant::now(), }, ); @@ -1016,6 +1051,7 @@ mod message_reconstructor { previous_fragments_set_id: Some(12345), next_fragments_set_id: Some(123), fragments: vec![], + last_fragment_timestamp: Instant::now(), }, ); @@ -1026,6 +1062,7 @@ mod message_reconstructor { previous_fragments_set_id: Some(1234), next_fragments_set_id: Some(12), fragments: vec![], + last_fragment_timestamp: Instant::now(), }, ); @@ -1036,6 +1073,7 @@ mod message_reconstructor { previous_fragments_set_id: Some(123), next_fragments_set_id: None, fragments: vec![], + last_fragment_timestamp: Instant::now(), }, ); @@ -1083,6 +1121,7 @@ mod message_reconstructor { previous_fragments_set_id: None, next_fragments_set_id: Some(1234), fragments: vec![], + last_fragment_timestamp: Instant::now(), }, ); reconstructor.reconstructed_sets.insert( @@ -1092,6 +1131,7 @@ mod message_reconstructor { previous_fragments_set_id: Some(12345), next_fragments_set_id: None, fragments: vec![], + last_fragment_timestamp: Instant::now(), }, ); assert_eq!(reconstructor.previous_linked_set_id(12345), None); @@ -1139,6 +1179,7 @@ mod message_reconstructor { previous_fragments_set_id: None, next_fragments_set_id: Some(1234), fragments: vec![], + last_fragment_timestamp: Instant::now(), }, ); reconstructor.reconstructed_sets.insert( @@ -1148,6 +1189,7 @@ mod message_reconstructor { previous_fragments_set_id: Some(12345), next_fragments_set_id: None, fragments: vec![], + last_fragment_timestamp: Instant::now(), }, ); assert_eq!(reconstructor.next_linked_set_id(12345), Some(1234)); diff --git a/common/nymsphinx/types/src/lib.rs b/common/nymsphinx/types/src/lib.rs index c9441dbc4d..517e45e783 100644 --- a/common/nymsphinx/types/src/lib.rs +++ b/common/nymsphinx/types/src/lib.rs @@ -30,7 +30,6 @@ pub use sphinx_packet::{ route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, SURBIdentifier}, surb::{SURBMaterial, SURB}, version::Version, - version::UPDATED_LEGACY_VERSION, Error as SphinxError, ProcessedPacket, ProcessedPacketData, }; @@ -91,12 +90,8 @@ impl NymPacket { destination: &Destination, delays: &[Delay], ) -> Result { - // FIXME: - // for now explicitly use the legacy version until sufficient number of nodes - // understand both variants Ok(NymPacket::Sphinx( SphinxPacketBuilder::new() - .with_version(UPDATED_LEGACY_VERSION) .with_payload_size(size) .build_packet(message, route, destination, delays)?, )) diff --git a/common/nyxd-scraper/src/block_processor/mod.rs b/common/nyxd-scraper/src/block_processor/mod.rs index e75d62b7df..d4007de96b 100644 --- a/common/nyxd-scraper/src/block_processor/mod.rs +++ b/common/nyxd-scraper/src/block_processor/mod.rs @@ -182,9 +182,11 @@ impl BlockProcessor { // the ones concerned with individual messages for (index, msg) in block_tx.tx.body.messages.iter().enumerate() { for msg_module in &mut self.msg_modules { - msg_module - .handle_msg(index, msg, &block_tx, &mut tx) - .await? + if msg.type_url == msg_module.type_url() { + msg_module + .handle_msg(index, msg, &block_tx, &mut tx) + .await? + } } } } diff --git a/common/nyxd-scraper/src/error.rs b/common/nyxd-scraper/src/error.rs index 6e413983bd..3337ee969c 100644 --- a/common/nyxd-scraper/src/error.rs +++ b/common/nyxd-scraper/src/error.rs @@ -83,6 +83,15 @@ pub enum ScraperError { source: cosmrs::ErrorReport, }, + #[error("could not parse msg in tx {hash} at index {index} into {type_url}: {source}")] + MsgParseFailure { + hash: Hash, + index: usize, + type_url: String, + #[source] + source: cosmrs::ErrorReport, + }, + #[error("received an invalid chain subscription event of kind {kind} while we were waiting for new block data (query: '{query}')")] InvalidSubscriptionEvent { query: String, kind: String }, diff --git a/common/nyxd-scraper/src/modules/msg_module.rs b/common/nyxd-scraper/src/modules/msg_module.rs index df64761f7d..1d195bee14 100644 --- a/common/nyxd-scraper/src/modules/msg_module.rs +++ b/common/nyxd-scraper/src/modules/msg_module.rs @@ -9,6 +9,8 @@ use cosmrs::Any; #[async_trait] pub trait MsgModule { + fn type_url(&self) -> String; + async fn handle_msg( &mut self, index: usize, diff --git a/common/pemstore/Cargo.toml b/common/pemstore/Cargo.toml index f3bc33a6ef..f8d13391dd 100644 --- a/common/pemstore/Cargo.toml +++ b/common/pemstore/Cargo.toml @@ -9,3 +9,4 @@ repository = { workspace = true } [dependencies] pem = { workspace = true } +tracing = { workspace = true } \ No newline at end of file diff --git a/common/pemstore/src/lib.rs b/common/pemstore/src/lib.rs index bd36158229..9f6d3e283b 100644 --- a/common/pemstore/src/lib.rs +++ b/common/pemstore/src/lib.rs @@ -6,6 +6,7 @@ use pem::Pem; use std::fs::File; use std::io::{self, Read, Write}; use std::path::{Path, PathBuf}; +use tracing::debug; pub mod traits; @@ -46,6 +47,10 @@ where T: PemStorableKey, P: AsRef, { + debug!( + "attempting to load key with the following pem type: {}", + T::pem_type() + ); let key_pem = read_pem_file(path)?; if T::pem_type() != key_pem.tag { diff --git a/common/service-provider-requests-common/Cargo.toml b/common/service-provider-requests-common/Cargo.toml index f88fdcc0e1..63aef2eb76 100644 --- a/common/service-provider-requests-common/Cargo.toml +++ b/common/service-provider-requests-common/Cargo.toml @@ -12,3 +12,7 @@ readme.workspace = true [dependencies] serde = { workspace = true, features = ["derive"] } +thiserror.workspace = true + +[dev-dependencies] +bincode.workspace = true diff --git a/common/service-provider-requests-common/src/lib.rs b/common/service-provider-requests-common/src/lib.rs index 3b294baba7..2840a9321c 100644 --- a/common/service-provider-requests-common/src/lib.rs +++ b/common/service-provider-requests-common/src/lib.rs @@ -1,8 +1,16 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use std::fmt; + use serde::{Deserialize, Serialize}; +#[derive(thiserror::Error, Debug)] +pub enum ProtocolError { + #[error("invalid service provider type: {0}")] + InvalidServiceProviderType(u8), +} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[repr(u8)] pub enum ServiceProviderType { @@ -11,8 +19,103 @@ pub enum ServiceProviderType { Authenticator = 2, } +impl fmt::Display for ServiceProviderType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::NetworkRequester => write!(f, "NetworkRequester"), + Self::IpPacketRouter => write!(f, "IpPacketRouter"), + Self::Authenticator => write!(f, "Authenticator"), + } + } +} + +impl TryFrom for ServiceProviderType { + type Error = ProtocolError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::NetworkRequester), + 1 => Ok(Self::IpPacketRouter), + 2 => Ok(Self::Authenticator), + _ => Err(ProtocolError::InvalidServiceProviderType(value)), + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Protocol { pub version: u8, pub service_provider_type: ServiceProviderType, } + +impl TryFrom<&[u8; 2]> for Protocol { + type Error = ProtocolError; + + fn try_from(bytes: &[u8; 2]) -> Result { + let version = bytes[0]; + let service_provider_type = ServiceProviderType::try_from(bytes[1]) + .map_err(|_| ProtocolError::InvalidServiceProviderType(bytes[1]))?; + + Ok(Self { + version, + service_provider_type, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bincode::Options; + + fn make_bincode_serializer() -> impl bincode::Options { + bincode::DefaultOptions::new() + .with_big_endian() + .with_varint_encoding() + } + + #[test] + fn protocol_serialization() { + let protocol = Protocol { + version: 4, + service_provider_type: ServiceProviderType::NetworkRequester, + }; + + let serialized = make_bincode_serializer().serialize(&protocol).unwrap(); + let deserialized: Protocol = make_bincode_serializer().deserialize(&serialized).unwrap(); + + assert_eq!(protocol, deserialized); + } + + #[test] + fn compact_serialization() { + let protocol = Protocol { + version: 4, + service_provider_type: ServiceProviderType::NetworkRequester, + }; + + let serialized = make_bincode_serializer().serialize(&protocol).unwrap(); + assert_eq!(serialized.len(), 2); + } + + #[test] + fn protocol_deserialization() { + let bytes = [4, ServiceProviderType::NetworkRequester as u8]; + let deserialized = Protocol::try_from(&bytes).unwrap(); + + let expected = Protocol { + version: 4, + service_provider_type: ServiceProviderType::NetworkRequester, + }; + + assert_eq!(expected, deserialized); + } + + #[test] + fn invalid_protocol_deserialization() { + let bytes = [4, 3]; + let deserialized = Protocol::try_from(&bytes); + + assert!(deserialized.is_err()); + } +} diff --git a/common/task/src/connections.rs b/common/task/src/connections.rs index af13111526..8557b84676 100644 --- a/common/task/src/connections.rs +++ b/common/task/src/connections.rs @@ -2,7 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use futures::channel::mpsc; -use std::collections::HashMap; +use std::{ + collections::HashMap, + time::{Duration, Instant}, +}; + +const LANE_CONSIDERED_CLEAR: usize = 10; pub type ConnectionId = u64; @@ -67,6 +72,32 @@ impl LaneQueueLengths { } } } + + pub fn total(&self) -> usize { + match self.0.lock() { + Ok(inner) => inner.values().sum(), + Err(err) => { + log::warn!("Failed to get total queue length: {err}"); + 0 + } + } + } + + pub async fn wait_until_clear(&self, lane: &TransmissionLane, timeout: Option) { + let total_time_waited = Instant::now(); + loop { + let lane_length = self.get(lane).unwrap_or_default(); + if lane_length < LANE_CONSIDERED_CLEAR { + break; + } + if timeout.is_some_and(|timeout| total_time_waited.elapsed() > timeout) { + log::warn!("Timeout reached while waiting for queue to clear"); + break; + } + log::trace!("Waiting for queue to clear ({} items left)", lane_length); + tokio::time::sleep(Duration::from_millis(100)).await; + } + } } impl Default for LaneQueueLengths { @@ -103,4 +134,8 @@ impl LaneQueueLengthsInner { { self.map.entry(*lane).and_modify(f); } + + pub fn total(&self) -> usize { + self.map.values().sum() + } } diff --git a/common/topology/src/lib.rs b/common/topology/src/lib.rs index 2150d587cb..4354721bc5 100644 --- a/common/topology/src/lib.rs +++ b/common/topology/src/lib.rs @@ -254,6 +254,15 @@ impl NymTopology { } } + pub fn with_additional_nodes(mut self, nodes: impl Iterator) -> Self + where + N: TryInto, + >::Error: Display, + { + self.add_additional_nodes(nodes); + self + } + pub fn has_node_details(&self, node_id: NodeId) -> bool { self.node_details.contains_key(&node_id) } diff --git a/common/wasm/client-core/src/config/mod.rs b/common/wasm/client-core/src/config/mod.rs index ecd7be7927..b038e20737 100644 --- a/common/wasm/client-core/src/config/mod.rs +++ b/common/wasm/client-core/src/config/mod.rs @@ -462,6 +462,10 @@ pub struct ReplySurbsWasm { /// Defines the maximum number of reply surbs the client wants to keep in its storage at any times. pub maximum_reply_surb_storage_threshold: usize, + /// Defines the soft threshold ontop of the minimum reply surb storage threshold for when the client + /// should proactively request additional reply surbs. + pub minimum_reply_surb_threshold_buffer: usize, + /// Defines the minimum number of reply surbs the client would request. pub minimum_reply_surb_request_size: u32, @@ -490,6 +494,9 @@ pub struct ReplySurbsWasm { /// Defines how many mix nodes the reply surb should go through. /// If not set, the default value is going to be used. pub surb_mix_hops: Option, + + /// Specifies if we should reset all the sender tags on startup + pub fresh_sender_tags: bool, } impl Default for ReplySurbsWasm { @@ -503,6 +510,7 @@ impl From for ConfigReplySurbs { ConfigReplySurbs { minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold, maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold, + minimum_reply_surb_threshold_buffer: reply_surbs.minimum_reply_surb_threshold_buffer, minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size, maximum_reply_surb_request_size: reply_surbs.maximum_reply_surb_request_size, maximum_allowed_reply_surb_request_size: reply_surbs @@ -520,6 +528,7 @@ impl From for ConfigReplySurbs { reply_surbs.maximum_reply_key_age_ms as u64, ), surb_mix_hops: reply_surbs.surb_mix_hops, + fresh_sender_tags: reply_surbs.fresh_sender_tags, } } } @@ -529,6 +538,7 @@ impl From for ReplySurbsWasm { ReplySurbsWasm { minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold, maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold, + minimum_reply_surb_threshold_buffer: reply_surbs.minimum_reply_surb_threshold_buffer, minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size, maximum_reply_surb_request_size: reply_surbs.maximum_reply_surb_request_size, maximum_allowed_reply_surb_request_size: reply_surbs @@ -542,6 +552,7 @@ impl From for ReplySurbsWasm { maximum_reply_surb_age_ms: reply_surbs.maximum_reply_surb_age.as_millis() as u32, maximum_reply_key_age_ms: reply_surbs.maximum_reply_key_age.as_millis() as u32, surb_mix_hops: reply_surbs.surb_mix_hops, + fresh_sender_tags: reply_surbs.fresh_sender_tags, } } } diff --git a/common/wasm/client-core/src/config/override.rs b/common/wasm/client-core/src/config/override.rs index 0a485e49c7..bc9275660a 100644 --- a/common/wasm/client-core/src/config/override.rs +++ b/common/wasm/client-core/src/config/override.rs @@ -339,6 +339,11 @@ pub struct ReplySurbsWasmOverride { #[tsify(optional)] pub maximum_reply_surb_storage_threshold: Option, + /// Defines the soft threshold ontop of the minimum reply surb storage threshold for when the client + /// should proactively request additional reply surbs. + #[tsify(optional)] + pub minimum_reply_surb_threshold_buffer: Option, + /// Defines the minimum number of reply surbs the client would request. #[tsify(optional)] pub minimum_reply_surb_request_size: Option, @@ -373,6 +378,9 @@ pub struct ReplySurbsWasmOverride { #[tsify(optional)] pub surb_mix_hops: Option, + + /// Specifies if we should reset all the sender tags on startup + pub fresh_sender_tags: bool, } impl From for ReplySurbsWasm { @@ -386,6 +394,9 @@ impl From for ReplySurbsWasm { maximum_reply_surb_storage_threshold: value .maximum_reply_surb_storage_threshold .unwrap_or(def.maximum_reply_surb_storage_threshold), + minimum_reply_surb_threshold_buffer: value + .minimum_reply_surb_threshold_buffer + .unwrap_or(def.minimum_reply_surb_threshold_buffer), minimum_reply_surb_request_size: value .minimum_reply_surb_request_size .unwrap_or(def.minimum_reply_surb_request_size), @@ -408,6 +419,7 @@ impl From for ReplySurbsWasm { .maximum_reply_key_age_ms .unwrap_or(def.maximum_reply_key_age_ms), surb_mix_hops: value.surb_mix_hops, + fresh_sender_tags: value.fresh_sender_tags, } } } diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 7a2e25bfb6..3f161750a1 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -1185,6 +1185,7 @@ name = "nym-pemstore" version = "0.3.0" dependencies = [ "pem", + "tracing", ] [[package]] @@ -1251,6 +1252,12 @@ dependencies = [ "regex", ] +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + [[package]] name = "pkcs8" version = "0.9.0" @@ -1470,9 +1477,9 @@ checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "schemars" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" dependencies = [ "dyn-clone", "schemars_derive", @@ -1482,9 +1489,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" dependencies = [ "proc-macro2", "quote", @@ -1522,18 +1529,18 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -1558,9 +1565,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -1777,9 +1784,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" dependencies = [ "deranged", "itoa", @@ -1794,15 +1801,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" dependencies = [ "num-conv", "time-core", @@ -1840,6 +1847,37 @@ dependencies = [ "winnow", ] +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + [[package]] name = "typenum" version = "1.18.0" diff --git a/deny.toml b/deny.toml index 4da5bf5c59..5bd402997e 100644 --- a/deny.toml +++ b/deny.toml @@ -122,6 +122,7 @@ exceptions = [ { allow = ["GPL-3.0"], crate = "nym-network-requester" }, { allow = ["GPL-3.0"], crate = "nym-node" }, { allow = ["GPL-3.0"], crate = "nym-validator-rewarder" }, + { allow = ["GPL-3.0"], crate = "nym-ip-packet-router" }, ] # Some crates don't have (easily) machine readable licensing information, diff --git a/documentation/docs/.gitignore b/documentation/docs/.gitignore index 515d7fce8d..60d8350455 100644 --- a/documentation/docs/.gitignore +++ b/documentation/docs/.gitignore @@ -7,4 +7,6 @@ package-lock.json # local env files .env*.local -.env \ No newline at end of file +.env + +scratch.md diff --git a/documentation/docs/components/explorer-link.tsx b/documentation/docs/components/explorer-link.tsx new file mode 100644 index 0000000000..90f3dba33a --- /dev/null +++ b/documentation/docs/components/explorer-link.tsx @@ -0,0 +1,23 @@ +import Image from "next/image"; +import Link from "next/link"; +import explorerLogo from "../public/images/smiley.png"; + +export const Explorer = () => { + return ( + + {"Network + + ); +}; diff --git a/documentation/docs/components/footer.tsx b/documentation/docs/components/footer.tsx index 1866426d22..b7148f45eb 100644 --- a/documentation/docs/components/footer.tsx +++ b/documentation/docs/components/footer.tsx @@ -5,7 +5,7 @@ const links = [ ["Matrix", "https://matrix.to/#/#dev:nymtech.chat"], ["GitHub", "https://nymtech.net/go/github/nym"], ["Nym Wallet", "https://nymtech.net/download/wallet"], - ["Nym Explorer", "https://explorer.nymtech.net/"], + ["Nym Explorer", "https://nym.com/explorer/"], ["Nym Blog", "https://nymtech.medium.com/"], ["Twitter", "https://nymtech.net/go/x"], ["Telegram", "https://nymtech.net/go/telegram"], diff --git a/documentation/docs/components/landing-page.tsx b/documentation/docs/components/landing-page.tsx index e3f104b7bd..a3e274994a 100644 --- a/documentation/docs/components/landing-page.tsx +++ b/documentation/docs/components/landing-page.tsx @@ -6,10 +6,6 @@ import { useTheme } from "@mui/material/styles"; import Image from "next/image"; import Link from "next/link"; -// import networkDocs from "../public/images/landing/network-docs.png"; -// import developerDocs from "../public/images/landing/developer-docs.png"; -// import sdkDocs from "../public/images/landing/sdk-docs.png"; -// import operatorGuide from "../public/images/landing/operator-guide.png"; import networkDocs from "../public/images/landing/Vector1.png"; import developerDocs from "../public/images/landing/Vector2.png"; import sdkDocs from "../public/images/landing/Vector3.png"; @@ -41,13 +37,6 @@ export const LandingPage = () => { href: "/developers", icon: sdkDocs, }, - // { - // text: "SDKs", - // description: "Rust and Typescript SDK docs", - - // href: "/developers/rust", - // icon: sdkDocs, - // }, { text: "APIs", description: "Interactive API specs for Nym infrastructure", diff --git a/documentation/docs/components/matrix-link.tsx b/documentation/docs/components/matrix-link.tsx index 754857688a..6d28e8837f 100644 --- a/documentation/docs/components/matrix-link.tsx +++ b/documentation/docs/components/matrix-link.tsx @@ -1,7 +1,7 @@ -import { Box } from "@mui/material"; import Image from "next/image"; import Link from "next/link"; import matrixLogo from "../public/images/matrix-logo.png"; + export const Matrix = () => { return ( { target="_blank" rel="noopener noreferrer" > - {"Matrix + {"Matrix ); }; diff --git a/documentation/docs/components/operators/snippets/entry-gateway-run-tab-snippet.mdx b/documentation/docs/components/operators/snippets/entry-gateway-run-tab-snippet.mdx index 677a54a82c..72c5e99420 100644 --- a/documentation/docs/components/operators/snippets/entry-gateway-run-tab-snippet.mdx +++ b/documentation/docs/components/operators/snippets/entry-gateway-run-tab-snippet.mdx @@ -5,7 +5,7 @@ If you run a `nym-node` for the first time, you will need to specify a few param To initialise and test run with yur node with all needed options, use this command: ```sh -./nym-node run --id --mode entry-gateway --public-ips "$(curl -4 https://ifconfig.me)" --hostname "" --http-bind-address 0.0.0.0:8080 --mixnet-bind-address 0.0.0.0:1789 --location --accept-operator-terms-and-conditions --wireguard-enabled true +./nym-node run --id --mode entry-gateway --public-ips "$(curl -4 https://ifconfig.me)" --hostname "" --location --accept-operator-terms-and-conditions --wireguard-enabled true ``` If you prefer to have a generic local identifier set to `default-nym-node`, skip `--id` option. @@ -18,7 +18,7 @@ In any case `--public-ips` is a necessity for your node to bond to API and commu Adding `--init-only` option results in `nym-node` initialising a configuration file `config.toml` without running - a good option for an initial node setup. Remember that if you using this flag on a node which already has a config file, this will not over-write the values, unless used with a specified flag `--write-changes` (`-w`) - a good option for introducing changes to your `config.toml` file. ```sh -./nym-node run --id --init-only --mode entry-gateway --public-ips "$(curl -4 https://ifconfig.me)" --hostname "" --http-bind-address 0.0.0.0:8080 --mixnet-bind-address 0.0.0.0:1789 --location --wireguard-enabled true +./nym-node run --id --init-only --mode entry-gateway --public-ips "$(curl -4 https://ifconfig.me)" --hostname "" --location --wireguard-enabled true ``` In the example above we dropped `--accept-operator-terms-and-conditions` as the flag must be added to a running command explicitly and it is not stored in the config, `--init-only` will not run the node. diff --git a/documentation/docs/components/operators/snippets/exit-gateway-run-tab-snippet.mdx b/documentation/docs/components/operators/snippets/exit-gateway-run-tab-snippet.mdx index cac6b42a7e..b80a2cbb6f 100644 --- a/documentation/docs/components/operators/snippets/exit-gateway-run-tab-snippet.mdx +++ b/documentation/docs/components/operators/snippets/exit-gateway-run-tab-snippet.mdx @@ -5,7 +5,7 @@ If you run a `nym-node` for the first time, you will need to specify a few param To initialise and test run your node, use this command: ```sh -./nym-node run --id --mode exit-gateway --public-ips "$(curl -4 https://ifconfig.me)" --hostname "" --http-bind-address 0.0.0.0:8080 --mixnet-bind-address 0.0.0.0:1789 --location --accept-operator-terms-and-conditions --wireguard-enabled true +./nym-node run --id --mode exit-gateway --public-ips "$(curl -4 https://ifconfig.me)" --hostname "" --location --accept-operator-terms-and-conditions --wireguard-enabled true ``` If you prefer to have a generic local identifier set to `default-nym-node`, skip `--id` option. @@ -19,7 +19,7 @@ In any case `--public-ips` is a necessity for your node to bond to API and commu Adding `--init-only` option results in `nym-node` initialising a configuration file `config.toml` without running - a good option for an initial node setup. Remember that if you using this flag on a node which already has a config file, this will not over-write the values, unless used with a specified flag `--write-changes` (`-w`) - a good option for introducing changes to your `config.toml` file. ```sh -./nym-node run --id --init-only --mode exit-gateway --public-ips "$(curl -4 https://ifconfig.me)" --hostname "" --http-bind-address 0.0.0.0:8080 --mixnet-bind-address 0.0.0.0:1789 --location --wireguard-enabled true +./nym-node run --id --init-only --mode exit-gateway --public-ips "$(curl -4 https://ifconfig.me)" --hostname "" --location --wireguard-enabled true ``` In the example above we dropped `--accept-operator-terms-and-conditions` as the flag must be added to a running command explicitly and it is not stored in the config, `--init-only` will not run the node. diff --git a/documentation/docs/components/operators/snippets/mixnode-run-tab-snippet.mdx b/documentation/docs/components/operators/snippets/mixnode-run-tab-snippet.mdx index 9686aeff17..8deb007cc2 100644 --- a/documentation/docs/components/operators/snippets/mixnode-run-tab-snippet.mdx +++ b/documentation/docs/components/operators/snippets/mixnode-run-tab-snippet.mdx @@ -6,7 +6,7 @@ If you run a `nym-node` for the first time, you will need to specify a few param To initialise and run your node, use this command: ```sh -./nym-node run --mode mixnode --mixnet-bind-address 0.0.0.0:1789 --verloc-bind-address 0.0.0.0:1790 --http-bind-address 0.0.0.0:8080 --public-ips "$(curl -4 https://ifconfig.me)" --accept-operator-terms-and-conditions +./nym-node run --mode mixnode --public-ips "$(curl -4 https://ifconfig.me)" --accept-operator-terms-and-conditions ``` **Init only** @@ -15,7 +15,7 @@ Adding `--init-only` option results in `nym-node` initialising a configuration f Initialise only with a custom `--id` and `--init-only` command: ```sh -./nym-node run --mode mixnode --id --init-only --mixnet-bind-address 0.0.0.0:1789 --verloc-bind-address 0.0.0.0:1790 --http-bind-address 0.0.0.0:8080 --public-ips "$(curl -4 https://ifconfig.me)" --accept-operator-terms-and-conditions +./nym-node run --mode mixnode --id --init-only --public-ips "$(curl -4 https://ifconfig.me)" --accept-operator-terms-and-conditions ``` If you prefer to have a generic local identifier set to `default-nym-node`, skip `--id` option. diff --git a/documentation/docs/components/operators/snippets/ntp-time-sync.mdx b/documentation/docs/components/operators/snippets/ntp-time-sync.mdx new file mode 100644 index 0000000000..945a39470a --- /dev/null +++ b/documentation/docs/components/operators/snippets/ntp-time-sync.mdx @@ -0,0 +1,10 @@ +- Run synchronization against [pool.ntp.org](https://www.ntppool.org/en/): +```bash +ntpdate -q pool.ntp.org +``` +- Enable `ntp` service, start and review the status: +```bash +systemctl enable --now ntp +service ntp start +service ntp status +``` diff --git a/documentation/docs/components/operators/snippets/nym-node-specs.mdx b/documentation/docs/components/operators/snippets/nym-node-specs.mdx index 545ad3457d..7cd9acbb79 100644 --- a/documentation/docs/components/operators/snippets/nym-node-specs.mdx +++ b/documentation/docs/components/operators/snippets/nym-node-specs.mdx @@ -7,7 +7,7 @@ You will need to rent a VPS to run your node on. One key reason for this is that Tor community created a very helpful table called [*Good Bad ISPs*](https://community.torproject.org/relay/community-resources/good-bad-isps/), you can use that one as a guideline for your choice of ISP for your VPS. -**Update:** Nym community started an ISP table called [*Where to host your nym node?*](../../community-counsel/isp-list.mdx), check it out and add your findings! +**Update:** Nym community initiated an ISP table called [*Where to host your nym node?*](https://nym.com/docs/operators/community-counsel/isp-list), check it out and add your findings!
mtu 1420 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 + link/none + +ESC[0;33mIP Addresses:ESC[0m +12: nymwg: mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000 + inet 10.1.0.1/32 brd 10.1.0.1 scope global nymwg + valid_lft forever preferred_lft forever +12: nymwg: mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000 + inet6 fc01::1/112 scope global + valid_lft forever preferred_lft forever + +ESC[0;33mIptables Chains:ESC[0m +IPv4 Chain: +Chain NYM-EXIT (1 references) + pkts bytes target prot opt in out source destination + 0 0 REJECT 0 -- * * 0.0.0.0/0 5.188.10.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 5.188.11.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 31.132.36.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 31.184.237.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 37.9.42.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 43.229.52.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 45.9.148.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 45.43.128.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 45.142.120.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 46.148.112.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 46.148.120.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 46.148.127.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 46.173.208.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 79.110.22.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 85.121.39.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 91.193.75.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 91.200.12.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 91.200.81.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 91.200.82.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 91.200.83.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 91.200.164.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 91.216.3.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 91.220.163.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 91.200.248.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 91.243.90.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 91.243.91.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 91.243.93.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 91.234.99.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 103.99.0.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 103.215.80.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 103.239.28.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 104.166.96.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 104.207.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 104.233.0.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 104.239.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 104.243.192.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 104.247.96.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 104.250.192.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 104.250.224.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 107.182.112.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 107.190.160.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 141.136.22.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 150.129.40.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 159.174.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 162.222.128.0/21 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 162.249.20.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 163.53.247.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 166.117.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 167.74.0.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 167.160.96.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 168.64.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 168.76.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 168.129.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 169.239.152.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 170.114.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 172.98.0.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 174.136.192.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 176.121.14.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 178.159.97.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 178.159.100.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 178.159.107.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.14.192.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.14.193.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.14.195.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.21.8.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.39.8.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.71.0.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.77.248.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.116.172.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.116.175.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.124.56.0/21 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.129.8.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.130.36.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.130.40.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.140.53.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.143.220.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.143.222.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.143.223.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.146.168.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.165.153.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.193.90.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.244.29.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.244.30.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.244.31.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 188.247.230.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 192.26.25.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 192.31.212.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 192.43.175.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 192.43.176.0/21 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 192.43.184.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 192.161.80.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 192.251.231.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 193.228.91.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 194.5.97.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 194.5.98.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 194.5.99.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 195.182.57.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 196.45.120.0/21 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 196.61.192.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 196.196.8.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 196.199.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 197.231.208.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 198.20.16.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 198.45.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 198.56.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 198.151.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 198.151.152.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 198.178.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 198.183.32.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 198.186.25.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 198.187.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 198.200.0.0/21 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 198.200.8.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 198.206.140.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 199.5.152.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 199.34.128.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 199.84.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 199.89.16.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 199.120.163.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 199.166.200.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 199.185.192.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 199.196.192.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 199.198.160.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 199.212.96.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 199.223.0.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 199.241.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 199.249.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 199.253.224.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 199.254.32.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 204.19.38.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 204.44.224.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 204.52.96.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 204.87.199.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 204.107.208.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 204.126.244.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 204.130.16.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 204.147.64.0/21 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 204.232.0.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 205.144.0.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 205.148.192.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 205.151.128.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 205.159.45.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 205.172.244.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 205.189.71.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 205.189.72.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 205.203.0.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 205.233.224.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 205.236.189.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 206.124.104.0/21 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 206.195.224.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 206.197.165.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 206.209.80.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 206.224.160.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 206.226.0.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 206.226.32.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 206.227.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 207.22.192.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 207.45.224.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 207.110.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 207.110.128.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 209.66.128.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 216.179.128.0/17 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 217.8.116.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 217.8.117.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 223.169.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 223.254.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 42.4.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 68.119.232.0/21 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 68.215.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 69.244.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 70.111.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 70.126.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 112.78.2.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 195.20.40.0/21 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 14.160.0.0/12 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 27.2.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 27.106.108.128/25 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 37.236.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.100.21.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 39.32.0.0/11 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 41.190.2.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 41.190.30.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 45.116.232.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 46.118.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 46.161.9.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 60.184.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 62.44.134.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 78.85.40.0/21 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 79.11.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 79.108.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 81.93.93.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 83.24.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 83.149.19.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 84.18.126.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 85.198.140.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 87.116.176.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 87.227.224.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 87.241.88.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 89.114.108.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 91.196.250.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 93.122.192.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 95.0.60.160/27 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 95.110.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 89.189.152.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 103.26.246.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 106.51.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 109.124.0.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 109.124.16.0/21 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 109.126.128.0/17 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 109.175.6.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 111.125.108.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 112.101.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 113.80.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 113.128.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 114.96.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 114.115.128.0/17 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 115.72.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 118.20.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 123.24.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 125.167.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 139.5.157.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 146.185.223.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 154.68.4.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 177.55.154.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 177.125.30.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 177.224.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 178.135.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 179.5.103.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 181.67.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 181.174.101.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 182.69.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 182.160.100.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 182.184.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 182.253.162.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 183.82.128.0/17 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 183.128.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.36.88.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.150.15.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 185.172.86.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 186.179.100.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 189.216.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 190.235.110.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 190.239.190.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 192.64.121.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 193.34.141.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 193.188.254.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 197.229.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 201.148.126.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 202.136.88.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 200.121.192.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 220.164.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 221.228.192.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 24.0.0.0/12 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 27.184.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 46.0.0.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 58.53.128.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 59.92.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 60.52.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 60.176.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 60.215.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 61.163.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 62.194.131.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 64.175.32.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 67.116.236.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 67.121.120.0/21 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 67.124.36.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 68.62.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 69.112.0.0/12 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 71.56.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 76.112.0.0/12 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 78.97.32.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 80.108.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 81.240.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 82.72.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 82.169.28.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 84.127.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 84.144.0.0/12 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 84.220.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 85.48.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 85.54.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 85.85.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 85.86.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 87.176.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 89.217.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 91.176.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 91.182.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 92.0.0.0/12 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 92.112.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 92.128.0.0/12 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 109.128.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 110.212.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 111.85.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 112.224.0.0/11 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 113.70.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 113.89.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 113.111.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 113.224.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 113.240.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 114.246.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 114.248.80.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 115.60.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 115.213.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 116.238.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 117.22.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 117.136.0.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 118.80.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 118.168.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 120.0.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 120.68.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 121.29.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 122.169.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 122.173.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 123.67.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 123.101.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 123.112.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 123.134.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 123.161.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 123.174.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 123.188.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 123.244.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 124.89.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 124.128.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 124.134.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 124.94.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 125.93.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 125.125.176.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 125.224.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 150.70.75.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 166.204.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 178.191.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 178.125.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 183.91.2.0/23 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 183.184.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 188.23.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 188.98.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 189.64.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 201.53.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 201.82.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 212.56.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 218.202.219.0/24 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 220.152.128.0/22 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 220.178.0.0/19 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 221.11.32.0/20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 222.183.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 222.240.216.0/21 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 82.165.159.132/31 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 91.208.144.164 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 209.182.193.155 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 213.205.38.29 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 5.79.71.205 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 5.79.71.225 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.129.41 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.129.213 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.144.42 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.147.11 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.151.95 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.153.71 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.153.115 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.168.194 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.169.101 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.170.84 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.174.35 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.179.9 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.182.164 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.184.75 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.186.110 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.186.114 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.188.186 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.191.189 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 46.244.21.4 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 50.21.181.152 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 50.63.202.35 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 52.5.245.208 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 64.71.166.50 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 64.71.188.178 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 67.215.255.139 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 74.200.48.169 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 74.208.153.9 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 74.208.164.166 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 74.208.64.191 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 85.17.31.122 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 85.17.31.82 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.18.146 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.149.145 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.149.153 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.18.112 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.18.141 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.190.153 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.190.154 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.190.157 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.20.192 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.24.200 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.253.18 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.26.9 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 95.211.230.75 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 104.42.225.122 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 104.244.14.252 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 109.70.26.37 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 144.217.74.156 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 146.148.124.166 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 148.81.111.111 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 151.80.148.103 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 176.58.104.168 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 178.162.203.202 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 178.162.203.211 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 178.162.203.226 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 178.162.217.107 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 184.105.76.250 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 184.105.192.2 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 192.0.72.20 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 192.0.72.21 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 192.169.69.25 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 192.42.116.41 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 192.42.119.41 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 193.166.255.170 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 193.166.255.171 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 204.11.56.48 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 208.91.197.46 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 212.227.20.93 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 212.227.20.116 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 212.227.20.164 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 213.165.83.176 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 216.218.135.114 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 216.218.185.162 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 216.218.208.114 reject-with icmp-port-unreachable + 0 0 REJECT 0 -- * * 0.0.0.0/0 216.66.15.109 reject-with icmp-port-unreachable + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:706 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:706 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5432 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:5432 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:2082:2083 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:2082:2083 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:8232:8233 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:8232:8233 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1500 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1500 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:123 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:123 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1293 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1293 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:11371 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:11371 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:443 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:110 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:110 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1194 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1194 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3074 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:3074 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1521 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1521 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:2049 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:2049 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:88 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:88 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:995 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:995 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5050 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:5050 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:43 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:43 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:991 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:991 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:143 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:143 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5228 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:5228 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:445 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:445 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1755 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1755 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:993 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:993 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9001 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9001 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:5222:5223 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:5222:5223 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:20:21 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:20:21 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:60000:61000 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:60000:61000 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:2102:2104 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:2102:2104 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:873 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:873 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:27000:27050 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:27000:27050 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9418 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9418 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1863 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1863 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:8087:8088 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:8087:8088 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9030 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9030 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:4643 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:4643 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9339 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9339 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:902:904 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:902:904 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1533 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1533 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:2095:2096 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:2095:2096 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5190 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:5190 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:749 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:749 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:4321 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:4321 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:10000 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:10000 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:53 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:53 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:19294 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:19294 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:220 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:220 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:8332:8333 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:8332:8333 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:64738 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:64738 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1723 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1723 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8443 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:8443 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8888 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:8888 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:2086:2087 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:2086:2087 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9735 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9735 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:554 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:554 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:853 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:853 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:22 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8082 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:8082 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:992 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:992 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:25565 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:25565 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3690 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:3690 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:464 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:464 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:981 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:981 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9053 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9053 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:50002 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:50002 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9443 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9443 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:389 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:389 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:80:81 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:80:81 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:27017 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:27017 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:5000:5005 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:5000:5005 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1433 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1433 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8883 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:8883 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:3306 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8767 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:8767 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1677 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1677 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:19638 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:19638 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1220 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1220 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:79 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:79 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:989:990 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:989:990 + 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:636 + 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:636 + 0 0 REJECT 0 -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-port-unreachable + +IPv6 Chain: +Chain NYM-EXIT (1 references) + pkts bytes target prot opt in out source destination + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:706 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:706 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:5432 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:5432 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:2082:2083 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:2082:2083 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:8232:8233 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:8232:8233 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1500 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1500 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:123 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:123 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1293 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1293 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:11371 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:11371 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:443 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:443 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:110 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:110 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1194 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1194 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:3074 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:3074 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1521 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1521 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:2049 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:2049 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:88 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:88 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:995 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:995 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:5050 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:5050 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:43 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:43 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:991 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:991 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:143 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:143 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:5228 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:5228 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:445 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:445 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1755 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1755 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:993 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:993 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9001 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9001 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:5222:5223 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:5222:5223 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:20:21 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:20:21 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:60000:61000 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:60000:61000 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:2102:2104 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:2102:2104 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:873 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:873 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:27000:27050 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:27000:27050 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9418 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9418 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1863 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1863 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:8087:8088 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:8087:8088 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9030 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9030 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:4643 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:4643 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9339 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9339 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:902:904 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:902:904 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1533 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1533 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:5222:5223 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:5222:5223 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:20:21 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:20:21 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:60000:61000 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:60000:61000 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:2102:2104 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:2102:2104 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:873 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:873 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:27000:27050 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:27000:27050 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9418 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9418 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1863 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1863 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:8087:8088 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:8087:8088 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9030 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9030 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:4643 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:4643 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9339 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9339 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:902:904 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:902:904 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1533 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1533 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:2095:2096 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:2095:2096 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:5190 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:5190 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:749 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:749 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:4321 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:4321 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:10000 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:10000 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:53 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:53 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:19294 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:19294 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:220 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:220 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:8332:8333 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:8332:8333 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:64738 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:64738 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1723 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1723 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:8443 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:8443 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:8888 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:8888 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:2086:2087 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:2086:2087 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9735 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9735 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:554 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:554 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:853 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:853 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:22 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:22 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:8082 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:8082 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:992 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:992 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:25565 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:25565 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:3690 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:3690 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:464 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:464 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:981 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:981 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9053 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9053 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:50002 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:50002 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9443 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9443 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:389 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:389 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:80:81 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:80:81 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:27017 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:27017 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:5000:5005 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:5000:5005 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1433 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1433 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:8883 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:8883 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:3306 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:3306 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:8767 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:8767 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1677 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1677 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:19638 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:19638 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1220 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1220 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:79 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:79 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:989:990 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:989:990 + 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:636 + 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:636 + 0 0 REJECT 0 -- * * ::/0 ::/0 reject-with icmp6-port-unreachable + +ESC[0;33mIP Forwarding:ESC[0m +IPv4: 1 +IPv6: 1 +``` diff --git a/documentation/docs/components/operators/snippets/wg-exit-policy-test-output.mdx b/documentation/docs/components/operators/snippets/wg-exit-policy-test-output.mdx new file mode 100644 index 0000000000..c7ee424647 --- /dev/null +++ b/documentation/docs/components/operators/snippets/wg-exit-policy-test-output.mdx @@ -0,0 +1,47 @@ +```console +Running Nym Exit Policy Verification Tests... +Testing Port Range Rules... +Testing FTP tcp port range 20-21 +✓ Rule exists: NYM-EXIT tcp port range 20:21 +Testing HTTP tcp port range 80-81 +✓ Rule exists: NYM-EXIT tcp port range 80:81 +Testing CPanel tcp port range 2082-2083 +✓ Rule exists: NYM-EXIT tcp port range 2082:2083 +Testing XMPP tcp port range 5222-5223 +✓ Rule exists: NYM-EXIT tcp port range 5222:5223 +Testing Steam (sampling) tcp port range 27000-27050 +✓ Rule exists: NYM-EXIT tcp port range 27000:27050 +Testing FTP over TLS tcp port range 989-990 +✓ Rule exists: NYM-EXIT tcp port range 989:990 +Testing RTP/VoIP tcp port range 5000-5005 +✓ Rule exists: NYM-EXIT tcp port range 5000:5005 +Testing Simplify Media tcp port range 8087-8088 +✓ Rule exists: NYM-EXIT tcp port range 8087:8088 +Testing Zcash tcp port range 8232-8233 +✓ Rule exists: NYM-EXIT tcp port range 8232:8233 +Testing Bitcoin tcp port range 8332-8333 +✓ Rule exists: NYM-EXIT tcp port range 8332:8333 +Test test_port_range_rules PASSED +Testing Critical Service Rules... +✓ Rule exists: NYM-EXIT tcp port 22 +✓ Rule exists: NYM-EXIT tcp port 53 +✓ Rule exists: NYM-EXIT tcp port 443 +✓ Rule exists: NYM-EXIT tcp port 853 +✓ Rule exists: NYM-EXIT tcp port 1194 +✓ Rule exists: NYM-EXIT udp port 53 +✓ Rule exists: NYM-EXIT udp port 123 +✓ Rule exists: NYM-EXIT udp port 1194 +Relevant existing rules for HTTP (port 80): +Test test_critical_services PASSED +This test takes some time, do not quit the process + +Testing Default Reject Rule... + +✓ Default REJECT rule exists +Test test_default_reject_rule PASSED + +Test Summary: +Total Tests: 3 +Failures: 0 +All Tests Passed Successfully! +``` diff --git a/documentation/docs/components/operators/snippets/wg-exit-policy-testing-from-outside.mdx b/documentation/docs/components/operators/snippets/wg-exit-policy-testing-from-outside.mdx new file mode 100644 index 0000000000..ebc11c315c --- /dev/null +++ b/documentation/docs/components/operators/snippets/wg-exit-policy-testing-from-outside.mdx @@ -0,0 +1,9 @@ +- Install these dependencies on your local machine: +```shell +sudo apt install tcpdump + +sudo tcpdump -i nymwg -n +``` +- Connect to [NymVPN](https://nym.com) and select your node as an Exit Gateway (after running the exit policy [manager script](#wireguard-exit-policy-configuration)) +- Run the `tcpdump` command before registering +- Have the output of the `echo $BLOCKED_IP` from your node and try to go into your browser or a registered client and try to connect - It will not resolve. diff --git a/documentation/docs/components/operators/snippets/wg-exit-policy-testing-from-server.mdx b/documentation/docs/components/operators/snippets/wg-exit-policy-testing-from-server.mdx new file mode 100644 index 0000000000..02fe54adfa --- /dev/null +++ b/documentation/docs/components/operators/snippets/wg-exit-policy-testing-from-server.mdx @@ -0,0 +1,11 @@ +Run this command to define variable `BLOCKED_IP` and try to `ping` it: +```shell +BLOCKED_IP=$(grep "ExitPolicy reject" /etc/nym/exit-policy.txt | head -1 | sed -E 's/ExitPolicy reject ([^:]+):.*/\1/' | sed 's/\/.*$//') + +ping -c 3 $BLOCKED_IP +``` +You should see `100% packet loss` as an outcome. +```shell +telnet $BLOCKED_IP 80 +``` +You should see `telnet: Unable to connect to remote host: Connection timed out`. diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/circulating-supply.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/circulating-supply.md index 16f5edc4de..f71e380d34 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/circulating-supply.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/circulating-supply.md @@ -1 +1 @@ -807_251_217 +808_623_916 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md index 64cb46e4ca..e36dbca0e2 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md @@ -1 +1 @@ -44.332 +44.811 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/stake-saturation.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/stake-saturation.md index 11c0eeb3d3..6a6f41769e 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/stake-saturation.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/stake-saturation.md @@ -1 +1 @@ -1_025_628 +1_028_488 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking-target.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking-target.md index 8bf62f72c2..dd8fe066d0 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking-target.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/staking-target.md @@ -1 +1 @@ -403_625_608 +404_311_958 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/token-table.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/token-table.md index dabaa45e3a..d4a66cde69 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/token-table.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/token-table.md @@ -1,7 +1,7 @@ | **Item** | **Description** | **Amount in NYM** | |:-------------------|:------------------------------------------------------|--------------------:| | Total Supply | Maximum amount of NYM token in existence | 1_000_000_000 | -| Mixmining Reserve | Tokens releasing for operators rewards | 192_748_782 | +| Mixmining Reserve | Tokens releasing for operators rewards | 191_376_083 | | Vesting Tokens | Tokens locked outside of cicrulation for future claim | 0 | -| Circulating Supply | Amount of unlocked tokens | 807_251_217 | -| Stake Saturation | Optimal size of node self-bond + delegation | 1_025_628 | +| Circulating Supply | Amount of unlocked tokens | 808_623_916 | +| Stake Saturation | Optimal size of node self-bond + delegation | 1_028_488 | diff --git a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md index 6b6e11be89..6a98dad716 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md @@ -1 +1 @@ -Monday, February 3rd 2025, 13:47:19 UTC +Wednesday, March 19th 2025, 18:56:53 UTC diff --git a/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md b/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md index 32508afe64..1a70eacf1d 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md @@ -16,8 +16,10 @@ Options: If this is a brand new nym-node, specify whether it should only be initialised without actually running the subprocesses [env: NYMNODE_INIT_ONLY=] --local Flag specifying this node will be running in a local setting [env: NYMNODE_LOCAL=] - --mode - Specifies the current mode of this nym-node [env: NYMNODE_MODE=] [possible values: mixnode, entry-gateway, exit-gateway] + --mode [...] + Specifies the current mode(s) of this nym-node [env: NYMNODE_MODE=] [possible values: mixnode, entry-gateway, exit-gateway, exit-providers-only] + --modes + Specifies the current mode(s) of this nym-node as a single flag [env: NYMNODE_MODES=] [possible values: mixnode, entry-gateway, exit-gateway, exit-providers-only] -w, --write-changes If this node has been initialised before, specify whether to write any new changes to the config file [env: NYMNODE_WRITE_CONFIG_CHANGES=] --bonding-information-output @@ -31,7 +33,7 @@ Options: --location Optional **physical** location of this node's server. Either full country name (e.g. 'Poland'), two-letter alpha2 (e.g. 'PL'), three-letter alpha3 (e.g. 'POL') or three-digit numeric-3 (e.g. '616') can be provided [env: NYMNODE_LOCATION=] --http-bind-address - Socket address this node will use for binding its http API. default: `0.0.0.0:8080` [env: NYMNODE_HTTP_BIND_ADDRESS=] + Socket address this node will use for binding its http API. default: `[::]:8080` [env: NYMNODE_HTTP_BIND_ADDRESS=] --landing-page-assets-path Path to assets directory of custom landing page of this node [env: NYMNODE_HTTP_LANDING_ASSETS=] --http-access-token @@ -43,27 +45,29 @@ Options: --expose-crypto-hardware Specify whether detailed system crypto hardware information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_CRYPTO_HARDWARE=] [possible values: true, false] --mixnet-bind-address - Address this node will bind to for listening for mixnet packets default: `0.0.0.0:1789` [env: NYMNODE_MIXNET_BIND_ADDRESS=] + Address this node will bind to for listening for mixnet packets default: `[::]:1789` [env: NYMNODE_MIXNET_BIND_ADDRESS=] --mixnet-announce-port If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when the node is behind a proxy [env: NYMNODE_MIXNET_ANNOUNCE_PORT=] --nym-api-urls Addresses to nym APIs from which the node gets the view of the network [env: NYMNODE_NYM_APIS=] --nyxd-urls Addresses to nyxd chain endpoint which the node will use for chain interactions [env: NYMNODE_NYXD=] + --enable-console-logging + Specify whether running statistics of this node should be logged to the console [env: NYMNODE_ENABLE_CONSOLE_LOGGING=] [possible values: true, false] --wireguard-enabled Specifies whether the wireguard service is enabled on this node [env: NYMNODE_WG_ENABLED=] [possible values: true, false] --wireguard-bind-address - Socket address this node will use for binding its wireguard interface. default: `0.0.0.0:51822` [env: NYMNODE_WG_BIND_ADDRESS=] + Socket address this node will use for binding its wireguard interface. default: `[::]:51822` [env: NYMNODE_WG_BIND_ADDRESS=] --wireguard-announced-port Port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=] --wireguard-private-network-prefix The prefix denoting the maximum number of the clients that can be connected via Wireguard. The maximum value for IPv4 is 32 and for IPv6 is 128 [env: NYMNODE_WG_PRIVATE_NETWORK_PREFIX=] --verloc-bind-address - Socket address this node will use for binding its verloc API. default: `0.0.0.0:1790` [env: NYMNODE_VERLOC_BIND_ADDRESS=] + Socket address this node will use for binding its verloc API. default: `[::]:1790` [env: NYMNODE_VERLOC_BIND_ADDRESS=] --verloc-announce-port If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when the node is behind a proxy [env: NYMNODE_VERLOC_ANNOUNCE_PORT=] --entry-bind-address - Socket address this node will use for binding its client websocket API. default: `0.0.0.0:9000` [env: NYMNODE_ENTRY_BIND_ADDRESS=] + Socket address this node will use for binding its client websocket API. default: `[::]:9000` [env: NYMNODE_ENTRY_BIND_ADDRESS=] --announce-ws-port Custom announced port for listening for websocket client traffic. If unspecified, the value from the `bind_address` will be used instead [env: NYMNODE_ENTRY_ANNOUNCE_WS_PORT=] --announce-wss-port diff --git a/documentation/docs/package.json b/documentation/docs/package.json index cd0a1371c0..172d7c85da 100644 --- a/documentation/docs/package.json +++ b/documentation/docs/package.json @@ -45,7 +45,7 @@ "chain-registry": "^1.19.0", "cosmjs-types": "^0.9.0", "lucide-react": "^0.438.0", - "next": "^13.4.19", + "next": "^14.2.15", "nextra": "2", "nextra-theme-docs": "2", "react": "^18.2.0", diff --git a/documentation/docs/pages/apis/cosmos-sdk-nyx/_meta.json b/documentation/docs/pages/apis/cosmos-sdk-nyx/_meta.json new file mode 100644 index 0000000000..8e5aad88d7 --- /dev/null +++ b/documentation/docs/pages/apis/cosmos-sdk-nyx/_meta.json @@ -0,0 +1,5 @@ + +{ + "mainnet":"Mainnet Endpoints", + "sandbox":"Sandbox Endpoints" +} diff --git a/documentation/docs/pages/apis/explorer-api.mdx b/documentation/docs/pages/apis/explorer-api.mdx index 2dbb4f30ba..77cd141ddc 100644 --- a/documentation/docs/pages/apis/explorer-api.mdx +++ b/documentation/docs/pages/apis/explorer-api.mdx @@ -1,7 +1,7 @@ # Explorer API -The Explorer API is the backend for the [Mixnet Explorer](https://explorer.nymtech.net/). +The Explorer API is the backend for the [Mixnet Explorer](https://nym.com/explorer). **This will soon be deprecated in favour of the [Node Status API](ns-api.mdx).** diff --git a/documentation/docs/pages/apis/explorer-api/_meta.json b/documentation/docs/pages/apis/explorer-api/_meta.json new file mode 100644 index 0000000000..8e5aad88d7 --- /dev/null +++ b/documentation/docs/pages/apis/explorer-api/_meta.json @@ -0,0 +1,5 @@ + +{ + "mainnet":"Mainnet Endpoints", + "sandbox":"Sandbox Endpoints" +} diff --git a/documentation/docs/pages/apis/ns-api.mdx b/documentation/docs/pages/apis/ns-api.mdx index 85d2ea4566..9e14e1509c 100644 --- a/documentation/docs/pages/apis/ns-api.mdx +++ b/documentation/docs/pages/apis/ns-api.mdx @@ -1,11 +1,8 @@ import { Callout } from 'nextra/components' # Node Status API - -The Node Status API contains information about the network, its topology, and the routing scores of all nodes within it. It offers broadly similar information to the experimental [Habourmaster frontend](https://harbourmaster.nymtech.net/) but is stable where the Harbourmaster is subject to sudden changes as we modify and experiment with how we scrape and present data about the Mixnet infrastructure. +The Node Status API serves information about individual `nym-nodes` in the Mixnet, such as which role they are operating in, statistics about them, services such as Network Requesters, as well as summaries of the state of the Mixnet. -People building applications or dashboards which requires information about nodes, their uptime, and their delegations should use this instead of Habourmaster. +We recommend that developers building applications such as explorers or analytics interfaces about the Mixnet run their own instance of the API, in order to promote a robust network of downstream services, and spread the load of API calls amongst as many endpoints as possible. - -The code for this service can be found [in our monorepo](https://github.com/nymtech/nym/tree/develop/nym-node-status-api). In the future we will encourage developers to run their own instance of this API in order to distribute endpoints and query load. diff --git a/documentation/docs/pages/apis/ns-api/_meta.json b/documentation/docs/pages/apis/ns-api/_meta.json new file mode 100644 index 0000000000..7d443d9f6d --- /dev/null +++ b/documentation/docs/pages/apis/ns-api/_meta.json @@ -0,0 +1,5 @@ +{ + "ns-api-run-deploy":"Run Instance", + "mainnet":"Mainnet Endpoints", + "sandbox":"Sandbox Endpoints" +} diff --git a/documentation/docs/pages/apis/ns-api/mainnet.mdx b/documentation/docs/pages/apis/ns-api/mainnet.mdx index a517ba0edd..00a6523ae7 100644 --- a/documentation/docs/pages/apis/ns-api/mainnet.mdx +++ b/documentation/docs/pages/apis/ns-api/mainnet.mdx @@ -1,9 +1,11 @@ import { RedocStandalone } from 'redoc'; +import { Callout } from 'nextra/components' The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json](https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://mainnet-node-status-api.nymtech.cc/swagger/](https://mainnet-node-status-api.nymtech.cc/swagger/).

+ +We invite developers to roll their own UI for their Node Status API instance. + + +## Docker Images +We will ship Docker images for both the `agent` and `api` in the future. There are Docker images for both in root of each corresponding crate ([`agent`](https://github.com/nymtech/nym/blob/09ea406c02e9a3beebc062f525e4ea1b4222dcbb/nym-node-status-api/nym-node-status-agent/Dockerfile), [`api`](https://github.com/nymtech/nym/blob/develop/nym-node-status-api/nym-node-status-api/Dockerfile)) which are used internally, which could be a starting point for developers to dockerize their instance for the moment. + +## Build +### Prerequisites +- Rust +- SQLite +- Get an `ipinfo` key following instructions [here](https://github.com/ipinfo/rust?tab=readme-ov-file#getting-started). + +### Compilation +```shell +cargo build --release --package nym-node-status-api --package nym-node-status-agent --package nym-node-status-client +``` + +## Run +Since the Node Status API depends on both flags and environmental variables, it might be easier to run the binary via a script like the one below - this this script essentially just `source`-s the defined `.env` file after exporting certain binary-specific variables, and then runs the binary. You can find the `.env` files [here](https://github.com/nymtech/nym/tree/master/envs). + + + All CLI flags are configurable as environmental variables and vice versa, so take the following scripts / setups as guides that you can change however best suits your setup. You can see all definitions [here](https://github.com/nymtech/nym/blob/develop/nym-node-status-api/nym-node-status-api/src/cli/mod.rs#L14). + + +```bash +#!/bin/bash + +set -e + +export ENVIRONMENT=${ENVIRONMENT:-"mainnet"} # see nym/envs/ for all possible environments +export NYM_API_CLIENT_TIMEOUT=60 +export NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL=120 +export IPINFO_API_TOKEN= + +monorepo_root= +set -a +source "${monorepo_root}/envs/${ENVIRONMENT}.env" +echo ${monorepo_root}/envs/${ENVIRONMENT}.env +set +a +export RUST_LOG=${RUST_LOG:-debug} # debug is useful to check everything is working initially, but quite verbose + +echo "Verifying environment variables were properly sourced:" +echo "RUST_LOG=${RUST_LOG}" +echo "BECH32_PREFIX=${BECH32_PREFIX}" +echo "NETWORK_NAME=${NETWORK_NAME}" + +nym-node-status-api -- --ipinfo-api-token $IPINFO_API_TOKEN +``` + +### Functionality Without Gateway Probe +Data will be restricted to information that doesn't involve Probe results; `routing` and `config` scores will be `0` and `last_probe` results `null`, as you can see in this snipped output of the `gateways` endpoint: + + +```shell +{ + "gateway_identity_key": "23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb", + "bonded": true, + "performance": 99, + "self_described": { + "authenticator": { + "address": "6Gdtw13Fa46AvkqkHELZZCMKWASDodoJeK9APRNpjjdj.7ji8DDkpjA2AdgwK7wbZm8yi4xZGogGJeypBQt4hAw3P@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb" + }, + "auxiliary_details": { + "accepted_operator_terms_and_conditions": true, + "announce_ports": { + "mix_port": null, + "verloc_port": null + }, + "location": null + }, + "build_information": { + "binary_name": "nym-node", + "build_timestamp": "2025-02-13T11:49:34.670488195Z", + "build_version": "1.5.0", + "cargo_profile": "release", + "cargo_triple": "x86_64-unknown-linux-gnu", + "commit_branch": "HEAD", + "commit_sha": "a3e19b4563843055b305ea9a397eb1ad84b5c378", + "commit_timestamp": "2025-02-10T18:14:47.000000000+01:00", + "rustc_channel": "stable", + "rustc_version": "1.84.1" + }, + "declared_role": { + "entry": true, + "exit_ipr": true, + "exit_nr": true, + "mixnode": false + }, + "host_information": { + "hostname": "bwng1.bwnym.xyz", + "ip_address": [ + "95.164.2.86" + ], + "keys": { + "ed25519": "23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb", + "x25519": "H6pFjqtdSVxkxEQ3wFnuSoobDAUqHx1bYMVJzPZdRByn", + "x25519_noise": null + } + }, + "ip_packet_router": { + "address": "7ms2D2uYiTuhX6MKeVL5rz5usgehEoxAAovwYm9nJyBF.3siMjk3wTU7ykaXLNi9c7LpX8yonYKPCA4BQoMwhsfTV@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb" + }, + "last_polled": "2025-03-03 09:48:03.635274187 +00:00:00", + "mixnet_websockets": { + "ws_port": 9000, + "wss_port": 9001 + }, + "network_requester": { + "address": "HuNL1pFprNSKW6jdqppibXP5KNKCNJxDh7ivpYcoULN9.C62NahRTUf6kqpNtDVHXoVriQr6yyaU5LtxdgpbsGrtA@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb", + "uses_exit_policy": true + }, + "wireguard": { + "port": 51822, + "public_key": "6o8x9GitFjcrkjrJnivWaQCPnxXykQPYLneNr2FEB8Vq" + } + }, + "explorer_pretty_bond": { + "identity_key": "23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb", + "location": { + "latitude": 52.5083, + "longitude": 5.475, + "two_letter_iso_country_code": "NL" + }, + "owner": "n1cp5gq0apat6c7qmenqp5zjprn2vwvc7jl29j8r", + "pledge_amount": { + "amount": "100000000", + "denom": "unym" + } + }, + "description": { + "moniker": "bwn_g1", + "website": "https://bwnym.xyz", + "security_contact": "bwnym@proton.me", + "details": "This gateway is part of the NYM project, which is dedicated to create outstanding privacy software that is legally compliant without sacrificing integrity or having any backdoors." + }, + "last_probe_result": null, + "last_probe_log": null, + "last_testrun_utc": null, + "last_updated_utc": "2025-03-03T10:45:48+00:00", + "routing_score": 0.0, + "config_score": 0 + }, +``` + + +If you have already run the API before, make sure to add the following to your script `--database-url "sqlite://node-status-api.sqlite?mode=rwc` so it will continue using the same DB. + +### Functionality with Gateway Probe +If you want to enable Gateway node probes and have the NS API store that data, you need to periodically run the `nym-node-status-agent`, authenticated with your `nym-node-status-api` instance. Authentication is to make sure that only the `-agent` you are operating (or others you trust) are submitting data to your API instance. + +#### Compile Gateway Probe +The `nym-node-status-agent` is a thin wrapper around the Gateway Probe binary which currently is in the NymVPN repo. `git checkout` to the most recent [release](https://github.com/nymtech/nym-vpn-client/releases), and compile the probe by following the [readme instructions](https://github.com/nymtech/nym-vpn-client/tree/develop/nym-vpn-core/crates/nym-gateway-probe). You will point the `-agent` at this binary when doing Probe testruns. + +#### Generate Keypair +```shell +nym-node-status-agent generate-keypair --path / +# e.g. +# nym-node-status-agent generate-keypair --path ~/.ssh/ns-agent-key +``` + +You will then want to export the generated `public-key` so its accessible to the `nym-node-status-api` however you are setting your environmental variables, as `NODE_STATUS_API_AGENT_KEY_LIST`: + +```bash +export NODE_STATUS_API_AGENT_KEY_LIST= # e.g. "H4z8kx5Kkf5JNQHfxaE1MwRndjDCD1C7HsVhHTFfBZ4J" +``` + +In this situation, you are probably only using one key. However, it is possible to set multiple keys as a comma seperated list, in case you wish to whitelist multiple `-agent`s to be able to submit to a single `-api` instance. + +#### Run the Node Status Agent +```shell +nym-node-status-agent run-probe --server-address http://127.0.0.1 --server-port 8000 --ns-api-auth-key "" --probe-path nym-gateway-probe +``` + +You will see a lot of output like so: + + +```shell +listen_port=48586 +public_key=3f95caf771b8a63b9bdae6c186f7aba2eae93483f1c82c0243b5e00c85b4ec26 +preshared_key=0000000000000000000000000000000000000000000000000000000000000000 +protocol_version=1 +endpoint=185.186.78.251:51822 +last_handshake_time_sec=0 +last_handshake_time_nsec=0 +tx_bytes=0 +rx_bytes=0 +persistent_keepalive_interval=0 +allowed_ip=0.0.0.0/0 +2025/03/03 18:58:28 Pinging nymtech.net seq=0 +2025/03/03 18:58:29 Ping latency: 44.503483ms +2025/03/03 18:58:29 Pinging nymtech.net seq=1 +2025/03/03 18:58:29 Ping latency: 42.852414ms +2025/03/03 18:58:29 Pinging nymtech.net seq=2 +2025/03/03 18:58:29 Ping latency: 43.627256ms +2025/03/03 18:58:29 Pinging nymtech.net seq=3 +2025/03/03 18:58:29 Ping latency: 43.638839ms +2025/03/03 18:58:29 Pinging nymtech.net seq=4 +2025/03/03 18:58:29 Ping latency: 43.345357ms +2025/03/03 18:58:29 Pinging 1.1.1.1 seq=0 +2025/03/03 18:58:29 Ping latency: 46.327233ms +2025/03/03 18:58:34 Pinging 1.1.1.1 seq=1 +2025/03/03 18:58:34 Ping latency: 46.273726ms +2025/03/03 18:58:39 Pinging 1.1.1.1 seq=2 +2025/03/03 18:58:39 Ping latency: 46.542774ms +2025/03/03 18:58:44 Pinging 1.1.1.1 seq=3 +2025/03/03 18:58:44 Ping latency: 45.663545ms +2025/03/03 18:58:49 Pinging 1.1.1.1 seq=4 +2025/03/03 18:58:49 Ping latency: 43.803063ms +2025/03/03 18:58:56 Downloaded file content length: 1.00 MB +2025/03/03 18:58:56 Download duration: 1.308072386s +2025/03/03 18:58:56 private_key=1083749e43f4f8fb008f3f7deef9107ef86a68969670ddbb9f07bf85f94fb564 +listen_port=39129 +public_key=3f95caf771b8a63b9bdae6c186f7aba2eae93483f1c82c0243b5e00c85b4ec26 +preshared_key=0000000000000000000000000000000000000000000000000000000000000000 +protocol_version=1 +endpoint=185.186.78.251:51822 +last_handshake_time_sec=0 +last_handshake_time_nsec=0 +tx_bytes=0 +rx_bytes=0 +persistent_keepalive_interval=0 +allowed_ip=::/0 +2025/03/03 18:58:56 Pinging ipv6.google.com seq=0 +2025/03/03 18:58:56 Ping latency: 42.839528ms +2025/03/03 18:58:56 Pinging ipv6.google.com seq=1 +2025/03/03 18:58:56 Ping latency: 54.844651ms +2025/03/03 18:58:56 Pinging ipv6.google.com seq=2 +2025/03/03 18:58:56 Ping latency: 51.23104ms +2025/03/03 18:58:56 Pinging ipv6.google.com seq=3 +2025/03/03 18:58:56 Ping latency: 43.320409ms +2025/03/03 18:58:56 Pinging ipv6.google.com seq=4 +2025/03/03 18:58:56 Ping latency: 63.517358ms +2025/03/03 18:58:56 Pinging 2001:4860:4860::8888 seq=0 +2025/03/03 18:58:56 Ping latency: 54.682534ms +2025/03/03 18:59:01 Pinging 2001:4860:4860::8888 seq=1 +2025/03/03 18:59:01 Ping latency: 55.56235ms +2025/03/03 18:59:06 Pinging 2001:4860:4860::8888 seq=2 +2025/03/03 18:59:06 Ping latency: 55.970418ms +2025/03/03 18:59:11 Pinging 2001:4860:4860::8888 seq=3 +2025/03/03 18:59:14 Failed to send ping: i/o timeout +2025/03/03 18:59:19 Pinging 2001:4860:4860::8888 seq=4 +2025/03/03 18:59:22 Failed to send ping: i/o timeout +2025/03/03 18:59:27 Pinging 2606:4700:4700::1111 seq=0 +2025/03/03 18:59:27 Ping latency: 45.072616ms +2025/03/03 18:59:32 Pinging 2606:4700:4700::1111 seq=1 +2025/03/03 18:59:32 Ping latency: 44.357306ms +2025/03/03 18:59:37 Pinging 2606:4700:4700::1111 seq=2 +2025/03/03 18:59:37 Ping latency: 44.013562ms +2025/03/03 18:59:42 Pinging 2606:4700:4700::1111 seq=3 +2025/03/03 18:59:42 Ping latency: 46.94342ms +2025/03/03 18:59:47 Pinging 2606:4700:4700::1111 seq=4 +2025/03/03 18:59:48 Ping latency: 43.372288ms +2025/03/03 18:59:53 Pinging 2620:fe::fe seq=0 +2025/03/03 18:59:53 Ping latency: 42.164952ms +2025/03/03 18:59:58 Pinging 2620:fe::fe seq=1 +2025/03/03 18:59:58 Ping latency: 42.295812ms +2025/03/03 19:00:03 Pinging 2620:fe::fe seq=2 +2025/03/03 19:00:03 Ping latency: 43.117534ms +2025/03/03 19:00:08 Pinging 2620:fe::fe seq=3 +2025/03/03 19:00:08 Ping latency: 44.26068ms +2025/03/03 19:00:13 Pinging 2620:fe::fe seq=4 +2025/03/03 19:00:13 Ping latency: 45.29956ms +2025/03/03 19:00:21 Downloaded file content length: 10.00 MB +2025/03/03 19:00:21 Download duration: 3.39529252s +``` + + +Whilst you can run the `-agent` directly, it might be easier to run multiple instances in parallel with a script like so: + +```bash +#!/bin/bash + +set -eu +export ENVIRONMENT=${ENVIRONMENT:-"mainnet"} + +probe_git_ref="nym-vpn-core-v1.4.0" # check for the most recent release +monorepo_root= + +set -a +source "${monorepo_root}/envs/${ENVIRONMENT}.env" +set +a + +export RUST_LOG="info" +export NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1" +export NODE_STATUS_AGENT_SERVER_PORT="8000" +export NODE_STATUS_AGENT_AUTH_KEY= +export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1" + +workers=${1:-1} +echo "Running $workers workers in parallel" + +function swarm() { + local workers=$1 + + for ((i = 1; i <= workers; i++)); do + ${monorepo_root}/target/release/nym-node-status-agent run-probe --probe-path ~//nym-vpn-client/nym-vpn-core/target/debug/nym-gateway-probe & + done + + wait + + echo "All agents completed" +} + +swarm $workers +``` + +And run specifying the number of workers with `./.sh `. + + +When running the probe, use logging level `RUST_LOG=info`. The Node Status API relies on that granularity for parsing probe results, and the logs grow incessantly if a lower level (e.g. `DEBUG`) is used. + + +### Ports +By default the API listens on `8000`, so you will need to configure this post to be reachable on your remote server. You can modify this with the `--http_port` flag. diff --git a/documentation/docs/pages/apis/nym-api/_meta.json b/documentation/docs/pages/apis/nym-api/_meta.json new file mode 100644 index 0000000000..3949bf02de --- /dev/null +++ b/documentation/docs/pages/apis/nym-api/_meta.json @@ -0,0 +1,3 @@ +{ + "mainnet":"Mainnet Endpoints" +} diff --git a/documentation/docs/pages/developers/clients/socks5/setup.mdx b/documentation/docs/pages/developers/clients/socks5/setup.mdx index 43f9c86171..59e46c2c19 100644 --- a/documentation/docs/pages/developers/clients/socks5/setup.mdx +++ b/documentation/docs/pages/developers/clients/socks5/setup.mdx @@ -30,7 +30,7 @@ The `--id` in the example above is a local identifier so that you can name your The `--use-reply-surbs` field denotes whether you wish to send [SURBs](../../network/concepts/anonymous-replies) along with your request. It defaults to `false`, we are explicitly setting it as `true`. It defaults to `false` for compatibility with versions of the pre-smoosh `nym-network-requester` binary which will soon be deprecated. -The `--provider` field needs to be filled with the Nym address of an Exit Gateway that can make network requests on your behalf. You can select one from the [mixnet explorer](https://explorer.nymtech.net/network-components/gateways) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [Harbourmaster](https://harbourmaster.nymtech.net/). +The `--provider` field needs to be filled with the Nym address of an Exit Gateway that can make network requests on your behalf. You can select one from the [mixnet explorer](https://nym.com/explorer) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [Harbourmaster](https://harbourmaster.nymtech.net/). ## Choosing a Gateway By default - as in the example above - your client will choose a random gateway to connect to. @@ -42,6 +42,6 @@ However, there are several options for choosing a gateway, if you do not want on * send few ping messages to all of them, and measure response times. * create a weighted distribution to randomly choose one, favouring ones with lower latency. -You can select one from the [mixnet explorer](https://explorer.nymtech.net/network-components/gateways) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [Harbourmaster](https://harbourmaster.nymtech.net/). +You can select one from the [mixnet explorer](https://nym.com/explorer) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [Harbourmaster](https://harbourmaster.nymtech.net/). > Note this doesn't mean that your client will pick the closest gateway to you, but it will be far more likely to connect to gateway with a 20ms ping rather than 200ms diff --git a/documentation/docs/pages/developers/clients/websocket/setup.md b/documentation/docs/pages/developers/clients/websocket/setup.md index 5c06faa69c..1cf0606626 100644 --- a/documentation/docs/pages/developers/clients/websocket/setup.md +++ b/documentation/docs/pages/developers/clients/websocket/setup.md @@ -31,7 +31,7 @@ Initialising a new client instance can be done with the following command: The `--id` in the example above is a local identifier so that you can name your clients; it is **never** transmitted over the network. -There is an optional `--gateway` flag that you can use if you want to use a specific gateway. The supplied argument is the `Identity Key` of the gateway you wish to use, which can be found on the [mixnet explorer](https://explorer.nymtech.net/network-components/gateways). Alternatively, you could use [Harbourmaster](https://harbourmaster.nymtech.net/) +There is an optional `--gateway` flag that you can use if you want to use a specific gateway. The supplied argument is the `Identity Key` of the gateway you wish to use, which can be found on the [mixnet explorer](https://nym.com/explorer). Alternatively, you could use [Harbourmaster](https://harbourmaster.nymtech.net/) Not passing this argument will randomly select a gateway for your client. diff --git a/documentation/docs/pages/developers/typescript/FAQ.mdx b/documentation/docs/pages/developers/typescript/FAQ.mdx index 22696a5e5b..5998a1e0a8 100644 --- a/documentation/docs/pages/developers/typescript/FAQ.mdx +++ b/documentation/docs/pages/developers/typescript/FAQ.mdx @@ -19,4 +19,4 @@ To illustrate this: `DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuert - `DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko`: is the client's identity key; - `ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx`: is the client's Diffie Hellman key; -- `2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh`: is the gateway's identity, which is what you'll need to check the state of the gateway in the [Nym Explorer](https://explorer.nymtech.net/network-components/gateways). +- `2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh`: is the gateway's identity, which is what you'll need to check the state of the gateway in the [Nym Explorer](https://nym.com/explorer). diff --git a/documentation/docs/pages/operators/_meta.json b/documentation/docs/pages/operators/_meta.json index 0d4ef647cf..b7c579746d 100644 --- a/documentation/docs/pages/operators/_meta.json +++ b/documentation/docs/pages/operators/_meta.json @@ -5,7 +5,7 @@ "variables": "Variables & Parameters", "sandbox": "Sandbox Testnet", "binaries": "Binaries", - "nodes": "Operator Guides", + "nodes": "Nodes & Validators Guides", "troubleshooting": "Troubleshooting", "tokenomics": "Tokenomics", "faq": "FAQ", diff --git a/documentation/docs/pages/operators/changelog.mdx b/documentation/docs/pages/operators/changelog.mdx index c4c88b6812..0c2ceb54fb 100644 --- a/documentation/docs/pages/operators/changelog.mdx +++ b/documentation/docs/pages/operators/changelog.mdx @@ -47,6 +47,398 @@ This page displays a full list of all the changes during our release cycle from +## `v2025.5-chokito` + +- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.5-chokito) +- [`nym-node`](nodes/nym-node.mdx) version `1.7.0` + +```shell +nym-node +Binary Name: nym-node +Build Timestamp: 2025-03-18T12:17:10.782554174Z +Build Version: 1.7.0 +Commit SHA: 82e82943aa14e530ab4eb3aaab28ce86d47e5298 +Commit Date: 2025-03-18T10:39:55.000000000+01:00 +Commit Branch: HEAD +rustc Version: 1.85.0 +rustc Channel: stable +cargo Profile: release +``` + +### Release Summary + +1. WebSocket Connection Counter Fix (this is the fix for which we have a [pre-release branch](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.4-dorina-nym-node-patch) out already which will be replaced by this release) + - Prevents incorrect session tracking for WebSockets + +2. Security Fixes in Dependencies + - Address known security vulnerabilities, making updating highly recommended + +3. Authorisation & Timestamp Changes + - Modifies how timestamps are validated in Auth v2 - this is going to be what the clients use. For example: the clients will start to use v2 variants of everything (prepping the network for the breaking change to come on April 1st which will force clients to only use v2). + +4. Wireguard exit policy [scripts](https://github.com/nymtech/nym/tree/develop/scripts/wireguard-exit-policy) + + +### Operators Updates & Tools + +- [**Wireguard exit policy is out!**](nodes/nym-node/configuration#wireguard-exit-policy-configuration) Operators can use a new guide with our scripts to setup wireguard exit policy using IP tables rules. + +- [Setup commands for `nym-node` simplified](nodes/nym-node/setup#setup--run): `config.toml` defaults to correct binding addresses and ports, no need to specify unless operators want to change those values. + +- [Virtualising dedicated server with KVM now includes IPv6 setup](nodes/preliminary-steps/vps-setup/advanced#installing-kvm-on-a-server-with-ubuntu-2204): Thanks to [LunarDAO](https://lunardao.net) squad we caught a missing IPv6 configuration in KVM installation guide. Operators can now run a bare metal or dedicated server and virtualise it into VMs each with a fully functional node, having all specs fully under control. + +- [NTP time synchronization guide](nodes/preliminary-steps/vps-setup#2-synchronize-time-of-your-server): Some servers had an issue with incorrect time. Please use [this guide](nodes/preliminary-steps/vps-setup#2-synchronize-time-of-your-server) to update time on your server to synchronize your node operation with the rest of the network. + +- [New community managed token page by SpectreDAO](https://explorer.nym.spectredao.net/token): Based on community request, one of the squad administrating several Nym Nodes created a live token dashboard [explorer.nym.spectredao.net/token](https://explorer.nym.spectredao.net/token). + + +### Features + +- [Display error messages if IPv4 or IPv6 address not found on nymtun0](https://github.com/nymtech/nym/pull/5465): This is with regards to issue [\#5461](https://github.com/nymtech/nym/pull/5461) + +- [Connection `fd` callback before actual connection](https://github.com/nymtech/nym/pull/5494) + +- [Add extra args for the probe](https://github.com/nymtech/nym/pull/5499) + +- [Use ct_eq for checking bearer token](https://github.com/nymtech/nym/pull/5501) + +- [Update version in Cargo.toml](https://github.com/nymtech/nym/pull/5503) + +- [Treat Gateways as Nym Nodes](https://github.com/nymtech/nym/pull/5504): entry/exit nodes are also scraped for description & packets mixed data. Generate a random moniker for nodes that don't have one set. + +- [Bump the patch-updates group with 2 updates](https://github.com/nymtech/nym/pull/5505): [log](https://github.com/rust-lang/log) and [tar](https://github.com/alexcrichton/tar-rs). + +- [Bump itertools from `0.13.0` to `0.14.0`](https://github.com/nymtech/nym/pull/5509): Bumps [itertools](https://github.com/rust-itertools/itertools). + +- [Bump flate2 from `1.0.35` to `1.1.0`](https://github.com/nymtech/nym/pull/5510): Bumps [flate2](https://github.com/rust-lang/flate2-rs). + +- [Make sure any terminated task kills the watcher and write run info to db](https://github.com/nymtech/nym/pull/5517) + +- [Make "Memo" visible per default on send NYM](https://github.com/nymtech/nym/pull/5524) + +- [Disallow routing mix packets to nodes not present in the topology](https://github.com/nymtech/nym/pull/5526) + +- [Set `RUSTUP_PERMIT_COPY_RENAME`](https://github.com/nymtech/nym/pull/5533): Try to fix rustup issues on our self-hosted runners by setting `RUSTUP_PERMIT_COPY_RENAME` + +- [v2 authentication request](https://github.com/nymtech/nym/pull/5537): This PR creates new v2 variant of client-gateway authentication message that increases protection against possible connection hijacking. + +- [Bump `rs_merkle` from `1.4.2` to `1.5.0`](https://github.com/nymtech/nym/pull/5541): Bumps [rs_merkle](https://github.com/antouhou/rs-merkle) + +- [Bump `uuid` from `1.13.2` to `1.15.1`](https://github.com/nymtech/nym/pull/5542): Bumps [`uuid`](https://github.com/uuid-rs/uuid) + +- [Bump the `patch-updates` group across 1 directory with 14 updates](https://github.com/nymtech/nym/pull/5549): + +| Package | From | To | +| --- | --- | --- | +| [`anyhow`](https://github.com/dtolnay/anyhow) | `1.0.96` | `1.0.97` | +| [`async-trait`](https://github.com/dtolnay/async-trait) | `0.1.86` | `0.1.87` | +| [`blake3`](https://github.com/BLAKE3-team/BLAKE3) | `1.6.0` | `1.6.1` | +| [`chrono`](https://github.com/chronotope/chrono) | `0.4.39` | `0.4.40` | +| [`clap`](https://github.com/clap-rs/clap) | `4.5.30` | `4.5.31` | +| [`clap_complete`](https://github.com/clap-rs/clap) | `4.5.45` | `4.5.46` | +| [`console`](https://github.com/console-rs/console) | `0.15.10` | `0.15.11` | +| [`getset`](https://github.com/jbaublitz/getset) | `0.1.4` | `0.1.5` | +| [`pin-project`](https://github.com/taiki-e/pin-project) | `1.1.9` | `1.1.10` | +| [`quote`](https://github.com/dtolnay/quote) | `1.0.38` | `1.0.39` | +| [`schemars`](https://github.com/GREsau/schemars) | `0.8.21` | `0.8.22` | +| [`serde_bytes`](https://github.com/serde-rs/bytes) | `0.11.15` | `0.11.16` | +| [`serde_json`](https://github.com/serde-rs/json) | `1.0.139` | `1.0.140` | +| [`thiserror`](https://github.com/dtolnay/thiserror) | `2.0.11` | `2.0.12` | + +- [Start sending v2 sphinx packets](https://github.com/nymtech/nym/pull/5554) + +- [Add /v3/nym-nodes](https://github.com/nymtech/nym/pull/5569): Returns extended node info from local DB + - Endpoint caching + - Add `bond_info` & `self_described` to DB `nym_nodes` + - Update mixnode & gateway bond status on data refresh + - Add `active` column to DB `nym_nodes` + - Use only active & bonded nodes in scraping/testrun tasks + +- [Rust SDK SURB example: change hardcoded file to tempdir](https://github.com/nymtech/nym/pull/5576): Change hardcoded filepath for client to tempdir + +- [Server Side internal DoT/DoH opt out](https://github.com/nymtech/nym/pull/5577): Allow server side (nodes and nym-api) to use nym-http-api-client without DoH/DoT resolver. + +- [Delete double memo field in send modal](https://github.com/nymtech/nym/pull/5578) + +- [Bump ring from `0.17.3` to `0.17.13` in /nym-wallet](https://github.com/nymtech/nym/pull/5582): Bumps [ring](https://github.com/briansmith/ring) + +- [Bump the patch-updates group with 8 updates](https://github.com/nymtech/nym/pull/5585): + +| Package | From | To | +| --- | --- | --- | +| [`bytes`](https://github.com/tokio-rs/bytes) | `1.10.0` | `1.10.1` | +| [`semver`](https://github.com/dtolnay/semver) | `1.0.25` | `1.0.26` | +| [`serde`](https://github.com/serde-rs/serde) | `1.0.218` | `1.0.219` | +| [`serde_bytes`](https://github.com/serde-rs/bytes) | `0.11.16` | `0.11.17` | +| [`serde_derive`](https://github.com/serde-rs/serde) | `1.0.218` | `1.0.219` | +| [`serde_repr`](https://github.com/dtolnay/serde-repr) | `0.1.19` | `0.1.20` | +| [`time`](https://github.com/time-rs/time) | `0.3.37` | `0.3.39` | +| [`ff`](https://github.com/zkcrypto/ff) | `0.13.0` | `0.13.1` | + +- [Bump `tokio` from `1.43.0` to `1.44.0`](https://github.com/nymtech/nym/pull/5587): Bumps [tokio](https://github.com/tokio-rs/tokio) + +- [Bump `tempfile` from `3.17.1` to `3.18.0`](https://github.com/nymtech/nym/pull/5588): Bumps [tempfile](https://github.com/Stebalien/tempfile) + +- [Bump `webpack-dev-middleware` from `5.3.3` to `5.3.4` in `/wasm/zknym-lib/internal-dev]`(https://github.com/nymtech/nym/pull/5589): Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) + +- [Bump `braces` from `3.0.2` to `3.0.3` in /wasm/zknym-lib/internal-dev](https://github.com/nymtech/nym/pull/5590): Bumps [braces](https://github.com/micromatch/braces) + +- [Bump `cookie` and `express` in `/wasm/client/internal-dev`](https://github.com/nymtech/nym/pull/5592): Bumps [cookie](https://github.com/jshttp/cookie) + +- [Bump `ws` from `8.13.0` to `8.18.1` in `/wasm/mix-fetch/internal-dev`](https://github.com/nymtech/nym/pull/5593): Bumps [ws](https://github.com/websockets/ws) + +- [Bump `serve-static` and `express` in `/wasm/mix-fetch/internal-dev`](https://github.com/nymtech/nym/pull/5594): Bumps [serve-static](https://github.com/expressjs/serve-static) + +- [Bump `body-parser` and express in `/wasm/mix-fetch/internal-dev`](https://github.com/nymtech/nym/pull/5596): Bumps [body-parser](https://github.com/expressjs/body-parser) + +- [Bump `webpack` from `5.77.0` to `5.98.0` in /wasm/mix-fetch/internal-dev`](https://github.com/nymtech/nym/pull/5597): Bumps [webpack](https://github.com/webpack/webpack) + +- [Introduce internal tool for checking signer status](https://github.com/nymtech/nym/pull/5598) + +- [Allow resetting all `SURB` sender tags](https://github.com/nymtech/nym/pull/5600) + +- [Chore/payment watcher debug endpoints](https://github.com/nymtech/nym/pull/5601) + +- [Change `auth` v2 timestamp skew and allow values from the future](https://github.com/nymtech/nym/pull/5604) + +- [Update bls12 381 fork](https://github.com/nymtech/nym/pull/5605) + +- [Bump `@babel/helpers` from `7.24.4` to `7.26.10`](https://github.com/nymtech/nym/pull/5606): Bumps [@babel/helpers](https://github.com/babel/babel/tree/HEAD/packages/babel-helpers) + +- [Chore/more payment watcher debug endpoints](https://github.com/nymtech/nym/pull/5608) + +- [Export lane queue lengths in sdk](https://github.com/nymtech/nym/pull/5609): Export the lane queue length type to be able to implement backpressure outside of the sdk mixnet client + +- [Bump `webpack-dev-middleware` from `5.3.3` to `5.3.4` in `/wasm/client/internal-dev`](https://github.com/nymtech/nym/pull/5610): Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) + +- [Bump `braces` from `3.0.2` to `3.0.3` in `/sdk/typescript/packages/nodejs-client`](https://github.com/nymtech/nym/pull/5611): Bumps [braces](https://github.com/micromatch/braces) + +- [Remove explorer-api from ci-build-binaries](https://github.com/nymtech/nym/pull/5616) + +### Bugfix + +- [`Cargo.lock` for contracts](https://github.com/nymtech/nym/pull/5489) + +- [Fix stats bug & remove HM caching](https://github.com/nymtech/nym/pull/5495) + +- [Fix `total_stake` on SQL update](https://github.com/nymtech/nym/pull/5514) + +- [Another `total_stake` SQL fix](https://github.com/nymtech/nym/pull/5516) + +- [Correctly increment `ws` connection counter](https://github.com/nymtech/nym/pull/5620) + +## `v2025.4-dorina-patched` + +Patched version of `dorina` with a few fixes and tweaks to the release. We would like to ask `nym-node` operators to upgrade to this version as quickly as possible to implement the fixes across the network and improve general quality before NymVPN launch. + +- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.4-dorina-patched) +- [`nym-node`](nodes/nym-node.mdx) version `1.6.2` + +```shell +nym-node +Binary Name: nym-node +Build Timestamp: 2025-03-06T20:32:36.922212778Z +Build Version: 1.6.2 +Commit SHA: 247ebb7c4339de0a298a7fcb2574122a8306c3b8 +Commit Date: 2025-03-06T21:26:16.000000000+01:00 +Commit Branch: HEAD +rustc Version: 1.85.0 +rustc Channel: stable +cargo Profile: release +``` + +- Use legacy crypto for constructing SURB headers ([#5579]) +- Bugfix: make sure to correctly decode response content when putting it into error message ([#5571]) +- Tweak surb management to be more conservative ([#5570]) +- Deserialize v5 authenticator requests ([#5568]) +- Chore: additional logs when attempting to load ecash keys ([#5567]) +- Add full response body to error message upon decoding failure ([#5566]) +- Hotfix: ensure we bail on merkle leaves insertion upon missing data ([#5565]) +- Feature: v2 authentication request (#5537) ([#5563]) +- Create authenticator v5 request/response types ([#5561]) + +[#5579]: https://github.com/nymtech/nym/pull/5579 +[#5571]: https://github.com/nymtech/nym/pull/5571 +[#5570]: https://github.com/nymtech/nym/pull/5570 +[#5568]: https://github.com/nymtech/nym/pull/5568 +[#5567]: https://github.com/nymtech/nym/pull/5567 +[#5566]: https://github.com/nymtech/nym/pull/5566 +[#5565]: https://github.com/nymtech/nym/pull/5565 +[#5563]: https://github.com/nymtech/nym/pull/5563 +[#5561]: https://github.com/nymtech/nym/pull/5561 + + +## `v2025.4-dorina` +- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.4-dorina) +- [`nym-node`](nodes/nym-node.mdx) version `1.6.0` +```shell +Binary Name: nym-node +Build Timestamp: 2025-03-04T09:03:11.322601809Z +Build Version: 1.6.0 +Commit SHA: 7060fa6dad58f17543f5086c73b1854ad1ceae60 +Commit Date: 2025-03-03T17:24:10.000000000Z +Commit Branch: release/2025.4-dorina +rustc Version: 1.86.0-nightly +rustc Channel: nightly +cargo Profile: release +``` + +### Operators Updates & Tools + +- New advanced [guide to virtualise a dedicated server](nodes/preliminary-steps/vps-setup/advanced), providing steps for experienced operators and aspiring sys-admins who seek for higher optimisation and better efficiency of their work orchestrating multiple nodes. +- New Service Grant program is being implemented by operators setting up new ~150 Exit Gateways following the updated [specs for `nym-node`](nodes#minimum-requirements) + +### Features +- [Feature/chain status api](https://github.com/nymtech/nym/pull/5539): +this PR introduces `/v1/network/chain-status` endpoint on nym api to give basic information about the current block as seen by this API (with some caching) and updates `/health` endpoint to give stall information. + +- [Add SURBs soft threshold](https://github.com/nymtech/nym/pull/5535): the IPR should try to keep a buffer of available SURBs to reduce latency. + +- [Simplify IPR v8](https://github.com/nymtech/nym/pull/5532): purge stuff from IPR v8 to simplify; use protocol field in v8. + +- [Shared instance for DNS AsyncResolver](https://github.com/nymtech/nym/pull/5523): +make the `HickoryDnsResolver` use a shared instance by default to limit fd use. Now the multiple `nym_http_api_client::Client`s that get built should take advantage of a shared connection pool for DNS lookups. If for some reason a client does need an independent AsyncResolver this can still be done with the added `thread_resolver` function. + +- [cherry-pick 17d3ff2d775f61aee381d90a304ed416c08f33fc onto dorina](https://github.com/nymtech/nym/pull/5519) + +- [cherry-pick 6e5d0dac1b75413c5f09122b0d953f8ec6ef48df onto dorina](https://github.com/nymtech/nym/pull/5518) + +- [feat: add config option for maximum number of client connections](https://github.com/nymtech/nym/pull/5513) + +- [IPR request types v8](https://github.com/nymtech/nym/pull/5498): Bump IPR request/response types to v8 + +- [Support static routes for HTTP requests](https://github.com/nymtech/nym/pull/5487) + +- [added missing import to doctest](https://github.com/nymtech/nym/pull/5480) + +- [adjusted TestSetup::new_complex to ensure bonded node's existence](https://github.com/nymtech/nym/pull/5478) + +- [Trigger contracts CI on main workspace Cargo changes](https://github.com/nymtech/nym/pull/5477): since the contracts workspace depends on the common code in the main workspace, and since the contracts are critical to not have regressions in, trigger contracts CI on any changes to the workspace Cargo.toml and lock files. + +- [Run cargo autoinherit](https://github.com/nymtech/nym/pull/5460): run `cargo autoinherit` to move a bunch of dependencies to the workspace level. `Cargo.lock` remains untouched. + +- [Disable debug in wasm and wallet workflows too](https://github.com/nymtech/nym/pull/5459) + +- [Fix clippy::precedence](https://github.com/nymtech/nym/pull/5457): fix clippy warnings for the Rust beta toolchain. + +- [Provide Interval context with node descriptor endpoints](https://github.com/nymtech/nym/pull/5456): add current interval context information to existing enpoints using `build_skimmed_nodes_response` under the hood. This allows clients checking for a refresh to send the `epoch_uid` as a query parameter when fetching updates so the server can tell it that there have been no changes, instead of sending duplicate data over and over. The changes in this PR should be backwards compatible and never interfere with existing clients. The additions to the `NodeParams` are optional, so there is no error if a client does not send them. Old clients will not send `epoch_uid` by accident so they cannot accidentally clear their set of known nodes. In the response the status field is optional so if it is missing (e.g. if a new client talks to an old server) the connection still works. For old clients speaking to new servers, the json parsing will simply ignore extra information not included in the objects json spec. + +}> + + Made a request to the `/api/v1/unstable/nym-nodes/semi-skimmed` endpoint: + - without entering an epoch_id + - with entering the current epoch_id + - with entering an old epoch_id + - with entering a future epoch_id + All results returned the same data, as expected + + + +- [Feature/add gbp currency](https://github.com/nymtech/nym/pull/5453) + +- [Add helper to extract a list of sqlite files with journal files wal/shm](https://github.com/nymtech/nym/pull/5452) + +- [Add a middleware layer to the nym api allowing for data compression](https://github.com/nymtech/nym/pull/5451): after Testing in a minimal example, this does work as expected. Routes still default to plain encoding, however if a client indicates support for a compressed encoding using the `Accept-Encoding` header then the served response will be compressed with the appropriate `Content-Encoding` header. ([Proof-of-Concept](https://gist.github.com/jmwample/c15a983e804fc338fee3d1b037d216b0)) + +}> + Sent requests with and without Accept-Encoding: gzip to observe response behaviour + Verified if responses included content-encoding: gzip when compression was requested + Checked response file output to confirm actual compression occurred + + +- [Condense core API functionalities and enable gzip decompression for reqwest payloads](https://github.com/nymtech/nym/pull/5450): this PR is intended to take out variables from our usage of HTTP requests in the `nym-http-api-client`. To do this the PR: (1) adds the `ApiClientCore` trait with the minimal feature required to send a request, (2) turns all request sending into trait extension automatically implemented for any type that implements `ApiClientCore`. This has the benefit of consistent expected behavior for all clients using `nym-http-api-client`, including features added going forward. FOR EXAMPLE this pr adds a default header `"accept-encoding: gzip;q=1.0, *;q=0.5"` which indicates that compression is preferred whenever available. Other features to keep in mind here are things like configurable retries, domain fallbacks, etc. Note: the `Apiclient` interface could be simplified, but that would require refactoring our downstream usages of the API. For now this isn't necessary as `ApiClient` is implemented automatically so it costs nothing to have it this way. It just allows divergent usage in downstream crates. + +}> + Measured nym-api response times before and after updating the API client using curl + Checked if the client automatically decompressed Gzip responses + + +- [Seedable clients](https://github.com/nymtech/nym/pull/5440): Adds `DerivationMaterial` and accompanying methods to builders. `DerivationMaterial` encapsulates parameters for deterministic key derivation using HKDF (SHA-512). Use the `derive_secret()` method to generate a 32-byte secret. To prepare for a new derivation, call the `next()` method which increments the index. **It is the caller's responsibility to track and persist the derivation index if keys need to be rederived.** + + + + ```rust + let master_key = [0u8; 32]; // your secret master key + let salt = "unique-salt-value".to_string(); + let material = DerivationMaterial::new(master_key, 0, salt.as_bytes()); + + // Derive a secret + let secret = material.derive_secret().expect("Failed to derive secret"); + + // Prepare for the next derivation + let next_material = material.next(); + ``` + + + +- [Remove all recv_with_delay and add shutdown condition to loops in client-core](https://github.com/nymtech/nym/pull/5435): inside client-core we want to prepare the ground for moving a behaviour close to what we have in the vpn client. Remove all the recv_with_delay since we want to just stop. Add shutdown condition to all select loops to guard against the shutdown listener being polled inside the select blocks. Remove unwraps when sending on unbounded channels in case the receiver exits before the sender. Move `TaskClient` to be a member field so make it easier to wrap log errors in a shutdown check. Update all fork names to use underscore consistently, since the task separator is hyphen + +}> + Validated that all binaries including `nym-node`, `nym-client`, `nym-network-requester`, and `nym-socks5-client` are behaving well without indicating the presence of any unexpected errors or crashes + + +- [Disable the test for checking the remaining bandwidth in nym-node-status-api](https://github.com/nymtech/nym/pull/5425): this check fails almost every time on CI, possibly due to rate limiting? It's not good to disable the check, but it's blocking CI as it stands now. Given that we have the check above for locating the ip, we at least have a little coverage. + +- [Dz nym node stats](https://github.com/nymtech/nym/pull/5418): removed obsolete fields from stats (blacklisted mixnodes, blacklisted gateways, bonded mixnodes, bonded gateways). Introduced nym-node scraping, beside just mixnodes. `/mixnodes/stats` now returns data for nym-nodes as well, which results in much more accurate "packets mixed" stats. + +- [Nymnode entrypoint docker](https://github.com/nymtech/nym/pull/5300) + +### Bugfix +- [bugfix: dont query for ecash apis unless necessary when spending ticketbooks](https://github.com/nymtech/nym/pull/5508) + +- [bugfix: bound check when recovering a reply SURB](https://github.com/nymtech/nym/pull/5502) + +- [fix: update fx average rate calcs to ignore 0 values](https://github.com/nymtech/nym/pull/5454) + +### Chore +- [chore: workspace global panic preventing lints](https://github.com/nymtech/nym/pull/5512) + +- [chore: removed all old coconut code](https://github.com/nymtech/nym/pull/5500) + +- [build(deps): bump the patch-updates group across 1 directory with 3 updates](https://github.com/nymtech/nym/pull/5482): updates `clap` from 4.5.28 to 4.5.30, updates `clap` from 4.5.28 to 4.5.30, updates `prost` from 0.13.4 to 0.13.5. + +- [build(deps): bump http from 1.1.0 to 1.2.0](https://github.com/nymtech/nym/pull/5472) + +- [build(deps): bump utoipa-swagger-ui from 8.0.3 to 8.1.0](https://github.com/nymtech/nym/pull/5471) + +- [build(deps): bump colored from 2.1.0 to 2.2.0](https://github.com/nymtech/nym/pull/5470) + +- [build(deps): bump celes from 2.4.0 to 2.5.0](https://github.com/nymtech/nym/pull/5469) + +- [build(deps): bump the patch-updates group with 2 updates](https://github.com/nymtech/nym/pull/5467): updates `clap` from 4.5.28 to 4.5.29, updates `prost` from 0.13.4 to 0.13.5. + +- [build(deps): bump elliptic from 6.5.4 to 6.6.1 in /docker/typescript_client/upload_contract](https://github.com/nymtech/nym/pull/5463): bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.4 to 6.6.1. + +- [build(deps): bump uniffi_build from 0.25.3 to 0.29.0](https://github.com/nymtech/nym/pull/5448) + +- [Upgrade tower to 0.5.2](https://github.com/nymtech/nym/pull/5446) + +- [build(deps): bump hickory-proto from 0.24.2 to 0.24.3 in /nym-wallet](https://github.com/nymtech/nym/pull/5445) + +- [build(deps): bump hickory-proto from 0.24.2 to 0.24.3](https://github.com/nymtech/nym/pull/5444) + +- [build(deps): bump the patch-updates group across 1 directory with 10 updates](https://github.com/nymtech/nym/pull/5439): +Bumps the patch-updates group with 10 updates in the directory: + +| Package | From | To | +| --- | --- | --- | +| [async-trait](https://github.com/dtolnay/async-trait) | `0.1.85` | `0.1.86` | +| [clap](https://github.com/clap-rs/clap) | `4.5.27` | `4.5.28` | +| [comfy-table](https://github.com/nukesor/comfy-table) | `7.1.3` | `7.1.4` | +| [hickory-resolver](https://github.com/hickory-dns/hickory-dns) | `0.24.2` | `0.24.3` | +| [once_cell](https://github.com/matklad/once_cell) | `1.20.2` | `1.20.3` | +| [pin-project](https://github.com/taiki-e/pin-project) | `1.1.8` | `1.1.9` | +| [serde_json_path](https://github.com/hiltontj/serde_json_path) | `0.7.1` | `0.7.2` | +| [toml](https://github.com/toml-rs/toml) | `0.8.19` | `0.8.20` | +| [cosmrs](https://github.com/cosmos/cosmos-rust) | `0.21.0` | `0.21.1` | +| [tokio-postgres](https://github.com/sfackler/rust-postgres) | `0.7.12` | `0.7.13` | + +- [build(deps): bump openssl from 0.10.56 to 0.10.70 in /nym-wallet](https://github.com/nymtech/nym/pull/5422) + +- [build(deps): bump hyper from 1.4.1 to 1.6.0](https://github.com/nymtech/nym/pull/5416) + +- [build(deps): bump publicsuffix from 2.2.3 to 2.3.0](https://github.com/nymtech/nym/pull/5367) + ## `v2025.3-ruta` - [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.3-ruta) @@ -97,7 +489,7 @@ As we announced in [`hu` release notes](#service-grant-program-v2), we are opene ##### Locations -These locations and slots are approximate and constantly change based on new operators submissions. Please keep in mind that we are going to chose people with respect to empty slots in the given location at the time of their submission. In case of competing submissions in the same location we will take the one with better specs vs price ratio. +These locations and slots are approximate and constantly change based on new operators submissions. Please keep in mind that we are going to chose people with respect to empty slots in the given location at the time of their submission. In case of competing submissions in the same location we will take the one with better specs vs price ratio. | LOCATION | SLOTS | | :-- | --: | @@ -151,7 +543,7 @@ These locations and slots are approximate and constantly change based on new ope - [Send shutdown instead of panic when reaching max fail](https://github.com/nymtech/nym/pull/5398): Remove a panic and an unwrap inside `client-core` that is hit occasionally in the vpn client. This is a change that can have wide ranging impact since it changes the task handling and it's inside `client-core`, which is used in many components and services, so preventing regressions is important. -- [Relocate a validator api function](https://github.com/nymtech/nym/pull/5401): Adds a function to the `nym-validator-client` crate that hits the network details endpoint and returns an object parsed from `json`. This was floating loose in the `nym-vpn-client` repo. +- [Relocate a validator api function](https://github.com/nymtech/nym/pull/5401): Adds a function to the `nym-validator-client` crate that hits the network details endpoint and returns an object parsed from `json`. This was floating loose in the `nym-vpn-client` repo. - [Bump the patch-updates group across 1 directory with 9 updates](https://github.com/nymtech/nym/pull/5406) @@ -199,7 +591,7 @@ From `nym-node v1.3.0` operators can technically choose multiple functionalities - Updated maintenance guides to [backup](nodes/maintenance#backup-a-node), [restore](nodes/maintenance#restoring-a-node) and [move](nodes/maintenance#moving-a-node) a node, containing a new and important commands to backup and restore `clients.sqlite` database. -- [New explanation of `nym-node` functionalities](nodes/nym-node/setup#functionality-mode) describing how Gateways get selected in Mixnet mode and Wireguard mode. +- [New explanation of `nym-node` functionalities](nodes/nym-node/setup#functionality-mode) describing how Gateways get selected in Mixnet mode and Wireguard mode. Wireguard nodes route data directly to the open internet. Therefore it exposes IP of operators server (VPS) to abuse complains. Before you decide to run a node with active wireguard routing, please read our [Community Counsel pages](community-counsel/exit-gateway) containing more information and some legal content. @@ -223,7 +615,7 @@ We have been notified that a handful of nodes have been taken down by abuse repo - Join [Community legal counsel](https://nym.com/docs/operators/community-counsel) - our collective knowledge hub. Add your findings by opening a [Pull Request](https://nym.com/docs/operators/add-content) - While we are working on a new list of more friendly providers, consider to move away from these provides as soon as possible: - + - Servinga / VPS2day (AS39378) - Frantech / Ponynet / BuyVM (AS53667) - OVH SAS / OVHcloud (AS16276) @@ -233,7 +625,7 @@ We have been notified that a handful of nodes have been taken down by abuse repo - Psychz Networks (AS40676) - 1337 Services GmbH / RDP.sh (AS210558) -- Backup your nodes to have access to `.nym` directory locally. Follow [node](nodes/maintenance#backup-a-node) and [proxy configuration](nodes/maintenance#backup-proxy-configuration) backup guides to be able to [restore your node](nodes/maintenance#restoring-a-node) later on on another machine, without losing your delegation. +- Backup your nodes to have access to `.nym` directory locally. Follow [node](nodes/maintenance#backup-a-node) and [proxy configuration](nodes/maintenance#backup-proxy-configuration) backup guides to be able to [restore your node](nodes/maintenance#restoring-a-node) later on on another machine, without losing your delegation. - We would like to ask operators who use reverse proxy and a domain (required for Gateways) to start using a common convention starting with `nym-exit` for their nodes URL. The entire address should have this new format: ``` @@ -258,7 +650,7 @@ nym-exit.mysquad.org **The `NYM-EXIT` part in the beginning is what's important.** -- When registering a domain, check [Top Level Domain (TLD)](https://www.techopedia.com/definition/1348/top-level-domain-tld) terms and conditions. For example `.icu` is a no go. Having a wrong TLD may lead to your domain being taken away from you when facing a DMCA report. +- When registering a domain, check [Top Level Domain (TLD)](https://www.techopedia.com/definition/1348/top-level-domain-tld) terms and conditions. For example `.icu` is a no go. Having a wrong TLD may lead to your domain being taken away from you when facing a DMCA report. - Write a message to your provider and introduce your intention to run a Nym Node on their service @@ -322,7 +714,7 @@ Undelegated due to high saturation: #### Service Grant Program v2 -Aside from delegating on top of nodes, Nym runs a Service Grant Program (SGP) to support Exit Gateway operators before they will be rewarded by collecting [zk-nym tickets](../network/cryptography/zk-nym) from users subscription. Operators included in SGP are long term active community members with the highest requirements on the technical setup and upgrading pace. We are about to start a second iteration of SGP very soon (SGPv2). The final slots and locations are yet to be concluded. Priority to participate in SGPv2 will be given to the current operators in SGP. Based on the number of slots, we will then determine how many more operators can sign up. +Aside from delegating on top of nodes, Nym runs a Service Grant Program (SGP) to support Exit Gateway operators before they will be rewarded by collecting [zk-nym tickets](../network/cryptography/zk-nym) from users subscription. Operators included in SGP are long term active community members with the highest requirements on the technical setup and upgrading pace. We are about to start a second iteration of SGP very soon (SGPv2). The final slots and locations are yet to be concluded. Priority to participate in SGPv2 will be given to the current operators in SGP. Based on the number of slots, we will then determine how many more operators can sign up. ##### Rules of SGPv2 @@ -330,7 +722,7 @@ Aside from delegating on top of nodes, Nym runs a Service Grant Program (SGP) to **We will share more info soon in the channels. The rules are not set in stone and could potentially be altered or updated in the future! Do *not* purchase new servers neither migrate your nodes just yet.** -As we finalising last details of *"Project Smoosh"*, where one binary - `nym-node` - can run as an `entry-gateway`, `mixnode` or `exit-gateway` in Mixnet mode as well as `entry-gateway` or `exit-gateway` in Wireguard mode, we plan to step up the game. SGPv2 grants will be higher if operators can meet new requirements. +As we finalising last details of *"Project Smoosh"*, where one binary - `nym-node` - can run as an `entry-gateway`, `mixnode` or `exit-gateway` in Mixnet mode as well as `entry-gateway` or `exit-gateway` in Wireguard mode, we plan to step up the game. SGPv2 grants will be higher if operators can meet new requirements. **Minimum Specs & Requirements** @@ -402,7 +794,7 @@ These are minimum requirements to become a part of SGPv2. We aim to have nodes o - [build(deps): bump criterion from `0.4.0` to `0.5.1`](https://github.com/nymtech/nym/pull/4911): Bumps [criterion](https://github.com/bheisler/criterion.rs) from `0.4.0` to `0.5.1`. -- [NS API: add mixnet scraper](https://github.com/nymtech/nym/pull/5200) +- [NS API: add mixnet scraper](https://github.com/nymtech/nym/pull/5200) - [build(deps): bump http from `1.1.0` to `1.2.0`](https://github.com/nymtech/nym/pull/5228): Bumps [http](https://github.com/hyperium/http) from `1.1.0` to `1.2.0`. @@ -410,41 +802,41 @@ These are minimum requirements to become a part of SGPv2. We aim to have nodes o - [Add windows to CI builds](https://github.com/nymtech/nym/pull/5269) -- [nym topology revamp](https://github.com/nymtech/nym/pull/5271): This PR changes the internals of the `NymTopology` to blur the lines between explicit mixnodes and gateways so that what used to be considered a "mixnode" could be a valid egress point of the network. `NymTopology` is no longer divided into `BTreeMap>` and `Vec`. instead there's information about the current rewarded set (to support future VRF work) and a simple map of `HashMap`. The new features are mostly controlled via 2 new flags/config values: +- [nym topology revamp](https://github.com/nymtech/nym/pull/5271): This PR changes the internals of the `NymTopology` to blur the lines between explicit mixnodes and gateways so that what used to be considered a "mixnode" could be a valid egress point of the network. `NymTopology` is no longer divided into `BTreeMap>` and `Vec`. instead there's information about the current rewarded set (to support future VRF work) and a simple map of `HashMap`. The new features are mostly controlled via 2 new flags/config values: - `use_extended_topology` that tells the client to retrieve **all** network nodes rather than the ones that got assigned "active" mixnode role (or support being a gateway) - `ignore_egress_epoch_role` that tells the client it's fine to construct egress packets to nodes that are **not** assigned entry or exit gateway role - + - [Nyx Chain Watcher](https://github.com/nymtech/nym/pull/5274) - [Include `IPINFO_API_TOKEN` in nightly CI](https://github.com/nymtech/nym/pull/5285) -- [Move tun constants to network defaults](https://github.com/nymtech/nym/pull/5286): +- [Move tun constants to network defaults](https://github.com/nymtech/nym/pull/5286): }> 1. **Regression Testing**: -- Verified no issues arose when running tests for the affected files. -- Tested TUN behaviour with new nym-nodes in the hu branch. - -**Results**: +- Verified no issues arose when running tests for the affected files. +- Tested TUN behaviour with new nym-nodes in the hu branch. -- **No bugs detected**. -- Tunnels are functioning as expected, with traffic routing and IP generation working seamlessly. - +**Results**: + +- **No bugs detected**. +- Tunnels are functioning as expected, with traffic routing and IP generation working seamlessly. + - [Add dependabot assignes for the root cargo ecosystem](https://github.com/nymtech/nym/pull/5297) - [build(deps): bump the patch-updates group across 1 directory with 35 updates](https://github.com/nymtech/nym/pull/5310): Bumps the `patch-updates` group with 33 updates -- [Periodically remove stale gateway messages](https://github.com/nymtech/nym/pull/5312): This PR introduces a simple task that removes gateway messages that haven't been retrieved in (by default) 24h. +- [Periodically remove stale gateway messages](https://github.com/nymtech/nym/pull/5312): This PR introduces a simple task that removes gateway messages that haven't been retrieved in (by default) 24h. }> **Automation Script for Data Cleanup Validation** Test Objective: Validate that the stale message cleanup mechanism in the database correctly removes records older than the configured threshold (24 hours). - + Test Setup: 1. Environment: * SQLite database * Bash script: used to insert data. - + Steps Performed: 1. Ran the insert_data.sh script to populate the database with test data: * Recent records inserted successfully. @@ -452,8 +844,8 @@ Steps Performed: 3. Confirmed that all 20 records (10 recent + 10 stale) were present. 4. Allowed the system to run for 24 hours to trigger the cleanup mechanism. 5. Queried the database again after 24 hours: sqlite3 gateway_storage.db "SELECT * FROM message_store;" -6. - +6. + Expected Result: * All stale records (older than 24 hours) should be removed. * Recent records should remain in the database. @@ -464,18 +856,18 @@ Actual Result: - [Use expect in geodata test to give error message on failure](https://github.com/nymtech/nym/pull/5314): Keep hitting this error on CI, from what I think is network hickup. But it's hard to tell form the log since the error is swallowed. Explicitly unwrap the result so we get a more detailed error output. }> -**Quick Code Review** - -**Summary** +**Quick Code Review** + +**Summary** 1. **CI Workflow**: Adjusted paths to optimise build triggers, avoiding unnecessary CI runs while ensuring coverage for key directories 2. **Geolocation Test**: Improved error handling by replacing assertions with `.expect` for clearer debugging in API regression tests - -**Conclusion** + +**Conclusion** Regression testing confirms everything works as intended. **Approved**. -- [`CancellationToken`-based shutdowns](https://github.com/nymtech/nym/pull/5325): This PR introduces scaffolding for using `CancellationToken` and `TaskTracker` for our graceful shutdowns rather than the existing `TaskClient` and `TaskManager`. +- [`CancellationToken`-based shutdowns](https://github.com/nymtech/nym/pull/5325): This PR introduces scaffolding for using `CancellationToken` and `TaskTracker` for our graceful shutdowns rather than the existing `TaskClient` and `TaskManager`. - [Introduce `/load` endpoint for self-reported quantised Nym Node load](https://github.com/nymtech/nym/pull/5326): This PR introduces a new `/load` endpoint on a `NymNode` to return its current load. It returns the following data: ```rust @@ -505,7 +897,7 @@ The actual values for`NodeLoad` are determined as follows: - Thus we calculate two additional auxiliary `Load` values, for memory usage and swap usage, i.e.: `used_memory / total_memory` and `used_swap / total_swap` respectively. - Then we check whether either of the `MemoryLoad` or `SwapLoad` is bigger than the current base `Load` of the machine we have determined, if so, it's increased by one tier / bucket. For example, say the current machine load is `Load::Low`, but the memory usage is at 90% (`Load::VeryHigh`). that would result in the reported `Load` being bumped up to `Load::Medium` instead. The same logic applies with swap load, **however, only if the total swap > 1GB**. this is to prevent weird edge cases where the machine has hardly any swap. - Finally, the `.total` `Load` uses the same "tier bumping" behaviour using the `.total` and `.network` loads, i.e. `if network > machine`, then `total = machine + 1`. for example if `machine` `Load` is `Load::Low`, but `network` `Load` is `Load::Medium`, then the `total` `Load` is set to `Load::Medium` instead. - + - [Bump the `patch-updates` group with 8 updates](https://github.com/nymtech/nym/pull/5336) @@ -517,29 +909,29 @@ The actual values for`NodeLoad` are determined as follows: - [Bump mikefarah/yq from `4.44.6` to `4.45.1`](https://github.com/nymtech/nym/pull/5342) -- [Update `indexed_db_futures`](https://github.com/nymtech/nym/pull/5347): Updates the `indexed_db_futures` dependency to stop relying on the fork. +- [Update `indexed_db_futures`](https://github.com/nymtech/nym/pull/5347): Updates the `indexed_db_futures` dependency to stop relying on the fork. - [Refresh wasm sdk](https://github.com/nymtech/nym/pull/5353): This PR refreshes the wasm clients to make them usable in the current network - [Client gateway selection](https://github.com/nymtech/nym/pull/5358): Changed how gateway is selected. - For init the target gateway can't support mixing - For egress, unless `ignore_epoch_roles` is specified, the gateway can't be currently assigned to a mixing layer. But it can be standby or inactive - + - [Exposed `NymApiClient` method for obtaining node performance history](https://github.com/nymtech/nym/pull/5360) - [Bind to `[::]` on `nym-node` for both IP versions](https://github.com/nymtech/nym/pull/5361) }> **IPv4 Configuration and Migration Testing** - + Testing Steps: - Initiated and ran a nym-node with version 1.3.1 on an IPv4-only machine, then updated it to the new 'hu', 1.4.0 version. - Initiated and ran a new nym-node with 'hu', 1.4.0 always on a machine with only IPv4. - Initiated and ran a new nym-node with 'hu', 1.4.0 on a machine with both IPv4 and IPv6 for regression testing. - + Results: - No functional issues during version updates. - Logged message on all versions: "no registered client for destination: ff02::2" - + Status: Pass @@ -554,10 +946,10 @@ Status: Pass - Ensured a client is able to select a node which aside from a gateway, can also act as a mixnode - Verified 'ignore_epoch_roles' is the default mode - _note; this is most likely not going to be a permanent solution_ - + **Results:** -- Clients and topology are behaving correctly - +- Clients and topology are behaving correctly + Status: Pass @@ -612,7 +1004,7 @@ We are developing a design where operators can enable multiple modes, and let th - Moved top level `authenticator` section to `service_providers` so that it'd live alongside NR and IPR - Added general `debug` section - Added `metrics` section -- All documentation migrated to a new URL [nym.com/docs](https://nym.com/docs) alongside the rebranding of Nym organisation. +- All documentation migrated to a new URL [nym.com/docs](https://nym.com/docs) alongside the rebranding of Nym organisation. - Updated [network architecture diagrams](https://nym.com/docs/network/architecture) - New blow-by-blow mixnet [traffic flow](https://nym.com/docs/network/architecture) section - [Winter Nym Squad League started](https://forum.nym.com/t/nym-squad-league-farewell-fall-welcome-winter/977) @@ -648,11 +1040,11 @@ We are developing a design where operators can enable multiple modes, and let th - Everything in `mixnode` directory has been removed because there was nothing really left there. The mixing socket listener was unified in `nym-node` and similarly `verloc` was also moved there - `gateway` directory was similarly reduced in size. Now it also creates appropriate tasks as opposed to the whole gateway process. eventually it might also be further stripped, but today is not that day. - Removed the generic parameter on the `GatewayStorage` to simplify all the generics down the stack. it wasn't used anyway - + CLI: - Added `--modes` argument to specify all node modes with a single command (or `env` variable). for example: `--modes="mixnode,entry"`. Can't be used alongside `--modes` - Extended `--mode` argument to allow specifying it multiple times, for example: `--mode mixnode --mode entry`. can't be used alongside `--mode` - + Config changes: - Replaced `mode` with `modes` to allow setting the node to run with say, `entry` + `mixnode` roles simultaneously - Added `maximum_forward_packet_delay` to `mixnet.debug` section @@ -677,17 +1069,17 @@ Config changes: - [Remove unneeded async function annotation](https://github.com/nymtech/nym/pull/5246) - [Add control messages to `GatewayTransciver`](https://github.com/nymtech/nym/pull/5247) }> -**Review and Testing: Forget Me Implementation** +**Review and Testing: Forget Me Implementation** -- Validated the encryption and delivery of `ForgetMe` control messages to the gateway - -**Testing: MixTrafficController Integration** +- Validated the encryption and delivery of `ForgetMe` control messages to the gateway -- Verified that the `MixTrafficController` invokes `ForgetMe` logic correctly during shutdown -- Tested behaviour for gateway transceiver failures while sending control messages - -**Testing: Gateway Storage Updates** -- Confirmed successful deletion of client data (e.g., inbox messages, bandwidth allocations) from persistent storage +**Testing: MixTrafficController Integration** + +- Verified that the `MixTrafficController` invokes `ForgetMe` logic correctly during shutdown +- Tested behaviour for gateway transceiver failures while sending control messages + +**Testing: Gateway Storage Updates** +- Confirmed successful deletion of client data (e.g., inbox messages, bandwidth allocations) from persistent storage - [Add conversion unit tests for auth msg](https://github.com/nymtech/nym/pull/5251) @@ -704,13 +1096,13 @@ Config changes: }> 1. **Review File: `common/credential-storage/src/backends/sqlite.rs`** - Verified addition of `close` method for the SQLite backend - + 2. **Review File: `common/credential-storage/src/ephemeral_storage.rs`** - Confirmed addition of `close` method for ephemeral storage with no action required - + 3. **Review File: `common/credential-storage/src/persistent_storage/mod.rs`** - Ensured `close` method integration for persistent storage - + 4. **Review File: `common/credential-storage/src/storage.rs`** - Verified updates to the `Storage` trait to include `close` and `cleanup_expired` methods @@ -719,15 +1111,15 @@ Config changes: }> 1. **Review File: `common/network-defaults/src/constants.rs`** - Confirmed updated `mixnet_vpn` constants were added. - + 2. **Review File: `service-providers/ip-packet-router/src/constants.rs`** - Checked replacement of legacy `TUN_*` constants with new `mixnet_vpn` constants. - Validated alignment of routing traffic configurations. - + 3. **Review File: `service-providers/ip-packet-router/src/ip_packet_router.rs`** - Ensured new `nym_network_defaults::constants::mixnet_vpn` constants replaced old references. - Verified `TunDeviceConfig` consistency. - + 4. **Review File: `service-providers/ip-packet-router/src/util/generate_new_ip.rs`** - Confirmed substitution of `TUN_DEVICE_*` constants with `NYM_TUN_DEVICE_*` constants. - Tested functionality for generating random IPs within subnet. @@ -743,54 +1135,54 @@ Config changes: - `nym_node_mixnet_ingress_excessive_delay_packets` - `nym_node_mixnet_ingress_forward_hop_packets_dropped` - `nym_node_mixnet_ingress_final_hop_packets_dropped` - + - `nym_node_mixnet_ingress_forward_hop_packets_received_rate` - - `nym_node_mixnet_ingress_final_hop_packets_received_rate` - - `nym_node_mixnet_ingress_malformed_packets_received_rate` - - `nym_node_mixnet_ingress_excessive_delay_packets_rate` + - `nym_node_mixnet_ingress_final_hop_packets_received_rate` + - `nym_node_mixnet_ingress_malformed_packets_received_rate` + - `nym_node_mixnet_ingress_excessive_delay_packets_rate` - `nym_node_mixnet_ingress_forward_hop_packets_dropped_rate` - `nym_node_mixnet_ingress_final_hop_packets_dropped_rate` - + - egress: - `nym_node_mixnet_egress_stored_on_disk_final_hop_packets` - `nym_node_mixnet_egress_forward_hop_packets_sent` - `nym_node_mixnet_egress_ack_packets_sent` - `nym_node_mixnet_egress_forward_hop_packets_dropped` - + - `nym_node_mixnet_egress_forward_hop_packets_sent_rate` - `nym_node_mixnet_egress_ack_packets_sent_rate` - `nym_node_mixnet_egress_forward_hop_packets_dropped_rate` - + - client sessions - `nym_node_entry_client_sessions_unique_users` - `nym_node_entry_client_sessions_sessions_started` - `nym_node_entry_client_sessions_finished_sessions` - `nym_node_entry_client_sessions_durations_{TYP}` (histogram), for example `nym_node_entry_client_sessions_durations_vpn` - + - wireguard: - `nym_node_wireguard_bytes_rx` - `nym_node_wireguard_bytes_tx` - `nym_node_wireguard_bytes_total_peers` - `nym_node_wireguard_bytes_active_peers` - + - `nym_node_wireguard_bytes_rx_rate` - `nym_node_wireguard_bytes_tx_rate` - - + + - network - `nym_node_network_active_ingress_mixnet_connections` - `nym_node_network_active_ingress_web_socket_connections` - `nym_node_network_active_egress_mixnet_connections` - + - process - `nym_node_process_forward_hop_packets_being_delayed` - `nym_node_process_packet_forwarder_queue_size` - `nym_node_process_topology_query_resolution_latency` (histogram) - `nym_node_process_final_hop_packets_pending_delivery` - - `nym_node_process_forward_hop_packets_pending_delivery` + - `nym_node_process_forward_hop_packets_pending_delivery` -- [Amend 250gb limit](https://github.com/nymtech/nym/pull/5313): Change bandwidth cap to 250gb +- [Amend 250gb limit](https://github.com/nymtech/nym/pull/5313): Change bandwidth cap to 250gb - [Warn users if node is run in exit mode only](https://github.com/nymtech/nym/pull/5320): Throws a warning if node is run in "exit" mode only as by default, this will **NOT** enable entry capabilities, i.e. opening the websocket. thus making it ineligible for rewarded set selection (and rewards) - [Reduce log severity for number of packets being delayed](https://github.com/nymtech/nym/pull/5321) - [Apply 1.84 linter suggestions](https://github.com/nymtech/nym/pull/5330) @@ -806,7 +1198,7 @@ Config changes: - [Make sure to apply gateway score filtering when choosing initial node](https://github.com/nymtech/nym/pull/5256) - [Fixed client session histogram buckets](https://github.com/nymtech/nym/pull/5316) - [Contract version assignment](https://github.com/nymtech/nym/pull/5318): This PR fixes updates to current nym-node version as well as introduces migration to fix the existing state of the mainnet contract - - [Make sure refresh data key matches bond info](https://github.com/nymtech/nym/pull/5329): This is to forbid operators from reusing the same underlying identity key for multiple nodes by overwriting the describe data. the reported key has to always match what the node has bonded with (and the contract enforces uniqueness) + - [Make sure refresh data key matches bond info](https://github.com/nymtech/nym/pull/5329): This is to forbid operators from reusing the same underlying identity key for multiple nodes by overwriting the describe data. the reported key has to always match what the node has bonded with (and the contract enforces uniqueness) ## Archived Changelog diff --git a/documentation/docs/pages/operators/faq/general-faq.mdx b/documentation/docs/pages/operators/faq/general-faq.mdx index a95a8b7496..400c9c0a56 100644 --- a/documentation/docs/pages/operators/faq/general-faq.mdx +++ b/documentation/docs/pages/operators/faq/general-faq.mdx @@ -10,7 +10,7 @@ Yes, there are.. **Built by Nym** -* [Nym Explorer](https://explorer.nymtech.net/) +* [Nym Explorer](https://nym.com/explorer) * [Sandbox testnet](https://sandbox-explorer.nymtech.net/) * [Nym Harbourmaster](https://harbourmaster.nymtech.net) diff --git a/documentation/docs/pages/operators/nodes/maintenance.mdx b/documentation/docs/pages/operators/nodes/maintenance.mdx index 5819fde852..a825b3ab32 100644 --- a/documentation/docs/pages/operators/nodes/maintenance.mdx +++ b/documentation/docs/pages/operators/nodes/maintenance.mdx @@ -128,10 +128,15 @@ mkdir -pv ###### 2. Backup `clients.sqlite` database + - Install `sqlite3` ```sh apt install sqlite3 ``` +- **Stop your node** +```sh +service nym-node stop +``` - Open sqlite CLI shell inside `clients.sqlite` database ```sh sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite @@ -151,10 +156,14 @@ sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite scp -r @:~/.nym/nym-nodes/ ``` -###### 4. Verify the success of the backup +###### 4. Verify the success of the backup & start your node The `scp` command should print logs, an operator can see directly whether it was successful or if it encountered any error. However, double check that all your needed configuration is in the backup target directory. +- Start your node +```sh +service nym-node start && journalctl -u nym-node -f +``` Now you have everything needed to restore your `nym-node` on another server. If you are in a need of doing so, follow the steps in [*Restoring a node*](#restoring-a-node) chapter below. @@ -360,7 +369,7 @@ mkdir ~/.nym/nym-nodes ``` ###### 2. Backup `clients.sqlite` database -- Stop your node +- **Stop your node** ```sh service nym-node stop ``` diff --git a/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx b/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx index 19edb7d619..ab425f8e4e 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx @@ -2,7 +2,13 @@ import { Callout } from 'nextra/components'; import { Tabs } from 'nextra/components'; import { VarInfo } from 'components/variable-info.tsx'; import { Steps } from 'nextra/components'; -import { AccordionTemplate } from 'components/accordion-template.tsx' +import { AccordionTemplate } from 'components/accordion-template.tsx'; +import ExitPolicyInstallOutput from 'components/operators/snippets/wg-exit-policy-install-output.mdx'; +import ExitPolicyStatusOutput from 'components/operators/snippets/wg-exit-policy-status-output.mdx'; +import ExitPolicyTestOutput from 'components/operators/snippets/wg-exit-policy-test-output.mdx'; +import ExitPolicyTestServer from 'components/operators/snippets/wg-exit-policy-testing-from-server.mdx'; +import ExitPolicyTestOutside from 'components/operators/snippets/wg-exit-policy-testing-from-outside.mdx'; + export const ManagerIPOutput = () => (
@@ -434,9 +440,101 @@ journalctl -u nym-node.service -f -n 100 ``` - Make sure that you get the validation of all connectivity. If there are still any problems, please refer to [troubleshooting section](../../troubleshooting/vps-isp.mdx#incorrect-gateway-network-check). +## Wireguard Exit Policy Configuration + +Nym Node running as Exit Gateway has contains multiple modules, one of them is Nym Network Requester(NR), routing TCP traffic to the internet. To make sure that the node is not just an open proxy, NR checks agains [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) following these conditions (in this exact order): + +1. Do we explicitly block those IP addresses regardless of ports? +2. Do we allow those specific ports regardless of IPs? +3. Do block EVERYTHING else! + +The exit policy is same for all NRs, the content is shaped by an offchain governance of Nym Node operators on our [forum](https://forum.nym.com/t/poll-a-new-nym-exit-policy-for-exit-gateways-and-the-nym-mixnet-is-inbound/464). + +There is a caveat though. NR is only routing TCP streams and therefore any other type of routing is *not* filtered thorugh the exit policy. To ensure that Nym Nodes follow the same exit policy when routing IP packets through wireguard and don't act as open proxies, the operators have to set up these rules via IP tables rules. + +**Follow these steps, using a [setup script]i(https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh) and [testing scripts](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/exit-policy-tests.sh) written by Nym quality assurance team, to setup exit policy for wireguard:** + + + +###### 1. Download the scripts and make executable + +- SSH to your node +- Create a folder `~/nym-binaries` and navigate there +```sh +mkdir $HOME/nym-binaries +cd $HOME/nym-binaries +``` +- Download the scripts +```sh +wget https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh + +wget https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/exit-policy-tests.sh +``` +- Make executable +```sh +chmod +x wireguard-exit-policy-manager.sh exit-policy-tests.sh +``` + +###### 2. Install `wireguard-exit-policy-manager.sh` +```sh +./wireguard-exit-policy-manager.sh install +``` +- The output should look like this: + + + + + +###### 3. Run `wireguard-exit-policy-manager.sh` +```sh +./wireguard-exit-policy-manager.sh status +``` + +- The output should look like this: + + + + +###### 4. Test with `exit-policy-tests.sh` + +```sh +./exit-policy-tests.sh +``` + +- The output should look like this: + + + + +###### 5. In case of problems, you can clear the exit policy rule +```sh +./wireguard-exit-policy-manager.sh clear + +./wireguard-exit-policy-manager.sh status +``` + + +Now your wireguart routing should have same rotuing permissions like [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) used on 5-hop (Mixnet) mode of NymVPN. + +### Testing Wireguard Exit Policy + +You can validate the application of the IP tables routes on your `nym-node` by checking it from the server side as well as from the outside. + +
+ From the server, + From the outside - using NymVPN + ]} defaultIndex={0}> + + + +
+ +Your node has successfully implemented wireguard exit policy with the same routing permissions like [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) used on 5-hop (Mixnet). + + ## Running `nym-node` as a non-root Some operators prefer to run `nym-node` without root privileges. It's possible but still `nym-node` binary needs higher privileges for network-level operations demanding these permissions. Below is a guide how to go about such setup: diff --git a/documentation/docs/pages/operators/nodes/nym-node/configuration/proxy-configuration.mdx b/documentation/docs/pages/operators/nodes/nym-node/configuration/proxy-configuration.mdx index b4ec5ac88a..7a5d89575d 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/configuration/proxy-configuration.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/configuration/proxy-configuration.mdx @@ -259,7 +259,7 @@ That can be done only on an IP+destination port basis, however. Common P2P ports

You also have the option of blocking this IP address and others on the Nym network if you so desire. - The Nym project provides a + The Nym project provides a web service to fetch a list of all IP addresses of Nym Gateway Exit nodes that allow exiting to a specified IP:port combination. Please be considerate when using these options.

diff --git a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx index 8290f14411..39ceee1219 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx @@ -20,12 +20,12 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](. ```sh nym-node Binary Name: nym-node -Build Timestamp: 2025-02-13T11:49:34.670488195Z -Build Version: 1.5.0 -Commit SHA: a3e19b4563843055b305ea9a397eb1ad84b5c378 -Commit Date: 2025-02-10T18:14:47.000000000+01:00 +Build Timestamp: 2025-03-18T12:17:10.782554174Z +Build Version: 1.7.0 +Commit SHA: 82e82943aa14e530ab4eb3aaab28ce86d47e5298 +Commit Date: 2025-03-18T10:39:55.000000000+01:00 Commit Branch: HEAD -rustc Version: 1.84.1 +rustc Version: 1.85.0 rustc Channel: stable cargo Profile: release ``` @@ -44,7 +44,7 @@ From `nym-node v1.3.0` operators can choose multiple functionalities for their ` ### Mixnet Routing -***Mixnet mode (5-hop) is the full anonymising option of NymVPN. Read more about the Mixnet architecture [here](http://localhost:3000/docs/network/architecture)*** +***Mixnet mode (5-hop) is the full anonymising option of NymVPN. Read more about the Mixnet architecture [here](../../../network/architecture)*** Nym Node has three functionalities in the Mixnet: `entry-gateway`, `mixnode` and `exit-gateway`. These are selected with a flag `--mode ` alongside `nym-node` command `run` . @@ -178,7 +178,7 @@ Make sure to use `--deny-init` flag to prevent initialisation of a new node. ## Functionality & Performance Check -We have a chapter called [Performance Monitoring & Testing](../nodes/performance-and-testing) including much more information and tooling. If you want to just quickly check your nodes performance, connectivity and much more, visit [harbourmaster.nymtech.net](https://harbourmaster.nymtech.net/). +We have a chapter called [Performance Monitoring & Testing](../performance-and-testing) including much more information and tooling. If you want to just quickly check your nodes performance, connectivity and much more, visit [harbourmaster.nymtech.net](https://harbourmaster.nymtech.net/). For more information about available endpoints and their status, you can refer to: ```sh diff --git a/documentation/docs/pages/operators/nodes/performance-and-testing/gateway-probe.mdx b/documentation/docs/pages/operators/nodes/performance-and-testing/gateway-probe.mdx index 22642fe083..d5a3c6fe53 100644 --- a/documentation/docs/pages/operators/nodes/performance-and-testing/gateway-probe.mdx +++ b/documentation/docs/pages/operators/nodes/performance-and-testing/gateway-probe.mdx @@ -3,7 +3,7 @@ Nym Node operators running Gateway functionality are already familiar with the monitoring tool [Harbourmaster.nymtech.net](https://harbourmaster.nymtech.net). Under the hood of Nym Harbourmaster runs iterations of `nym-gateway-probe` doing various checks and displaying the results on the interface. Operators don't have to rely on the probe ran by Nym and wait for the data to refresh. With `nym-gateway-probe` everyone can check any Gateway's networking status from their own computer at any time. In one command the client queries data from: - [`nym-api`](https://validator.nymtech.net/api/v1/gateways) -- [`explorer-api`](https://explorer.nymtech.net/api/v1/gateways) +- [`explorer-api`](https://mainnet-node-status-api.nymtech.cc/swagger/#/Gateways) - [`harbour-master`](https://harbourmaster.nymtech.net/) @@ -88,4 +88,3 @@ For any `nym-node --mode exit-gateway` the aim is to have this outcome: **If your Gateway is blacklisted, the probe will not work.** If you don't provide a `--gateway` flag it will pick a random one to test. - diff --git a/documentation/docs/pages/operators/nodes/preliminary-steps/vps-setup.mdx b/documentation/docs/pages/operators/nodes/preliminary-steps/vps-setup.mdx index 3488295629..5cba5bdbed 100644 --- a/documentation/docs/pages/operators/nodes/preliminary-steps/vps-setup.mdx +++ b/documentation/docs/pages/operators/nodes/preliminary-steps/vps-setup.mdx @@ -3,8 +3,9 @@ import { VarInfo } from 'components/variable-info.tsx'; import { Steps } from 'nextra/components';import { Tabs } from 'nextra/components'; import { MyTab } from 'components/generic-tabs.tsx'; import PortsNymNode from 'components/operators/snippets/ports-nym-node.mdx'; -import PortsValidator from 'components/operators/snippets/ports-validator.mdx' -import NymNodeSpecs from 'components/operators/snippets/nym-node-specs.mdx' +import PortsValidator from 'components/operators/snippets/ports-validator.mdx'; +import NymNodeSpecs from 'components/operators/snippets/nym-node-specs.mdx'; +import NTPSync from 'components/operators/snippets/ntp-time-sync.mdx' # VPS Setup & Configuration @@ -42,14 +43,18 @@ apt update -y && apt --fix-broken install ``` - Install dependencies ```sh -apt -y install ca-certificates jq curl wget ufw jq tmux pkg-config build-essential libssl-dev git +apt -y install ca-certificates jq curl wget ufw jq tmux pkg-config build-essential libssl-dev git ntp ntpdate ``` - Double check ufw is installed correctly ```sh apt install ufw --fix-missing ``` -###### 2. Configure your firewall using Uncomplicated Firewall (UFW) +###### 2. Synchronize time of your server + + + +###### 3. Configure your firewall using Uncomplicated Firewall (UFW) For a `nym-node` or Nyx validator to recieve traffic, you need to open ports on the server. The following commands will allow you to set up a firewall using `ufw`. @@ -70,7 +75,7 @@ ufw enable ufw status ``` -###### 3. Open all needed ports to have your firewall for `nym-node` working correctly +###### 4. Open all needed ports to have your firewall for `nym-node` working correctly
+ +## Virtualising a Dedicated Server + +Some operators or squads of operators orchestrate multiple Nym nodes. Among other benefits (which are out of scope of this page), these operators can decide to acquire one larger dedicated (or bare-metal) server with enough specs (CPU, RAM, storage, bandwidth and port speed) to meet [minimum requirements](../../../nodes#minimum-requirements) for multiple nodes run in parallel. + +This guide explains how to prepare your server in order to be able to host multiple nodes running on separated VMs. + + +This guide is based on Ubuntu 22.04, in case you prefer another OS, you may have to do a bit of your own research to troubleshoot networking configuration and other parameters. + + +### Installing KVM on a Server with Ubuntu 22.04 + +**KVM** stands for **Kernel-based Virtual Machine**. It is a virtualization technology for Linux that allows a user to run multiple virtual machines (VMs) on a single physical machine. KVM turns the Linux kernel into a hypervisor, enabling it to manage multiple virtualised systems. + +Follow the steps below to install KVM on Ubuntu 22.04 LTS. + +#### Prerequisites + + +Operators aiming to run Nym node as mixnet [Exit Gateway](../../../community-counsel/exit-gateway) or with wireguard enabled should familiarize themselves with the challenges possibly coming along `nym-node` operation, described in our [community counsel](../../../community-counsel) and follow up with [legal suggestions](../../../community-counsel/legal). Particularly important is to [introduce yourself](../../../community-counsel/legal#introduce-nym-node-to-your-provider) and your intentions to run a Nym node to your provider. + +This step is essential part of legal self defense because it may prevent your provider immediately shutting down your entire service (with all the VMs on it) when receiving first abuse report. + +Additionally, before purchasing a large server, **contact the provider and ask if the offered CPU supports Virtualization Technology (VT)**, without this feature you will not be able to proceed. + + +Start with obtaining a server with Ubuntu 22.04 LTS: +- Make sure that your server meets [minimum requirements](../vps-setup#nym-node---dedicated-server) multiplied by number of `nym-node` instance you aim to run on it. +- Most people rent a server from a provider and it comes with a pre-installed OS (in this guide we use Ubuntu 22.04). In case your choice is a bare-metal machine, you probably know what you are doing, there are some useful guides to install a new OS, like [this one on ostechnix.com](https://ostechnix.com/install-ubuntu-server/). + +Make sure thay your system actually supports hardware virtualisation: +- Check out the methods documented in [this guide by ostechnix.com](https://ostechnix.com/how-to-find-if-a-cpu-supports-virtualization-technology-vt/). + +Order enough IPv4 and IPv6 (static and public) addresses to have one of each for each planned VM plus one extra for the main machine. + + +When you have your OS installed, validated CPU virtualisation support and obtained IP addresses, you can start configuring your VMs, following the steps below. + +> Note that the commands below require root permission. You can either go through the setup as `root` or use `sudo` prefix with the commands used in the guide. You can switch to `root` shell by entering one of these commands `sudo su` or `sudo -i`. + + +##### 1. Install KVM + +- Install KVM and required components: +```sh +apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virtinst +``` +
+ +- `qemu-kvm`: Provides the core **KVM virtualization** support using QEMU. +- `libvirt-daemon-system`: Manages virtual machines via the **libvirt daemon**. +- `libvirt-clients` Provides command-line tools like `virsh` to manage VMs. +- `bridge-utils`: Enables **network bridging**, allowing VMs to communicate over the network. +- `virtinst`: Includes `virt-install` for **creating virtual machines** via CLI. + + +- Start the `libvertd` service: +```sh +systemctl enable libvirtd +systemctl start libvirtd +``` +- Validate by checking status of `libvirt` service: +```sh +systemctl status libvirtd +``` +
+ +The command output should look similar to this one: +``` +root@nym-exit:~# systemctl status libvirtd +● libvirtd.service - Virtualization daemon + Loaded: loaded (/lib/systemd/system/libvirtd.service; enabled; vendor preset: enabled) + Active: active (running) since Thu 2025-02-27 14:25:28 MSK; 2min 1s ago +TriggeredBy: ● libvirtd-ro.socket + ● libvirtd.socket + ● libvirtd-admin.socket + Docs: man:libvirtd(8) + https://libvirt.org + Main PID: 6232 (libvirtd) + Tasks: 21 (limit: 32768) + Memory: 11.8M + CPU: 852ms + CGroup: /system.slice/libvirtd.service + ├─6232 /usr/sbin/libvirtd + ├─6460 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf --leasefile-ro --dhcp-script=/usr/lib/libvirt/libvirt_leaseshelper + └─6461 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf --leasefile-ro --dhcp-script=/usr/lib/libvirt/libvirt_leaseshelper + +Feb 27 14:25:28 nym-exit.example.com systemd[1]: Started Virtualization daemon. +Feb 27 14:25:30 nym-exit.example.com dnsmasq[6460]: started, version 2.90 cachesize 150 +Feb 27 14:25:30 nym-exit.example.com dnsmasq[6460]: compile time options: IPv6 GNU-getopt DBus no-UBus i18n IDN2 DHCP DHCPv6 no-Lua TFTP conntrack ipset no-nftset auth cryptohash DNSSEC loop-detect inotify dump> +Feb 27 14:25:30 nym-exit.example.com dnsmasq-dhcp[6460]: DHCP, IP range 192.168.122.2 -- 192.168.122.254, lease time 1h +Feb 27 14:25:30 nym-exit.example.com dnsmasq-dhcp[6460]: DHCP, sockets bound exclusively to interface virbr0 +Feb 27 14:25:30 nym-exit.example.com dnsmasq[6460]: reading /etc/resolv.conf +Feb 27 14:25:30 nym-exit.example.com dnsmasq[6460]: using nameserver 127.0.0.53#53 +Feb 27 14:25:30 nym-exit.example.com dnsmasq[6460]: read /etc/hosts - 8 names +Feb 27 14:25:30 nym-exit.example.com dnsmasq[6460]: read /var/lib/libvirt/dnsmasq/default.addnhosts - 0 names +Feb 27 14:25:30 nym-exit.example.com dnsmasq-dhcp[6460]: read /var/lib/libvirt/dnsmasq/default.hostsfile +``` + + +- In case you don't configure KVM as `root`, add your current user to the `kvm` and `libvirt` groups to enable VM creation and management using the `virsh` command-line tool or the `virt-manager` GUI: +```bash +usermod -aG kvm $USER +usermod -aG libvirt $USER +``` + +##### 2. Setup Bridge Networking with KVM + +A **bridged network** lets VMs share the host’s network interface, allowing direct IPv4/IPv6 access like a physical machine. + +By default, KVM sets up a **private virtual bridge**, enabling VM-to-VM communication within the host. It provides its own subnet, DHCP, and NAT for external access. + +Check the IP of KVM’s default virtual interfaces with: + +```bash +ip a +``` +
+ +The command output should look similar to this one: +``` +root@nym-exit:~# ip a +1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 + inet 127.0.0.1/8 scope host lo + valid_lft forever preferred_lft forever + inet6 ::1/128 scope host + valid_lft forever preferred_lft forever +2: eno1: mtu 1500 qdisc noop state DOWN group default qlen 1000 + link/ether 14:02:ec:35:2e:14 brd ff:ff:ff:ff:ff:ff + altname enp2s0f0 +3: eno49: mtu 1500 qdisc mq state UP group default qlen 1000 + link/ether 38:63:bb:2e:9d:20 brd ff:ff:ff:ff:ff:ff + altname enp4s0f0 + inet 31.222.238.222/24 brd 31.222.238.255 scope global eno49 + valid_lft forever preferred_lft forever + inet6 fe80::3a63:bbff:fe2e:9d20/64 scope link + valid_lft forever preferred_lft forever +4: eno2: mtu 1500 qdisc noop state DOWN group default qlen 1000 + link/ether 14:02:ec:35:2e:15 brd ff:ff:ff:ff:ff:ff + altname enp2s0f1 +5: eno3: mtu 1500 qdisc noop state DOWN group default qlen 1000 + link/ether 14:02:ec:35:2e:16 brd ff:ff:ff:ff:ff:ff + altname enp2s0f2 +6: eno50: mtu 1500 qdisc noop state DOWN group default qlen 1000 + link/ether 38:63:bb:2e:9d:24 brd ff:ff:ff:ff:ff:ff + altname enp4s0f1 +7: eno4: mtu 1500 qdisc noop state DOWN group default qlen 1000 + link/ether 14:02:ec:35:2e:17 brd ff:ff:ff:ff:ff:ff + altname enp2s0f3 +8: virbr0: mtu 1500 qdisc noqueue state DOWN group default qlen 1000 + link/ether 52:54:00:ac:d3:ba brd ff:ff:ff:ff:ff:ff + inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0 + valid_lft forever preferred_lft forever +``` + + + +By default, KVM uses the `virbr0` network with `.1/24`, assigning guest VMs IPs in the `.0/24` range. The host OS is reachable at `.1`, allowing SSH and file transfers (`scp`) between the host and guests. + +This setup works if you only access VMs from the host. However, remote systems on a different subnet (e.g., `.0/24`) **cannot** reach the VMs. + +To enable external access, we need a *public bridge* that connects VMs to the host’s main network, using its DHCP. This ensures VMs get IPs in the same range as the host. + +Before configuring a public bridge, **disable Netfilter** on bridges for better performance and security, as it is enabled by default. + +- Create a file located at `/etc/sysctl.d/bridge.conf`: +```bash +nano /etc/sysctl.d/bridge.conf + +# in case of using custom editor, replace nano in the syntax +``` + +- Paste inside the following block, save and exit: +```ini +net.bridge.bridge-nf-call-ip6tables=0 +net.bridge.bridge-nf-call-iptables=0 +net.bridge.bridge-nf-call-arptables=0 +``` + +- Create a file `/etc/udev/rules.d/99-bridge.rules`: +```bash +nano /etc/udev/rules.d/99-bridge.rules +``` + +- Paste this line, save and exit: +```bash +ACTION=="add", SUBSYSTEM=="module", KERNEL=="br_netfilter", RUN+="/sbin/sysctl -p /etc/sysctl.d/bridge.conf" +``` + +This disables Netfilter on bridges at startup. Save, exit, and reboot to apply changes. + +- Disable KVM’s default networking. Find the default network interface with: +```bash +ip link +``` + +
+ +The command output should look similar to this one: +``` +root@nym-exit:~# ip link +1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 +2: eno1: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 + link/ether 14:02:ec:35:2e:14 brd ff:ff:ff:ff:ff:ff + altname enp2s0f0 +3: eno2: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 + link/ether 14:02:ec:35:2e:15 brd ff:ff:ff:ff:ff:ff + altname enp2s0f1 +4: eno49: mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000 + link/ether 38:63:bb:2e:9d:20 brd ff:ff:ff:ff:ff:ff + altname enp4s0f0 +5: eno3: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 + link/ether 14:02:ec:35:2e:16 brd ff:ff:ff:ff:ff:ff + altname enp2s0f2 +6: eno50: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 + link/ether 38:63:bb:2e:9d:24 brd ff:ff:ff:ff:ff:ff + altname enp4s0f1 +7: eno4: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 + link/ether 14:02:ec:35:2e:17 brd ff:ff:ff:ff:ff:ff + altname enp2s0f3 +8: virbr0: mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default qlen 1000 + link/ether 52:54:00:ac:d3:ba brd ff:ff:ff:ff:ff:ff +``` + +The `virbr0` interface is KVM’s default network. Note your physical interface’s MAC address (e.g., `eno49`). It's the only interface that is currently `UP` and running (`LOWER_UP` state). Other interfaces are `DOWN` and not in use. + + +- Remove the default KVM network: +```bash +virsh net-destroy default +``` + +- Remove the default network configuration: +```bash +virsh net-undefine default +``` + +- In case last two commands didn't work, try this: +```bash +ip link delete virbr0 type bridge +``` +- Verify that the `virbr0` and `virbr0-nic` interfaces are deleted: +```bash +ip link +``` + +The command output should look similar to this one: +``` +root@nym-exit:~# ip link +1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 +2: eno1: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 + link/ether 14:02:ec:35:2e:14 brd ff:ff:ff:ff:ff:ff + altname enp2s0f0 +3: eno2: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 + link/ether 14:02:ec:35:2e:15 brd ff:ff:ff:ff:ff:ff + altname enp2s0f1 +4: eno49: mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000 + link/ether 38:63:bb:2e:9d:20 brd ff:ff:ff:ff:ff:ff + altname enp4s0f0 +5: eno3: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 + link/ether 14:02:ec:35:2e:16 brd ff:ff:ff:ff:ff:ff + altname enp2s0f2 +6: eno50: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 + link/ether 38:63:bb:2e:9d:24 brd ff:ff:ff:ff:ff:ff + altname enp4s0f1 +7: eno4: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 + link/ether 14:02:ec:35:2e:17 brd ff:ff:ff:ff:ff:ff + altname enp2s0f3 +``` +KVM network is gone. + + + +##### 3. Setup KVM public bridge for new VMs + +To create a KVM network bridge on Ubuntu, edit a config file located in `/etc/netplan/` either called `00-installer.yaml` or `00-installer-config.yaml` and add the bridge details. + +- Before you edit the file, make a backup to stay on the save side: +```bash +cp /etc/netplan/00-installer-config.yaml /etc/netplan/00-installer-config.yaml.bak +# or +cp /etc/netplan/00-installer.yaml /etc/netplan/00-installer.yaml.bak +``` + +- Open `00-installer-config.yaml` or `00-installer.yaml.`config in a text editor: +```bash +nano /etc/netplan/00-installer.yaml +# or +nano /etc/netplan/00-installer-config.yaml +``` + +- Edit the block below and paste it to the config file, save and exit: +```yaml +##################################################### +######## CHANGE ALL VARIABLES IN <> BRACKETS ######## +##################################################### + +# is your own one, you can get with command ip link show +# is your server main IPv4 address +# is your server main IPv6 address +# value can be found by running: ip -4 route | grep default +# value can be found by running: ip -6 route | grep default + +--- +network: + version: 2 + ethernets: + : + dhcp4: false + dhcp6: false + + # Bridge interface configuration + bridges: + br0: + interfaces: [] + addresses: + - /24 # IPv4> Address + - /64 # IPv6 Address + routes: + - to: default + via: # IPv4 Gateway + - to: default + via: # IPv6 Gateway + mtu: 1500 + nameservers: + addresses: + - 1.1.1.1 # Cloudflare IPv4 DNS + - 8.8.8.8 # Google IPv4 DNS + - 8.8.4.4 # Secondary Google IPv4 DNS + - 2606:4700:4700::1111 # Cloudflare IPv6 DNS + - 2001:4860:4860::8888 # Google IPv6 DNS + search: [] + parameters: + stp: false # Disable STP unless multiple bridges exist + forward-delay: 15 # Can be shortened, 15 sec is a common default +``` + + +Ensure the indentation matches exactly as shown above. Incorrect spacing will prevent the bridged network interface from activating. + + +- Validate `netplan` configuration without applying to prevent breaking network changes: +```bash +netplan generate + +# Correct configuration output will show nothing +``` + +- Safety test your changes to catch syntax errors before applying: +```bash +netplan try +``` + +- Apply your changes: +```bash +netplan --debug apply +``` + +- In case of proubems try some of these steps: + +- Validate YAML configuration, given that YAML is syntax sensitive: +```bash +apt install yamllint -y + +yamllint /etc/netplan/00-installer.yaml +# or +yamllint /etc/netplan/00-installer-config.yaml + + +``` +- Apply correct permissions: +```bash +chmod 600 /etc/netplan/00-installer.yaml +chown root:root /etc/netplan/00-installer.yaml +``` + +- Manually bring up the bridge: +```bash +ip link add name br0 type bridge +ip link set br0 up +ip a show br0 +``` + +- ensure `systemd-networkd` is enabled: +```bash +systemctl restart systemd-networkd +systemctl status systemd-networkd +# if inactive, enable it: +systemctl enable --now systemd-networkd +``` + + +- If things went wrong, you can always revert from the backed up file: +```bash +cp /etc/netplan/00-installer-config.yaml.bak /etc/netplan/00-installer-config.yaml +# or +cp /etc/netplan/00-installer.yaml.bak /etc/netplan/00-installer.yaml +# and +netplan apply +``` + + +Using different IPs for your physical NIC and KVM bridge will disconnect SSH when applying changes. Reconnect using the bridge's new IP. If both share the same IP, no disruption occurs. + + + +- Verify that the IP address has been assigned to the bridge interface: +```bash +ip a +``` + +The command output should look similar to this one: +``` +root@nym-exit:~# ip a +1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 + inet 127.0.0.1/8 scope host lo + valid_lft forever preferred_lft forever + inet6 ::1/128 scope host + valid_lft forever preferred_lft forever +2: eno1: mtu 1500 qdisc noop state DOWN group default qlen 1000 + link/ether 14:02:ec:35:2e:14 brd ff:ff:ff:ff:ff:ff + altname enp2s0f0 +3: eno2: mtu 1500 qdisc noop state DOWN group default qlen 1000 + link/ether 14:02:ec:35:2e:15 brd ff:ff:ff:ff:ff:ff + altname enp2s0f1 +4: eno3: mtu 1500 qdisc noop state DOWN group default qlen 1000 + link/ether 14:02:ec:35:2e:16 brd ff:ff:ff:ff:ff:ff + altname enp2s0f2 +5: eno49: mtu 1500 qdisc mq master br0 state UP group default qlen 1000 + link/ether 38:63:bb:2e:9d:20 brd ff:ff:ff:ff:ff:ff + altname enp4s0f0 +6: eno4: mtu 1500 qdisc noop state DOWN group default qlen 1000 + link/ether 14:02:ec:35:2e:17 brd ff:ff:ff:ff:ff:ff + altname enp2s0f3 +7: eno50: mtu 1500 qdisc noop state DOWN group default qlen 1000 + link/ether 38:63:bb:2e:9d:24 brd ff:ff:ff:ff:ff:ff + altname enp4s0f1 +8: br0: mtu 1500 qdisc noqueue state UP group default qlen 1000 + link/ether 46:50:aa:c0:49:a5 brd ff:ff:ff:ff:ff:ff + inet 31.222.238.222/24 brd 31.222.238.255 scope global br0 + valid_lft forever preferred_lft forever + inet6 fe80::4450:aaff:fec0:49a5/64 scope link + valid_lft forever preferred_lft forever +``` +The bridged interface `br0` now has the IP ``, and `` shows `master br0`, indicating it is part of the bridge. + + +Alternatively you can use `brctl` command to display the KVM bridge network status: +```bash +brctl show br0 +``` + +##### 4. Add Bridge Network to KVM + +- Configure KVM to use the bridge by creating `host-bridge.xml`, open a text editor and pate the block below: +```bash +nano host-bridge.xml +``` + +```xml + + host-bridge + + + +``` + +- Start the new bridge and set it as the default for VMs: +```bash +virsh net-define host-bridge.xml +virsh net-start host-bridge +virsh net-autostart host-bridge +``` + +- Verify that the KVM bridge is active: +```bash +virsh net-list --all +``` + +```bash +root@nym-exit:~# virsh net-list --all + Name State Autostart Persistent +------------------------------------------------ + host-bridge active yes yes +``` + + +KVM bridge networking is successfully set up and active! + +Your KVM installation is now ready to deploy and manage VMs. + +
+ +### Setting Up Virtual Machines + +After finishing the [installation of KVM](#installing-kvm-on-a-server-with-ubuntu-2204), we can move to the virtualisation configuration. + +> **The steps below will guide you through a setup of one VM, therefore you will have to repeat this process for each VM**. That also means that you have to be mindful of space and memory allocation. + + +##### 1. Install OS for VMs + +This is the OS on which the nodes themselves will run. You can chose any GNU/Linux of your preference. For this guide we are going to be using Ubuntu 24.04 LTS (Noble Numbat) cloud image from [cloud-images.ubuntu.com](https://cloud-images.ubuntu.com/noble/current/). + +- Download Ubuntu Cloud image: +```bash +wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img +``` +- Copy the image to to `/var/lib/libvirt/images/` asigning to it a name your VM +```bash +cp noble-server-cloudimg-amd64.img /var/lib/libvirt/images/.img + +# for example: +# cp noble-server-cloudimg-amd64.img /var/lib/libvirt/images/ubuntu-1.img +``` + +##### 2. Create and resize a virtual machine + +- Get `guestfs-tools` to be able to customize your login credentials: +```bash +apt install guestfs-tools +``` + +- Define login credentials: +```bash +virt-customize -a /var/lib/libvirt/images/.img --root-password password: +# for example +# virt-customize -a /var/lib/libvirt/images/ubuntu-1.img --root-password password:makesuretosaveyourpasswordslocallytoapasswordmanager +``` + +- Use `qemu-img` tool with a command `resize` to create a VM according your needs. You can see `qemu` [documentation page`](https://www.qemu.org/docs/master/tools/qemu-img.html) for more info on how to use it correctly. +```bash +qemu-img resize /var/lib/libvirt/images/.img +G +# for example +# qemu-img resize /var/lib/libvirt/images/ubuntu-1.img +100G +``` + +- Resize it from within it after `virt-install` command: +```bash +virt-install \ +--name \ +--ram= \ +--vcpus= \ +--cpu host \ +--hvm \ +--disk bus=virtio,path=/var/lib/libvirt/images/.img \ +--network bridge=br0 \ +--graphics none \ +--console pty,target_type=serial \ +--osinfo \ +--import +``` + +- In our example we go with 4 GB RAM on the same machine as before: +
+ +```bash +virt-install \ +--name ubuntu-1 \ +--ram=4096 \ +--vcpus=4 \ +--cpu host \ +--hvm \ +--disk bus=virtio,path=/var/lib/libvirt/images/ubuntu-1.img \ +--network bridge=br0 \ +--graphics none \ +--console pty,target_type=serial \ +--osinfo ubuntunoble \ +--import +``` + + +- After loading you should see a login console, you can also initiate it by: +```bash +virsh console +# for example +# virsh console ubuntu-1 +``` + +- Log in to your new VM using your credentials. + +##### 3. Validate your setup + +- Make sure the `root` disk has the expected space by running: +```bash +df -h +``` + +- If not, run: +```bash +growpart /dev/vda 1 +resize2fs /dev/vda1 +``` + +##### 4. Configure networking for the VM + +As this guide is based on a newer Ubuntu, we use `netplan`, this may be different on different OS. + +- Open `/etc/netplan/01-network-config.yaml` in your favourite text editor: +```bash +nano /etc/netplan/01-network-config.yaml +``` + +- Insert this config, using your correct IP configuration, save and exit: +```yaml +##################################################### +######## CHANGE ALL VARIABLES IN <> BRACKETS ######## +##################################################### + +# is your own one, you can get with command ip link show +# is your VM IPv4 address +# is your VM Pv6 address +# value can be found by running: ip -4 route | grep default +# value can be found by running: ip -6 route | grep default + +network: + version: 2 + renderer: networkd + ethernets: + : + dhcp4: false + dhcp6: false # Set to true if you want automatic IPv6 assignment + addresses: + - /24 # Assign IPv4 address to the VM + - /64 # Assign IPv6 address to the VM + routes: + - to: default + via: # IPv4 gateway + - to: default + via: # IPv6 gateway + nameservers: + addresses: + - 1.1.1.1 # Cloudflare IPv4 DNS + - 8.8.8.8 # Google IPv4 DNS + - 8.8.4.4 # Secondary Google IPv4 DNS + - 2606:4700:4700::1111 # Cloudflare IPv6 DNS + - 2001:4860:4860::8888 # Google IPv6 DNS +``` +- Fix wide permissions on the config file: +```bash +chmod 600 /etc/netplan/01-network-config.yaml +``` + +- Check if the config has any errors: +```bash +netplan generate +``` + +- Apply the configuration: +```bash +netplan --debug apply +``` + +- Verify by checking if IPv4 and IPv6 are assigned correctly and if they route: +```bash +ip -4 a +ip -6 a +``` +```bash +ip -4 r +ip -6 r +``` +```bash +# to ping through IPv6, use: +ping6 nym.com +``` +- You should be able to ping your new VM from a local machine: +```bash +ping +ping6 +``` + +
+ +Your VM should be working and fully routable. To be able to use it properly, we will create a direct SSH access to the VM. + +#### Configure VM SSH access + + + +##### 1. Log in to your VM, update and upgrade your OS: +- Log in to your server using as `root` or as a non-root user with `sudo` privileges +```bash +apt update; apt upgrade +``` + +##### 2. Generate new host SSH keys + +Since we used a `cloud-init` image without an SSH server, we need to generate SSH host keys for client authentication and server identity verification. All of them will be saved to this location: `/etc/ssh/`. + +- Generate a new RSA host key: +```bash +ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key +``` +- Generate a new DSA host key: +```bash +ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key +``` +- Generate a new ECDSA host key: +```bash +ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key +``` +- Finally, generate a new ED25519 host key: +```bash +ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key +``` +##### 3. Restart the SSH service on the server +- Run: +```bash +systemctl restart ssh.service +``` + +##### 4. Check if the SSH serice is active +- Run: +```bash +systemctl status ssh.service +``` + +##### 5. Create file `~/.ssh/authorized_keys` and add you public key: +- Create `.ssh` directory: +```bash +mkdir ~/.ssh +``` + +- Open with your favourite text editor: +```bash +nano ~/.ssh/authorized_keys +``` +- Paste your SSH public key, save and exit + +- In case of non-root, setup a correct ownership and permissions: +```bash +chmod 600 ~/.ssh/authorized_keys +chmod 700 ~/.ssh +chown : ~/.ssh +``` + +##### 5. Test by connecting via SSH + +- Now you should be able to connect to the VM directly from your local terminal +```bash +ssh root@ -i ~/.ssh/your_ssh_key +``` + + +Now your VM is almost ready for `nym-node` [setup](../../nym-node/setup). Before you proceed, ssh in and [configure all prerequisities](../vps-setup#vps-configuration) needed for `nym-node` installation and operation. diff --git a/documentation/docs/pages/operators/nodes/validator-setup/nym-api.mdx b/documentation/docs/pages/operators/nodes/validator-setup/nym-api.mdx index 23cc443f4b..5b4847aaba 100644 --- a/documentation/docs/pages/operators/nodes/validator-setup/nym-api.mdx +++ b/documentation/docs/pages/operators/nodes/validator-setup/nym-api.mdx @@ -94,7 +94,7 @@ The example value of `100` for `pruning-interval` can be customised as per your ### Credential Generation -Validators that took part in the DKG ceremony became part of the 'quorum' generating and verifying zk-nym credentials. These will initially be used for private proof of payment for NymVPN (moe [here](https://blog.nym.com/nymvpn-an-invitation-for-privacy-experts-and-enthusiasts-63644139d09d) and [here](https://blog.nym.com/zk-nyms-are-here-a-major-milestone-towards-a-market-ready-mixnet-a3470c9ab10a)), and in the future will be expanded into more general use-cases such as [offline ecash](https://arxiv.org/abs/2303.08221). +Validators that took part in the DKG ceremony became part of the 'quorum' generating and verifying zk-nym credentials. These will initially be used for private proof of payment for NymVPN (more [here](https://nym.com/blog/invitation-for-privacy-experts-and-enthusiasts) and [here](https://nym.com/blog/zk-nyms-are-here-a-major-milestone-towards-a-market-ready-mixnet), and in the future will be expanded into more general use-cases such as [offline ecash](https://arxiv.org/abs/2303.08221). The DKG ceremony was used to create a subset of existing validators who run `nym-api` alongside a Nyx full-node. As outlined above, they are the ones taking part in the generation and verification of zk-nym credentials. The size of the 'minimum viable quorum' is 10 - the intial set taking part in DKG was 17 validators. This is in order to have some redundancy in the case of a validator dropping or going offline. diff --git a/documentation/docs/pages/operators/tokenomics.mdx b/documentation/docs/pages/operators/tokenomics.mdx index ef5986df04..64d9fe1e2a 100644 --- a/documentation/docs/pages/operators/tokenomics.mdx +++ b/documentation/docs/pages/operators/tokenomics.mdx @@ -21,7 +21,7 @@ import { AccordionTemplate } from 'components/accordion-template.tsx'; Nym Network is composed of two main elements, the Mixnet represented by [Nym Nodes](nodes/nym-node/nym-node.mdx) routing and mixing the data packets, and Nyx blockchain distributted accros [validator set](tokenomics/validator-rewards.mdx), using smart contracts (based on [cosmwasm](https://cosmwasm.com/)) to monitor and reward Nym Nodes by querying API endpoints and distributing NYM token to operators according to work done by their nodes. All Nym nodes and validators are run by decentralised community of operators. * Nym tokenomics are based on the research paper [*Reward Sharing for Mixnets*](https://nym.com/nym-cryptoecon-paper.pdf) -* For a more comprehensive overview, token live data and supply graphs, visit [*nym.com/about/token*](https://nymtech.net/about/token) +* For a more comprehensive overview, token live data and graphs, visit a community managed dashboard [*explorer.nym.spectredao.net/token*](https://explorer.nym.spectredao.net/dashboard) * To read about rewards calculation, visit [Nym Node rewards page](tokenomics/mixnet-rewards.mdx) * To understand the implementation and release plan, see [Nym operators roadmap](tokenomics/mixnet-rewards.mdx#roadmap) @@ -87,7 +87,7 @@ To get live data, visit [Nym token page](https://nym.com.net/about/token) or see ### Calculation & Explanation -To get a full comprehension of [node operators rewards](tokenomics/mixnet-rewards.mdx) calculation and [delegators APR height](https://nym.com/about/token), we need to understand some basic logic behind the numbers presented. This chapter covers some of the most essential variables in Nym tokenomics flow. +To get a full comprehension of [node operators rewards](tokenomics/mixnet-rewards.mdx) calculation and delegators APR height, we need to understand some basic logic behind the numbers presented. This chapter covers some of the most essential variables in Nym tokenomics flow. ```ascii @@ -104,7 +104,7 @@ To get a full comprehension of [node operators rewards](tokenomics/mixnet-reward
Circulating supply is NYM. -NYM token is capped at 1b. Visit [Nym token page](https://nym.com/about/token) to see live data and graphs. +NYM token is capped at 1b. Visit [SpectreDAO token dashboard](https://explorer.nym.spectredao.net/token) to see live data, graphs and historical data. #### Staking target diff --git a/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx b/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx index ea5cab1beb..7f76d40e98 100644 --- a/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx +++ b/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx @@ -360,7 +360,7 @@ $1 \ NYM = 1 \_ 000 \_ 000 \ uNYM$ The Reward Estimation API endpoint allows Mix Node operators to estimate the rewards they could earn for running a Nym Mix Node with a specific `MIX_ID`. -> The `` can be found in the "Mix ID" column of the [Network Explorer](https://explorer.nymtech.net/network-components/mixnodes/active). +> The `` can be found in the "Mix ID" column of the [Network Explorer](https://nym.com/explorer). The endpoint is a particularly common for Mix Node operators as it can provide an estimate of potential earnings based on factors such as the amount of traffic routed through the Mix Node, the quality of the Mix Node's performance, and the overall demand for Mix Nodes in the network. This information can be useful for Mix Node operators in deciding whether or not to run a Mix Node and in optimizing its operations for maximum profitability. diff --git a/documentation/docs/pages/operators/troubleshooting/nodes.mdx b/documentation/docs/pages/operators/troubleshooting/nodes.mdx index c8e5d06b25..64c09e4a6c 100644 --- a/documentation/docs/pages/operators/troubleshooting/nodes.mdx +++ b/documentation/docs/pages/operators/troubleshooting/nodes.mdx @@ -73,7 +73,7 @@ The directory structure for each node will be roughly as follows: ``` -If you `cat` the `public_sphinx.pem` key, the output will be different from the public key you will see on Nym [dashboard](https://explorer.nymtech.net/). The reason for this is that `.pem` files are encoded in **base64**, however on the web they are in **base58**. Don't be confused if your keys look different. They are the same keys, just with different encoding. +If you `cat` the `public_sphinx.pem` key, the output will be different from the public key you will see on Nym [dashboard](https://nym.com/explorer). The reason for this is that `.pem` files are encoded in **base64**, however on the web they are in **base58**. Don't be confused if your keys look different. They are the same keys, just with different encoding. ### Accidentally killing your node process on exiting session @@ -101,7 +101,7 @@ You don't have to do any additional configuration for your node to implement thi ### How can I tell my node is up and running and mixing traffic? First of all check the 'Mixnodes' section of either of the Nym Network Explorers: -* [Mainnet](https://explorer.nymtech.net/) +* [Mainnet](https://nym.com/explorer) * [Sandbox testnet](https://sandbox-explorer.nymtech.net/) You can also check [Nym Harbourmaster](https://harbourmaster.nymtech.net) which now also includes mixnode mode. diff --git a/documentation/docs/pnpm-lock.yaml b/documentation/docs/pnpm-lock.yaml index 48f2a523b0..7c1df27a6c 100644 --- a/documentation/docs/pnpm-lock.yaml +++ b/documentation/docs/pnpm-lock.yaml @@ -99,14 +99,14 @@ importers: specifier: ^0.438.0 version: 0.438.0(react@18.3.1) next: - specifier: ^13.4.19 - version: 13.5.7(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^14.2.15 + version: 14.2.15(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nextra: specifier: '2' - version: 2.13.4(next@13.5.7(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.13.4(next@14.2.15(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nextra-theme-docs: specifier: '2' - version: 2.13.4(next@13.5.7(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@2.13.4(next@13.5.7(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.13.4(next@14.2.15(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@2.13.4(next@14.2.15(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.2.0 version: 18.3.1 @@ -232,6 +232,7 @@ packages: '@confio/ics23@0.6.8': resolution: {integrity: sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w==} + deprecated: Unmaintained. The codebase for this package was moved to https://github.com/cosmos/ics23 but then the JS implementation was removed in https://github.com/cosmos/ics23/pull/353. Please consult the maintainers of https://github.com/cosmos for further assistance. '@coreui/coreui@5.1.2': resolution: {integrity: sha512-ub7KfpLo5OomUQ2Ah6rvSgmX+JzJmJ7XfU951BYpcpveCdAy1GWaKhH83JGVqcKQyHVBe+z8Y5Z5s19WmWzilw==} @@ -874,62 +875,62 @@ packages: resolution: {integrity: sha512-jMxvwzkKzd3cXo2EB9GM2ic0eYo2rP/BS6gJt6HnWbsDO1O8GSD4k7o2Cpr2YERtMpGF/MGcDfsfj2EbQPtrXw==} engines: {node: '>= 10'} - '@next/env@13.5.7': - resolution: {integrity: sha512-uVuRqoj28Ys/AI/5gVEgRAISd0KWI0HRjOO1CTpNgmX3ZsHb5mdn14Y59yk0IxizXdo7ZjsI2S7qbWnO+GNBcA==} + '@next/env@14.2.15': + resolution: {integrity: sha512-S1qaj25Wru2dUpcIZMjxeMVSwkt8BK4dmWHHiBuRstcIyOsMapqT4A4jSB6onvqeygkSSmOkyny9VVx8JIGamQ==} '@next/eslint-plugin-next@13.4.13': resolution: {integrity: sha512-RpZeXlPxQ9FLeYN84XHDqRN20XxmVNclYCraLYdifRsmibtcWUWdwE/ANp2C8kgesFRsvwfsw6eOkYNl9sLJ3A==} - '@next/swc-darwin-arm64@13.5.7': - resolution: {integrity: sha512-7SxmxMex45FvKtRoP18eftrDCMyL6WQVYJSEE/s7A1AW/fCkznxjEShKet2iVVzf89gWp8HbXGaL4hCaseux6g==} + '@next/swc-darwin-arm64@14.2.15': + resolution: {integrity: sha512-Rvh7KU9hOUBnZ9TJ28n2Oa7dD9cvDBKua9IKx7cfQQ0GoYUwg9ig31O2oMwH3wm+pE3IkAQ67ZobPfEgurPZIA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@13.5.7': - resolution: {integrity: sha512-6iENvgyIkGFLFszBL4b1VfEogKC3TDPEB6/P/lgxmgXVXIV09Q4or1MVn+U/tYyYmm7oHMZ3oxGpHAyJ80nA6g==} + '@next/swc-darwin-x64@14.2.15': + resolution: {integrity: sha512-5TGyjFcf8ampZP3e+FyCax5zFVHi+Oe7sZyaKOngsqyaNEpOgkKB3sqmymkZfowy3ufGA/tUgDPPxpQx931lHg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@13.5.7': - resolution: {integrity: sha512-P42jDX56wu9zEdVI+Xv4zyTeXB3DpqgE1Gb4bWrc0s2RIiDYr6uKBprnOs1hCGIwfVyByxyTw5Va66QCdFFNUg==} + '@next/swc-linux-arm64-gnu@14.2.15': + resolution: {integrity: sha512-3Bwv4oc08ONiQ3FiOLKT72Q+ndEMyLNsc/D3qnLMbtUYTQAmkx9E/JRu0DBpHxNddBmNT5hxz1mYBphJ3mfrrw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@13.5.7': - resolution: {integrity: sha512-A06vkj+8X+tLRzSja5REm/nqVOCzR+x5Wkw325Q/BQRyRXWGCoNbQ6A+BR5M86TodigrRfI3lUZEKZKe3QJ9Bg==} + '@next/swc-linux-arm64-musl@14.2.15': + resolution: {integrity: sha512-k5xf/tg1FBv/M4CMd8S+JL3uV9BnnRmoe7F+GWC3DxkTCD9aewFRH1s5rJ1zkzDa+Do4zyN8qD0N8c84Hu96FQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@13.5.7': - resolution: {integrity: sha512-UdHm7AlxIbdRdMsK32cH0EOX4OmzAZ4Xm+UVlS0YdvwLkI3pb7AoBEoVMG5H0Wj6Wpz6GNkrFguHTRLymTy6kw==} + '@next/swc-linux-x64-gnu@14.2.15': + resolution: {integrity: sha512-kE6q38hbrRbKEkkVn62reLXhThLRh6/TvgSP56GkFNhU22TbIrQDEMrO7j0IcQHcew2wfykq8lZyHFabz0oBrA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@13.5.7': - resolution: {integrity: sha512-c50Y8xBKU16ZGj038H6C13iedRglxvdQHD/1BOtes56gwUrIRDX2Nkzn3mYtpz3Wzax0gfAF9C0Nqljt93IxvA==} + '@next/swc-linux-x64-musl@14.2.15': + resolution: {integrity: sha512-PZ5YE9ouy/IdO7QVJeIcyLn/Rc4ml9M2G4y3kCM9MNf1YKvFY4heg3pVa/jQbMro+tP6yc4G2o9LjAz1zxD7tQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@13.5.7': - resolution: {integrity: sha512-NcUx8cmkA+JEp34WNYcKW6kW2c0JBhzJXIbw+9vKkt9m/zVJ+KfizlqmoKf04uZBtzFN6aqE2Fyv2MOd021WIA==} + '@next/swc-win32-arm64-msvc@14.2.15': + resolution: {integrity: sha512-2raR16703kBvYEQD9HNLyb0/394yfqzmIeyp2nDzcPV4yPjqNUG3ohX6jX00WryXz6s1FXpVhsCo3i+g4RUX+g==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-ia32-msvc@13.5.7': - resolution: {integrity: sha512-wXp+/3NVcuyJDED6gJiLXs5dqHaWO7moAB6aBtjlKZvsxBDxpcyjsfRbtHPeYtaT20zCkmPs69H0K25lrVZmlA==} + '@next/swc-win32-ia32-msvc@14.2.15': + resolution: {integrity: sha512-fyTE8cklgkyR1p03kJa5zXEaZ9El+kDNM5A+66+8evQS5e/6v0Gk28LqA0Jet8gKSOyP+OTm/tJHzMlGdQerdQ==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@next/swc-win32-x64-msvc@13.5.7': - resolution: {integrity: sha512-PLyD3Dl6jTTkLG8AoqhPGd5pXtSs8wbqIhWPQt3yEMfnYld/dGYuF2YPs3YHaVFrijCIF9pXY3+QOyvP23Zn7g==} + '@next/swc-win32-x64-msvc@14.2.15': + resolution: {integrity: sha512-SzqGbsLsP9OwKNUG9nekShTwhj6JSB9ZLMWQ8g1gG6hdE5gQLncbnbymrwy2yVmH9nikSLYRYxYMFu78Ggp7/g==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2483,11 +2484,14 @@ packages: '@starknet-io/types-js@0.7.7': resolution: {integrity: sha512-WLrpK7LIaIb8Ymxu6KF/6JkGW1sso988DweWu7p5QY/3y7waBIiPvzh27D9bX5KIJNRDyOoOVoHVEKYUYWZ/RQ==} + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + '@swc/helpers@0.5.13': resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==} - '@swc/helpers@0.5.2': - resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==} + '@swc/helpers@0.5.5': + resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} '@tanstack/react-virtual@3.10.8': resolution: {integrity: sha512-VbzbVGSsZlQktyLrP5nxE+vE1ZR+U0NFAWPbJLoG2+DKPwd2D7dVICTVIIaYlJqX1ZCEnYDbaOpmMwbsyhBoIA==} @@ -5035,8 +5039,8 @@ packages: nan@2.22.0: resolution: {integrity: sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==} - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -5071,18 +5075,21 @@ packages: react: '*' react-dom: '*' - next@13.5.7: - resolution: {integrity: sha512-W7KIRTE+hPcgGdq89P3mQLDX3m7pJ6nxSyC+YxYaUExE+cS4UledB+Ntk98tKoyhsv6fjb2TRAnD7VDvoqmeFg==} - engines: {node: '>=16.14.0'} + next@14.2.15: + resolution: {integrity: sha512-h9ctmOokpoDphRvMGnwOJAedT6zKhwqyZML9mDtspgf4Rh3Pn7UTYKqePNoDvhsWBAO5GoPNYshnAUGIazVGmw==} + engines: {node: '>=18.17.0'} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 react: ^18.2.0 react-dom: ^18.2.0 sass: ^1.3.0 peerDependenciesMeta: '@opentelemetry/api': optional: true + '@playwright/test': + optional: true sass: optional: true @@ -6469,10 +6476,6 @@ packages: vscode-textmate@8.0.0: resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} - watchpack@2.4.0: - resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} - engines: {node: '>=10.13.0'} - watchpack@2.4.2: resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} engines: {node: '>=10.13.0'} @@ -7898,37 +7901,37 @@ snapshots: '@napi-rs/simple-git-win32-arm64-msvc': 0.1.19 '@napi-rs/simple-git-win32-x64-msvc': 0.1.19 - '@next/env@13.5.7': {} + '@next/env@14.2.15': {} '@next/eslint-plugin-next@13.4.13': dependencies: glob: 7.1.7 - '@next/swc-darwin-arm64@13.5.7': + '@next/swc-darwin-arm64@14.2.15': optional: true - '@next/swc-darwin-x64@13.5.7': + '@next/swc-darwin-x64@14.2.15': optional: true - '@next/swc-linux-arm64-gnu@13.5.7': + '@next/swc-linux-arm64-gnu@14.2.15': optional: true - '@next/swc-linux-arm64-musl@13.5.7': + '@next/swc-linux-arm64-musl@14.2.15': optional: true - '@next/swc-linux-x64-gnu@13.5.7': + '@next/swc-linux-x64-gnu@14.2.15': optional: true - '@next/swc-linux-x64-musl@13.5.7': + '@next/swc-linux-x64-musl@14.2.15': optional: true - '@next/swc-win32-arm64-msvc@13.5.7': + '@next/swc-win32-arm64-msvc@14.2.15': optional: true - '@next/swc-win32-ia32-msvc@13.5.7': + '@next/swc-win32-ia32-msvc@14.2.15': optional: true - '@next/swc-win32-x64-msvc@13.5.7': + '@next/swc-win32-x64-msvc@14.2.15': optional: true '@nextui-org/accordion@2.0.40(@nextui-org/system@2.2.6(@nextui-org/theme@2.2.11(tailwindcss@3.4.14))(framer-motion@11.11.9(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.2.11(tailwindcss@3.4.14))(framer-motion@11.11.9(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': @@ -10642,12 +10645,15 @@ snapshots: '@starknet-io/types-js@0.7.7': {} + '@swc/counter@0.1.3': {} + '@swc/helpers@0.5.13': dependencies: tslib: 2.8.0 - '@swc/helpers@0.5.2': + '@swc/helpers@0.5.5': dependencies: + '@swc/counter': 0.1.3 tslib: 2.8.0 '@tanstack/react-virtual@3.10.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': @@ -12256,7 +12262,7 @@ snapshots: eslint: 8.46.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(eslint@8.46.0))(eslint@8.46.0) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.6.3)(eslint@8.46.0) eslint-plugin-jsx-a11y: 6.10.1(eslint@8.46.0) eslint-plugin-react: 7.37.2(eslint@8.46.0) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.46.0) @@ -12281,20 +12287,20 @@ snapshots: debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.46.0 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.46.0) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.6.3)(eslint@8.46.0) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.46.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -12305,7 +12311,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.6.3)(eslint@8.46.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -12316,7 +12322,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.46.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.46.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -14008,7 +14014,7 @@ snapshots: nan@2.22.0: {} - nanoid@3.3.7: {} + nanoid@3.3.8: {} natural-compare@1.4.0: {} @@ -14032,44 +14038,44 @@ snapshots: transitivePeerDependencies: - supports-color - next-seo@6.6.0(next@13.5.7(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-seo@6.6.0(next@14.2.15(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - next: 13.5.7(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 14.2.15(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next-themes@0.2.1(next@13.5.7(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-themes@0.2.1(next@14.2.15(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - next: 13.5.7(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 14.2.15(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next@13.5.7(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@14.2.15(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@next/env': 13.5.7 - '@swc/helpers': 0.5.2 + '@next/env': 14.2.15 + '@swc/helpers': 0.5.5 busboy: 1.6.0 caniuse-lite: 1.0.30001669 + graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.1(babel-plugin-macros@3.1.0)(react@18.3.1) - watchpack: 2.4.0 optionalDependencies: - '@next/swc-darwin-arm64': 13.5.7 - '@next/swc-darwin-x64': 13.5.7 - '@next/swc-linux-arm64-gnu': 13.5.7 - '@next/swc-linux-arm64-musl': 13.5.7 - '@next/swc-linux-x64-gnu': 13.5.7 - '@next/swc-linux-x64-musl': 13.5.7 - '@next/swc-win32-arm64-msvc': 13.5.7 - '@next/swc-win32-ia32-msvc': 13.5.7 - '@next/swc-win32-x64-msvc': 13.5.7 + '@next/swc-darwin-arm64': 14.2.15 + '@next/swc-darwin-x64': 14.2.15 + '@next/swc-linux-arm64-gnu': 14.2.15 + '@next/swc-linux-arm64-musl': 14.2.15 + '@next/swc-linux-x64-gnu': 14.2.15 + '@next/swc-linux-x64-musl': 14.2.15 + '@next/swc-win32-arm64-msvc': 14.2.15 + '@next/swc-win32-ia32-msvc': 14.2.15 + '@next/swc-win32-x64-msvc': 14.2.15 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@2.13.4(next@13.5.7(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@2.13.4(next@13.5.7(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + nextra-theme-docs@2.13.4(next@14.2.15(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@2.13.4(next@14.2.15(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@headlessui/react': 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@popperjs/core': 2.11.8 @@ -14080,16 +14086,16 @@ snapshots: git-url-parse: 13.1.1 intersection-observer: 0.12.2 match-sorter: 6.3.4 - next: 13.5.7(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - next-seo: 6.6.0(next@13.5.7(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - next-themes: 0.2.1(next@13.5.7(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - nextra: 2.13.4(next@13.5.7(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 14.2.15(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next-seo: 6.6.0(next@14.2.15(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next-themes: 0.2.1(next@14.2.15(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + nextra: 2.13.4(next@14.2.15(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) scroll-into-view-if-needed: 3.1.0 zod: 3.23.8 - nextra@2.13.4(next@13.5.7(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + nextra@2.13.4(next@14.2.15(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@headlessui/react': 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mdx-js/mdx': 2.3.0 @@ -14103,7 +14109,7 @@ snapshots: gray-matter: 4.0.3 katex: 0.16.11 lodash.get: 4.4.2 - next: 13.5.7(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 14.2.15(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-mdx-remote: 4.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) p-limit: 3.1.0 react: 18.3.1 @@ -14452,19 +14458,19 @@ snapshots: postcss@8.4.31: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.8 picocolors: 1.1.1 source-map-js: 1.2.1 postcss@8.4.38: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.8 picocolors: 1.1.1 source-map-js: 1.2.1 postcss@8.4.47: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.8 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -15727,11 +15733,6 @@ snapshots: vscode-textmate@8.0.0: {} - watchpack@2.4.0: - dependencies: - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - watchpack@2.4.2: dependencies: glob-to-regexp: 0.4.1 diff --git a/documentation/docs/public/images/smiley.png b/documentation/docs/public/images/smiley.png new file mode 100644 index 0000000000..57a1301867 Binary files /dev/null and b/documentation/docs/public/images/smiley.png differ diff --git a/documentation/docs/theme.config.tsx b/documentation/docs/theme.config.tsx index e0bb456e64..19bc1049ab 100644 --- a/documentation/docs/theme.config.tsx +++ b/documentation/docs/theme.config.tsx @@ -1,7 +1,8 @@ import React from "react"; import { DocsThemeConfig, useConfig } from "nextra-theme-docs"; -import { Footer } from "./components/footer"; +// import { Footer } from "./components/footer"; import { Matrix } from "./components/matrix-link"; +import { Explorer } from "./components/explorer-link"; import { useRouter } from "next/router"; const config: DocsThemeConfig = { @@ -66,9 +67,9 @@ const config: DocsThemeConfig = { }, docsRepositoryBase: "https://github.com/nymtech/nym/tree/develop/documentation/docs/", - footer: { - text: Footer, - }, + // footer: { + // text: Footer, + // }, darkMode: true, nextThemes: { defaultTheme: "dark", @@ -77,8 +78,14 @@ const config: DocsThemeConfig = { defaultMenuCollapseLevel: 1, autoCollapse: true, }, + navbar: { - extraContent: , + extraContent: ( + <> + + + + ), }, toc: { float: false, diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index 42f4af8204..6bff50d87a 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "explorer-api" -version = "1.1.46" +version = "1.1.48" edition = "2021" license.workspace = true diff --git a/explorer-nextjs/package.json b/explorer-nextjs/package.json index 05ed83fcf2..2f60c7bb87 100644 --- a/explorer-nextjs/package.json +++ b/explorer-nextjs/package.json @@ -11,7 +11,7 @@ "dependencies": { "@nymproject/react": "^1.0.0", "@nymproject/nym-validator-client": "0.18.0", - "next": "14.1.4", + "next": "14.2.21", "react": "^18", "react-dom": "^18", "react-error-boundary": "^4.0.13", diff --git a/explorer-v2/.eslintrc.json b/explorer-v2/.eslintrc.json new file mode 100644 index 0000000000..3722418549 --- /dev/null +++ b/explorer-v2/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["next/core-web-vitals", "next/typescript"] +} diff --git a/explorer-v2/.gitignore b/explorer-v2/.gitignore new file mode 100644 index 0000000000..d32cc78b89 --- /dev/null +++ b/explorer-v2/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/explorer-v2/README.md b/explorer-v2/README.md new file mode 100644 index 0000000000..e215bc4ccf --- /dev/null +++ b/explorer-v2/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/explorer-v2/biome.json b/explorer-v2/biome.json new file mode 100644 index 0000000000..509af0d9bd --- /dev/null +++ b/explorer-v2/biome.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": ["node_modules", ".next", "public"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space" + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "warn" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + } +} diff --git a/explorer-v2/next.config.ts b/explorer-v2/next.config.ts new file mode 100644 index 0000000000..9c0e03fea6 --- /dev/null +++ b/explorer-v2/next.config.ts @@ -0,0 +1,23 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + reactStrictMode: true, + + basePath: "/explorer", + assetPrefix: "/explorer", + trailingSlash: false, + + async redirects() { + return [ + // Change the basePath to /explorer + { + source: "/", + destination: "/explorer", + basePath: false, + permanent: true, + }, + ] + } +}; + +export default nextConfig; diff --git a/explorer-v2/package.json b/explorer-v2/package.json new file mode 100644 index 0000000000..9ec1f64cd8 --- /dev/null +++ b/explorer-v2/package.json @@ -0,0 +1,64 @@ +{ + "name": "@nymproject/explorer-v2", + "version": "0.1.0", + "scripts": { + "dev": "next dev", + "build": "next build", + "build:prod": "yarn --cwd .. build && next build", + "start": "next start", + "lint": "biome check --fix" + }, + "engines": { + "node": ">=20.0.0" + }, + "dependencies": { + "@chain-registry/types": "^0.50.36", + "@cosmos-kit/keplr-extension": "^2.14.0", + "@cosmos-kit/react": "^2.20.1", + "@emotion/cache": "^11.13.5", + "@emotion/react": "^11.13.5", + "@emotion/styled": "^11.13.5", + "@interchain-ui/react": "^1.26.1", + "@mui/icons-material": "^5.16.11", + "@mui/material": "^6.1.10", + "@mui/material-nextjs": "^6.1.9", + "@mui/x-date-pickers": "^7.23.2", + "@nivo/line": "^0.88.0", + "@nymproject/contract-clients": "^1.4.1", + "@nymproject/react": "1.0.0", + "@tanstack/react-query": "^5.64.2", + "@tanstack/react-query-devtools": "^5.64.2", + "@tanstack/react-query-next-experimental": "^5.66.0", + "@tanstack/react-table": "^8.20.6", + "@types/qs": "^6.9.18", + "@uidotdev/usehooks": "^2.4.1", + "chain-registry": "^1.69.64", + "cldr-compact-number": "^0.4.0", + "date-fns": "^4.1.0", + "i18next": "^24.2.2", + "i18next-resources-to-backend": "^1.2.1", + "isomorphic-dompurify": "^2.21.0", + "material-react-table": "^3.0.3", + "next": "^15.2.0", + "openapi-fetch": "^0.13.4", + "qrcode.react": "^4.1.0", + "qs": "^6.14.0", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "react-i18next": "^15.4.0", + "react-markdown": "^9.0.3", + "react-random-avatars": "^1.3.1", + "react-world-flags": "^1.6.0", + "zod": "^3.24.1" + }, + "devDependencies": { + "@biomejs/biome": "1.9.4", + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "eslint": "^8", + "eslint-config-next": "15.0.3", + "lefthook": "^1.8.5", + "typescript": "^5" + } +} diff --git a/explorer-v2/public/file.svg b/explorer-v2/public/file.svg new file mode 100644 index 0000000000..004145cddf --- /dev/null +++ b/explorer-v2/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/explorer-v2/public/globe.svg b/explorer-v2/public/globe.svg new file mode 100644 index 0000000000..567f17b0d7 --- /dev/null +++ b/explorer-v2/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/explorer-v2/public/icons/arrow-up-right.svg b/explorer-v2/public/icons/arrow-up-right.svg new file mode 100644 index 0000000000..85c8811910 --- /dev/null +++ b/explorer-v2/public/icons/arrow-up-right.svg @@ -0,0 +1,61 @@ + + + + + + + diff --git a/explorer-v2/public/icons/arrow.svg b/explorer-v2/public/icons/arrow.svg new file mode 100644 index 0000000000..3e5d0f6f14 --- /dev/null +++ b/explorer-v2/public/icons/arrow.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/explorer-v2/public/icons/buy-card.svg b/explorer-v2/public/icons/buy-card.svg new file mode 100644 index 0000000000..b51ac05a31 --- /dev/null +++ b/explorer-v2/public/icons/buy-card.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/explorer-v2/public/icons/chevron.svg b/explorer-v2/public/icons/chevron.svg new file mode 100644 index 0000000000..d6426509f1 --- /dev/null +++ b/explorer-v2/public/icons/chevron.svg @@ -0,0 +1,13 @@ + + + + + + + diff --git a/explorer-v2/public/icons/chevronMenu.svg b/explorer-v2/public/icons/chevronMenu.svg new file mode 100644 index 0000000000..640cce1853 --- /dev/null +++ b/explorer-v2/public/icons/chevronMenu.svg @@ -0,0 +1,14 @@ + + + diff --git a/explorer-v2/public/icons/copy-file.svg b/explorer-v2/public/icons/copy-file.svg new file mode 100644 index 0000000000..ff12690bfb --- /dev/null +++ b/explorer-v2/public/icons/copy-file.svg @@ -0,0 +1,35 @@ + + + + + diff --git a/explorer-v2/public/icons/cross.svg b/explorer-v2/public/icons/cross.svg new file mode 100644 index 0000000000..72732bb0b5 --- /dev/null +++ b/explorer-v2/public/icons/cross.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + diff --git a/explorer-v2/public/icons/discord.svg b/explorer-v2/public/icons/discord.svg new file mode 100644 index 0000000000..ed48b19966 --- /dev/null +++ b/explorer-v2/public/icons/discord.svg @@ -0,0 +1,3 @@ + + + diff --git a/explorer-v2/public/icons/document.svg b/explorer-v2/public/icons/document.svg new file mode 100644 index 0000000000..ba6c3209a4 --- /dev/null +++ b/explorer-v2/public/icons/document.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/explorer-v2/public/icons/download.svg b/explorer-v2/public/icons/download.svg new file mode 100644 index 0000000000..88b6b35201 --- /dev/null +++ b/explorer-v2/public/icons/download.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/explorer-v2/public/icons/elips.svg b/explorer-v2/public/icons/elips.svg new file mode 100644 index 0000000000..7f95c1055a --- /dev/null +++ b/explorer-v2/public/icons/elips.svg @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/explorer-v2/public/icons/explorer-card.svg b/explorer-v2/public/icons/explorer-card.svg new file mode 100644 index 0000000000..d146f4c382 --- /dev/null +++ b/explorer-v2/public/icons/explorer-card.svg @@ -0,0 +1,3 @@ + + + diff --git a/explorer-v2/public/icons/gateway.svg b/explorer-v2/public/icons/gateway.svg new file mode 100644 index 0000000000..50673da861 --- /dev/null +++ b/explorer-v2/public/icons/gateway.svg @@ -0,0 +1,79 @@ + + + + + + + + + diff --git a/explorer-v2/public/icons/github.svg b/explorer-v2/public/icons/github.svg new file mode 100644 index 0000000000..583e772c18 --- /dev/null +++ b/explorer-v2/public/icons/github.svg @@ -0,0 +1,3 @@ + + + diff --git a/explorer-v2/public/icons/nym-logo.svg b/explorer-v2/public/icons/nym-logo.svg new file mode 100644 index 0000000000..d6c718150a --- /dev/null +++ b/explorer-v2/public/icons/nym-logo.svg @@ -0,0 +1,14 @@ + + + + + diff --git a/explorer-v2/public/icons/operate-card.svg b/explorer-v2/public/icons/operate-card.svg new file mode 100644 index 0000000000..ccce4549ae --- /dev/null +++ b/explorer-v2/public/icons/operate-card.svg @@ -0,0 +1,3 @@ + + + diff --git a/explorer-v2/public/icons/stake-card.svg b/explorer-v2/public/icons/stake-card.svg new file mode 100644 index 0000000000..2b7c24e2a4 --- /dev/null +++ b/explorer-v2/public/icons/stake-card.svg @@ -0,0 +1,3 @@ + + + diff --git a/explorer-v2/public/icons/telegram.svg b/explorer-v2/public/icons/telegram.svg new file mode 100644 index 0000000000..ab1bd1e5fd --- /dev/null +++ b/explorer-v2/public/icons/telegram.svg @@ -0,0 +1,3 @@ + + + diff --git a/explorer-v2/public/icons/token.svg b/explorer-v2/public/icons/token.svg new file mode 100644 index 0000000000..6b56daa618 --- /dev/null +++ b/explorer-v2/public/icons/token.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/explorer-v2/public/icons/twitter.svg b/explorer-v2/public/icons/twitter.svg new file mode 100644 index 0000000000..2fa76f9dd9 --- /dev/null +++ b/explorer-v2/public/icons/twitter.svg @@ -0,0 +1,4 @@ + + + + diff --git a/explorer-v2/public/icons/youtube.svg b/explorer-v2/public/icons/youtube.svg new file mode 100644 index 0000000000..3899653f45 --- /dev/null +++ b/explorer-v2/public/icons/youtube.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/explorer-v2/public/icons/youtubeInverted.svg b/explorer-v2/public/icons/youtubeInverted.svg new file mode 100644 index 0000000000..d89e357463 --- /dev/null +++ b/explorer-v2/public/icons/youtubeInverted.svg @@ -0,0 +1,4 @@ + + + + diff --git a/explorer-v2/public/images/Network.webp b/explorer-v2/public/images/Network.webp new file mode 100644 index 0000000000..f8eec41111 Binary files /dev/null and b/explorer-v2/public/images/Network.webp differ diff --git a/explorer-v2/public/images/buy-article.webp b/explorer-v2/public/images/buy-article.webp new file mode 100644 index 0000000000..7d59ca36ac Binary files /dev/null and b/explorer-v2/public/images/buy-article.webp differ diff --git a/explorer-v2/public/images/placeholder.webp b/explorer-v2/public/images/placeholder.webp new file mode 100644 index 0000000000..2c5e4325ee Binary files /dev/null and b/explorer-v2/public/images/placeholder.webp differ diff --git a/explorer-v2/public/images/stake-article.webp b/explorer-v2/public/images/stake-article.webp new file mode 100644 index 0000000000..7ea416c3ec Binary files /dev/null and b/explorer-v2/public/images/stake-article.webp differ diff --git a/explorer-v2/public/next.svg b/explorer-v2/public/next.svg new file mode 100644 index 0000000000..5174b28c56 --- /dev/null +++ b/explorer-v2/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/explorer-v2/public/profileImagePlaceholder.png b/explorer-v2/public/profileImagePlaceholder.png new file mode 100644 index 0000000000..bbeef84ed0 Binary files /dev/null and b/explorer-v2/public/profileImagePlaceholder.png differ diff --git a/explorer-v2/public/vercel.svg b/explorer-v2/public/vercel.svg new file mode 100644 index 0000000000..7705396033 --- /dev/null +++ b/explorer-v2/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/explorer-v2/public/window.svg b/explorer-v2/public/window.svg new file mode 100644 index 0000000000..b2b2a44f6e --- /dev/null +++ b/explorer-v2/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/explorer-v2/src/app/(pages)/account/[address]/not-found/page.tsx b/explorer-v2/src/app/(pages)/account/[address]/not-found/page.tsx new file mode 100644 index 0000000000..2cbac6434d --- /dev/null +++ b/explorer-v2/src/app/(pages)/account/[address]/not-found/page.tsx @@ -0,0 +1,62 @@ +// import BlogArticlesCards from "@/components/blogs/BlogArticleCards"; +import { ContentLayout } from "@/components/contentLayout/ContentLayout"; +import SectionHeading from "@/components/headings/SectionHeading"; +import ExplorerButtonGroup from "@/components/toggleButton/ToggleButton"; +import { Box, Typography } from "@mui/material"; +import Grid from "@mui/material/Grid2"; +import Markdown from "react-markdown"; + +export default async function Account({ + params, +}: { + params: Promise<{ address: string }>; +}) { + try { + const address = (await params).address; + + return ( + + + + + + + + + + + + This account does’t have a Nym node bonded. Is this your account? + Start [setting up your node](https://nym.com/docs) today! + + + {/* + + + + + */} + + ); + } catch (error) { + let errorMessage = "An error occurred"; + if (error instanceof Error) { + errorMessage = error.message; + } + throw new Error(errorMessage); + } +} diff --git a/explorer-v2/src/app/(pages)/account/[address]/page.tsx b/explorer-v2/src/app/(pages)/account/[address]/page.tsx new file mode 100644 index 0000000000..283494e74f --- /dev/null +++ b/explorer-v2/src/app/(pages)/account/[address]/page.tsx @@ -0,0 +1,67 @@ +import { fetchObservatoryNodes } from "@/app/api"; +import type { IObservatoryNode } from "@/app/api/types"; +import { Box, Typography } from "@mui/material"; +import Grid from "@mui/material/Grid2"; +import { AccountBalancesCard } from "../../../../components/accountPageComponents/AccountBalancesCard"; +import { AccountInfoCard } from "../../../../components/accountPageComponents/AccountInfoCard"; +import { ContentLayout } from "../../../../components/contentLayout/ContentLayout"; +import SectionHeading from "../../../../components/headings/SectionHeading"; +import ExplorerButtonGroup from "../../../../components/toggleButton/ToggleButton"; + +export default async function Account({ + params, +}: { + params: Promise<{ address: string }>; +}) { + try { + const address = (await params).address; + + const nymNodes: IObservatoryNode[] = await fetchObservatoryNodes(); + + const nymNode = nymNodes.find( + (node) => node.bonding_address === address, + ); + + return ( + + + + + + + + + + + + + + + + + + + + + ); + } catch (error) { + console.error("error :>> ", error); + return Error loading account data; + } +} diff --git a/explorer-v2/src/app/(pages)/nym-node/[id]/page.tsx b/explorer-v2/src/app/(pages)/nym-node/[id]/page.tsx new file mode 100644 index 0000000000..84daa3c76c --- /dev/null +++ b/explorer-v2/src/app/(pages)/nym-node/[id]/page.tsx @@ -0,0 +1,128 @@ +import { fetchNodeIdByIdentityKey, fetchNodeInfo } from "@/app/api"; +import { ContentLayout } from "@/components/contentLayout/ContentLayout"; +import SectionHeading from "@/components/headings/SectionHeading"; +import { BasicInfoCard } from "@/components/nymNodePageComponents/BasicInfoCard"; +import { NodeDataCard } from "@/components/nymNodePageComponents/NodeDataCard"; +// import { NodeChatCard } from "@/components/nymNodePageComponents/ChatCard"; +import NodeDelegationsCard from "@/components/nymNodePageComponents/NodeDelegationsCard"; +import { NodeParametersCard } from "@/components/nymNodePageComponents/NodeParametersCard"; +import { NodeProfileCard } from "@/components/nymNodePageComponents/NodeProfileCard"; +import { NodeRoleCard } from "@/components/nymNodePageComponents/NodeRoleCard"; +import ExplorerButtonGroup from "@/components/toggleButton/ToggleButton"; +import { Box } from "@mui/material"; +import Grid from "@mui/material/Grid2"; + +export default async function NymNode({ + params, +}: { + params: Promise<{ id: string }>; // node_id or identity_key +}) { + try { + let id: string | number; + const paramsId = (await params).id; + + // check if the params id is a node_id or identity_key + + if (paramsId.length > 10) { + id = await fetchNodeIdByIdentityKey(paramsId); + } else { + id = Number(paramsId); + } + + const observatoryNymNode = await fetchNodeInfo(id); + + if (!observatoryNymNode) { + return null; + } + + return ( + + + + + + {observatoryNymNode.bonding_address && ( + + )} + + + + + + + + + + + + + + + + + + + + + {/* + + + */} + + + ); + } catch (error) { + let errorMessage = "An error occurred"; + if (error instanceof Error) { + errorMessage = error.message; + } + throw new Error(errorMessage); + } +} diff --git a/explorer-v2/src/app/(pages)/onboarding/[slug]/page.tsx b/explorer-v2/src/app/(pages)/onboarding/[slug]/page.tsx new file mode 100644 index 0000000000..07eed3224c --- /dev/null +++ b/explorer-v2/src/app/(pages)/onboarding/[slug]/page.tsx @@ -0,0 +1,155 @@ +import TableOfContents from "@/components/blogs/TableOfContents"; +import type BlogArticle from "@/components/blogs/types"; +import { Breadcrumbs } from "@/components/breadcrumbs/Breadcrumbs"; +import { ContentLayout } from "@/components/contentLayout/ContentLayout"; +import SectionHeading from "@/components/headings/SectionHeading"; +import { Link } from "@/components/muiLink"; +import { Wrapper } from "@/components/wrapper"; +import { Box, Stack, Typography } from "@mui/material"; +import Grid from "@mui/material/Grid2"; +import { format } from "date-fns"; +import Image from "next/image"; +import Markdown from "react-markdown"; + +export default async function BlogPage({ + params, +}: { + params: Promise<{ slug: string }>; +}) { + const { slug } = await params; + + try { + const blogArticle: BlogArticle = await import(`@/data/${slug}.json`); + + const breadcrumbItems = [ + { + label: "Onboarding", + href: "/onboarding", + }, + { label: blogArticle.title, isCurrentPage: true }, + ]; + return ( + + + + + + + + + + + Author + {(blogArticle?.attributes?.blogAuthors?.length ?? 0) > 1 + ? "s" + : ""} + :{" "} + {blogArticle?.attributes?.blogAuthors?.map( + (author: string) => ( + + {author} + + ) + )} + + + + + {blogArticle.attributes.readingTime}{" "} + {blogArticle.attributes.readingTime > 1 ? "mins" : "min"}{" "} + read + + + blog-image + + {blogArticle.overview.content.map(({ text }) => ( + + + + {text} + + + + ))} + + {blogArticle.sections.map((section) => ( + + + {section.text.map(({ text }) => ( + + + + {text} + + + + ))} + + ))} + + + + ({ + heading: section.heading, + id: section.id, + }))} + /> + + + + + ); + } catch (error) { + console.log(error); + + return ( + + + + + Oops! Looks like the page you’re looking for got mixed up in the + noise. Don’t worry, your privacy is intact. Let’s get you + back to the homepage. + + + + ); + } +} diff --git a/explorer-v2/src/app/(pages)/onboarding/page.tsx b/explorer-v2/src/app/(pages)/onboarding/page.tsx new file mode 100644 index 0000000000..9808a075b0 --- /dev/null +++ b/explorer-v2/src/app/(pages)/onboarding/page.tsx @@ -0,0 +1,15 @@ +import BlogArticlesCards from "@/components/blogs/BlogArticleCards"; +import { ContentLayout } from "@/components/contentLayout/ContentLayout"; +import SectionHeading from "@/components/headings/SectionHeading"; +import Grid from "@mui/material/Grid2"; + +export default function OnboardingPage() { + return ( + + + + + + + ); +} diff --git a/explorer-v2/src/app/(pages)/stake/page.tsx b/explorer-v2/src/app/(pages)/stake/page.tsx new file mode 100644 index 0000000000..0ce0d012ed --- /dev/null +++ b/explorer-v2/src/app/(pages)/stake/page.tsx @@ -0,0 +1,24 @@ +// import BlogArticlesCards from "@/components/blogs/BlogArticleCards"; +// import Grid from "@mui/material/Grid2"; +import { ContentLayout } from "../../../components/contentLayout/ContentLayout"; +import SectionHeading from "../../../components/headings/SectionHeading"; +import OverviewCards from "../../../components/staking/OverviewCards"; +import StakeTableWithAction from "../../../components/staking/StakeTableWithAction"; +import SubHeaderRow from "../../../components/staking/SubHeaderRow"; + +export default async function StakingPage() { + return ( + + + + + + {/* + + + + + */} + + ); +} diff --git a/explorer-v2/src/app/(pages)/table/page.tsx b/explorer-v2/src/app/(pages)/table/page.tsx new file mode 100644 index 0000000000..7e6fb4b62d --- /dev/null +++ b/explorer-v2/src/app/(pages)/table/page.tsx @@ -0,0 +1,30 @@ +// import BlogArticlesCards from "@/components/blogs/BlogArticleCards"; +import { ContentLayout } from "@/components/contentLayout/ContentLayout"; +import SectionHeading from "@/components/headings/SectionHeading"; +import NodeTableWithAction from "@/components/nodeTable/NodeTableWithAction"; +import NodeAndAddressSearch from "@/components/search/NodeAndAddressSearch"; +import { Wrapper } from "@/components/wrapper"; +import { Box, Stack } from "@mui/material"; +// import Grid from "@mui/material/Grid2"; + +export default function ExplorerPage() { + return ( + + + + + + + + + + {/* + + + + + */} + + + ); +} diff --git a/explorer-v2/src/app/api/index.tsx b/explorer-v2/src/app/api/index.tsx new file mode 100644 index 0000000000..687372c2f2 --- /dev/null +++ b/explorer-v2/src/app/api/index.tsx @@ -0,0 +1,259 @@ +import { addSeconds } from "date-fns"; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import type { + CurrentEpochData, + ExplorerData, + GatewayStatus, + IAccountBalancesInfo, + IObservatoryNode, + IPacketsAndStakingData, + NodeRewardDetails, + NymTokenomics, + ObservatoryBalance, +} from "./types"; +import { + CURRENT_EPOCH, + CURRENT_EPOCH_REWARDS, + DATA_OBSERVATORY_BALANCES_URL, + DATA_OBSERVATORY_NODES_URL, + NYM_ACCOUNT_ADDRESS, + NYM_PRICES_API, + OBSERVATORY_GATEWAYS_URL, +} from "./urls"; + +// Fetch function for epoch rewards +export const fetchEpochRewards = async (): Promise< + ExplorerData["currentEpochRewardsData"] +> => { + const response = await fetch(CURRENT_EPOCH_REWARDS, { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + cache: "no-store", // Ensures fresh data on every request + }); + + if (!response.ok) { + throw new Error("Failed to fetch epoch rewards"); + } + + return response.json(); +}; + +// Fetch gateway status based on identity key +export const fetchGatewayStatus = async ( + identityKey: string, +): Promise => { + const response = await fetch(`${OBSERVATORY_GATEWAYS_URL}/${identityKey}`); + + if (!response.ok) { + throw new Error("Failed to fetch gateway status"); + } + + return response.json(); +}; + +export const fetchNodeInfo = async ( + id: number, +): Promise => { + const nodes = await fetchObservatoryNodes(); + return nodes?.find((node) => node.node_id === id); +}; + +export const fetchNodeIdByIdentityKey = async ( + identity_key: string, +): Promise => { + const nodes = await fetchObservatoryNodes(); + const node = nodes?.find((node) => node.identity_key === identity_key); + return node?.node_id || 0; +}; + +export const fetchNodeDelegations = async ( + id: number, +): Promise => { + const response = await fetch( + `${DATA_OBSERVATORY_NODES_URL}/${id}/delegations`, + { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + }, + ); + + if (!response.ok) { + throw new Error("Failed to fetch delegations"); + } + + return response.json(); +}; + +export const fetchCurrentEpoch = async () => { + const response = await fetch(CURRENT_EPOCH, { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + cache: "no-store", // Ensures fresh data on every request + }); + + if (!response.ok) { + throw new Error("Failed to fetch current epoch data"); + } + + const data: CurrentEpochData = await response.json(); + const epochEndTime = addSeconds( + new Date(data.current_epoch_start), + data.epoch_length.secs, + ).toISOString(); + + return { ...data, current_epoch_end: epochEndTime }; +}; + +// Fetch balances based on the address +export const fetchBalances = async (address: string): Promise => { + const response = await fetch(`${DATA_OBSERVATORY_BALANCES_URL}/${address}`, { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch balances"); + } + + const balances: ObservatoryBalance = await response.json(); + + // Calculate total stake + return ( + Number(balances.rewards.staking_rewards.amount) + + Number(balances.delegated.amount) + ); +}; + +// Fetch function to get total staker rewards +export const fetchTotalStakerRewards = async ( + address: string, +): Promise => { + const response = await fetch(`${DATA_OBSERVATORY_BALANCES_URL}/${address}`, { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch balances"); + } + + const balances: ObservatoryBalance = await response.json(); + + // Return the staking rewards amount + return Number(balances.rewards.staking_rewards.amount); +}; + +// Fetch function to get the original stake +export const fetchOriginalStake = async (address: string): Promise => { + const response = await fetch(`${DATA_OBSERVATORY_BALANCES_URL}/${address}`, { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch balances"); + } + + const balances: ObservatoryBalance = await response.json(); + + // Return the delegated amount + return Number(balances.delegated.amount); +}; + +export const fetchNoise = async (): Promise => { + if (!process.env.NEXT_PUBLIC_NS_API_MIXNODES_STATS) { + throw new Error( + "NEXT_PUBLIC_NS_API_MIXNODES_STATS environment variable is not defined", + ); + } + const response = await fetch(process.env.NEXT_PUBLIC_NS_API_MIXNODES_STATS, { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + }); + + const data: IPacketsAndStakingData[] = await response.json(); + return data; +}; + +// Fetch Account Balance +export const fetchAccountBalance = async ( + address: string, +): Promise => { + const res = await fetch(`${NYM_ACCOUNT_ADDRESS}/${address}`, { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + }); + if (!res.ok) { + throw new Error("Failed to fetch account balance error from api"); + } + + const data: IAccountBalancesInfo = await res.json(); + return data; +}; + + + +export const fetchObservatoryNodes = async (): Promise => { + const allNodes: IObservatoryNode[] = []; + let page = 1; + const PAGE_SIZE = 200; + let hasMoreData = true; + + while (hasMoreData) { + const response = await fetch( + `${DATA_OBSERVATORY_NODES_URL}?page=${page}&limit=${PAGE_SIZE}`, + { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + }, + ); + + if (!response.ok) { + throw new Error(`Failed to fetch observatory nodes (page ${page})`); + } + + const nodes: IObservatoryNode[] = await response.json(); + allNodes.push(...nodes); + + if (nodes.length < PAGE_SIZE) { + hasMoreData = false; // Stop fetching when the last page has fewer than 200 items + } else { + page++; // Move to the next page + } + } + + return allNodes; +}; + +// 🔹 Fetch NYM Price +export const fetchNymPrice = async (): Promise => { + const res = await fetch(NYM_PRICES_API, { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + }); + if (!res.ok) { + throw new Error("Failed to fetch NYM price"); + } + const data: NymTokenomics = await res.json(); + return data; +}; diff --git a/explorer-v2/src/app/api/strapi.d.ts b/explorer-v2/src/app/api/strapi.d.ts new file mode 100644 index 0000000000..2c177527cb --- /dev/null +++ b/explorer-v2/src/app/api/strapi.d.ts @@ -0,0 +1,23417 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/about-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/about-page"]; + put: operations["put/about-page"]; + post?: never; + delete: operations["delete/about-page"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/about-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/about-page/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/basic-pages": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/basic-pages"]; + put?: never; + post: operations["post/basic-pages"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/basic-pages/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/basic-pages/{id}"]; + put: operations["put/basic-pages/{id}"]; + post?: never; + delete: operations["delete/basic-pages/{id}"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/basic-pages/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/basic-pages/{id}/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/blog-authors": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/blog-authors"]; + put?: never; + post: operations["post/blog-authors"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/blog-authors/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/blog-authors/{id}"]; + put: operations["put/blog-authors/{id}"]; + post?: never; + delete: operations["delete/blog-authors/{id}"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/blog-authors/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/blog-authors/{id}/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/blog-categories": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/blog-categories"]; + put?: never; + post: operations["post/blog-categories"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/blog-categories/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/blog-categories/{id}"]; + put: operations["put/blog-categories/{id}"]; + post?: never; + delete: operations["delete/blog-categories/{id}"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/blog-categories/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/blog-categories/{id}/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/blog-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/blog-page"]; + put: operations["put/blog-page"]; + post?: never; + delete: operations["delete/blog-page"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/blog-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/blog-page/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/blog-posts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/blog-posts"]; + put?: never; + post: operations["post/blog-posts"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/blog-posts/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/blog-posts/{id}"]; + put: operations["put/blog-posts/{id}"]; + post?: never; + delete: operations["delete/blog-posts/{id}"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/blog-posts/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/blog-posts/{id}/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/downloads": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/downloads"]; + put?: never; + post: operations["post/downloads"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/downloads/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/downloads/{id}"]; + put: operations["put/downloads/{id}"]; + post?: never; + delete: operations["delete/downloads/{id}"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/downloads/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/downloads/{id}/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/downloads-menu": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/downloads-menu"]; + put: operations["put/downloads-menu"]; + post?: never; + delete: operations["delete/downloads-menu"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/downloads-menu/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/downloads-menu/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/features": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/features"]; + put?: never; + post: operations["post/features"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/features/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/features/{id}"]; + put: operations["put/features/{id}"]; + post?: never; + delete: operations["delete/features/{id}"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/features/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/features/{id}/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/feature-categories": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/feature-categories"]; + put?: never; + post: operations["post/feature-categories"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/feature-categories/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/feature-categories/{id}"]; + put: operations["put/feature-categories/{id}"]; + post?: never; + delete: operations["delete/feature-categories/{id}"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/feature-categories/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/feature-categories/{id}/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/features-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/features-page"]; + put: operations["put/features-page"]; + post?: never; + delete: operations["delete/features-page"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/features-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/features-page/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/footer": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/footer"]; + put: operations["put/footer"]; + post?: never; + delete: operations["delete/footer"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/footer/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/footer/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/glossary-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/glossary-page"]; + put: operations["put/glossary-page"]; + post?: never; + delete: operations["delete/glossary-page"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/glossary-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/glossary-page/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/glossary-terms": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/glossary-terms"]; + put?: never; + post: operations["post/glossary-terms"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/glossary-terms/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/glossary-terms/{id}"]; + put: operations["put/glossary-terms/{id}"]; + post?: never; + delete: operations["delete/glossary-terms/{id}"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/glossary-terms/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/glossary-terms/{id}/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/home-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/home-page"]; + put: operations["put/home-page"]; + post?: never; + delete: operations["delete/home-page"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/home-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/home-page/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/litepaper": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/litepaper"]; + put: operations["put/litepaper"]; + post?: never; + delete: operations["delete/litepaper"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/litepaper/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/litepaper/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/navigation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/navigation"]; + put: operations["put/navigation"]; + post?: never; + delete: operations["delete/navigation"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/navigation/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/navigation/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/network-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/network-page"]; + put: operations["put/network-page"]; + post?: never; + delete: operations["delete/network-page"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/network-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/network-page/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/page-not-found": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/page-not-found"]; + put: operations["put/page-not-found"]; + post?: never; + delete: operations["delete/page-not-found"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/page-not-found/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/page-not-found/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/press-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/press-page"]; + put: operations["put/press-page"]; + post?: never; + delete: operations["delete/press-page"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/press-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/press-page/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/press-posts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/press-posts"]; + put?: never; + post: operations["post/press-posts"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/press-posts/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/press-posts/{id}"]; + put: operations["put/press-posts/{id}"]; + post?: never; + delete: operations["delete/press-posts/{id}"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/press-posts/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/press-posts/{id}/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/team-members": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/team-members"]; + put?: never; + post: operations["post/team-members"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/team-members/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/team-members/{id}"]; + put: operations["put/team-members/{id}"]; + post?: never; + delete: operations["delete/team-members/{id}"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/team-members/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/team-members/{id}/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/trust-center-advisors-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/trust-center-advisors-page"]; + put: operations["put/trust-center-advisors-page"]; + post?: never; + delete: operations["delete/trust-center-advisors-page"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/trust-center-advisors-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/trust-center-advisors-page/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/trust-center-categories": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/trust-center-categories"]; + put?: never; + post: operations["post/trust-center-categories"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/trust-center-categories/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/trust-center-categories/{id}"]; + put: operations["put/trust-center-categories/{id}"]; + post?: never; + delete: operations["delete/trust-center-categories/{id}"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/trust-center-categories/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/trust-center-categories/{id}/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/trust-center-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/trust-center-page"]; + put: operations["put/trust-center-page"]; + post?: never; + delete: operations["delete/trust-center-page"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/trust-center-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/trust-center-page/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/trust-center-posts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/trust-center-posts"]; + put?: never; + post: operations["post/trust-center-posts"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/trust-center-posts/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/trust-center-posts/{id}"]; + put: operations["put/trust-center-posts/{id}"]; + post?: never; + delete: operations["delete/trust-center-posts/{id}"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/trust-center-posts/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/trust-center-posts/{id}/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/use-cases": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/use-cases"]; + put?: never; + post: operations["post/use-cases"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/use-cases/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/use-cases/{id}"]; + put: operations["put/use-cases/{id}"]; + post?: never; + delete: operations["delete/use-cases/{id}"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/use-cases/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/use-cases/{id}/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/use-cases-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get/use-cases-page"]; + put: operations["put/use-cases-page"]; + post?: never; + delete: operations["delete/use-cases-page"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/use-cases-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["post/use-cases-page/localizations"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/upload": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** @description Upload files */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** @description Upload files */ + requestBody: { + content: { + "multipart/form-data": { + /** @description The folder where the file(s) will be uploaded to (only supported on strapi-provider-upload-aws-s3). */ + path?: string; + /** @description The ID of the entry which the file(s) will be linked to */ + refId?: string; + /** @description The unique ID (uid) of the model which the file(s) will be linked to (api::restaurant.restaurant). */ + ref?: string; + /** @description The field of the entry which the file(s) will be precisely linked to. */ + field?: string; + files: string[]; + }; + }; + }; + responses: { + /** @description response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UploadFile"][]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/upload?id={id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** @description Upload file information */ + post: { + parameters: { + query: { + /** @description File id */ + id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + /** @description Upload files */ + requestBody: { + content: { + "multipart/form-data": { + fileInfo?: { + name?: string; + alternativeText?: string; + caption?: string; + }; + /** Format: binary */ + files?: string; + }; + }; + }; + responses: { + /** @description response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UploadFile"][]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/upload/files": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Get a list of files */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UploadFile"][]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/upload/files/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Get a specific file */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UploadFile"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Delete a file */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UploadFile"]; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/connect/{provider}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Login with a provider + * @description Redirects to provider login before being redirect to /auth/{provider}/callback + */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Provider name */ + provider: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Redirect response */ + 301: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/local": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Local login + * @description Returns a jwt token and user info + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + /** @example { + * "identifier": "foobar", + * "password": "Test1234" + * } */ + "application/json": { + identifier?: string; + password?: string; + }; + }; + }; + responses: { + /** @description Connection */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Users-Permissions-UserRegistration"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/local/register": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Register a user + * @description Returns a jwt token and user info + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + /** @example { + * "username": "foobar", + * "email": "foo.bar@strapi.io", + * "password": "Test1234" + * } */ + "application/json": { + username?: string; + email?: string; + password?: string; + }; + }; + }; + responses: { + /** @description Successful registration */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Users-Permissions-UserRegistration"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/{provider}/callback": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Default Callback from provider auth */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Provider name */ + provider: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns a jwt token and user info */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Users-Permissions-UserRegistration"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/forgot-password": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Send rest password email */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + /** @example { + * "email": "foo.bar@strapi.io" + * } */ + "application/json": { + email?: string; + }; + }; + }; + responses: { + /** @description Returns ok */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @enum {string} */ + ok?: true; + }; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/reset-password": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Rest user password */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + /** @example { + * "password": "Test1234", + * "passwordConfirmation": "Test1234", + * "code": "zertyoaizndoianzodianzdonaizdoinaozdnia" + * } */ + "application/json": { + password?: string; + passwordConfirmation?: string; + code?: string; + }; + }; + }; + responses: { + /** @description Returns a jwt token and user info */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Users-Permissions-UserRegistration"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/change-password": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Update user's own password */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + password: string; + currentPassword: string; + passwordConfirmation: string; + }; + }; + }; + responses: { + /** @description Returns a jwt token and user info */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Users-Permissions-UserRegistration"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/email-confirmation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Confirm user email */ + get: { + parameters: { + query?: { + /** @description confirmation token received by email */ + confirmation?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Redirects to the configure email confirmation redirect url */ + 301: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/send-email-confirmation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Send confirmation email */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + email?: string; + }; + }; + }; + responses: { + /** @description Returns email and boolean to confirm email was sent */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + email?: string; + /** @enum {string} */ + sent?: true; + }; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/users-permissions/permissions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get default generated permissions */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns the permissions tree */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + permissions?: components["schemas"]["Users-Permissions-PermissionsTree"]; + }; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/users-permissions/roles": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List roles */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns list of roles */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + roles?: (components["schemas"]["Users-Permissions-Role"] & { + nb_users?: number; + })[]; + }; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + /** Create a role */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: components["requestBodies"]["Users-Permissions-RoleRequest"]; + responses: { + /** @description Returns ok if the role was create */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @enum {string} */ + ok?: true; + }; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/users-permissions/roles/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get a role */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** @description role Id */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns the role */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + role?: components["schemas"]["Users-Permissions-Role"]; + }; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/users-permissions/roles/{role}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** Update a role */ + put: { + parameters: { + query?: never; + header?: never; + path: { + /** @description role Id */ + role: string; + }; + cookie?: never; + }; + requestBody: components["requestBodies"]["Users-Permissions-RoleRequest"]; + responses: { + /** @description Returns ok if the role was udpated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @enum {string} */ + ok?: true; + }; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + post?: never; + /** Delete a role */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + /** @description role Id */ + role: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns ok if the role was delete */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @enum {string} */ + ok?: true; + }; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/users": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get list of users */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns an array of users */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Users-Permissions-User"][]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + /** Create a user */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + /** @example { + * "username": "foo", + * "email": "foo@strapi.io", + * "password": "foo-password" + * } */ + "application/json": { + email: string; + username: string; + password: string; + }; + }; + }; + responses: { + /** @description Returns created user info */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Users-Permissions-User"] & { + role?: components["schemas"]["Users-Permissions-Role"]; + }; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/users/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get a user */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** @description user Id */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns a user */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Users-Permissions-User"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + /** Update a user */ + put: { + parameters: { + query?: never; + header?: never; + path: { + /** @description user Id */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** @example { + * "username": "foo", + * "email": "foo@strapi.io", + * "password": "foo-password" + * } */ + "application/json": { + email: string; + username: string; + password: string; + }; + }; + }; + responses: { + /** @description Returns updated user info */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Users-Permissions-User"] & { + role?: components["schemas"]["Users-Permissions-Role"]; + }; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + post?: never; + /** Delete a user */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + /** @description user Id */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns deleted user info */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Users-Permissions-User"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/users/me": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get authenticated user info */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns user info */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Users-Permissions-User"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/users/count": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get user count */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns a number */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + Error: { + data?: (Record | Record[]) | null; + error: { + status?: number; + name?: string; + message?: string; + details?: Record; + }; + }; + AboutPageLocalizationRequest: { + SEO?: components["schemas"]["SharedSeoComponent"]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + )[]; + locale: string; + }; + AboutPageRequest: { + data: { + SEO?: components["schemas"]["SharedSeoComponent"]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + )[]; + locale?: string; + }; + }; + AboutPageResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["AboutPage"]; + }; + AboutPageLocalizationResponse: { + data?: components["schemas"]["AboutPageResponseDataObjectLocalized"]; + meta?: Record; + }; + AboutPageListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["AboutPage"]; + }; + AboutPageLocalizationListResponse: { + data?: components["schemas"]["AboutPageListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + AboutPageListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["AboutPage"]; + }; + AboutPageListResponse: { + data?: components["schemas"]["AboutPageListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + AboutPage: { + SEO?: components["schemas"]["SharedSeoComponent"]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + )[]; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["AboutPageListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + AboutPageResponseDataObject: { + id?: number; + attributes?: components["schemas"]["AboutPage"]; + }; + AboutPageResponse: { + data?: components["schemas"]["AboutPageResponseDataObject"]; + meta?: Record; + }; + SharedSeoComponent: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: { + name?: string; + pathId?: number; + parent?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + children?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + files?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: { + firstname?: string; + lastname?: string; + username?: string; + /** Format: email */ + email?: string; + resetPasswordToken?: string; + registrationToken?: string; + isActive?: boolean; + roles?: { + data?: { + id?: number; + attributes?: { + name?: string; + code?: string; + description?: string; + users?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + permissions?: { + data?: { + id?: number; + attributes?: { + action?: string; + actionParameters?: unknown; + subject?: string; + properties?: unknown; + conditions?: unknown; + role?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + blocked?: boolean; + preferedLanguage?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + path?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + UiLinkComponent: { + id?: number; + title?: string; + url?: string; + }; + DynamicZoneValuePropositionWithTextComponent: { + id?: number; + __component?: string; + title?: string; + text?: string; + links?: components["schemas"]["UiLinkComponent"][]; + media?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + /** @enum {string} */ + direction?: "text-right" | "text-left"; + }; + UiBulletWithImageComponent: { + id?: number; + title?: string; + text?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + }; + DynamicZoneValuePropositionWithBulletsComponent: { + id?: number; + __component?: string; + title?: string; + links?: components["schemas"]["UiLinkComponent"][]; + media?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + /** @enum {string} */ + direction?: "text-right" | "text-left"; + bullets?: components["schemas"]["UiBulletWithImageComponent"][]; + }; + UiHeadingComponent: { + id?: number; + title?: string; + /** @enum {string} */ + level?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + }; + TilesTileComponent: { + id?: number; + title?: string; + subtitle?: string; + description?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + url?: string; + }; + DynamicZoneTilesComponent: { + id?: number; + __component?: string; + heading?: components["schemas"]["UiHeadingComponent"]; + tiles?: components["schemas"]["TilesTileComponent"][]; + }; + DynamicZoneTeamMembersComponent: { + id?: number; + __component?: string; + heading?: components["schemas"]["UiHeadingComponent"]; + subtitle?: string; + teamMembers?: { + data?: { + id?: number; + attributes?: { + name?: string; + role?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + bio?: string; + socialNetworks?: { + id?: number; + links?: { + id?: number; + url?: string; + /** @enum {string} */ + network?: "linkedin" | "twitter" | "wikipedia" | "website"; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + }; + QuotesQuoteComponent: { + id?: number; + title?: string; + quote?: string; + }; + DynamicZoneQuotesComponent: { + id?: number; + __component?: string; + heading?: components["schemas"]["UiHeadingComponent"]; + quotes?: components["schemas"]["QuotesQuoteComponent"][]; + }; + UiHeadingExtendedComponent: { + id?: number; + title?: string; + /** @enum {string} */ + level?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + /** @enum {string} */ + style?: "h1" | "h2" | "h3" | "h4"; + /** @enum {string} */ + alignment?: "left" | "center"; + }; + DynamicZoneParagraphWithTitleComponent: { + id?: number; + __component?: string; + heading?: components["schemas"]["UiHeadingExtendedComponent"]; + text?: string; + primaryLinks?: components["schemas"]["UiLinkComponent"][]; + secondaryLinks?: components["schemas"]["UiLinkComponent"][]; + background?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + }; + DynamicZoneLogoCarouselComponent: { + id?: number; + __component?: string; + heading?: components["schemas"]["UiHeadingComponent"]; + isStatic?: boolean; + images?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + }; + TilesContentTileComponent: { + id?: number; + title?: string; + subtitle?: string; + text?: string; + label1?: string; + label2?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + button?: { + id?: number; + title?: string; + url?: string; + }; + dataTable?: string; + }; + DynamicZoneContentTilesComponent: { + id?: number; + __component?: string; + heading?: components["schemas"]["UiHeadingComponent"]; + tiles?: components["schemas"]["TilesContentTileComponent"][]; + }; + DynamicZoneBlogPostsComponent: { + id?: number; + __component?: string; + blogPosts?: { + data?: { + id?: number; + attributes?: { + title?: string; + slug?: string; + subtitle?: string; + content?: string; + excerpt?: string; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + blogCategories?: { + data?: { + id?: number; + attributes?: { + title?: string; + slug?: string; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + blogPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + description?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + blogAuthors?: { + data?: { + id?: number; + attributes?: { + name?: string; + bio?: string; + avatar?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + slug?: string; + blogPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + role?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + relatedPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + /** Format: date */ + date?: string; + readingTime?: number; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + heading?: components["schemas"]["UiHeadingComponent"]; + }; + BlocksBlockComponent: { + id?: number; + title?: string; + description?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + }; + DynamicZoneBlocksComponent: { + id?: number; + __component?: string; + heading?: components["schemas"]["UiHeadingComponent"]; + subtitle?: string; + blocks?: components["schemas"]["BlocksBlockComponent"][]; + /** @enum {string} */ + type?: "high-density" | "low-density"; + }; + FaqsQuestionComponent: { + id?: number; + question?: string; + answer?: string; + }; + FaqsFaQsComponent: { + id?: number; + __component?: string; + heading?: components["schemas"]["UiHeadingComponent"]; + questions?: components["schemas"]["FaqsQuestionComponent"][]; + }; + CarouselCarouselSlideQuoteComponent: { + id?: number; + quote?: string; + source?: string; + }; + DynamicZoneCarouselQuoteComponent: { + id?: number; + __component?: string; + heading?: components["schemas"]["UiHeadingComponent"]; + slides?: components["schemas"]["CarouselCarouselSlideQuoteComponent"][]; + }; + CarouselCarouselSlideTextImageComponent: { + id?: number; + text?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + }; + DynamicZoneCarouselTextImageComponent: { + id?: number; + __component?: string; + heading?: components["schemas"]["UiHeadingComponent"]; + slides?: components["schemas"]["CarouselCarouselSlideTextImageComponent"][]; + }; + BasicPageLocalizationRequest: { + title: string; + slug: string; + dynamicZone?: ( + | components["schemas"]["DynamicZoneRichTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + SEO?: components["schemas"]["SharedSeoComponent"]; + locale: string; + }; + BasicPageRequest: { + data: { + title: string; + slug: string; + dynamicZone?: ( + | components["schemas"]["DynamicZoneRichTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + SEO?: components["schemas"]["SharedSeoComponent"]; + locale?: string; + }; + }; + BasicPageResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["BasicPage"]; + }; + BasicPageLocalizationResponse: { + data?: components["schemas"]["BasicPageResponseDataObjectLocalized"]; + meta?: Record; + }; + BasicPageListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["BasicPage"]; + }; + BasicPageLocalizationListResponse: { + data?: components["schemas"]["BasicPageListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + BasicPageListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["BasicPage"]; + }; + BasicPageListResponse: { + data?: components["schemas"]["BasicPageListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + BasicPage: { + title: string; + slug: string; + dynamicZone?: ( + | components["schemas"]["DynamicZoneRichTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + SEO?: components["schemas"]["SharedSeoComponent"]; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["BasicPageListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + BasicPageResponseDataObject: { + id?: number; + attributes?: components["schemas"]["BasicPage"]; + }; + BasicPageResponse: { + data?: components["schemas"]["BasicPageResponseDataObject"]; + meta?: Record; + }; + DynamicZoneRichTextComponent: { + id?: number; + __component?: string; + content?: string; + tableOfContents?: boolean; + heading?: components["schemas"]["UiHeadingComponent"]; + }; + BlogAuthorLocalizationRequest: { + name: string; + bio?: string; + /** @example string or id */ + avatar?: number | string; + slug: string; + blogPosts?: (number | string)[]; + role?: string; + locale: string; + }; + BlogAuthorRequest: { + data: { + name: string; + bio?: string; + /** @example string or id */ + avatar?: number | string; + slug: string; + blogPosts?: (number | string)[]; + role?: string; + locale?: string; + }; + }; + BlogAuthorResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["BlogAuthor"]; + }; + BlogAuthorLocalizationResponse: { + data?: components["schemas"]["BlogAuthorResponseDataObjectLocalized"]; + meta?: Record; + }; + BlogAuthorListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["BlogAuthor"]; + }; + BlogAuthorLocalizationListResponse: { + data?: components["schemas"]["BlogAuthorListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + BlogAuthorListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["BlogAuthor"]; + }; + BlogAuthorListResponse: { + data?: components["schemas"]["BlogAuthorListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + BlogAuthor: { + name: string; + bio?: string; + avatar?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: { + name?: string; + pathId?: number; + parent?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + children?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + files?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: { + firstname?: string; + lastname?: string; + username?: string; + /** Format: email */ + email?: string; + resetPasswordToken?: string; + registrationToken?: string; + isActive?: boolean; + roles?: { + data?: { + id?: number; + attributes?: { + name?: string; + code?: string; + description?: string; + users?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + permissions?: { + data?: { + id?: number; + attributes?: { + action?: string; + actionParameters?: unknown; + subject?: string; + properties?: unknown; + conditions?: unknown; + role?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + blocked?: boolean; + preferedLanguage?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + path?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + slug: string; + blogPosts?: { + data?: { + id?: number; + attributes?: { + title?: string; + slug?: string; + subtitle?: string; + content?: string; + excerpt?: string; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + blogCategories?: { + data?: { + id?: number; + attributes?: { + title?: string; + slug?: string; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + blogPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + description?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + blogAuthors?: { + data?: { + id?: number; + attributes?: { + name?: string; + bio?: string; + avatar?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + slug?: string; + blogPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + role?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + relatedPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + /** Format: date */ + date?: string; + readingTime?: number; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + role?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["BlogAuthorListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + BlogAuthorResponseDataObject: { + id?: number; + attributes?: components["schemas"]["BlogAuthor"]; + }; + BlogAuthorResponse: { + data?: components["schemas"]["BlogAuthorResponseDataObject"]; + meta?: Record; + }; + BlogCategoryLocalizationRequest: { + title: string; + slug: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + blogPosts?: (number | string)[]; + description?: string; + locale: string; + }; + BlogCategoryRequest: { + data: { + title: string; + slug: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + blogPosts?: (number | string)[]; + description?: string; + locale?: string; + }; + }; + BlogCategoryResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["BlogCategory"]; + }; + BlogCategoryLocalizationResponse: { + data?: components["schemas"]["BlogCategoryResponseDataObjectLocalized"]; + meta?: Record; + }; + BlogCategoryListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["BlogCategory"]; + }; + BlogCategoryLocalizationListResponse: { + data?: components["schemas"]["BlogCategoryListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + BlogCategoryListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["BlogCategory"]; + }; + BlogCategoryListResponse: { + data?: components["schemas"]["BlogCategoryListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + BlogCategory: { + title: string; + slug: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + blogPosts?: { + data?: { + id?: number; + attributes?: { + title?: string; + slug?: string; + subtitle?: string; + content?: string; + excerpt?: string; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + blogCategories?: { + data?: { + id?: number; + attributes?: { + title?: string; + slug?: string; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + blogPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + description?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + blogAuthors?: { + data?: { + id?: number; + attributes?: { + name?: string; + bio?: string; + avatar?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + slug?: string; + blogPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + role?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + relatedPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + /** Format: date */ + date?: string; + readingTime?: number; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + description?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["BlogCategoryListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + BlogCategoryResponseDataObject: { + id?: number; + attributes?: components["schemas"]["BlogCategory"]; + }; + BlogCategoryResponse: { + data?: components["schemas"]["BlogCategoryResponseDataObject"]; + meta?: Record; + }; + BlogPageLocalizationRequest: { + featuredPosts?: (number | string)[]; + SEO?: components["schemas"]["SharedSeoComponent"]; + title: string; + categoriesFilter?: (number | string)[]; + featuredCategories?: (number | string)[]; + locale: string; + }; + BlogPageRequest: { + data: { + featuredPosts?: (number | string)[]; + SEO?: components["schemas"]["SharedSeoComponent"]; + title: string; + categoriesFilter?: (number | string)[]; + featuredCategories?: (number | string)[]; + locale?: string; + }; + }; + BlogPageResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["BlogPage"]; + }; + BlogPageLocalizationResponse: { + data?: components["schemas"]["BlogPageResponseDataObjectLocalized"]; + meta?: Record; + }; + BlogPageListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["BlogPage"]; + }; + BlogPageLocalizationListResponse: { + data?: components["schemas"]["BlogPageListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + BlogPageListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["BlogPage"]; + }; + BlogPageListResponse: { + data?: components["schemas"]["BlogPageListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + BlogPage: { + featuredPosts?: { + data?: { + id?: number; + attributes?: { + title?: string; + slug?: string; + subtitle?: string; + content?: string; + excerpt?: string; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: { + name?: string; + pathId?: number; + parent?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + children?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + files?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: { + firstname?: string; + lastname?: string; + username?: string; + /** Format: email */ + email?: string; + resetPasswordToken?: string; + registrationToken?: string; + isActive?: boolean; + roles?: { + data?: { + id?: number; + attributes?: { + name?: string; + code?: string; + description?: string; + users?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }[]; + }; + permissions?: { + data?: { + id?: number; + attributes?: { + action?: string; + actionParameters?: unknown; + subject?: string; + properties?: unknown; + conditions?: unknown; + role?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + }; + }[]; + }; + blocked?: boolean; + preferedLanguage?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + path?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + blogCategories?: { + data?: { + id?: number; + attributes?: { + title?: string; + slug?: string; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + blogPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + description?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + blogAuthors?: { + data?: { + id?: number; + attributes?: { + name?: string; + bio?: string; + avatar?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + slug?: string; + blogPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + role?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + relatedPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + /** Format: date */ + date?: string; + readingTime?: number; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + SEO?: components["schemas"]["SharedSeoComponent"]; + title: string; + categoriesFilter?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + featuredCategories?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["BlogPageListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + BlogPageResponseDataObject: { + id?: number; + attributes?: components["schemas"]["BlogPage"]; + }; + BlogPageResponse: { + data?: components["schemas"]["BlogPageResponseDataObject"]; + meta?: Record; + }; + BlogPostLocalizationRequest: { + title: string; + slug: string; + subtitle?: string; + content?: string; + excerpt?: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + blogCategories?: (number | string)[]; + blogAuthors?: (number | string)[]; + relatedPosts?: (number | string)[]; + /** @example string or id */ + image?: number | string; + /** Format: date */ + date?: string; + readingTime?: number; + locale: string; + }; + BlogPostRequest: { + data: { + title: string; + slug: string; + subtitle?: string; + content?: string; + excerpt?: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + blogCategories?: (number | string)[]; + blogAuthors?: (number | string)[]; + relatedPosts?: (number | string)[]; + /** @example string or id */ + image?: number | string; + /** Format: date */ + date?: string; + readingTime?: number; + locale?: string; + }; + }; + BlogPostResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["BlogPost"]; + }; + BlogPostLocalizationResponse: { + data?: components["schemas"]["BlogPostResponseDataObjectLocalized"]; + meta?: Record; + }; + BlogPostListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["BlogPost"]; + }; + BlogPostLocalizationListResponse: { + data?: components["schemas"]["BlogPostListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + BlogPostListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["BlogPost"]; + }; + BlogPostListResponse: { + data?: components["schemas"]["BlogPostListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + BlogPost: { + title: string; + slug: string; + subtitle?: string; + content?: string; + excerpt?: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + blogCategories?: { + data?: { + id?: number; + attributes?: { + title?: string; + slug?: string; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + blogPosts?: { + data?: { + id?: number; + attributes?: { + title?: string; + slug?: string; + subtitle?: string; + content?: string; + excerpt?: string; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + blogCategories?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + blogAuthors?: { + data?: { + id?: number; + attributes?: { + name?: string; + bio?: string; + avatar?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + slug?: string; + blogPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + role?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + relatedPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + /** Format: date */ + date?: string; + readingTime?: number; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + description?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + blogAuthors?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + relatedPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + /** Format: date */ + date?: string; + readingTime?: number; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["BlogPostListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + BlogPostResponseDataObject: { + id?: number; + attributes?: components["schemas"]["BlogPost"]; + }; + BlogPostResponse: { + data?: components["schemas"]["BlogPostResponseDataObject"]; + meta?: Record; + }; + DownloadLocalizationRequest: { + title: string; + slug: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + bullets?: components["schemas"]["UiBulletWithImageComponent"][]; + info?: string; + /** @example string or id */ + image?: number | string; + /** @example string or id */ + menuIcon?: number | string; + buttons?: components["schemas"]["UiButtonComponent"][]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneInstallComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + menuTitle?: string; + minimumSupport?: string; + locale: string; + }; + DownloadRequest: { + data: { + title: string; + slug: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + bullets?: components["schemas"]["UiBulletWithImageComponent"][]; + info?: string; + /** @example string or id */ + image?: number | string; + /** @example string or id */ + menuIcon?: number | string; + buttons?: components["schemas"]["UiButtonComponent"][]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneInstallComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + menuTitle?: string; + minimumSupport?: string; + locale?: string; + }; + }; + DownloadResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["Download"]; + }; + DownloadLocalizationResponse: { + data?: components["schemas"]["DownloadResponseDataObjectLocalized"]; + meta?: Record; + }; + DownloadListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["Download"]; + }; + DownloadLocalizationListResponse: { + data?: components["schemas"]["DownloadListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + DownloadListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["Download"]; + }; + DownloadListResponse: { + data?: components["schemas"]["DownloadListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + Download: { + title: string; + slug: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + bullets?: components["schemas"]["UiBulletWithImageComponent"][]; + info?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + menuIcon?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + buttons?: components["schemas"]["UiButtonComponent"][]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneInstallComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + menuTitle?: string; + minimumSupport?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["DownloadListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + DownloadResponseDataObject: { + id?: number; + attributes?: components["schemas"]["Download"]; + }; + DownloadResponse: { + data?: components["schemas"]["DownloadResponseDataObject"]; + meta?: Record; + }; + UiButtonComponent: { + id?: number; + title?: string; + url?: string; + /** @enum {string} */ + type?: "primary" | "outlined"; + /** @enum {string} */ + size?: "large" | "small"; + }; + DownloadsHowToDownloadComponent: { + id?: number; + menuTitle?: string; + menuIcon?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + title?: string; + content?: string; + steps?: { + id?: number; + title?: string; + content?: string; + }[]; + }; + DynamicZoneInstallComponent: { + id?: number; + __component?: string; + heading?: components["schemas"]["UiHeadingComponent"]; + subTitle?: string; + versions?: components["schemas"]["DownloadsHowToDownloadComponent"][]; + }; + DownloadsMenuLocalizationRequest: { + downloads?: (number | string)[]; + locale: string; + }; + DownloadsMenuRequest: { + data: { + downloads?: (number | string)[]; + locale?: string; + }; + }; + DownloadsMenuResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["DownloadsMenu"]; + }; + DownloadsMenuLocalizationResponse: { + data?: components["schemas"]["DownloadsMenuResponseDataObjectLocalized"]; + meta?: Record; + }; + DownloadsMenuListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["DownloadsMenu"]; + }; + DownloadsMenuLocalizationListResponse: { + data?: components["schemas"]["DownloadsMenuListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + DownloadsMenuListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["DownloadsMenu"]; + }; + DownloadsMenuListResponse: { + data?: components["schemas"]["DownloadsMenuListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + DownloadsMenu: { + downloads?: { + data?: { + id?: number; + attributes?: { + title?: string; + slug?: string; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: { + name?: string; + pathId?: number; + parent?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + children?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + files?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: { + firstname?: string; + lastname?: string; + username?: string; + /** Format: email */ + email?: string; + resetPasswordToken?: string; + registrationToken?: string; + isActive?: boolean; + roles?: { + data?: { + id?: number; + attributes?: { + name?: string; + code?: string; + description?: string; + users?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }[]; + }; + permissions?: { + data?: { + id?: number; + attributes?: { + action?: string; + actionParameters?: unknown; + subject?: string; + properties?: unknown; + conditions?: unknown; + role?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + }; + }[]; + }; + blocked?: boolean; + preferedLanguage?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + path?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + bullets?: { + id?: number; + title?: string; + text?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + }[]; + info?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + menuIcon?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + buttons?: { + id?: number; + title?: string; + url?: string; + /** @enum {string} */ + type?: "primary" | "outlined"; + /** @enum {string} */ + size?: "large" | "small"; + }[]; + dynamicZone?: ( + | { + id?: number; + __component?: string; + title?: string; + text?: string; + links?: { + id?: number; + title?: string; + url?: string; + }[]; + media?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + /** @enum {string} */ + direction?: "text-right" | "text-left"; + } + | { + id?: number; + __component?: string; + title?: string; + links?: { + id?: number; + title?: string; + url?: string; + }[]; + media?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + /** @enum {string} */ + direction?: "text-right" | "text-left"; + bullets?: { + id?: number; + title?: string; + text?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + }[]; + } + | { + id?: number; + __component?: string; + heading?: { + id?: number; + title?: string; + /** @enum {string} */ + level?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + }; + tiles?: { + id?: number; + title?: string; + subtitle?: string; + description?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + url?: string; + }[]; + } + | { + id?: number; + __component?: string; + heading?: { + id?: number; + title?: string; + /** @enum {string} */ + level?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + }; + subtitle?: string; + teamMembers?: { + data?: { + id?: number; + attributes?: { + name?: string; + role?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + bio?: string; + socialNetworks?: { + id?: number; + links?: { + id?: number; + url?: string; + /** @enum {string} */ + network?: + | "linkedin" + | "twitter" + | "wikipedia" + | "website"; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + } + | { + id?: number; + __component?: string; + heading?: { + id?: number; + title?: string; + /** @enum {string} */ + level?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + }; + quotes?: { + id?: number; + title?: string; + quote?: string; + }[]; + } + | { + id?: number; + __component?: string; + heading?: { + id?: number; + title?: string; + /** @enum {string} */ + level?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + /** @enum {string} */ + style?: "h1" | "h2" | "h3" | "h4"; + /** @enum {string} */ + alignment?: "left" | "center"; + }; + text?: string; + primaryLinks?: { + id?: number; + title?: string; + url?: string; + }[]; + secondaryLinks?: { + id?: number; + title?: string; + url?: string; + }[]; + background?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + } + | { + id?: number; + __component?: string; + heading?: { + id?: number; + title?: string; + /** @enum {string} */ + level?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + }; + isStatic?: boolean; + images?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + } + | { + id?: number; + __component?: string; + heading?: { + id?: number; + title?: string; + /** @enum {string} */ + level?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + }; + tiles?: { + id?: number; + title?: string; + subtitle?: string; + text?: string; + label1?: string; + label2?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + button?: { + id?: number; + title?: string; + url?: string; + }; + dataTable?: string; + }[]; + } + | { + id?: number; + __component?: string; + blogPosts?: { + data?: { + id?: number; + attributes?: { + title?: string; + slug?: string; + subtitle?: string; + content?: string; + excerpt?: string; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + blogCategories?: { + data?: { + id?: number; + attributes?: { + title?: string; + slug?: string; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + blogPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + description?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + blogAuthors?: { + data?: { + id?: number; + attributes?: { + name?: string; + bio?: string; + avatar?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + slug?: string; + blogPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + role?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + relatedPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + /** Format: date */ + date?: string; + readingTime?: number; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + heading?: { + id?: number; + title?: string; + /** @enum {string} */ + level?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + }; + } + | { + id?: number; + __component?: string; + heading?: { + id?: number; + title?: string; + /** @enum {string} */ + level?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + }; + subtitle?: string; + blocks?: { + id?: number; + title?: string; + description?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + }[]; + /** @enum {string} */ + type?: "high-density" | "low-density"; + } + | { + id?: number; + __component?: string; + heading?: { + id?: number; + title?: string; + /** @enum {string} */ + level?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + }; + questions?: { + id?: number; + question?: string; + answer?: string; + }[]; + } + | { + id?: number; + __component?: string; + heading?: { + id?: number; + title?: string; + /** @enum {string} */ + level?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + }; + subTitle?: string; + versions?: { + id?: number; + menuTitle?: string; + menuIcon?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + title?: string; + content?: string; + steps?: { + id?: number; + title?: string; + content?: string; + }[]; + }[]; + } + | { + id?: number; + __component?: string; + heading?: { + id?: number; + title?: string; + /** @enum {string} */ + level?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + }; + slides?: { + id?: number; + text?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + }[]; + } + | { + id?: number; + __component?: string; + heading?: { + id?: number; + title?: string; + /** @enum {string} */ + level?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + }; + slides?: { + id?: number; + quote?: string; + source?: string; + }[]; + } + )[]; + menuTitle?: string; + minimumSupport?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["DownloadsMenuListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + DownloadsMenuResponseDataObject: { + id?: number; + attributes?: components["schemas"]["DownloadsMenu"]; + }; + DownloadsMenuResponse: { + data?: components["schemas"]["DownloadsMenuResponseDataObject"]; + meta?: Record; + }; + FeatureLocalizationRequest: { + title: string; + description?: string; + featureCategories?: (number | string)[]; + available?: boolean; + /** @example string or id */ + icon?: number | string; + slug: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + content?: string; + locale: string; + }; + FeatureRequest: { + data: { + title: string; + description?: string; + featureCategories?: (number | string)[]; + available?: boolean; + /** @example string or id */ + icon?: number | string; + slug: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + content?: string; + locale?: string; + }; + }; + FeatureResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["Feature"]; + }; + FeatureLocalizationResponse: { + data?: components["schemas"]["FeatureResponseDataObjectLocalized"]; + meta?: Record; + }; + FeatureListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["Feature"]; + }; + FeatureLocalizationListResponse: { + data?: components["schemas"]["FeatureListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + FeatureListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["Feature"]; + }; + FeatureListResponse: { + data?: components["schemas"]["FeatureListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + Feature: { + title: string; + description?: string; + featureCategories?: { + data?: { + id?: number; + attributes?: { + title?: string; + features?: { + data?: { + id?: number; + attributes?: { + title?: string; + description?: string; + featureCategories?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + available?: boolean; + icon?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: { + name?: string; + pathId?: number; + parent?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + children?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + files?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: { + firstname?: string; + lastname?: string; + username?: string; + /** Format: email */ + email?: string; + resetPasswordToken?: string; + registrationToken?: string; + isActive?: boolean; + roles?: { + data?: { + id?: number; + attributes?: { + name?: string; + code?: string; + description?: string; + users?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }[]; + }; + permissions?: { + data?: { + id?: number; + attributes?: { + action?: string; + actionParameters?: unknown; + subject?: string; + properties?: unknown; + conditions?: unknown; + role?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + }; + }[]; + }; + blocked?: boolean; + preferedLanguage?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + }; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + path?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + slug?: string; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + content?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + available?: boolean; + icon?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + slug: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + content?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["FeatureListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + FeatureResponseDataObject: { + id?: number; + attributes?: components["schemas"]["Feature"]; + }; + FeatureResponse: { + data?: components["schemas"]["FeatureResponseDataObject"]; + meta?: Record; + }; + FeatureCategoryLocalizationRequest: { + title: string; + features?: (number | string)[]; + locale: string; + }; + FeatureCategoryRequest: { + data: { + title: string; + features?: (number | string)[]; + locale?: string; + }; + }; + FeatureCategoryResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["FeatureCategory"]; + }; + FeatureCategoryLocalizationResponse: { + data?: components["schemas"]["FeatureCategoryResponseDataObjectLocalized"]; + meta?: Record; + }; + FeatureCategoryListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["FeatureCategory"]; + }; + FeatureCategoryLocalizationListResponse: { + data?: components["schemas"]["FeatureCategoryListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + FeatureCategoryListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["FeatureCategory"]; + }; + FeatureCategoryListResponse: { + data?: components["schemas"]["FeatureCategoryListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + FeatureCategory: { + title: string; + features?: { + data?: { + id?: number; + attributes?: { + title?: string; + description?: string; + featureCategories?: { + data?: { + id?: number; + attributes?: { + title?: string; + features?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: { + firstname?: string; + lastname?: string; + username?: string; + /** Format: email */ + email?: string; + resetPasswordToken?: string; + registrationToken?: string; + isActive?: boolean; + roles?: { + data?: { + id?: number; + attributes?: { + name?: string; + code?: string; + description?: string; + users?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + permissions?: { + data?: { + id?: number; + attributes?: { + action?: string; + actionParameters?: unknown; + subject?: string; + properties?: unknown; + conditions?: unknown; + role?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + blocked?: boolean; + preferedLanguage?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + available?: boolean; + icon?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: { + name?: string; + pathId?: number; + parent?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + children?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + files?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + path?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + slug?: string; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + content?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["FeatureCategoryListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + FeatureCategoryResponseDataObject: { + id?: number; + attributes?: components["schemas"]["FeatureCategory"]; + }; + FeatureCategoryResponse: { + data?: components["schemas"]["FeatureCategoryResponseDataObject"]; + meta?: Record; + }; + FeaturesPageLocalizationRequest: { + SEO?: components["schemas"]["SharedSeoComponent"]; + featureCategories?: (number | string)[]; + dynamicZoneTop?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + dynamicZoneBottom?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + locale: string; + }; + FeaturesPageRequest: { + data: { + SEO?: components["schemas"]["SharedSeoComponent"]; + featureCategories?: (number | string)[]; + dynamicZoneTop?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + dynamicZoneBottom?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + locale?: string; + }; + }; + FeaturesPageResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["FeaturesPage"]; + }; + FeaturesPageLocalizationResponse: { + data?: components["schemas"]["FeaturesPageResponseDataObjectLocalized"]; + meta?: Record; + }; + FeaturesPageListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["FeaturesPage"]; + }; + FeaturesPageLocalizationListResponse: { + data?: components["schemas"]["FeaturesPageListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + FeaturesPageListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["FeaturesPage"]; + }; + FeaturesPageListResponse: { + data?: components["schemas"]["FeaturesPageListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + FeaturesPage: { + SEO?: components["schemas"]["SharedSeoComponent"]; + featureCategories?: { + data?: { + id?: number; + attributes?: { + title?: string; + features?: { + data?: { + id?: number; + attributes?: { + title?: string; + description?: string; + featureCategories?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + available?: boolean; + icon?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + slug?: string; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + content?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + dynamicZoneTop?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + dynamicZoneBottom?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["FeaturesPageListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + FeaturesPageResponseDataObject: { + id?: number; + attributes?: components["schemas"]["FeaturesPage"]; + }; + FeaturesPageResponse: { + data?: components["schemas"]["FeaturesPageResponseDataObject"]; + meta?: Record; + }; + FooterLocalizationRequest: { + legalContent1?: string; + legalContent2?: string; + linkBlocks?: components["schemas"]["FooterLinkBlockComponent"][]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + locale: string; + }; + FooterRequest: { + data: { + legalContent1?: string; + legalContent2?: string; + linkBlocks?: components["schemas"]["FooterLinkBlockComponent"][]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + locale?: string; + }; + }; + FooterResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["Footer"]; + }; + FooterLocalizationResponse: { + data?: components["schemas"]["FooterResponseDataObjectLocalized"]; + meta?: Record; + }; + FooterListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["Footer"]; + }; + FooterLocalizationListResponse: { + data?: components["schemas"]["FooterListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + FooterListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["Footer"]; + }; + FooterListResponse: { + data?: components["schemas"]["FooterListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + Footer: { + legalContent1?: string; + legalContent2?: string; + linkBlocks?: components["schemas"]["FooterLinkBlockComponent"][]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["FooterListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + FooterResponseDataObject: { + id?: number; + attributes?: components["schemas"]["Footer"]; + }; + FooterResponse: { + data?: components["schemas"]["FooterResponseDataObject"]; + meta?: Record; + }; + FooterLinkBlockComponent: { + id?: number; + heading?: { + id?: number; + title?: string; + /** @enum {string} */ + level?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + }; + links?: { + id?: number; + title?: string; + url?: string; + }[]; + }; + GlossaryPageLocalizationRequest: { + SEO?: components["schemas"]["SharedSeoComponent"]; + locale: string; + }; + GlossaryPageRequest: { + data: { + SEO?: components["schemas"]["SharedSeoComponent"]; + locale?: string; + }; + }; + GlossaryPageResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["GlossaryPage"]; + }; + GlossaryPageLocalizationResponse: { + data?: components["schemas"]["GlossaryPageResponseDataObjectLocalized"]; + meta?: Record; + }; + GlossaryPageListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["GlossaryPage"]; + }; + GlossaryPageLocalizationListResponse: { + data?: components["schemas"]["GlossaryPageListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + GlossaryPageListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["GlossaryPage"]; + }; + GlossaryPageListResponse: { + data?: components["schemas"]["GlossaryPageListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + GlossaryPage: { + SEO?: components["schemas"]["SharedSeoComponent"]; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["GlossaryPageListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + GlossaryPageResponseDataObject: { + id?: number; + attributes?: components["schemas"]["GlossaryPage"]; + }; + GlossaryPageResponse: { + data?: components["schemas"]["GlossaryPageResponseDataObject"]; + meta?: Record; + }; + GlossaryTermLocalizationRequest: { + title?: string; + description?: string; + slug: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + locale: string; + }; + GlossaryTermRequest: { + data: { + title?: string; + description?: string; + slug: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + locale?: string; + }; + }; + GlossaryTermResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["GlossaryTerm"]; + }; + GlossaryTermLocalizationResponse: { + data?: components["schemas"]["GlossaryTermResponseDataObjectLocalized"]; + meta?: Record; + }; + GlossaryTermListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["GlossaryTerm"]; + }; + GlossaryTermLocalizationListResponse: { + data?: components["schemas"]["GlossaryTermListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + GlossaryTermListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["GlossaryTerm"]; + }; + GlossaryTermListResponse: { + data?: components["schemas"]["GlossaryTermListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + GlossaryTerm: { + title?: string; + description?: string; + slug: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["GlossaryTermListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + GlossaryTermResponseDataObject: { + id?: number; + attributes?: components["schemas"]["GlossaryTerm"]; + }; + GlossaryTermResponse: { + data?: components["schemas"]["GlossaryTermResponseDataObject"]; + meta?: Record; + }; + HomePageLocalizationRequest: { + SEO?: components["schemas"]["SharedSeoComponent"]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + locale: string; + }; + HomePageRequest: { + data: { + SEO?: components["schemas"]["SharedSeoComponent"]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + locale?: string; + }; + }; + HomePageResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["HomePage"]; + }; + HomePageLocalizationResponse: { + data?: components["schemas"]["HomePageResponseDataObjectLocalized"]; + meta?: Record; + }; + HomePageListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["HomePage"]; + }; + HomePageLocalizationListResponse: { + data?: components["schemas"]["HomePageListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + HomePageListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["HomePage"]; + }; + HomePageListResponse: { + data?: components["schemas"]["HomePageListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + HomePage: { + SEO?: components["schemas"]["SharedSeoComponent"]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["HomePageListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + HomePageResponseDataObject: { + id?: number; + attributes?: components["schemas"]["HomePage"]; + }; + HomePageResponse: { + data?: components["schemas"]["HomePageResponseDataObject"]; + meta?: Record; + }; + LitepaperLocalizationRequest: { + title: string; + content?: string; + /** @example string or id */ + pdf?: number | string; + SEO?: components["schemas"]["SharedSeoComponent"]; + locale: string; + }; + LitepaperRequest: { + data: { + title: string; + content?: string; + /** @example string or id */ + pdf?: number | string; + SEO?: components["schemas"]["SharedSeoComponent"]; + locale?: string; + }; + }; + LitepaperResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["Litepaper"]; + }; + LitepaperLocalizationResponse: { + data?: components["schemas"]["LitepaperResponseDataObjectLocalized"]; + meta?: Record; + }; + LitepaperListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["Litepaper"]; + }; + LitepaperLocalizationListResponse: { + data?: components["schemas"]["LitepaperListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + LitepaperListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["Litepaper"]; + }; + LitepaperListResponse: { + data?: components["schemas"]["LitepaperListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + Litepaper: { + title: string; + content?: string; + pdf?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: { + name?: string; + pathId?: number; + parent?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + children?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + files?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: { + firstname?: string; + lastname?: string; + username?: string; + /** Format: email */ + email?: string; + resetPasswordToken?: string; + registrationToken?: string; + isActive?: boolean; + roles?: { + data?: { + id?: number; + attributes?: { + name?: string; + code?: string; + description?: string; + users?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + permissions?: { + data?: { + id?: number; + attributes?: { + action?: string; + actionParameters?: unknown; + subject?: string; + properties?: unknown; + conditions?: unknown; + role?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + blocked?: boolean; + preferedLanguage?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + path?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + SEO?: components["schemas"]["SharedSeoComponent"]; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["LitepaperListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + LitepaperResponseDataObject: { + id?: number; + attributes?: components["schemas"]["Litepaper"]; + }; + LitepaperResponse: { + data?: components["schemas"]["LitepaperResponseDataObject"]; + meta?: Record; + }; + NavigationLocalizationRequest: { + links?: components["schemas"]["NavigationNavigationItemComponent"][]; + button?: components["schemas"]["UiLinkComponent"]; + backText?: string; + locale: string; + }; + NavigationRequest: { + data: { + links?: components["schemas"]["NavigationNavigationItemComponent"][]; + button?: components["schemas"]["UiLinkComponent"]; + backText?: string; + locale?: string; + }; + }; + NavigationResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["Navigation"]; + }; + NavigationLocalizationResponse: { + data?: components["schemas"]["NavigationResponseDataObjectLocalized"]; + meta?: Record; + }; + NavigationListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["Navigation"]; + }; + NavigationLocalizationListResponse: { + data?: components["schemas"]["NavigationListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + NavigationListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["Navigation"]; + }; + NavigationListResponse: { + data?: components["schemas"]["NavigationListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + Navigation: { + links?: components["schemas"]["NavigationNavigationItemComponent"][]; + button?: components["schemas"]["UiLinkComponent"]; + backText?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["NavigationListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + NavigationResponseDataObject: { + id?: number; + attributes?: components["schemas"]["Navigation"]; + }; + NavigationResponse: { + data?: components["schemas"]["NavigationResponseDataObject"]; + meta?: Record; + }; + NavigationNavigationItemComponent: { + id?: number; + title?: string; + url?: string; + subItems?: { + id?: number; + title?: string; + description?: string; + url?: string; + }[]; + subPanel?: { + id?: number; + title?: string; + url?: string; + links?: { + id?: number; + title?: string; + links?: { + id?: number; + title?: string; + url?: string; + icon?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: { + name?: string; + pathId?: number; + parent?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + children?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + files?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: { + firstname?: string; + lastname?: string; + username?: string; + /** Format: email */ + email?: string; + resetPasswordToken?: string; + registrationToken?: string; + isActive?: boolean; + roles?: { + data?: { + id?: number; + attributes?: { + name?: string; + code?: string; + description?: string; + users?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }[]; + }; + permissions?: { + data?: { + id?: number; + attributes?: { + action?: string; + actionParameters?: unknown; + subject?: string; + properties?: unknown; + conditions?: unknown; + role?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + }; + }[]; + }; + blocked?: boolean; + preferedLanguage?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + path?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + }[]; + }[]; + }; + }; + NetworkPageLocalizationRequest: { + SEO?: components["schemas"]["SharedSeoComponent"]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + locale: string; + }; + NetworkPageRequest: { + data: { + SEO?: components["schemas"]["SharedSeoComponent"]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + locale?: string; + }; + }; + NetworkPageResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["NetworkPage"]; + }; + NetworkPageLocalizationResponse: { + data?: components["schemas"]["NetworkPageResponseDataObjectLocalized"]; + meta?: Record; + }; + NetworkPageListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["NetworkPage"]; + }; + NetworkPageLocalizationListResponse: { + data?: components["schemas"]["NetworkPageListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + NetworkPageListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["NetworkPage"]; + }; + NetworkPageListResponse: { + data?: components["schemas"]["NetworkPageListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + NetworkPage: { + SEO?: components["schemas"]["SharedSeoComponent"]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["NetworkPageListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + NetworkPageResponseDataObject: { + id?: number; + attributes?: components["schemas"]["NetworkPage"]; + }; + NetworkPageResponse: { + data?: components["schemas"]["NetworkPageResponseDataObject"]; + meta?: Record; + }; + PageNotFoundLocalizationRequest: { + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneRichTextComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + locale: string; + }; + PageNotFoundRequest: { + data: { + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneRichTextComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + locale?: string; + }; + }; + PageNotFoundResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["PageNotFound"]; + }; + PageNotFoundLocalizationResponse: { + data?: components["schemas"]["PageNotFoundResponseDataObjectLocalized"]; + meta?: Record; + }; + PageNotFoundListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["PageNotFound"]; + }; + PageNotFoundLocalizationListResponse: { + data?: components["schemas"]["PageNotFoundListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + PageNotFoundListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["PageNotFound"]; + }; + PageNotFoundListResponse: { + data?: components["schemas"]["PageNotFoundListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + PageNotFound: { + dynamicZone?: ( + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneRichTextComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["PageNotFoundListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + PageNotFoundResponseDataObject: { + id?: number; + attributes?: components["schemas"]["PageNotFound"]; + }; + PageNotFoundResponse: { + data?: components["schemas"]["PageNotFoundResponseDataObject"]; + meta?: Record; + }; + PressPageLocalizationRequest: { + SEO?: components["schemas"]["SharedSeoComponent"]; + locale: string; + }; + PressPageRequest: { + data: { + SEO?: components["schemas"]["SharedSeoComponent"]; + locale?: string; + }; + }; + PressPageResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["PressPage"]; + }; + PressPageLocalizationResponse: { + data?: components["schemas"]["PressPageResponseDataObjectLocalized"]; + meta?: Record; + }; + PressPageListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["PressPage"]; + }; + PressPageLocalizationListResponse: { + data?: components["schemas"]["PressPageListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + PressPageListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["PressPage"]; + }; + PressPageListResponse: { + data?: components["schemas"]["PressPageListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + PressPage: { + SEO?: components["schemas"]["SharedSeoComponent"]; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["PressPageListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + PressPageResponseDataObject: { + id?: number; + attributes?: components["schemas"]["PressPage"]; + }; + PressPageResponse: { + data?: components["schemas"]["PressPageResponseDataObject"]; + meta?: Record; + }; + PressPostLocalizationRequest: { + title: string; + /** Format: date */ + date?: string; + url: string; + locale: string; + }; + PressPostRequest: { + data: { + title: string; + /** Format: date */ + date?: string; + url: string; + locale?: string; + }; + }; + PressPostResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["PressPost"]; + }; + PressPostLocalizationResponse: { + data?: components["schemas"]["PressPostResponseDataObjectLocalized"]; + meta?: Record; + }; + PressPostListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["PressPost"]; + }; + PressPostLocalizationListResponse: { + data?: components["schemas"]["PressPostListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + PressPostListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["PressPost"]; + }; + PressPostListResponse: { + data?: components["schemas"]["PressPostListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + PressPost: { + title: string; + /** Format: date */ + date?: string; + url: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: { + firstname?: string; + lastname?: string; + username?: string; + /** Format: email */ + email?: string; + resetPasswordToken?: string; + registrationToken?: string; + isActive?: boolean; + roles?: { + data?: { + id?: number; + attributes?: { + name?: string; + code?: string; + description?: string; + users?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + permissions?: { + data?: { + id?: number; + attributes?: { + action?: string; + actionParameters?: unknown; + subject?: string; + properties?: unknown; + conditions?: unknown; + role?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + blocked?: boolean; + preferedLanguage?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["PressPostListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + PressPostResponseDataObject: { + id?: number; + attributes?: components["schemas"]["PressPost"]; + }; + PressPostResponse: { + data?: components["schemas"]["PressPostResponseDataObject"]; + meta?: Record; + }; + TeamMemberLocalizationRequest: { + name?: string; + role?: string; + /** @example string or id */ + image?: number | string; + bio?: string; + socialNetworks?: components["schemas"]["SocialSocialsComponent"]; + locale: string; + }; + TeamMemberRequest: { + data: { + name?: string; + role?: string; + /** @example string or id */ + image?: number | string; + bio?: string; + socialNetworks?: components["schemas"]["SocialSocialsComponent"]; + locale?: string; + }; + }; + TeamMemberResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["TeamMember"]; + }; + TeamMemberLocalizationResponse: { + data?: components["schemas"]["TeamMemberResponseDataObjectLocalized"]; + meta?: Record; + }; + TeamMemberListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["TeamMember"]; + }; + TeamMemberLocalizationListResponse: { + data?: components["schemas"]["TeamMemberListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + TeamMemberListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["TeamMember"]; + }; + TeamMemberListResponse: { + data?: components["schemas"]["TeamMemberListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + TeamMember: { + name?: string; + role?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: { + name?: string; + pathId?: number; + parent?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + children?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + files?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: { + firstname?: string; + lastname?: string; + username?: string; + /** Format: email */ + email?: string; + resetPasswordToken?: string; + registrationToken?: string; + isActive?: boolean; + roles?: { + data?: { + id?: number; + attributes?: { + name?: string; + code?: string; + description?: string; + users?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + permissions?: { + data?: { + id?: number; + attributes?: { + action?: string; + actionParameters?: unknown; + subject?: string; + properties?: unknown; + conditions?: unknown; + role?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + blocked?: boolean; + preferedLanguage?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + path?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + bio?: string; + socialNetworks?: components["schemas"]["SocialSocialsComponent"]; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["TeamMemberListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + TeamMemberResponseDataObject: { + id?: number; + attributes?: components["schemas"]["TeamMember"]; + }; + TeamMemberResponse: { + data?: components["schemas"]["TeamMemberResponseDataObject"]; + meta?: Record; + }; + SocialSocialsComponent: { + id?: number; + links?: { + id?: number; + url?: string; + /** @enum {string} */ + network?: "linkedin" | "twitter" | "wikipedia" | "website"; + }[]; + }; + TrustCenterAdvisorsPageLocalizationRequest: { + SEO?: components["schemas"]["SharedSeoComponent"]; + dynamicZone?: ( + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + )[]; + title?: string; + locale: string; + }; + TrustCenterAdvisorsPageRequest: { + data: { + SEO?: components["schemas"]["SharedSeoComponent"]; + dynamicZone?: ( + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + )[]; + title?: string; + locale?: string; + }; + }; + TrustCenterAdvisorsPageResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["TrustCenterAdvisorsPage"]; + }; + TrustCenterAdvisorsPageLocalizationResponse: { + data?: components["schemas"]["TrustCenterAdvisorsPageResponseDataObjectLocalized"]; + meta?: Record; + }; + TrustCenterAdvisorsPageListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["TrustCenterAdvisorsPage"]; + }; + TrustCenterAdvisorsPageLocalizationListResponse: { + data?: components["schemas"]["TrustCenterAdvisorsPageListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + TrustCenterAdvisorsPageListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["TrustCenterAdvisorsPage"]; + }; + TrustCenterAdvisorsPageListResponse: { + data?: components["schemas"]["TrustCenterAdvisorsPageListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + TrustCenterAdvisorsPage: { + SEO?: components["schemas"]["SharedSeoComponent"]; + dynamicZone?: ( + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + )[]; + title?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["TrustCenterAdvisorsPageListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + TrustCenterAdvisorsPageResponseDataObject: { + id?: number; + attributes?: components["schemas"]["TrustCenterAdvisorsPage"]; + }; + TrustCenterAdvisorsPageResponse: { + data?: components["schemas"]["TrustCenterAdvisorsPageResponseDataObject"]; + meta?: Record; + }; + TrustCenterCategoryLocalizationRequest: { + title: string; + description?: string; + slug: string; + trustCenterPosts?: (number | string)[]; + locale: string; + }; + TrustCenterCategoryRequest: { + data: { + title: string; + description?: string; + slug: string; + trustCenterPosts?: (number | string)[]; + locale?: string; + }; + }; + TrustCenterCategoryResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["TrustCenterCategory"]; + }; + TrustCenterCategoryLocalizationResponse: { + data?: components["schemas"]["TrustCenterCategoryResponseDataObjectLocalized"]; + meta?: Record; + }; + TrustCenterCategoryListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["TrustCenterCategory"]; + }; + TrustCenterCategoryLocalizationListResponse: { + data?: components["schemas"]["TrustCenterCategoryListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + TrustCenterCategoryListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["TrustCenterCategory"]; + }; + TrustCenterCategoryListResponse: { + data?: components["schemas"]["TrustCenterCategoryListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + TrustCenterCategory: { + title: string; + description?: string; + slug: string; + trustCenterPosts?: { + data?: { + id?: number; + attributes?: { + title?: string; + slug?: string; + excerpt?: string; + isLink?: boolean; + link?: string; + relatedTrustCenterPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + dynamicZone?: ( + | { + id?: number; + __component?: string; + items?: { + id?: number; + title?: string; + url?: string; + authors?: string; + published?: string; + date?: string; + }[]; + } + | { + id?: number; + __component?: string; + items?: { + id?: number; + title?: string; + url?: string; + company?: string; + authors?: string; + date?: string; + }[]; + } + )[]; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: { + name?: string; + pathId?: number; + parent?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + children?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + files?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: { + firstname?: string; + lastname?: string; + username?: string; + /** Format: email */ + email?: string; + resetPasswordToken?: string; + registrationToken?: string; + isActive?: boolean; + roles?: { + data?: { + id?: number; + attributes?: { + name?: string; + code?: string; + description?: string; + users?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }[]; + }; + permissions?: { + data?: { + id?: number; + attributes?: { + action?: string; + actionParameters?: unknown; + subject?: string; + properties?: unknown; + conditions?: unknown; + role?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + }; + }[]; + }; + blocked?: boolean; + preferedLanguage?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + path?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + content?: string; + readingTime?: number; + trustCenterCategory?: { + data?: { + id?: number; + attributes?: { + title?: string; + description?: string; + slug?: string; + trustCenterPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }; + }; + subtitle?: string; + /** Format: date */ + date?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["TrustCenterCategoryListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + TrustCenterCategoryResponseDataObject: { + id?: number; + attributes?: components["schemas"]["TrustCenterCategory"]; + }; + TrustCenterCategoryResponse: { + data?: components["schemas"]["TrustCenterCategoryResponseDataObject"]; + meta?: Record; + }; + TrustCenterPageLocalizationRequest: { + SEO?: components["schemas"]["SharedSeoComponent"]; + title?: string; + locale: string; + }; + TrustCenterPageRequest: { + data: { + SEO?: components["schemas"]["SharedSeoComponent"]; + title?: string; + locale?: string; + }; + }; + TrustCenterPageResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["TrustCenterPage"]; + }; + TrustCenterPageLocalizationResponse: { + data?: components["schemas"]["TrustCenterPageResponseDataObjectLocalized"]; + meta?: Record; + }; + TrustCenterPageListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["TrustCenterPage"]; + }; + TrustCenterPageLocalizationListResponse: { + data?: components["schemas"]["TrustCenterPageListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + TrustCenterPageListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["TrustCenterPage"]; + }; + TrustCenterPageListResponse: { + data?: components["schemas"]["TrustCenterPageListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + TrustCenterPage: { + SEO?: components["schemas"]["SharedSeoComponent"]; + title?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["TrustCenterPageListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + TrustCenterPageResponseDataObject: { + id?: number; + attributes?: components["schemas"]["TrustCenterPage"]; + }; + TrustCenterPageResponse: { + data?: components["schemas"]["TrustCenterPageResponseDataObject"]; + meta?: Record; + }; + TrustCenterPostLocalizationRequest: { + title: string; + slug?: string; + excerpt?: string; + isLink?: boolean; + link?: string; + relatedTrustCenterPosts?: (number | string)[]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneDocumentListComponent"] + | components["schemas"]["DynamicZoneAuditListComponent"] + )[]; + SEO?: components["schemas"]["SharedSeoComponent"]; + content?: string; + readingTime?: number; + /** @example string or id */ + trustCenterCategory?: number | string; + subtitle?: string; + /** Format: date */ + date?: string; + /** @example string or id */ + image?: number | string; + locale: string; + }; + TrustCenterPostRequest: { + data: { + title: string; + slug?: string; + excerpt?: string; + isLink?: boolean; + link?: string; + relatedTrustCenterPosts?: (number | string)[]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneDocumentListComponent"] + | components["schemas"]["DynamicZoneAuditListComponent"] + )[]; + SEO?: components["schemas"]["SharedSeoComponent"]; + content?: string; + readingTime?: number; + /** @example string or id */ + trustCenterCategory?: number | string; + subtitle?: string; + /** Format: date */ + date?: string; + /** @example string or id */ + image?: number | string; + locale?: string; + }; + }; + TrustCenterPostResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["TrustCenterPost"]; + }; + TrustCenterPostLocalizationResponse: { + data?: components["schemas"]["TrustCenterPostResponseDataObjectLocalized"]; + meta?: Record; + }; + TrustCenterPostListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["TrustCenterPost"]; + }; + TrustCenterPostLocalizationListResponse: { + data?: components["schemas"]["TrustCenterPostListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + TrustCenterPostListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["TrustCenterPost"]; + }; + TrustCenterPostListResponse: { + data?: components["schemas"]["TrustCenterPostListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + TrustCenterPost: { + title: string; + slug?: string; + excerpt?: string; + isLink?: boolean; + link?: string; + relatedTrustCenterPosts?: { + data?: { + id?: number; + attributes?: { + title?: string; + slug?: string; + excerpt?: string; + isLink?: boolean; + link?: string; + relatedTrustCenterPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + dynamicZone?: ( + | { + id?: number; + __component?: string; + items?: { + id?: number; + title?: string; + url?: string; + authors?: string; + published?: string; + date?: string; + }[]; + } + | { + id?: number; + __component?: string; + items?: { + id?: number; + title?: string; + url?: string; + company?: string; + authors?: string; + date?: string; + }[]; + } + )[]; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: { + name?: string; + pathId?: number; + parent?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + children?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + files?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: { + firstname?: string; + lastname?: string; + username?: string; + /** Format: email */ + email?: string; + resetPasswordToken?: string; + registrationToken?: string; + isActive?: boolean; + roles?: { + data?: { + id?: number; + attributes?: { + name?: string; + code?: string; + description?: string; + users?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }[]; + }; + permissions?: { + data?: { + id?: number; + attributes?: { + action?: string; + actionParameters?: unknown; + subject?: string; + properties?: unknown; + conditions?: unknown; + role?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record< + string, + never + >; + }; + }; + }; + }[]; + }; + blocked?: boolean; + preferedLanguage?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }[]; + }; + path?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + content?: string; + readingTime?: number; + trustCenterCategory?: { + data?: { + id?: number; + attributes?: { + title?: string; + description?: string; + slug?: string; + trustCenterPosts?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }; + }; + subtitle?: string; + /** Format: date */ + date?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + dynamicZone?: ( + | components["schemas"]["DynamicZoneDocumentListComponent"] + | components["schemas"]["DynamicZoneAuditListComponent"] + )[]; + SEO?: components["schemas"]["SharedSeoComponent"]; + content?: string; + readingTime?: number; + trustCenterCategory?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + subtitle?: string; + /** Format: date */ + date?: string; + image?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["TrustCenterPostListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + TrustCenterPostResponseDataObject: { + id?: number; + attributes?: components["schemas"]["TrustCenterPost"]; + }; + TrustCenterPostResponse: { + data?: components["schemas"]["TrustCenterPostResponseDataObject"]; + meta?: Record; + }; + DynamicZoneDocumentListItemComponent: { + id?: number; + title?: string; + url?: string; + authors?: string; + published?: string; + date?: string; + }; + DynamicZoneDocumentListComponent: { + id?: number; + __component?: string; + items?: components["schemas"]["DynamicZoneDocumentListItemComponent"][]; + }; + DynamicZoneAuditListItemComponent: { + id?: number; + title?: string; + url?: string; + company?: string; + authors?: string; + date?: string; + }; + DynamicZoneAuditListComponent: { + id?: number; + __component?: string; + items?: components["schemas"]["DynamicZoneAuditListItemComponent"][]; + }; + UseCaseLocalizationRequest: { + title: string; + slug: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + excerpt?: string; + features?: (number | string)[]; + locale: string; + }; + UseCaseRequest: { + data: { + title: string; + slug: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + excerpt?: string; + features?: (number | string)[]; + locale?: string; + }; + }; + UseCaseResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["UseCase"]; + }; + UseCaseLocalizationResponse: { + data?: components["schemas"]["UseCaseResponseDataObjectLocalized"]; + meta?: Record; + }; + UseCaseListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["UseCase"]; + }; + UseCaseLocalizationListResponse: { + data?: components["schemas"]["UseCaseListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + UseCaseListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["UseCase"]; + }; + UseCaseListResponse: { + data?: components["schemas"]["UseCaseListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + UseCase: { + title: string; + slug: string; + SEO?: components["schemas"]["SharedSeoComponent"]; + excerpt?: string; + features?: { + data?: { + id?: number; + attributes?: { + title?: string; + description?: string; + featureCategories?: { + data?: { + id?: number; + attributes?: { + title?: string; + features?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + available?: boolean; + icon?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + slug?: string; + SEO?: { + id?: number; + metaTitle?: string; + metaDescription?: string; + metaImage?: { + data?: { + id?: number; + attributes?: { + name?: string; + alternativeText?: string; + caption?: string; + width?: number; + height?: number; + formats?: unknown; + hash?: string; + ext?: string; + mime?: string; + /** Format: float */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: unknown; + related?: { + data?: { + id?: number; + attributes?: Record; + }[]; + }; + folder?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + folderPath?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + }; + }; + }; + keywords?: string; + metaRobots?: string; + structuredData?: unknown; + canonicalURL?: string; + }; + content?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: unknown[]; + }; + locale?: string; + }; + }[]; + }; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["UseCaseListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + UseCaseResponseDataObject: { + id?: number; + attributes?: components["schemas"]["UseCase"]; + }; + UseCaseResponse: { + data?: components["schemas"]["UseCaseResponseDataObject"]; + meta?: Record; + }; + UseCasesPageLocalizationRequest: { + SEO?: components["schemas"]["SharedSeoComponent"]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + locale: string; + }; + UseCasesPageRequest: { + data: { + SEO?: components["schemas"]["SharedSeoComponent"]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + locale?: string; + }; + }; + UseCasesPageResponseDataObjectLocalized: { + id?: number; + attributes?: components["schemas"]["UseCasesPage"]; + }; + UseCasesPageLocalizationResponse: { + data?: components["schemas"]["UseCasesPageResponseDataObjectLocalized"]; + meta?: Record; + }; + UseCasesPageListResponseDataItemLocalized: { + id?: number; + attributes?: components["schemas"]["UseCasesPage"]; + }; + UseCasesPageLocalizationListResponse: { + data?: components["schemas"]["UseCasesPageListResponseDataItemLocalized"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + UseCasesPageListResponseDataItem: { + id?: number; + attributes?: components["schemas"]["UseCasesPage"]; + }; + UseCasesPageListResponse: { + data?: components["schemas"]["UseCasesPageListResponseDataItem"][]; + meta?: { + pagination?: { + page?: number; + pageSize?: number; + pageCount?: number; + total?: number; + }; + }; + }; + UseCasesPage: { + SEO?: components["schemas"]["SharedSeoComponent"]; + dynamicZone?: ( + | components["schemas"]["DynamicZoneBlogPostsComponent"] + | components["schemas"]["FaqsFaQsComponent"] + | components["schemas"]["DynamicZoneBlocksComponent"] + | components["schemas"]["DynamicZoneTilesComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithTextComponent"] + | components["schemas"]["DynamicZoneValuePropositionWithBulletsComponent"] + | components["schemas"]["DynamicZoneTeamMembersComponent"] + | components["schemas"]["DynamicZoneParagraphWithTitleComponent"] + | components["schemas"]["DynamicZoneLogoCarouselComponent"] + | components["schemas"]["DynamicZoneContentTilesComponent"] + | components["schemas"]["DynamicZoneQuotesComponent"] + | components["schemas"]["DynamicZoneCarouselTextImageComponent"] + | components["schemas"]["DynamicZoneCarouselQuoteComponent"] + )[]; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: date-time */ + publishedAt?: string; + createdBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + updatedBy?: { + data?: { + id?: number; + attributes?: Record; + }; + }; + localizations?: { + data?: components["schemas"]["UseCasesPageListResponseDataItemLocalized"][]; + }; + locale?: string; + }; + UseCasesPageResponseDataObject: { + id?: number; + attributes?: components["schemas"]["UseCasesPage"]; + }; + UseCasesPageResponse: { + data?: components["schemas"]["UseCasesPageResponseDataObject"]; + meta?: Record; + }; + UploadFile: { + id?: number; + name?: string; + alternativeText?: string; + caption?: string; + /** Format: integer */ + width?: number; + /** Format: integer */ + height?: number; + formats?: number; + hash?: string; + ext?: string; + mime?: string; + /** Format: double */ + size?: number; + url?: string; + previewUrl?: string; + provider?: string; + provider_metadata?: Record; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + }; + "Users-Permissions-Role": { + id?: number; + name?: string; + description?: string; + type?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + }; + "Users-Permissions-User": { + /** @example 1 */ + id?: number; + /** @example foo.bar */ + username?: string; + /** @example foo.bar@strapi.io */ + email?: string; + /** @example local */ + provider?: string; + /** @example true */ + confirmed?: boolean; + /** @example false */ + blocked?: boolean; + /** + * Format: date-time + * @example 2022-06-02T08:32:06.258Z + */ + createdAt?: string; + /** + * Format: date-time + * @example 2022-06-02T08:32:06.267Z + */ + updatedAt?: string; + }; + "Users-Permissions-UserRegistration": { + /** @example eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c */ + jwt?: string; + user?: components["schemas"]["Users-Permissions-User"]; + }; + "Users-Permissions-PermissionsTree": { + [key: string]: { + /** @description every controller of the api */ + controllers?: { + [key: string]: { + [key: string]: { + enabled?: boolean; + policy?: string; + }; + }; + }; + }; + }; + }; + responses: never; + parameters: never; + requestBodies: { + "Users-Permissions-RoleRequest": { + content: { + /** @example { + * "name": "foo", + * "description": "role foo", + * "permissions": { + * "api::content-type.content-type": { + * "controllers": { + * "controllerA": { + * "find": { + * "enabled": true + * } + * } + * } + * } + * } + * } */ + "application/json": { + name?: string; + description?: string; + type?: string; + permissions?: components["schemas"]["Users-Permissions-PermissionsTree"]; + }; + }; + }; + }; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + "get/about-page": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AboutPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/about-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AboutPageRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AboutPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/about-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/about-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AboutPageLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AboutPageLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/basic-pages": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BasicPageListResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/basic-pages": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BasicPageRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BasicPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/basic-pages/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BasicPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/basic-pages/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BasicPageRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BasicPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/basic-pages/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/basic-pages/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BasicPageLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BasicPageLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/blog-authors": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogAuthorListResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/blog-authors": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BlogAuthorRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogAuthorResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/blog-authors/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogAuthorResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/blog-authors/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BlogAuthorRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogAuthorResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/blog-authors/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/blog-authors/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BlogAuthorLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogAuthorLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/blog-categories": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogCategoryListResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/blog-categories": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BlogCategoryRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogCategoryResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/blog-categories/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogCategoryResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/blog-categories/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BlogCategoryRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogCategoryResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/blog-categories/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/blog-categories/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BlogCategoryLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogCategoryLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/blog-page": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/blog-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BlogPageRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/blog-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/blog-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BlogPageLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogPageLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/blog-posts": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogPostListResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/blog-posts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BlogPostRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogPostResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/blog-posts/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogPostResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/blog-posts/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BlogPostRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogPostResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/blog-posts/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/blog-posts/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BlogPostLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BlogPostLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/downloads": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DownloadListResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/downloads": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DownloadRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DownloadResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/downloads/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DownloadResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/downloads/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DownloadRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DownloadResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/downloads/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/downloads/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DownloadLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DownloadLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/downloads-menu": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DownloadsMenuResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/downloads-menu": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DownloadsMenuRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DownloadsMenuResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/downloads-menu": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/downloads-menu/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DownloadsMenuLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DownloadsMenuLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/features": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FeatureListResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/features": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FeatureRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FeatureResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/features/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FeatureResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/features/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FeatureRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FeatureResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/features/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/features/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FeatureLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FeatureLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/feature-categories": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FeatureCategoryListResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/feature-categories": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FeatureCategoryRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FeatureCategoryResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/feature-categories/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FeatureCategoryResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/feature-categories/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FeatureCategoryRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FeatureCategoryResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/feature-categories/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/feature-categories/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FeatureCategoryLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FeatureCategoryLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/features-page": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FeaturesPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/features-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FeaturesPageRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FeaturesPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/features-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/features-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FeaturesPageLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FeaturesPageLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/footer": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FooterResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/footer": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FooterRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FooterResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/footer": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/footer/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FooterLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FooterLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/glossary-page": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GlossaryPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/glossary-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GlossaryPageRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GlossaryPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/glossary-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/glossary-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GlossaryPageLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GlossaryPageLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/glossary-terms": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GlossaryTermListResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/glossary-terms": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GlossaryTermRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GlossaryTermResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/glossary-terms/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GlossaryTermResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/glossary-terms/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GlossaryTermRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GlossaryTermResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/glossary-terms/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/glossary-terms/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GlossaryTermLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GlossaryTermLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/home-page": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HomePageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/home-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["HomePageRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HomePageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/home-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/home-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["HomePageLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HomePageLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/litepaper": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["LitepaperResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/litepaper": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["LitepaperRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["LitepaperResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/litepaper": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/litepaper/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["LitepaperLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["LitepaperLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/navigation": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["NavigationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/navigation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["NavigationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["NavigationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/navigation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/navigation/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["NavigationLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["NavigationLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/network-page": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["NetworkPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/network-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["NetworkPageRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["NetworkPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/network-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/network-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["NetworkPageLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["NetworkPageLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/page-not-found": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PageNotFoundResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/page-not-found": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PageNotFoundRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PageNotFoundResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/page-not-found": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/page-not-found/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PageNotFoundLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PageNotFoundLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/press-page": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PressPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/press-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PressPageRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PressPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/press-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/press-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PressPageLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PressPageLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/press-posts": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PressPostListResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/press-posts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PressPostRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PressPostResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/press-posts/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PressPostResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/press-posts/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PressPostRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PressPostResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/press-posts/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/press-posts/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PressPostLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PressPostLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/team-members": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TeamMemberListResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/team-members": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TeamMemberRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TeamMemberResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/team-members/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TeamMemberResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/team-members/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TeamMemberRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TeamMemberResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/team-members/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/team-members/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TeamMemberLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TeamMemberLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/trust-center-advisors-page": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TrustCenterAdvisorsPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/trust-center-advisors-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TrustCenterAdvisorsPageRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TrustCenterAdvisorsPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/trust-center-advisors-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/trust-center-advisors-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TrustCenterAdvisorsPageLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TrustCenterAdvisorsPageLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/trust-center-categories": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TrustCenterCategoryListResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/trust-center-categories": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TrustCenterCategoryRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TrustCenterCategoryResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/trust-center-categories/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TrustCenterCategoryResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/trust-center-categories/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TrustCenterCategoryRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TrustCenterCategoryResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/trust-center-categories/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/trust-center-categories/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TrustCenterCategoryLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TrustCenterCategoryLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/trust-center-page": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TrustCenterPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/trust-center-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TrustCenterPageRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TrustCenterPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/trust-center-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/trust-center-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TrustCenterPageLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TrustCenterPageLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/trust-center-posts": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TrustCenterPostListResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/trust-center-posts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TrustCenterPostRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TrustCenterPostResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/trust-center-posts/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TrustCenterPostResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/trust-center-posts/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TrustCenterPostRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TrustCenterPostResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/trust-center-posts/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/trust-center-posts/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TrustCenterPostLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TrustCenterPostLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/use-cases": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UseCaseListResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/use-cases": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UseCaseRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UseCaseResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/use-cases/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UseCaseResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/use-cases/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UseCaseRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UseCaseResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/use-cases/{id}": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/use-cases/{id}/localizations": { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UseCaseLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UseCaseLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "get/use-cases-page": { + parameters: { + query?: { + /** @description Sort by attributes ascending (asc) or descending (desc) */ + sort?: string; + /** @description Return page/pageSize (default: true) */ + "pagination[withCount]"?: boolean; + /** @description Page number (default: 0) */ + "pagination[page]"?: number; + /** @description Page size (default: 25) */ + "pagination[pageSize]"?: number; + /** @description Offset value (default: 0) */ + "pagination[start]"?: number; + /** @description Number of entities to return (default: 25) */ + "pagination[limit]"?: number; + /** @description Fields to return (ex: title,author) */ + fields?: string; + /** @description Relations to return */ + populate?: string; + /** @description Filters to apply */ + filters?: { + [key: string]: unknown; + }; + /** @description Locale to apply */ + locale?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UseCasesPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "put/use-cases-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UseCasesPageRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UseCasesPageResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "delete/use-cases-page": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": number; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + "post/use-cases-page/localizations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UseCasesPageLocalizationRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UseCasesPageLocalizationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; +} diff --git a/explorer-v2/src/app/api/types.ts b/explorer-v2/src/app/api/types.ts new file mode 100644 index 0000000000..bc306f1a6e --- /dev/null +++ b/explorer-v2/src/app/api/types.ts @@ -0,0 +1,482 @@ +export type API_RESPONSE = { + data: T[]; +}; + +export type Denom = "unym" | "nym"; + +export interface IPacketsAndStakingData { + date_utc: string; + total_packets_received: number; + total_packets_sent: number; + total_packets_dropped: number; + total_stake: number; +} + +export interface CurrentEpochData { + id: number; + current_epoch_id: number; + current_epoch_start: string; + epoch_length: { secs: number; nanos: number }; + epochs_in_interval: number; + total_elapsed_epochs: number; +} +export interface ExplorerData { + circulatingNymSupplyData: { + circulating_supply: { denom: Denom; amount: string }; + mixmining_reserve: { denom: Denom; amount: string }; + total_supply: { denom: Denom; amount: string }; + vesting_tokens: { denom: Denom; amount: string }; + }; + nymNodesData: { + gateways: { + bonded: { count: number; last_updated_utc: string }; + blacklisted: { count: number; last_updated_utc: string }; + historical: { count: number; last_updated_utc: string }; + explorer: { count: number; last_updated_utc: string }; + }; + mixnodes: { + bonded: { + count: number; + active: number; + inactive: number; + reserve: number; + last_updated_utc: string; + }; + blacklisted: { + count: number; + last_updated_utc: string; + }; + historical: { count: number; last_updated_utc: string }; + }; + }; + packetsAndStakingData: IPacketsAndStakingData[]; + + currentEpochRewardsData: { + interval: { + reward_pool: string; + staking_supply: string; + staking_supply_scale_factor: string; + epoch_reward_budget: string; + stake_saturation_point: string; + active_set_work_factor: string; + interval_pool_emission: string; + sybil_resistance: string; + }; + rewarded_set: { + entry_gateways: number; + exit_gateways: number; + mixnodes: number; + standby: number; + }; + }; +} + +export type NodeDescription = { + last_polled: string; + host_information: { + ip_address: string[]; + hostname: string; + keys: { + ed25519: string; + x25519: string; + x25519_noise: string | null; + }; + }; + declared_role: { + mixnode: boolean; + entry: boolean; + exit_nr: boolean; + exit_ipr: boolean; + }; + auxiliary_details: { + location: string; + announce_ports: { + verloc_port: number | null; + mix_port: number | null; + }; + accepted_operator_terms_and_conditions: boolean; + }; + build_information: { + binary_name: string; + build_timestamp: string; + build_version: string; + commit_sha: string; + commit_timestamp: string; + commit_branch: string; + rustc_version: string; + rustc_channel: string; + cargo_profile: string; + cargo_triple: string; + }; + network_requester: { + address: string; + uses_exit_policy: boolean; + }; + ip_packet_router: { + address: string; + }; + authenticator: { + address: string; + }; + wireguard: string | null; + mixnet_websockets: { + ws_port: number; + wss_port: number | null; + }; +} | null; + +export type BondInformation = { + node_id: number; + owner: string; + original_pledge: { + denom: string; + amount: string; + }; + bonding_height: number; + is_unbonding: boolean; + node: { + host: string; + custom_http_port: number; + identity_key: string; + }; +}; + +export type RewardingDetails = { + cost_params: { + profit_margin_percent: string; + interval_operating_cost: { + denom: string; + amount: string; + }; + }; + operator: string; + delegates: string; + total_unit_reward: string; + unit_delegation: string; + last_rewarded_epoch: number; + unique_delegations: number; +}; + +export type Location = { + two_letter_iso_country_code?: string; + three_letter_iso_country_code?: string; + country_name?: string; + latitude?: number; + longitude?: number; +}; + +export type NodeData = { + node_id: number; + contract_node_type: string; + description: NodeDescription; + bond_information: BondInformation; + rewarding_details: RewardingDetails; + location: Location; +}; + +// ACCOUNT BALANCES + +export interface IRewardDetails { + amount_staked: IAmountDetails; + node_id: number; + node_still_fully_bonded: boolean; + rewards: IAmountDetails; +} + +export interface IAmountDetails { + denom: string; + amount: string; +} + +export interface IDelegationDetails { + node_id: number; + delegated: IAmountDetails; + height: number; + proxy: null | string; +} + +export interface IAccountBalancesInfo { + accumulated_rewards: IRewardDetails[]; + address: string; + balances: IAmountDetails[]; + claimable_rewards: IAmountDetails; + delegations: IDelegationDetails[]; + operator_rewards?: null | IAmountDetails; + total_delegations: IAmountDetails; + total_value: IAmountDetails; + vesting_account?: null | string; +} + +export interface IObservatoryNode { + accepted_tnc: boolean; + bonded: boolean; + bonding_address: string; + description: { + authenticator: { + address: string; + }; + auxiliary_details: { + accepted_operator_terms_and_conditions: boolean; + announce_ports: { + mix_port: number | null; + verloc_port: number | null; + }; + location: string | null; + }; + build_information: { + binary_name: string; + build_timestamp: string; + build_version: string; + cargo_profile: string; + cargo_triple: string; + commit_branch: string; + commit_sha: string; + commit_timestamp: string; + rustc_channel: string; + rustc_version: string; + }; + declared_role: { + entry: boolean; + exit_ipr: boolean; + exit_nr: boolean; + mixnode: boolean; + }; + host_information: { + hostname: string | null; + ip_address: string[]; + }; + keys: { + ed25519: string; + x25519: string; + x25519_noise: string | null; + }; + ip_packet_router: { + address: string; + }; + last_polled: string; + mixnet_websockets: { + ws_port: number; + wss_port: number | null; + }; + network_requester: { + address: string; + uses_exit_policy: boolean; + }; + wireguard: string | null; + geoip: { + city: string; + country: string; + ip_address: string; + loc: string; + node_id: number; + org: string; + postal: string; + region: string; + }; + }; + identity_key: string; + ip_address: string; + node_id: number; + node_type: string; + original_pledge: number; + rewarding_details: { + cost_params: { + interval_operating_cost: { + amount: string; + denom: string; + }; + profit_margin_percent: string; + }; + delegates: string; + last_rewarded_epoch: number; + operator: string; + total_unit_reward: string; + unique_delegations: number; + unit_delegation: string; + }; + self_description: { + details: string; + moniker: string; + security_contact: string; + website: string; + }; + total_stake: number; + uptime: number; +} +export interface NodeRewardDetails { + amount: { + amount: string; + denom: string; + }; + cumulative_reward_ratio: string; + height: number; + node_id: number; + owner: string; +} + +export type LastProbeResult = { + gateway: string; + outcome: { + as_entry: { + can_connect: boolean; + can_route: boolean; + }; + as_exit: { + can_connect: boolean; + can_route_ip_external_v4: boolean; + can_route_ip_external_v6: boolean; + can_route_ip_v4: boolean; + can_route_ip_v6: boolean; + }; + wg: { + can_handshake_v4: boolean; + can_handshake_v6: boolean; + can_register: boolean; + can_resolve_dns_v4: boolean; + can_resolve_dns_v6: boolean; + ping_hosts_performance_v4: number; + ping_hosts_performance_v6: number; + ping_ips_performance_v4: number; + ping_ips_performance_v6: number; + }; + }; +}; + +export type GatewayStatus = { + blacklisted: boolean; + bonded: boolean; + config_score: number; + description: { + details: string; + moniker: string; + security_contact: string; + website: string; + }; + explorer_pretty_bond: { + identity_key: string; + location: { + latitude: number; + longitude: number; + two_letter_iso_country_code: string; + }; + owner: string; + pledge_amount: { + amount: string; + denom: string; + }; + }; + gateway_identity_key: string; + last_probe_log: string; + last_probe_result: LastProbeResult; // Reference to the separate type + last_testrun_utc: string; + last_updated_utc: string; + performance: number; + routing_score: number; + self_described: { + authenticator: { + address: string; + }; + auxiliary_details: { + accepted_operator_terms_and_conditions: boolean; + announce_ports: { + mix_port: number | null; + verloc_port: number | null; + }; + location: string; + }; + build_information: { + binary_name: string; + build_timestamp: string; + build_version: string; + cargo_profile: string; + cargo_triple: string; + }; + declared_role: { + entry: boolean; + exit_ipr: boolean; + exit_nr: boolean; + mixnode: boolean; + }; + host_information: { + hostname: string; + ip_address: string[]; + keys: { + ed25519: string; + x25519: string; + x25519_noise: string | null; + }; + }; + ip_packet_router: { + address: string; + }; + last_polled: string; + mixnet_websockets: { + ws_port: number; + wss_port: number | null; + }; + network_requester: { + address: string; + uses_exit_policy: boolean; + }; + wireguard: { + port: number; + public_key: string; + }; + }; +}; + +type BalanceDetails = { + amount: number; + denom: string; +}; + +export type ObservatoryRewards = { + operator_commissions: BalanceDetails; + staking_rewards: BalanceDetails; + unlocked: BalanceDetails; +}; + +export type ObservatoryBalance = { + delegated: BalanceDetails; + locked: BalanceDetails; + rewards: ObservatoryRewards; + self_bonded: BalanceDetails; + spendable: BalanceDetails; +}; + +export type Quote = { + ath_date: string; + ath_price: number; + market_cap: number; + market_cap_change_24h: number; + percent_change_12h: number; + percent_change_15m: number; + percent_change_1h: number; + percent_change_1y: number; + percent_change_24h: number; + percent_change_30d: number; + percent_change_30m: number; + percent_change_6h: number; + percent_change_7d: number; + percent_from_price_ath: number; + price: number; + volume_24h: number; + volume_24h_change_24h: number; +}; + +export type Quotes = { + USD: Quote; +}; + +export type NymTokenomics = { + beta_value: number; + first_data_at: string; + id: string; + last_updated: string; + max_supply: number; + name: string; + quotes: Quotes; + rank: number; + symbol: string; + total_supply: number; +}; diff --git a/explorer-v2/src/app/api/urls.ts b/explorer-v2/src/app/api/urls.ts new file mode 100644 index 0000000000..a2f439318f --- /dev/null +++ b/explorer-v2/src/app/api/urls.ts @@ -0,0 +1,21 @@ + +export const CURRENT_EPOCH = + "https://validator.nymtech.net/api/v1/epoch/current"; +export const CURRENT_EPOCH_REWARDS = + "https://validator.nymtech.net/api/v1/epoch/reward_params"; +export const NYM_NODE_BONDED = + "https://validator.nymtech.net/api/v1/nym-nodes/bonded"; +export const NYM_ACCOUNT_ADDRESS = + "https://explorer.nymtech.net/api/v1/tmp/unstable/account"; +export const NYM_PRICES_API = "https://api.nym.spectredao.net/api/v1/nym-price"; +export const VALIDATOR_BASE_URL = + process.env.NEXT_PUBLIC_VALIDATOR_URL || "https://rpc.nymtech.net"; +export const DATA_OBSERVATORY_NODES_URL = + "https://api.nym.spectredao.net/api/v1/nodes"; + +export const DATA_OBSERVATORY_DELEGATIONS_URL = + "https://api.nym.spectredao.net/api/v1/delegations"; +export const DATA_OBSERVATORY_BALANCES_URL = + "https://api.nym.spectredao.net/api/v1/balances"; +export const OBSERVATORY_GATEWAYS_URL = + "https://mainnet-node-status-api.nymtech.cc/v2/gateways"; diff --git a/explorer-v2/src/app/constants.ts b/explorer-v2/src/app/constants.ts new file mode 100644 index 0000000000..0bd154131a --- /dev/null +++ b/explorer-v2/src/app/constants.ts @@ -0,0 +1 @@ +export const TABLET_WIDTH = "(min-width:700px)"; diff --git a/explorer-v2/src/app/error.tsx b/explorer-v2/src/app/error.tsx new file mode 100644 index 0000000000..b15e0e8c58 --- /dev/null +++ b/explorer-v2/src/app/error.tsx @@ -0,0 +1,27 @@ +"use client"; + +import { ContentLayout } from "@/components/contentLayout/ContentLayout"; +import { Link } from "@/components/muiLink"; +import { Button, Stack, Typography } from "@mui/material"; + +const ErrorPage = ({ error }: { error: Error }) => { + return ( + + + + An error occurred: {error.message} + + + Please try again later or contact support + + + + + + + ); +}; + +export default ErrorPage; diff --git a/explorer-v2/src/app/favicon.png b/explorer-v2/src/app/favicon.png new file mode 100644 index 0000000000..9d12bdcdc0 Binary files /dev/null and b/explorer-v2/src/app/favicon.png differ diff --git a/explorer-v2/src/app/features/footer/api/getFooter.ts b/explorer-v2/src/app/features/footer/api/getFooter.ts new file mode 100644 index 0000000000..5c5202d30b --- /dev/null +++ b/explorer-v2/src/app/features/footer/api/getFooter.ts @@ -0,0 +1,34 @@ +// API +import { client } from "../../../lib/strapiClient"; + +// Types +import type { Languages } from "../../../i18n"; + +import type { components } from "@/app/lib/strapi"; +// Constants +import { footerApiPath } from "../../footer/config/constants"; + +// Fetch footer data +export const getFooter = async ( + locale: Languages, +): Promise<{ + id?: number; + attributes?: components["schemas"]["Footer"]; +} | null> => { + const footer = await client.GET(footerApiPath, { + params: { + query: { + locale, + // @ts-expect-error - populate is not typed correctly? + + populate: { + linkBlocks: { + populate: "*", + }, + }, + }, + }, + }); + + return footer?.data?.data ? footer?.data?.data : null; +}; diff --git a/explorer-v2/src/app/features/footer/components/footer-links.tsx b/explorer-v2/src/app/features/footer/components/footer-links.tsx new file mode 100644 index 0000000000..1d2272cc18 --- /dev/null +++ b/explorer-v2/src/app/features/footer/components/footer-links.tsx @@ -0,0 +1,72 @@ +// Types +import type { components } from "../../../lib/strapi"; + +// Components +import { Link } from "@/components/muiLink"; + +// MUI Components +import { Box, Grid2, Typography } from "@mui/material"; + +export const FooterLinks = ({ + linkBlocks = [], +}: { + linkBlocks: components["schemas"]["FooterLinkBlockComponent"][]; +}) => { + return ( + + {linkBlocks?.map((block) => { + return ( + + + {block?.heading?.title} + + + {block?.links?.map((link) => { + const isLinkExternal = link.url?.startsWith("http"); + return ( + + + + {link.title} + {isLinkExternal ? " ↗" : ""} + + + + ); + })} + + + ); + })} + + ); +}; diff --git a/explorer-v2/src/app/features/footer/config/constants.ts b/explorer-v2/src/app/features/footer/config/constants.ts new file mode 100644 index 0000000000..9ec398e59d --- /dev/null +++ b/explorer-v2/src/app/features/footer/config/constants.ts @@ -0,0 +1 @@ +export const footerApiPath = "/footer"; diff --git a/explorer-v2/src/app/globals.css b/explorer-v2/src/app/globals.css new file mode 100644 index 0000000000..2a6043d863 --- /dev/null +++ b/explorer-v2/src/app/globals.css @@ -0,0 +1,68 @@ +:root { + --max-width: 1120px; + --border-radius: 8px; +} + +@media (prefers-color-scheme: dark) { + :root { + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + } +} + +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +html, +body { + max-width: 100vw; +} + +a { + color: inherit; + text-decoration: none; +} + +@media (prefers-color-scheme: dark) { + html { + color-scheme: dark; + } +} + +.MuiCardActionArea-focusHighlight { + background: transparent; +} + +::-webkit-scrollbar-track { + background-color: transparent; +} + +::-webkit-scrollbar { + right: 0; + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-thumb { + background-color: #aaa; + border-radius: 8px; + width: 8px; + height: 8px; +} + +.reactMarkDownLink a { + color: #000000; + display: inline; + font-weight: 700; +} + +.reactMarkDownList ul { + margin-left: 20px; +} + +.reactMarkDownList ol { + margin-left: 20px; +} diff --git a/explorer-v2/src/app/i18n/index.ts b/explorer-v2/src/app/i18n/index.ts new file mode 100644 index 0000000000..997cc5d311 --- /dev/null +++ b/explorer-v2/src/app/i18n/index.ts @@ -0,0 +1,41 @@ +import { createInstance } from "i18next"; +import resourcesToBackend from "i18next-resources-to-backend"; +import { initReactI18next } from "react-i18next/initReactI18next"; +import { getOptions } from "./settings"; +import type { Languages } from "./types"; + +const initI18next = async ({ lng, ns }: { lng: Languages; ns?: string }) => { + const i18nInstance = createInstance(); + await i18nInstance + .use(initReactI18next) + .use( + resourcesToBackend( + (language: string, namespace: string) => + import(`./locales/${language}/${namespace}.json`), + ), + ) + .init(getOptions(lng, ns)); + return i18nInstance; +}; + +export const useTranslation = async ({ + lng, + ns, + options, +}: { + lng: Languages; + ns?: string; + options?: { keyPrefix: string }; +}) => { + const i18nextInstance = await initI18next({ lng, ns }); + return { + t: i18nextInstance.getFixedT( + lng, + Array.isArray(ns) ? ns[0] : ns, + options?.keyPrefix, + ), + i18n: i18nextInstance, + }; +}; + +export type { Languages, GeneralTranslations } from "./types"; diff --git a/explorer-v2/src/app/i18n/locales/en/footer.json b/explorer-v2/src/app/i18n/locales/en/footer.json new file mode 100644 index 0000000000..4903474d4b --- /dev/null +++ b/explorer-v2/src/app/i18n/locales/en/footer.json @@ -0,0 +1,92 @@ +{ + "langSelectorLabel": "Language", + "socialLinks": [ + { "name": "Telegram", "link": "https://nymtech.net/go/telegram" }, + { "name": "Twitter", "link": "https://nymtech.net/go/x" }, + { + "name": "Discord", + "link": "https://nymtech.net/go/discord" + }, + { "name": "GitHub", "link": "https://nymtech.net/go/github" }, + { "name": "YouTube", "link": "https://nymtech.net/go/youtube" } + ], + "links": [ + { + "title": "Get started", + "links": [ + { + "name": "Download NymVPN", + "link": "download" + }, + { + "name": "Test NymVPN", + "link": "alpha" + }, + { + "name": "Become an operator", + "link": "https://nymtech.net/operators" + }, + { "name": "Visit nymtech.net", "link": "https://nymtech.net" }, + { + "name": "Subscribe to Nym’s newsletter", + "link": "https://eepurl.com/gdor_f" + } + ] + }, + { + "title": "Popular blog posts", + "links": [ + { "name": "Decentralized VPNs", "link": "blog/decentralized-vpns" }, + { + "name": "Blockchain-based VPNs", + "link": "blog/blockchain-based-vpns-all-you-need-to-know" + }, + { + "name": "Privacy protection with VPN", + "link": "blog/how-does-a-vpn-protect-you-and-your-privacy" + }, + { + "name": "Tracking prevention with VPN", + "link": "blog/can-you-be-tracked-while-using-a-vpn" + }, + { + "name": "Online privacy threats", + "link": "blog/internet-privacy-main-threats-and-protections" + } + ] + }, + { + "title": "Resources", + "links": [ + { "name": "NymVPN blog", "link": "blog" }, + { + "name": "NymVPN public roadmap", + "link": "https://trello.com/b/qVhBo3e2/nymvpn-public-roadmap" + }, + { + "name": "NymVPN localization", + "link": "https://weblate.nymte.ch/projects/nymvpn/" + } + ] + }, + { + "title": "Company", + "links": [ + { "name": "Contact", "link": "contact" }, + { + "name": "Careers", + "link": "https://nym.teamtailor.com/" + }, + { + "name": "Support", + "link": "https://support.nymvpn.com/hc/en-us" + }, + { "name": "Imprint", "link": "imprint" }, + { "name": "Privacy statements", "link": "privacy" }, + { "name": "Terms of use", "link": "terms" } + ] + } + ], + "trademarkText": "WireGuard is a registered trademark of Jason A. Donenfeld", + "rightsText": "© 2024 Nym Technologies S.A., all rights reserved" +} diff --git a/explorer-v2/src/app/i18n/locales/en/social-channels.json b/explorer-v2/src/app/i18n/locales/en/social-channels.json new file mode 100644 index 0000000000..16550a018c --- /dev/null +++ b/explorer-v2/src/app/i18n/locales/en/social-channels.json @@ -0,0 +1,12 @@ +{ + "socialChannels": [ + { "name": "Telegram", "link": "https://nym.com/go/telegram" }, + { "name": "Twitter", "link": "https://nym.com/go/x" }, + { + "name": "Discord", + "link": "https://nym.com/go/discord" + }, + { "name": "GitHub", "link": "https://nym.com/go/github" }, + { "name": "YouTube", "link": "https://nym.com/go/youtube" } + ] +} diff --git a/explorer-v2/src/app/i18n/settings.ts b/explorer-v2/src/app/i18n/settings.ts new file mode 100644 index 0000000000..8f2fc2179a --- /dev/null +++ b/explorer-v2/src/app/i18n/settings.ts @@ -0,0 +1,16 @@ +export const fallbackLng = "en"; +export const languages = [fallbackLng, "es"] as const; +export const defaultNS = "translation"; +export const cookieName = "i18next"; + +export function getOptions(lng = fallbackLng, ns = defaultNS) { + return { + // debug: true, + supportedLngs: languages, + fallbackLng, + lng, + fallbackNS: defaultNS, + defaultNS, + ns, + }; +} diff --git a/explorer-v2/src/app/i18n/types/freepass.ts b/explorer-v2/src/app/i18n/types/freepass.ts new file mode 100644 index 0000000000..056e81767e --- /dev/null +++ b/explorer-v2/src/app/i18n/types/freepass.ts @@ -0,0 +1,46 @@ +export interface RedeemFreePassCopy { + errors: { + invalidCode: string; + alreadyRedeemed: string; + unknown: string; + }; +} + +type GetStarted = { + title: string; + description: string; + ctaText: string; + inputLabel: string; + prompt: string; +}; + +export type AlphaPageSteps = { + title: string; + description: string; +}; + +export type AlphaPageTranslations = { + hero: { + title: { + firstLine: string; + secondLine?: string; + }; + getCredential: GetStarted; + signUp: GetStarted; + imageAlt: string; + stepsTitle: string; + finalSteps: AlphaPageSteps[]; + finalStepsAlert: string; + credentialsTitle: string; + credentialsButton: string; + }; + redeemFreePass: RedeemFreePassCopy; + firstSection: { + title: string; + content: string[]; + }; + secondSection: { + title: string; + content: string[]; + }; +}; diff --git a/explorer-v2/src/app/i18n/types/index.ts b/explorer-v2/src/app/i18n/types/index.ts new file mode 100644 index 0000000000..4e33a30ab1 --- /dev/null +++ b/explorer-v2/src/app/i18n/types/index.ts @@ -0,0 +1,13 @@ +import type { languages } from "../settings"; + +export type Languages = (typeof languages)[number]; + +// They don't belong to any specific page, that's why I defined them here, but open to suggestions. +export type GeneralTranslations = { + alert: string; + copyToClipboard: { + copy: string; + copied: string; + copiedTimeLimitation: string; + }; +}; diff --git a/explorer-v2/src/app/icon.png b/explorer-v2/src/app/icon.png new file mode 100644 index 0000000000..9d12bdcdc0 Binary files /dev/null and b/explorer-v2/src/app/icon.png differ diff --git a/explorer-v2/src/app/layout.tsx b/explorer-v2/src/app/layout.tsx new file mode 100644 index 0000000000..4d1266f7ed --- /dev/null +++ b/explorer-v2/src/app/layout.tsx @@ -0,0 +1,30 @@ +import { Header } from "@/components/header"; +import { Wrapper } from "@/components/wrapper"; +import Providers from "@/providers"; +import type { Metadata } from "next"; + +import "./globals.css"; +import "@interchain-ui/react/styles"; +import { Footer } from "@/components/footer"; + +export const metadata: Metadata = { + title: "Nym Explorer V2", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + +
+ {children} +