Compare commits
160 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21f1fa94de | |||
| 8ed09d74b3 | |||
| 3f05c0d4b9 | |||
| 1a37e60483 | |||
| 0abc07c96f | |||
| 04664c8ae1 | |||
| 4da68438c0 | |||
| 2b83442a6d | |||
| f982cb49c2 | |||
| 0c05727e58 | |||
| 3c432ac073 | |||
| 52ffd2e798 | |||
| be8c7b4953 | |||
| 8e4bc12b87 | |||
| 4895820985 | |||
| 8500618fe9 | |||
| a5b390b98f | |||
| ff66674f61 | |||
| a7cf34e812 | |||
| a85dad6bd7 | |||
| 5b8a14f74b | |||
| 730c2efea6 | |||
| c9d6a8cc25 | |||
| 230b2b1784 | |||
| e4e9615535 | |||
| a19ee8f2aa | |||
| abfc68108a | |||
| 7bf1adff28 | |||
| ed90e358fb | |||
| c7d0e26946 | |||
| 8d65c25986 | |||
| a143d5f4f6 | |||
| c041d11673 | |||
| 82e82943aa | |||
| e4fd87be2c | |||
| 19ffe217f1 | |||
| 079bfa52e7 | |||
| be9a2c26e7 | |||
| d6f3eb6411 | |||
| 144f3bed9c | |||
| c1174e64d4 | |||
| 312ecbe4dc | |||
| d2afa587e4 | |||
| 224c4c1870 | |||
| 3f8abdb74f | |||
| 0f6ec8610e | |||
| 3baac1292d | |||
| c3b8c4b2f7 | |||
| 271b9e545c | |||
| 9641f01670 | |||
| a7bb3e8d91 | |||
| dc88650d6d | |||
| 79ce611d21 | |||
| 960e817b8f | |||
| 8b03e66ba7 | |||
| 6a35581299 | |||
| ce124a29a7 | |||
| f62d8813e0 | |||
| a9cf016af2 | |||
| a8403b585b | |||
| e9a7b48da0 | |||
| 66792f57ed | |||
| f8d863249e | |||
| 7d59a2477a | |||
| eca88b0fa4 | |||
| b80a4c8614 | |||
| ec5d342e3a | |||
| 6565655861 | |||
| 5aba886f14 | |||
| 3ee73d541e | |||
| 4588a3036e | |||
| 6194ac07b8 | |||
| a7fcfef5a3 | |||
| fa927b82d8 | |||
| f724478763 | |||
| 040f4f2500 | |||
| 63002e784a | |||
| 4a0b683b70 | |||
| 9e84b1f0c1 | |||
| bf031ad6de | |||
| 933769401c | |||
| ddd85704bb | |||
| 17860c809f | |||
| 2d00fcd934 | |||
| c2c3df98cb | |||
| f429092e21 | |||
| d7ef68d8d1 | |||
| 1a334b575d | |||
| 2126736aff | |||
| a69aa23609 | |||
| 8a2d98e3ce | |||
| 9c4243914e | |||
| 143ede268d | |||
| 81bddb5f6d | |||
| 247ebb7c43 | |||
| 01c052e9a4 | |||
| 3880971e57 | |||
| 6bd31b9521 | |||
| 430c33eb04 | |||
| d45d1eb313 | |||
| 3cb3ebd79b | |||
| b42e5b063e | |||
| f6b30d0db6 | |||
| c33e4c0836 | |||
| be92ccf0da | |||
| 35bf49c48c | |||
| 7335a3dad4 | |||
| 698883c03f | |||
| 8ddef08c72 | |||
| 0d8b3abc6f | |||
| aa2f336904 | |||
| eacaf84430 | |||
| c284b1e8b1 | |||
| 7785d085cf | |||
| bb5b2eafcf | |||
| 09ea406c02 | |||
| 681c054890 | |||
| f623bbd57c | |||
| 8c6f84b3fe | |||
| 27dc9c8024 | |||
| 42d559bc69 | |||
| 41b9b0e5bd | |||
| 6c781a0064 | |||
| 080ec80722 | |||
| 9c17239831 | |||
| f6c19ec02b | |||
| 94ff8a79ee | |||
| 155c4d37ef | |||
| 7060fa6dad | |||
| 9be9c04f52 | |||
| 2a6fe6624d | |||
| 4f7124e661 | |||
| f52f07f6ec | |||
| b709d3ba0b | |||
| 128f69a5d6 | |||
| 40dd7dc95e | |||
| f13ce6bf2d | |||
| 856dbfe1ac | |||
| b2f6836756 | |||
| 87e429d78a | |||
| 4178809555 | |||
| e6f6e1342f | |||
| 65175fee09 | |||
| 69b2448500 | |||
| 8ba5322997 | |||
| 2cb3817b2c | |||
| 80b395cd8e | |||
| 8f5457e698 | |||
| 9de5d7213a | |||
| 94eb362a71 | |||
| 0f615f48f2 | |||
| d511611641 | |||
| 26f97d3c34 | |||
| 17d3ff2d77 | |||
| dd3dcfa7fe | |||
| 86ea2d23cb | |||
| 42a37442e8 | |||
| d4d576f363 | |||
| 63a8f96ea5 | |||
| 0d397ab5cc |
@@ -1 +1,2 @@
|
|||||||
nym-validator-rewarder/.sqlx/** diff=nodiff
|
nym-validator-rewarder/.sqlx/** diff=nodiff
|
||||||
|
nym-node-status-api/nym-node-status-api/.sqlx/** diff=nodiff
|
||||||
|
|||||||
+149
-265
@@ -9,7 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^1.10.1",
|
||||||
"@actions/github": "^5.1.1",
|
"@actions/github": "^6.0.0",
|
||||||
"@octokit/auth-action": "^4.0.1",
|
"@octokit/auth-action": "^4.0.1",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"hasha": "^5.2.0",
|
"hasha": "^5.2.0",
|
||||||
@@ -29,22 +29,34 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/github": {
|
"node_modules/@actions/github": {
|
||||||
"version": "5.1.1",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.0.tgz",
|
||||||
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
|
"integrity": "sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/http-client": "^2.0.1",
|
"@actions/http-client": "^2.2.0",
|
||||||
"@octokit/core": "^3.6.0",
|
"@octokit/core": "^5.0.1",
|
||||||
"@octokit/plugin-paginate-rest": "^2.17.0",
|
"@octokit/plugin-paginate-rest": "^9.0.0",
|
||||||
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
|
"@octokit/plugin-rest-endpoint-methods": "^10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/http-client": {
|
"node_modules/@actions/http-client": {
|
||||||
"version": "2.1.1",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz",
|
||||||
"integrity": "sha512-qhrkRMB40bbbLo7gF+0vu+X+UawOvQQqNAA/5Unx774RS8poaOhThDOG6BGmxvAnxhQnDp2BG/ZUm65xZILTpw==",
|
"integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"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": {
|
"node_modules/@octokit/auth-action": {
|
||||||
@@ -59,14 +71,6 @@
|
|||||||
"node": ">= 18"
|
"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": {
|
"node_modules/@octokit/auth-action/node_modules/@octokit/openapi-types": {
|
||||||
"version": "20.0.0",
|
"version": "20.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
|
||||||
@@ -81,115 +85,152 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/auth-token": {
|
"node_modules/@octokit/auth-token": {
|
||||||
"version": "2.5.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
|
||||||
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
|
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
|
||||||
"dependencies": {
|
"license": "MIT",
|
||||||
"@octokit/types": "^6.0.3"
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/core": {
|
"node_modules/@octokit/core": {
|
||||||
"version": "3.6.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz",
|
||||||
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
|
"integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-token": "^2.4.4",
|
"@octokit/auth-token": "^4.0.0",
|
||||||
"@octokit/graphql": "^4.5.8",
|
"@octokit/graphql": "^7.1.0",
|
||||||
"@octokit/request": "^5.6.3",
|
"@octokit/request": "^8.3.1",
|
||||||
"@octokit/request-error": "^2.0.5",
|
"@octokit/request-error": "^5.1.0",
|
||||||
"@octokit/types": "^6.0.3",
|
"@octokit/types": "^13.0.0",
|
||||||
"before-after-hook": "^2.2.0",
|
"before-after-hook": "^2.2.0",
|
||||||
"universal-user-agent": "^6.0.0"
|
"universal-user-agent": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/endpoint": {
|
"node_modules/@octokit/endpoint": {
|
||||||
"version": "6.0.12",
|
"version": "9.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
|
||||||
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
|
"integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/types": "^6.0.3",
|
"@octokit/types": "^13.1.0",
|
||||||
"is-plain-object": "^5.0.0",
|
|
||||||
"universal-user-agent": "^6.0.0"
|
"universal-user-agent": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/graphql": {
|
"node_modules/@octokit/graphql": {
|
||||||
"version": "4.8.0",
|
"version": "7.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.0.tgz",
|
||||||
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
|
"integrity": "sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/request": "^5.6.0",
|
"@octokit/request": "^8.3.0",
|
||||||
"@octokit/types": "^6.0.3",
|
"@octokit/types": "^13.0.0",
|
||||||
"universal-user-agent": "^6.0.0"
|
"universal-user-agent": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/openapi-types": {
|
"node_modules/@octokit/openapi-types": {
|
||||||
"version": "12.11.0",
|
"version": "23.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz",
|
||||||
"integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ=="
|
"integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/plugin-paginate-rest": {
|
"node_modules/@octokit/plugin-paginate-rest": {
|
||||||
"version": "2.21.3",
|
"version": "9.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz",
|
||||||
"integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==",
|
"integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/types": "^6.40.0"
|
"@octokit/types": "^12.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"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": {
|
"node_modules/@octokit/plugin-rest-endpoint-methods": {
|
||||||
"version": "5.16.2",
|
"version": "10.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz",
|
||||||
"integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==",
|
"integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/types": "^6.39.0",
|
"@octokit/types": "^12.6.0"
|
||||||
"deprecation": "^2.3.1"
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"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": {
|
"node_modules/@octokit/request": {
|
||||||
"version": "5.6.3",
|
"version": "8.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz",
|
||||||
"integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
|
"integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/endpoint": "^6.0.1",
|
"@octokit/endpoint": "^9.0.6",
|
||||||
"@octokit/request-error": "^2.1.0",
|
"@octokit/request-error": "^5.1.1",
|
||||||
"@octokit/types": "^6.16.1",
|
"@octokit/types": "^13.1.0",
|
||||||
"is-plain-object": "^5.0.0",
|
|
||||||
"node-fetch": "^2.6.7",
|
|
||||||
"universal-user-agent": "^6.0.0"
|
"universal-user-agent": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/request-error": {
|
"node_modules/@octokit/request-error": {
|
||||||
"version": "2.1.0",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz",
|
||||||
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
|
"integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/types": "^6.0.3",
|
"@octokit/types": "^13.1.0",
|
||||||
"deprecation": "^2.0.0",
|
"deprecation": "^2.0.0",
|
||||||
"once": "^1.4.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": {
|
"engines": {
|
||||||
"node": "4.x || >=6.0.0"
|
"node": ">= 18"
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"encoding": "^0.1.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"encoding": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/rest": {
|
"node_modules/@octokit/rest": {
|
||||||
@@ -206,89 +247,6 @@
|
|||||||
"node": ">= 18"
|
"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": {
|
"node_modules/@octokit/rest/node_modules/@octokit/plugin-request-log": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.0.tgz",
|
||||||
@@ -300,75 +258,13 @@
|
|||||||
"@octokit/core": ">=5"
|
"@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": {
|
"node_modules/@octokit/types": {
|
||||||
"version": "6.41.0",
|
"version": "13.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
|
||||||
"integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
|
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/openapi-types": "^12.11.0"
|
"@octokit/openapi-types": "^23.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vercel/ncc": {
|
"node_modules/@vercel/ncc": {
|
||||||
@@ -396,7 +292,8 @@
|
|||||||
"node_modules/deprecation": {
|
"node_modules/deprecation": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
|
||||||
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
|
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
|
||||||
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/fetch-blob": {
|
"node_modules/fetch-blob": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
@@ -446,14 +343,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/is-stream": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||||
@@ -504,15 +393,11 @@
|
|||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||||
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"wrappy": "1"
|
"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": {
|
"node_modules/tunnel": {
|
||||||
"version": "0.0.6",
|
"version": "0.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||||
@@ -529,6 +414,18 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/universal-user-agent": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
|
||||||
@@ -550,24 +447,11 @@
|
|||||||
"node": ">= 8"
|
"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": {
|
"node_modules/wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^1.10.1",
|
||||||
"@actions/github": "^5.1.1",
|
"@actions/github": "^6.0.0",
|
||||||
"@octokit/auth-action": "^4.0.1",
|
"@octokit/auth-action": "^4.0.1",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"hasha": "^5.2.0",
|
"hasha": "^5.2.0",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
|
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
@@ -99,7 +100,6 @@ jobs:
|
|||||||
cp target/release/nymvisor $OUTPUT_DIR
|
cp target/release/nymvisor $OUTPUT_DIR
|
||||||
cp target/release/nym-node $OUTPUT_DIR
|
cp target/release/nym-node $OUTPUT_DIR
|
||||||
cp target/release/nym-cli $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
|
if [ ${{ github.event_name == 'workflow_dispatch' && inputs.enable_deb == true }} = true ]; then
|
||||||
cp target/debian/*.deb $OUTPUT_DIR
|
cp target/debian/*.deb $OUTPUT_DIR
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ jobs:
|
|||||||
runs-on: arc-ubuntu-22.04
|
runs-on: arc-ubuntu-22.04
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
|
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository code
|
- name: Check out repository code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ on:
|
|||||||
- '.github/workflows/ci-build.yml'
|
- '.github/workflows/ci-build.yml'
|
||||||
workflow_dispatch:
|
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:
|
jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
@@ -37,6 +43,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
IPINFO_API_TOKEN: ${{ secrets.IPINFO_API_TOKEN }}
|
IPINFO_API_TOKEN: ${{ secrets.IPINFO_API_TOKEN }}
|
||||||
|
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||||
steps:
|
steps:
|
||||||
- name: Install Dependencies (Linux)
|
- 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
|
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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -11,6 +11,8 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: arc-ubuntu-20.04
|
runs-on: arc-ubuntu-20.04
|
||||||
|
env:
|
||||||
|
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: documentation/docs
|
working-directory: documentation/docs
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: arc-ubuntu-20.04
|
runs-on: arc-ubuntu-20.04
|
||||||
|
env:
|
||||||
|
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: rlespinasse/github-slug-action@v3.x
|
- uses: rlespinasse/github-slug-action@v3.x
|
||||||
@@ -42,7 +44,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.20'
|
go-version: "1.23.7"
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: yarn
|
run: yarn
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ jobs:
|
|||||||
runs-on: arc-ubuntu-20.04
|
runs-on: arc-ubuntu-20.04
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
|
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||||
steps:
|
steps:
|
||||||
- name: Install Dependencies (Linux)
|
- 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
|
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
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ jobs:
|
|||||||
runs-on: arc-ubuntu-20.04
|
runs-on: arc-ubuntu-20.04
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
|
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.20'
|
go-version: "1.23.7"
|
||||||
|
|
||||||
- name: Install wasm-pack
|
- name: Install wasm-pack
|
||||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
"build-tools;$SDK_BUILDTOOLS_VERSION"
|
"build-tools;$SDK_BUILDTOOLS_VERSION"
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@1.90.0
|
uses: dtolnay/rust-toolchain@1.100.0
|
||||||
|
|
||||||
- name: Install rust android targets
|
- name: Install rust android targets
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -31,12 +31,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "1.20"
|
go-version: "1.23.7"
|
||||||
|
|
||||||
- name: Install TinyGo
|
|
||||||
uses: acifani/setup-tinygo@v2
|
|
||||||
with:
|
|
||||||
tinygo-version: "0.27.0"
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
run: yarn
|
||||||
|
|||||||
+220
-10
@@ -4,6 +4,216 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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)
|
## [2025.3-ruta] (2025-02-10)
|
||||||
|
|
||||||
- Push down forget me to client configs ([#5431])
|
- 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])
|
- Downgrade harmless log message from info to debug ([#5403])
|
||||||
- Redirect from mixnode page to nodes page ([#5397])
|
- Redirect from mixnode page to nodes page ([#5397])
|
||||||
- chore :update version of chain watcher and validator rewarder ([#5394])
|
- 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])
|
- bugfix: terminate mixnet socket listener on shutdown ([#5389])
|
||||||
- feat: make client ignore dual mode nodes by default ([#5388])
|
- feat: make client ignore dual mode nodes by default ([#5388])
|
||||||
- Handle ecash network errors differently ([#5378])
|
- 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])
|
- Use expect in geodata test to give error message on failure ([#5314])
|
||||||
- feature: periodically remove stale gateway messages ([#5312])
|
- feature: periodically remove stale gateway messages ([#5312])
|
||||||
- build(deps): bump the patch-updates group across 1 directory with 35 updates ([#5310])
|
- 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])
|
- Move tun constants to network defaults ([#5286])
|
||||||
- Include IPINFO_API_TOKEN in nightly CI ([#5285])
|
- Include IPINFO_API_TOKEN in nightly CI ([#5285])
|
||||||
- Nyx Chain Watcher ([#5274])
|
- 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)
|
## [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: readjusted --mode behaviour to fix the regression ([#5331])
|
||||||
- chore: apply 1.84 linter suggestions ([#5330])
|
- chore: apply 1.84 linter suggestions ([#5330])
|
||||||
- bugfix: make sure refresh data key matches bond info ([#5329])
|
- 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)
|
## [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
|
- Fixes compatibility issues between nym-nodes and older clients
|
||||||
|
|
||||||
## [2024.14-crunch] (2024-12-11)
|
## [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])
|
- Merge/release/2024.14-crunch ([#5242])
|
||||||
- bugfix: added explicit openapi servers to account for route prefixes ([#5237])
|
- bugfix: added explicit openapi servers to account for route prefixes ([#5237])
|
||||||
- Further config score adjustments ([#5225])
|
- Further config score adjustments ([#5225])
|
||||||
- feature: remve any filtering on node semver ([#5224])
|
- feature: remove any filtering on node semver ([#5224])
|
||||||
- Backport #5218 ([#5220])
|
- Backport #5218 ([#5220])
|
||||||
- Derive serialize for UserAgent (#5210) ([#5217])
|
- Derive serialize for UserAgent (#5210) ([#5217])
|
||||||
- dont consider legacy nodes for rewarded set selection ([#5215])
|
- 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])
|
- bugfix/feature: added NymApiClient method to get all skimmed nodes ([#5062])
|
||||||
- Merge1/release/2024.13 magura ([#5061])
|
- Merge1/release/2024.13 magura ([#5061])
|
||||||
- added hacky routes to return nymnodes alongside legacy nodes ([#5051])
|
- 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: adjust runtime storage migration ([#5047])
|
||||||
- bugfix: supersede 'cb13be27f8f61d9ae74d924e85d2e6787895eb14' by using… ([#5046])
|
- bugfix: supersede 'cb13be27f8f61d9ae74d924e85d2e6787895eb14' by using… ([#5046])
|
||||||
- bugfix: restore default http port for nym-api ([#5045])
|
- 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])
|
- Fix broken build after merge ([#4937])
|
||||||
- bugfix: correctly paginate through 'search_tx' endpoint ([#4936])
|
- bugfix: correctly paginate through 'search_tx' endpoint ([#4936])
|
||||||
- Add more conversions for responses of authenticator messages ([#4929])
|
- 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])
|
- Migrate Legacy Node (Frontend) ([#4826])
|
||||||
- Fix critical issues SI84 and SI85 from Cure53 ([#4758])
|
- 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])
|
- Remove stale peers ([#4640])
|
||||||
- Add generic wg private network routing ([#4636])
|
- Add generic wg private network routing ([#4636])
|
||||||
- Feature/new node endpoints ([#4635])
|
- 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])
|
- validate nym-node public ips on startup ([#4630])
|
||||||
- Bump defguard wg ([#4625])
|
- Bump defguard wg ([#4625])
|
||||||
- Fix cargo warnings ([#4624])
|
- 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])
|
- 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 validator_api to nym_api
|
||||||
- renamed all references to nymd to nyxd ([#2696])
|
- 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
|
### 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))
|
- 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))
|
- 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))
|
- 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))
|
- 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))
|
- 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))
|
- Only use ts-rs in tests [\#894](https://github.com/nymtech/nym/pull/894) ([durch](https://github.com/durch))
|
||||||
|
|||||||
Generated
+388
-830
File diff suppressed because it is too large
Load Diff
+46
-37
@@ -66,7 +66,6 @@ members = [
|
|||||||
"common/nym-id",
|
"common/nym-id",
|
||||||
"common/nym-metrics",
|
"common/nym-metrics",
|
||||||
"common/nym_offline_compact_ecash",
|
"common/nym_offline_compact_ecash",
|
||||||
"common/nymcoconut",
|
|
||||||
"common/nymsphinx",
|
"common/nymsphinx",
|
||||||
"common/nymsphinx/acknowledgements",
|
"common/nymsphinx/acknowledgements",
|
||||||
"common/nymsphinx/addressing",
|
"common/nymsphinx/addressing",
|
||||||
@@ -99,9 +98,9 @@ members = [
|
|||||||
"common/wireguard",
|
"common/wireguard",
|
||||||
"common/wireguard-types",
|
"common/wireguard-types",
|
||||||
"documentation/autodoc",
|
"documentation/autodoc",
|
||||||
"explorer-api",
|
# "explorer-api",
|
||||||
"explorer-api/explorer-api-requests",
|
# "explorer-api/explorer-api-requests",
|
||||||
"explorer-api/explorer-client",
|
# "explorer-api/explorer-client",
|
||||||
"gateway",
|
"gateway",
|
||||||
"integrations/bity",
|
"integrations/bity",
|
||||||
"nym-api",
|
"nym-api",
|
||||||
@@ -138,7 +137,7 @@ members = [
|
|||||||
"tools/internal/testnet-manager",
|
"tools/internal/testnet-manager",
|
||||||
"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/testnet-manager/dkg-bypass-contract", "tools/internal/validator-status-check",
|
||||||
"tools/nym-cli",
|
"tools/nym-cli",
|
||||||
"tools/nym-id-cli",
|
"tools/nym-id-cli",
|
||||||
"tools/nym-nr-query",
|
"tools/nym-nr-query",
|
||||||
@@ -154,7 +153,6 @@ members = [
|
|||||||
default-members = [
|
default-members = [
|
||||||
"clients/native",
|
"clients/native",
|
||||||
"clients/socks5",
|
"clients/socks5",
|
||||||
"explorer-api",
|
|
||||||
"nym-api",
|
"nym-api",
|
||||||
"nym-credential-proxy/nym-credential-proxy",
|
"nym-credential-proxy/nym-credential-proxy",
|
||||||
"nym-node",
|
"nym-node",
|
||||||
@@ -192,10 +190,10 @@ aes = "0.8.1"
|
|||||||
aes-gcm = "0.10.1"
|
aes-gcm = "0.10.1"
|
||||||
aes-gcm-siv = "0.11.1"
|
aes-gcm-siv = "0.11.1"
|
||||||
ammonia = "4"
|
ammonia = "4"
|
||||||
anyhow = "1.0.95"
|
anyhow = "1.0.97"
|
||||||
arc-swap = "1.7.1"
|
arc-swap = "1.7.1"
|
||||||
argon2 = "0.5.0"
|
argon2 = "0.5.0"
|
||||||
async-trait = "0.1.86"
|
async-trait = "0.1.88"
|
||||||
axum = "0.7.5"
|
axum = "0.7.5"
|
||||||
axum-client-ip = "0.6.1"
|
axum-client-ip = "0.6.1"
|
||||||
axum-extra = "0.9.4"
|
axum-extra = "0.9.4"
|
||||||
@@ -206,24 +204,24 @@ bincode = "1.3.3"
|
|||||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||||
bit-vec = "0.7.0" # can we unify those?
|
bit-vec = "0.7.0" # can we unify those?
|
||||||
bitvec = "1.0.0"
|
bitvec = "1.0.0"
|
||||||
blake3 = "1.5.5"
|
blake3 = "1.6.1"
|
||||||
bloomfilter = "1.0.14"
|
bloomfilter = "1.0.14"
|
||||||
bs58 = "0.5.1"
|
bs58 = "0.5.1"
|
||||||
bytecodec = "0.4.15"
|
bytecodec = "0.4.15"
|
||||||
bytes = "1.7.2"
|
bytes = "1.10.1"
|
||||||
cargo_metadata = "0.18.1"
|
cargo_metadata = "0.18.1"
|
||||||
celes = "2.5.0"
|
celes = "2.6.0"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
chacha20 = "0.9.0"
|
chacha20 = "0.9.0"
|
||||||
chacha20poly1305 = "0.10.1"
|
chacha20poly1305 = "0.10.1"
|
||||||
chrono = "0.4.39"
|
chrono = "0.4.40"
|
||||||
cipher = "0.4.3"
|
cipher = "0.4.3"
|
||||||
clap = "4.5.30"
|
clap = "4.5.32"
|
||||||
clap_complete = "4.5"
|
clap_complete = "4.5"
|
||||||
clap_complete_fig = "4.5"
|
clap_complete_fig = "4.5"
|
||||||
colored = "2.2"
|
colored = "2.2"
|
||||||
comfy-table = "7.1.4"
|
comfy-table = "7.1.4"
|
||||||
console = "0.15.10"
|
console = "0.15.11"
|
||||||
console-subscriber = "0.1.1"
|
console-subscriber = "0.1.1"
|
||||||
console_error_panic_hook = "0.1"
|
console_error_panic_hook = "0.1"
|
||||||
const-str = "0.5.6"
|
const-str = "0.5.6"
|
||||||
@@ -242,29 +240,30 @@ doc-comment = "0.3"
|
|||||||
dotenvy = "0.15.6"
|
dotenvy = "0.15.6"
|
||||||
ecdsa = "0.16"
|
ecdsa = "0.16"
|
||||||
ed25519-dalek = "2.1"
|
ed25519-dalek = "2.1"
|
||||||
env_logger = "0.11.6"
|
encoding_rs = "0.8.35"
|
||||||
|
env_logger = "0.11.7"
|
||||||
envy = "0.4"
|
envy = "0.4"
|
||||||
etherparse = "0.13.0"
|
etherparse = "0.13.0"
|
||||||
eyre = "0.6.9"
|
eyre = "0.6.9"
|
||||||
fastrand = "2.1.1"
|
fastrand = "2.1.1"
|
||||||
flate2 = "1.0.35"
|
flate2 = "1.1.0"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
generic-array = "0.14.7"
|
generic-array = "0.14.7"
|
||||||
getrandom = "0.2.10"
|
getrandom = "0.2.10"
|
||||||
getset = "0.1.4"
|
getset = "0.1.5"
|
||||||
handlebars = "3.5.5"
|
handlebars = "3.5.5"
|
||||||
headers = "0.4.0"
|
headers = "0.4.0"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
hex-literal = "0.3.3"
|
hex-literal = "0.3.3"
|
||||||
hickory-resolver = "0.24.3"
|
hickory-resolver = "0.24.4"
|
||||||
hkdf = "0.12.3"
|
hkdf = "0.12.3"
|
||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
http = "1"
|
http = "1"
|
||||||
http-body-util = "0.1"
|
http-body-util = "0.1"
|
||||||
httpcodec = "0.2.3"
|
httpcodec = "0.2.3"
|
||||||
human-repr = "1.1.0"
|
human-repr = "1.1.0"
|
||||||
humantime = "2.1.0"
|
humantime = "2.2.0"
|
||||||
humantime-serde = "1.1.1"
|
humantime-serde = "1.1.1"
|
||||||
hyper = "1.6.0"
|
hyper = "1.6.0"
|
||||||
hyper-util = "0.1"
|
hyper-util = "0.1"
|
||||||
@@ -273,7 +272,7 @@ inquire = "0.6.2"
|
|||||||
ip_network = "0.4.1"
|
ip_network = "0.4.1"
|
||||||
ipnetwork = "0.20"
|
ipnetwork = "0.20"
|
||||||
isocountry = "0.3.2"
|
isocountry = "0.3.2"
|
||||||
itertools = "0.13.0"
|
itertools = "0.14.0"
|
||||||
k256 = "0.13"
|
k256 = "0.13"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
ledger-transport = "0.10.0"
|
ledger-transport = "0.10.0"
|
||||||
@@ -285,7 +284,7 @@ moka = { version = "0.12", features = ["future"] }
|
|||||||
nix = "0.27.1"
|
nix = "0.27.1"
|
||||||
notify = "5.1.0"
|
notify = "5.1.0"
|
||||||
okapi = "0.7.0"
|
okapi = "0.7.0"
|
||||||
once_cell = "1.20.3"
|
once_cell = "1.21.1"
|
||||||
opentelemetry = "0.19.0"
|
opentelemetry = "0.19.0"
|
||||||
opentelemetry-jaeger = "0.18.0"
|
opentelemetry-jaeger = "0.18.0"
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
@@ -308,21 +307,21 @@ reqwest = { version = "0.12.4", default-features = false }
|
|||||||
rocket = "0.5.0"
|
rocket = "0.5.0"
|
||||||
rocket_cors = "0.6.0"
|
rocket_cors = "0.6.0"
|
||||||
rocket_okapi = "0.8.0"
|
rocket_okapi = "0.8.0"
|
||||||
rs_merkle = "1.4.2"
|
rs_merkle = "1.5.0"
|
||||||
safer-ffi = "0.1.13"
|
safer-ffi = "0.1.13"
|
||||||
schemars = "0.8.21"
|
schemars = "0.8.22"
|
||||||
semver = "1.0.25"
|
semver = "1.0.26"
|
||||||
serde = "1.0.217"
|
serde = "1.0.219"
|
||||||
serde_bytes = "0.11.15"
|
serde_bytes = "0.11.17"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0.138"
|
serde_json = "1.0.140"
|
||||||
serde_json_path = "0.7.2"
|
serde_json_path = "0.7.2"
|
||||||
serde_repr = "0.1"
|
serde_repr = "0.1"
|
||||||
serde_with = "3.9.0"
|
serde_with = "3.9.0"
|
||||||
serde_yaml = "0.9.25"
|
serde_yaml = "0.9.25"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
si-scale = "0.2.3"
|
si-scale = "0.2.3"
|
||||||
sphinx-packet = "0.3.1"
|
sphinx-packet = "=0.3.2"
|
||||||
sqlx = "0.7.4"
|
sqlx = "0.7.4"
|
||||||
strum = "0.26"
|
strum = "0.26"
|
||||||
strum_macros = "0.26"
|
strum_macros = "0.26"
|
||||||
@@ -330,17 +329,17 @@ subtle-encoding = "0.5"
|
|||||||
syn = "1"
|
syn = "1"
|
||||||
sysinfo = "0.33.0"
|
sysinfo = "0.33.0"
|
||||||
tap = "1.0.1"
|
tap = "1.0.1"
|
||||||
tar = "0.4.43"
|
tar = "0.4.44"
|
||||||
tempfile = "3.15"
|
tempfile = "3.19"
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
time = "0.3.37"
|
time = "0.3.39"
|
||||||
tokio = "1.43"
|
tokio = "1.44"
|
||||||
tokio-postgres = "0.7"
|
tokio-postgres = "0.7"
|
||||||
tokio-stream = "0.1.17"
|
tokio-stream = "0.1.17"
|
||||||
tokio-test = "0.4.4"
|
tokio-test = "0.4.4"
|
||||||
tokio-tun = "0.11.5"
|
tokio-tun = "0.11.5"
|
||||||
tokio-tungstenite = { version = "0.20.1" }
|
tokio-tungstenite = { version = "0.20.1" }
|
||||||
tokio-util = "0.7.13"
|
tokio-util = "0.7.14"
|
||||||
toml = "0.8.20"
|
toml = "0.8.20"
|
||||||
tower = "0.5.2"
|
tower = "0.5.2"
|
||||||
tower-http = "0.5.2"
|
tower-http = "0.5.2"
|
||||||
@@ -362,7 +361,7 @@ vergen = { version = "=8.3.1", default-features = false }
|
|||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
wasm-bindgen-test = "0.3.49"
|
wasm-bindgen-test = "0.3.49"
|
||||||
x25519-dalek = "2.0.0"
|
x25519-dalek = "2.0.0"
|
||||||
zeroize = "1.6.0"
|
zeroize = "1.8.1"
|
||||||
|
|
||||||
prometheus = { version = "0.13.0" }
|
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
|
# 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
|
# 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
|
# 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 }
|
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"
|
subtle = "2.5.0"
|
||||||
|
|
||||||
# cosmwasm-related
|
# cosmwasm-related
|
||||||
@@ -403,7 +402,7 @@ prost = { version = "0.13", default-features = false }
|
|||||||
gloo-utils = "0.2.0"
|
gloo-utils = "0.2.0"
|
||||||
gloo-net = "0.6.0"
|
gloo-net = "0.6.0"
|
||||||
|
|
||||||
indexed_db_futures = "0.6.0"
|
indexed_db_futures = "0.6.1"
|
||||||
js-sys = "0.3.76"
|
js-sys = "0.3.76"
|
||||||
serde-wasm-bindgen = "0.6.5"
|
serde-wasm-bindgen = "0.6.5"
|
||||||
tsify = "0.4.5"
|
tsify = "0.4.5"
|
||||||
@@ -438,3 +437,13 @@ opt-level = 'z'
|
|||||||
[profile.release.package.mix-fetch-wasm]
|
[profile.release.package.mix-fetch-wasm]
|
||||||
# lto = true
|
# lto = true
|
||||||
opt-level = 'z'
|
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"
|
||||||
|
|||||||
@@ -67,3 +67,13 @@ As a general approach, licensing is as follows this pattern:
|
|||||||
- documentation is Apache 2.0 or CC0-1.0
|
- 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).
|
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
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nym-client"
|
name = "nym-client"
|
||||||
version = "1.1.48"
|
version = "1.1.51"
|
||||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||||
description = "Implementation of the Nym Client"
|
description = "Implementation of the Nym Client"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nym-socks5-client"
|
name = "nym-socks5-client"
|
||||||
version = "1.1.48"
|
version = "1.1.51"
|
||||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
allow-unwrap-in-tests = true
|
allow-unwrap-in-tests = true
|
||||||
allow-expect-in-tests = true
|
allow-expect-in-tests = true
|
||||||
|
allow-panic-in-tests = true
|
||||||
@@ -6,14 +6,15 @@ pub mod v1;
|
|||||||
pub mod v2;
|
pub mod v2;
|
||||||
pub mod v3;
|
pub mod v3;
|
||||||
pub mod v4;
|
pub mod v4;
|
||||||
|
pub mod v5;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
pub use error::Error;
|
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 {
|
fn make_bincode_serializer() -> impl bincode::Options {
|
||||||
use bincode::Options;
|
use bincode::Options;
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ use nym_sphinx::addressing::clients::Recipient;
|
|||||||
use nym_wireguard_types::PeerPublicKey;
|
use nym_wireguard_types::PeerPublicKey;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
v1, v2, v3,
|
v1, v2, v3, v4,
|
||||||
v4::{self, registration::IpPair},
|
v5::{self, registration::IpPair},
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ pub enum AuthenticatorVersion {
|
|||||||
V2,
|
V2,
|
||||||
V3,
|
V3,
|
||||||
V4,
|
V4,
|
||||||
|
V5,
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +35,8 @@ impl From<Protocol> for AuthenticatorVersion {
|
|||||||
AuthenticatorVersion::V3
|
AuthenticatorVersion::V3
|
||||||
} else if value.version == v4::VERSION {
|
} else if value.version == v4::VERSION {
|
||||||
AuthenticatorVersion::V4
|
AuthenticatorVersion::V4
|
||||||
|
} else if value.version == v5::VERSION {
|
||||||
|
AuthenticatorVersion::V5
|
||||||
} else {
|
} else {
|
||||||
AuthenticatorVersion::UNKNOWN
|
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 {
|
pub trait FinalMessage {
|
||||||
fn pub_key(&self) -> PeerPublicKey;
|
fn pub_key(&self) -> PeerPublicKey;
|
||||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error>;
|
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)
|
self.gateway_client.verify(private_key, nonce)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn private_ips(&self) -> IpPair {
|
||||||
|
self.gateway_client.private_ips.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||||
|
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 {
|
fn private_ips(&self) -> IpPair {
|
||||||
self.gateway_client.private_ips
|
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 {
|
pub enum AuthenticatorRequest {
|
||||||
Initial {
|
Initial {
|
||||||
msg: Box<dyn InitMessage + Send + Sync + 'static>,
|
msg: Box<dyn InitMessage + Send + Sync + 'static>,
|
||||||
protocol: Protocol,
|
protocol: Protocol,
|
||||||
reply_to: Recipient,
|
reply_to: Option<Recipient>,
|
||||||
request_id: u64,
|
request_id: u64,
|
||||||
},
|
},
|
||||||
Final {
|
Final {
|
||||||
msg: Box<dyn FinalMessage + Send + Sync + 'static>,
|
msg: Box<dyn FinalMessage + Send + Sync + 'static>,
|
||||||
protocol: Protocol,
|
protocol: Protocol,
|
||||||
reply_to: Recipient,
|
reply_to: Option<Recipient>,
|
||||||
request_id: u64,
|
request_id: u64,
|
||||||
},
|
},
|
||||||
QueryBandwidth {
|
QueryBandwidth {
|
||||||
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
|
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
|
||||||
protocol: Protocol,
|
protocol: Protocol,
|
||||||
reply_to: Recipient,
|
reply_to: Option<Recipient>,
|
||||||
request_id: u64,
|
request_id: u64,
|
||||||
},
|
},
|
||||||
TopUpBandwidth {
|
TopUpBandwidth {
|
||||||
msg: Box<dyn TopUpMessage + Send + Sync + 'static>,
|
msg: Box<dyn TopUpMessage + Send + Sync + 'static>,
|
||||||
protocol: Protocol,
|
protocol: Protocol,
|
||||||
reply_to: Recipient,
|
reply_to: Option<Recipient>,
|
||||||
request_id: u64,
|
request_id: u64,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -218,7 +255,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
|
|||||||
version: value.version,
|
version: value.version,
|
||||||
service_provider_type: ServiceProviderType::Authenticator,
|
service_provider_type: ServiceProviderType::Authenticator,
|
||||||
},
|
},
|
||||||
reply_to: value.reply_to,
|
reply_to: Some(value.reply_to),
|
||||||
request_id: value.request_id,
|
request_id: value.request_id,
|
||||||
},
|
},
|
||||||
v1::request::AuthenticatorRequestData::Final(gateway_client) => Self::Final {
|
v1::request::AuthenticatorRequestData::Final(gateway_client) => Self::Final {
|
||||||
@@ -227,7 +264,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
|
|||||||
version: value.version,
|
version: value.version,
|
||||||
service_provider_type: ServiceProviderType::Authenticator,
|
service_provider_type: ServiceProviderType::Authenticator,
|
||||||
},
|
},
|
||||||
reply_to: value.reply_to,
|
reply_to: Some(value.reply_to),
|
||||||
request_id: value.request_id,
|
request_id: value.request_id,
|
||||||
},
|
},
|
||||||
v1::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
v1::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||||
@@ -237,7 +274,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
|
|||||||
version: value.version,
|
version: value.version,
|
||||||
service_provider_type: ServiceProviderType::Authenticator,
|
service_provider_type: ServiceProviderType::Authenticator,
|
||||||
},
|
},
|
||||||
reply_to: value.reply_to,
|
reply_to: Some(value.reply_to),
|
||||||
request_id: value.request_id,
|
request_id: value.request_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,20 +288,20 @@ impl From<v2::request::AuthenticatorRequest> for AuthenticatorRequest {
|
|||||||
v2::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
v2::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||||
msg: Box::new(init_message),
|
msg: Box::new(init_message),
|
||||||
protocol: value.protocol,
|
protocol: value.protocol,
|
||||||
reply_to: value.reply_to,
|
reply_to: Some(value.reply_to),
|
||||||
request_id: value.request_id,
|
request_id: value.request_id,
|
||||||
},
|
},
|
||||||
v2::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
v2::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||||
msg: final_message,
|
msg: final_message,
|
||||||
protocol: value.protocol,
|
protocol: value.protocol,
|
||||||
reply_to: value.reply_to,
|
reply_to: Some(value.reply_to),
|
||||||
request_id: value.request_id,
|
request_id: value.request_id,
|
||||||
},
|
},
|
||||||
v2::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
v2::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||||
Self::QueryBandwidth {
|
Self::QueryBandwidth {
|
||||||
msg: Box::new(peer_public_key),
|
msg: Box::new(peer_public_key),
|
||||||
protocol: value.protocol,
|
protocol: value.protocol,
|
||||||
reply_to: value.reply_to,
|
reply_to: Some(value.reply_to),
|
||||||
request_id: value.request_id,
|
request_id: value.request_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,20 +315,20 @@ impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
|
|||||||
v3::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
v3::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||||
msg: Box::new(init_message),
|
msg: Box::new(init_message),
|
||||||
protocol: value.protocol,
|
protocol: value.protocol,
|
||||||
reply_to: value.reply_to,
|
reply_to: Some(value.reply_to),
|
||||||
request_id: value.request_id,
|
request_id: value.request_id,
|
||||||
},
|
},
|
||||||
v3::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
v3::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||||
msg: final_message,
|
msg: final_message,
|
||||||
protocol: value.protocol,
|
protocol: value.protocol,
|
||||||
reply_to: value.reply_to,
|
reply_to: Some(value.reply_to),
|
||||||
request_id: value.request_id,
|
request_id: value.request_id,
|
||||||
},
|
},
|
||||||
v3::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
v3::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||||
Self::QueryBandwidth {
|
Self::QueryBandwidth {
|
||||||
msg: Box::new(peer_public_key),
|
msg: Box::new(peer_public_key),
|
||||||
protocol: value.protocol,
|
protocol: value.protocol,
|
||||||
reply_to: value.reply_to,
|
reply_to: Some(value.reply_to),
|
||||||
request_id: value.request_id,
|
request_id: value.request_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,7 +336,7 @@ impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
|
|||||||
Self::TopUpBandwidth {
|
Self::TopUpBandwidth {
|
||||||
msg: top_up_message,
|
msg: top_up_message,
|
||||||
protocol: value.protocol,
|
protocol: value.protocol,
|
||||||
reply_to: value.reply_to,
|
reply_to: Some(value.reply_to),
|
||||||
request_id: value.request_id,
|
request_id: value.request_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -313,20 +350,20 @@ impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
|
|||||||
v4::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
v4::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||||
msg: Box::new(init_message),
|
msg: Box::new(init_message),
|
||||||
protocol: value.protocol,
|
protocol: value.protocol,
|
||||||
reply_to: value.reply_to,
|
reply_to: Some(value.reply_to),
|
||||||
request_id: value.request_id,
|
request_id: value.request_id,
|
||||||
},
|
},
|
||||||
v4::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
v4::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||||
msg: final_message,
|
msg: final_message,
|
||||||
protocol: value.protocol,
|
protocol: value.protocol,
|
||||||
reply_to: value.reply_to,
|
reply_to: Some(value.reply_to),
|
||||||
request_id: value.request_id,
|
request_id: value.request_id,
|
||||||
},
|
},
|
||||||
v4::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
v4::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||||
Self::QueryBandwidth {
|
Self::QueryBandwidth {
|
||||||
msg: Box::new(peer_public_key),
|
msg: Box::new(peer_public_key),
|
||||||
protocol: value.protocol,
|
protocol: value.protocol,
|
||||||
reply_to: value.reply_to,
|
reply_to: Some(value.reply_to),
|
||||||
request_id: value.request_id,
|
request_id: value.request_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,7 +371,42 @@ impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
|
|||||||
Self::TopUpBandwidth {
|
Self::TopUpBandwidth {
|
||||||
msg: top_up_message,
|
msg: top_up_message,
|
||||||
protocol: value.protocol,
|
protocol: value.protocol,
|
||||||
reply_to: value.reply_to,
|
reply_to: Some(value.reply_to),
|
||||||
|
request_id: value.request_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<v5::request::AuthenticatorRequest> 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,
|
request_id: value.request_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,478 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||||
|
|
||||||
|
use crate::{v4, v5};
|
||||||
|
|
||||||
|
impl From<v4::request::AuthenticatorRequest> 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<v4::request::AuthenticatorRequestData> 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<v4::registration::InitMessage> for v5::registration::InitMessage {
|
||||||
|
fn from(init_msg: v4::registration::InitMessage) -> Self {
|
||||||
|
Self {
|
||||||
|
pub_key: init_msg.pub_key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<v4::registration::FinalMessage> 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<v4::registration::GatewayClient> 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<v5::registration::GatewayClient> 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<v4::registration::ClientMac> for v5::registration::ClientMac {
|
||||||
|
fn from(client_mac: v4::registration::ClientMac) -> Self {
|
||||||
|
Self::new((*client_mac).clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<v5::registration::ClientMac> for v4::registration::ClientMac {
|
||||||
|
fn from(client_mac: v5::registration::ClientMac) -> Self {
|
||||||
|
Self::new((*client_mac).clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Box<v4::topup::TopUpMessage>> for Box<v5::topup::TopUpMessage> {
|
||||||
|
fn from(top_up_message: Box<v4::topup::TopUpMessage>) -> Self {
|
||||||
|
Box::new(v5::topup::TopUpMessage {
|
||||||
|
pub_key: top_up_message.pub_key,
|
||||||
|
credential: top_up_message.credential,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<v4::response::AuthenticatorResponse> 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<v4::response::AuthenticatorResponseData> 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<v4::response::RegisteredResponse> for v5::response::RegisteredResponse {
|
||||||
|
fn from(value: v4::response::RegisteredResponse) -> Self {
|
||||||
|
Self {
|
||||||
|
request_id: value.request_id,
|
||||||
|
reply: value.reply.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<v4::response::PendingRegistrationResponse> for v5::response::PendingRegistrationResponse {
|
||||||
|
fn from(value: v4::response::PendingRegistrationResponse) -> Self {
|
||||||
|
Self {
|
||||||
|
request_id: value.request_id,
|
||||||
|
reply: value.reply.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<v4::registration::RegistrationData> 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<v5::registration::RegistrationData> 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<v4::response::RemainingBandwidthResponse> 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<v4::response::TopUpBandwidthResponse> for v5::response::TopUpBandwidthResponse {
|
||||||
|
fn from(value: v4::response::TopUpBandwidthResponse) -> Self {
|
||||||
|
Self {
|
||||||
|
request_id: value.request_id,
|
||||||
|
reply: value.reply.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<v4::registration::RegistredData> 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<v4::registration::RemainingBandwidthData> for v5::registration::RemainingBandwidthData {
|
||||||
|
fn from(value: v4::registration::RemainingBandwidthData) -> Self {
|
||||||
|
Self {
|
||||||
|
available_bandwidth: value.available_bandwidth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<v4::registration::IpPair> for v5::registration::IpPair {
|
||||||
|
fn from(value: v4::registration::IpPair) -> Self {
|
||||||
|
Self {
|
||||||
|
ipv4: value.ipv4,
|
||||||
|
ipv6: value.ipv6,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<v5::registration::IpPair> 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// 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;
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
// -2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// 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<PeerPublicKey, RegistrationData>;
|
||||||
|
pub type PrivateIPs = HashMap<IpPair, Taken>;
|
||||||
|
|
||||||
|
#[cfg(feature = "verify")]
|
||||||
|
pub type HmacSha256 = Hmac<Sha256>;
|
||||||
|
|
||||||
|
pub type Nonce = u64;
|
||||||
|
pub type Taken = Option<SystemTime>;
|
||||||
|
|
||||||
|
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<IpAddr> 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<CredentialSpendingData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<u8>);
|
||||||
|
|
||||||
|
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<u8>) -> Self {
|
||||||
|
ClientMac(mac)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for ClientMac {
|
||||||
|
type Target = Vec<u8>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ClientMac {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mac_bytes: Vec<u8> =
|
||||||
|
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<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
let encoded_key = general_purpose::STANDARD.encode(self.0.clone());
|
||||||
|
serializer.serialize_str(&encoded_key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for ClientMac {
|
||||||
|
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// 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<Self, bincode::Error> {
|
||||||
|
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<Vec<u8>, bincode::Error> {
|
||||||
|
use bincode::Options;
|
||||||
|
make_bincode_serializer().serialize(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub enum AuthenticatorRequestData {
|
||||||
|
Initial(InitMessage),
|
||||||
|
Final(Box<FinalMessage>),
|
||||||
|
QueryBandwidth(PeerPublicKey),
|
||||||
|
TopUpBandwidth(Box<TopUpMessage>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// 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<RemainingBandwidthData>,
|
||||||
|
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<Vec<u8>, bincode::Error> {
|
||||||
|
use bincode::Options;
|
||||||
|
make_bincode_serializer().serialize(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_reconstructed_message(
|
||||||
|
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||||
|
) -> Result<Self, bincode::Error> {
|
||||||
|
use bincode::Options;
|
||||||
|
make_bincode_serializer().deserialize(&message.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> Option<u64> {
|
||||||
|
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<RemainingBandwidthData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct TopUpBandwidthResponse {
|
||||||
|
pub request_id: u64,
|
||||||
|
pub reply: RemainingBandwidthData,
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// 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,
|
||||||
|
}
|
||||||
@@ -105,26 +105,24 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
|||||||
async fn get_aggregate_verification_key(
|
async fn get_aggregate_verification_key(
|
||||||
&self,
|
&self,
|
||||||
epoch_id: EpochId,
|
epoch_id: EpochId,
|
||||||
apis: &mut ApiClientsWrapper,
|
ecash_apis: &mut ApiClientsWrapper<'_, C>,
|
||||||
) -> Result<VerificationKeyAuth, BandwidthControllerError>
|
) -> Result<VerificationKeyAuth, BandwidthControllerError>
|
||||||
where
|
where
|
||||||
C: DkgQueryClient + Sync + Send,
|
C: DkgQueryClient + Sync + Send,
|
||||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
<St as Storage>::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
|
get_aggregate_verification_key(&self.storage, epoch_id, ecash_apis).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_coin_index_signatures(
|
async fn get_coin_index_signatures(
|
||||||
&self,
|
&self,
|
||||||
epoch_id: EpochId,
|
epoch_id: EpochId,
|
||||||
apis: &mut ApiClientsWrapper,
|
ecash_apis: &mut ApiClientsWrapper<'_, C>,
|
||||||
) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError>
|
) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError>
|
||||||
where
|
where
|
||||||
C: DkgQueryClient + Sync + Send,
|
C: DkgQueryClient + Sync + Send,
|
||||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
<St as Storage>::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
|
get_coin_index_signatures(&self.storage, epoch_id, ecash_apis).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,13 +130,12 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
|||||||
&self,
|
&self,
|
||||||
epoch_id: EpochId,
|
epoch_id: EpochId,
|
||||||
expiration_date: Date,
|
expiration_date: Date,
|
||||||
apis: &mut ApiClientsWrapper,
|
ecash_apis: &mut ApiClientsWrapper<'_, C>,
|
||||||
) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError>
|
) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError>
|
||||||
where
|
where
|
||||||
C: DkgQueryClient + Sync + Send,
|
C: DkgQueryClient + Sync + Send,
|
||||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
<St as Storage>::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
|
get_expiration_date_signatures(&self.storage, epoch_id, expiration_date, ecash_apis).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +151,7 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
|||||||
{
|
{
|
||||||
let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
|
let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
|
||||||
let expiration_date = retrieved_ticketbook.ticketbook.expiration_date();
|
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
|
let verification_key = self
|
||||||
.get_aggregate_verification_key(epoch_id, &mut api_clients)
|
.get_aggregate_verification_key(epoch_id, &mut api_clients)
|
||||||
|
|||||||
@@ -21,30 +21,67 @@ use rand::thread_rng;
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
// it really doesn't need the RwLock because it's never moved across tasks,
|
pub(crate) trait EcashClientsProvider {
|
||||||
// but we need all the Send/Sync action
|
async fn try_get_ecash_clients(
|
||||||
#[derive(Default)]
|
|
||||||
pub(crate) struct ApiClientsWrapper(Option<Vec<EcashApiClient>>);
|
|
||||||
|
|
||||||
impl ApiClientsWrapper {
|
|
||||||
pub(crate) async fn get_or_init<C>(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
|
) -> Result<Vec<EcashApiClient>, BandwidthControllerError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EcashClientsProvider for Vec<EcashApiClient> {
|
||||||
|
async fn try_get_ecash_clients(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<Vec<EcashApiClient>, BandwidthControllerError> {
|
||||||
|
Ok(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> EcashClientsProvider for &mut ApiClientsWrapper<'_, C>
|
||||||
|
where
|
||||||
|
C: DkgQueryClient + Sync + Send,
|
||||||
|
{
|
||||||
|
async fn try_get_ecash_clients(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<Vec<EcashApiClient>, BandwidthControllerError> {
|
||||||
|
self.clients().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum ApiClientsWrapper<'a, C> {
|
||||||
|
Uninitialised {
|
||||||
|
query_client: &'a C,
|
||||||
epoch_id: EpochId,
|
epoch_id: EpochId,
|
||||||
dkg_client: &C,
|
},
|
||||||
) -> Result<Vec<EcashApiClient>, BandwidthControllerError>
|
Cached {
|
||||||
|
clients: Vec<EcashApiClient>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Vec<EcashApiClient>, BandwidthControllerError>
|
||||||
where
|
where
|
||||||
C: DkgQueryClient + Sync + Send,
|
C: DkgQueryClient + Sync + Send,
|
||||||
{
|
{
|
||||||
if let Some(cached) = &self.0 {
|
match self {
|
||||||
return Ok(cached.clone());
|
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<St>(
|
pub(crate) async fn get_aggregate_verification_key<St>(
|
||||||
storage: &St,
|
storage: &St,
|
||||||
epoch_id: EpochId,
|
epoch_id: EpochId,
|
||||||
ecash_apis: Vec<EcashApiClient>,
|
mut ecash_apis: impl EcashClientsProvider,
|
||||||
) -> Result<VerificationKeyAuth, BandwidthControllerError>
|
) -> Result<VerificationKeyAuth, BandwidthControllerError>
|
||||||
where
|
where
|
||||||
St: Storage,
|
St: Storage,
|
||||||
@@ -90,6 +127,8 @@ where
|
|||||||
return Ok(stored);
|
return Ok(stored);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let ecash_apis = ecash_apis.try_get_ecash_clients().await?;
|
||||||
|
|
||||||
let master_vk = query_random_apis_until_success(
|
let master_vk = query_random_apis_until_success(
|
||||||
ecash_apis,
|
ecash_apis,
|
||||||
|api| async move { api.api_client.master_verification_key(Some(epoch_id)).await },
|
|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<St>(
|
pub(crate) async fn get_coin_index_signatures<St>(
|
||||||
storage: &St,
|
storage: &St,
|
||||||
epoch_id: EpochId,
|
epoch_id: EpochId,
|
||||||
ecash_apis: Vec<EcashApiClient>,
|
mut ecash_apis: impl EcashClientsProvider,
|
||||||
) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError>
|
) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError>
|
||||||
where
|
where
|
||||||
St: Storage,
|
St: Storage,
|
||||||
@@ -129,6 +168,8 @@ where
|
|||||||
return Ok(stored);
|
return Ok(stored);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let ecash_apis = ecash_apis.try_get_ecash_clients().await?;
|
||||||
|
|
||||||
let index_sigs = query_random_apis_until_success(
|
let index_sigs = query_random_apis_until_success(
|
||||||
ecash_apis,
|
ecash_apis,
|
||||||
|api| async move {
|
|api| async move {
|
||||||
@@ -159,7 +200,7 @@ pub(crate) async fn get_expiration_date_signatures<St>(
|
|||||||
storage: &St,
|
storage: &St,
|
||||||
epoch_id: EpochId,
|
epoch_id: EpochId,
|
||||||
expiration_date: Date,
|
expiration_date: Date,
|
||||||
ecash_apis: Vec<EcashApiClient>,
|
mut ecash_apis: impl EcashClientsProvider,
|
||||||
) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError>
|
) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError>
|
||||||
where
|
where
|
||||||
St: Storage,
|
St: Storage,
|
||||||
@@ -173,6 +214,8 @@ where
|
|||||||
return Ok(stored);
|
return Ok(stored);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let ecash_apis = ecash_apis.try_get_ecash_clients().await?;
|
||||||
|
|
||||||
let expiration_sigs = query_random_apis_until_success(
|
let expiration_sigs = query_random_apis_until_success(
|
||||||
ecash_apis,
|
ecash_apis,
|
||||||
|api| async move {
|
|api| async move {
|
||||||
|
|||||||
@@ -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
|
// clients/client-core/src/client/replies/reply_storage/surb_storage.rs
|
||||||
const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
|
const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
|
||||||
const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200;
|
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
|
// define how much to request at once
|
||||||
// clients/client-core/src/client/replies/reply_controller.rs
|
// clients/client-core/src/client/replies/reply_controller.rs
|
||||||
const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10;
|
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;
|
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.
|
/// 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,
|
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.
|
/// Defines the minimum number of reply surbs the client would request.
|
||||||
pub minimum_reply_surb_request_size: u32,
|
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
|
/// Specifies the number of mixnet hops the packet should go through. If not specified, then
|
||||||
/// the default value is used.
|
/// the default value is used.
|
||||||
pub surb_mix_hops: Option<u8>,
|
pub surb_mix_hops: Option<u8>,
|
||||||
|
|
||||||
|
/// Specifies if we should reset all the sender tags on startup
|
||||||
|
pub fresh_sender_tags: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ReplySurbs {
|
impl Default for ReplySurbs {
|
||||||
@@ -660,6 +668,7 @@ impl Default for ReplySurbs {
|
|||||||
ReplySurbs {
|
ReplySurbs {
|
||||||
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
||||||
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_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,
|
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
|
||||||
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_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,
|
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_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
|
||||||
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
|
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
|
||||||
surb_mix_hops: None,
|
surb_mix_hops: None,
|
||||||
|
fresh_sender_tags: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ impl From<ConfigV5> for Config {
|
|||||||
maximum_reply_surb_age: value.debug.reply_surbs.maximum_reply_surb_age,
|
maximum_reply_surb_age: value.debug.reply_surbs.maximum_reply_surb_age,
|
||||||
maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age,
|
maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age,
|
||||||
surb_mix_hops: value.debug.reply_surbs.surb_mix_hops,
|
surb_mix_hops: value.debug.reply_surbs.surb_mix_hops,
|
||||||
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
|
|||||||
let db_path = db_path.as_ref();
|
let db_path = db_path.as_ref();
|
||||||
if db_path.exists() {
|
if db_path.exists() {
|
||||||
info!("loading existing surb database");
|
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),
|
Ok(backend) => Ok(backend),
|
||||||
Err(err) => {
|
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");
|
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");
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ pub enum InputMessage {
|
|||||||
recipient: Recipient,
|
recipient: Recipient,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
lane: TransmissionLane,
|
lane: TransmissionLane,
|
||||||
|
max_retransmissions: Option<u32>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Creates a message used for a duplex anonymous communication where the recipient
|
/// Creates a message used for a duplex anonymous communication where the recipient
|
||||||
@@ -43,6 +44,7 @@ pub enum InputMessage {
|
|||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
reply_surbs: u32,
|
reply_surbs: u32,
|
||||||
lane: TransmissionLane,
|
lane: TransmissionLane,
|
||||||
|
max_retransmissions: Option<u32>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Attempt to use our internally received and stored `ReplySurb` to send the message back
|
/// Attempt to use our internally received and stored `ReplySurb` to send the message back
|
||||||
@@ -53,6 +55,7 @@ pub enum InputMessage {
|
|||||||
recipient_tag: AnonymousSenderTag,
|
recipient_tag: AnonymousSenderTag,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
lane: TransmissionLane,
|
lane: TransmissionLane,
|
||||||
|
max_retransmissions: Option<u32>,
|
||||||
},
|
},
|
||||||
|
|
||||||
MessageWrapper {
|
MessageWrapper {
|
||||||
@@ -92,6 +95,7 @@ impl InputMessage {
|
|||||||
recipient,
|
recipient,
|
||||||
data,
|
data,
|
||||||
lane,
|
lane,
|
||||||
|
max_retransmissions: None,
|
||||||
};
|
};
|
||||||
if let Some(packet_type) = packet_type {
|
if let Some(packet_type) = packet_type {
|
||||||
InputMessage::new_wrapper(message, packet_type)
|
InputMessage::new_wrapper(message, packet_type)
|
||||||
@@ -112,28 +116,7 @@ impl InputMessage {
|
|||||||
data,
|
data,
|
||||||
reply_surbs,
|
reply_surbs,
|
||||||
lane,
|
lane,
|
||||||
};
|
max_retransmissions: None,
|
||||||
if let Some(packet_type) = packet_type {
|
|
||||||
InputMessage::new_wrapper(message, packet_type)
|
|
||||||
} else {
|
|
||||||
message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IMHO `new_anonymous` should take `mix_hops: Option<u8>` 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<u8>,
|
|
||||||
reply_surbs: u32,
|
|
||||||
lane: TransmissionLane,
|
|
||||||
packet_type: Option<PacketType>,
|
|
||||||
) -> Self {
|
|
||||||
let message = InputMessage::Anonymous {
|
|
||||||
recipient,
|
|
||||||
data,
|
|
||||||
reply_surbs,
|
|
||||||
lane,
|
|
||||||
};
|
};
|
||||||
if let Some(packet_type) = packet_type {
|
if let Some(packet_type) = packet_type {
|
||||||
InputMessage::new_wrapper(message, packet_type)
|
InputMessage::new_wrapper(message, packet_type)
|
||||||
@@ -152,6 +135,7 @@ impl InputMessage {
|
|||||||
recipient_tag,
|
recipient_tag,
|
||||||
data,
|
data,
|
||||||
lane,
|
lane,
|
||||||
|
max_retransmissions: None,
|
||||||
};
|
};
|
||||||
if let Some(packet_type) = packet_type {
|
if let Some(packet_type) = packet_type {
|
||||||
InputMessage::new_wrapper(message, packet_type)
|
InputMessage::new_wrapper(message, packet_type)
|
||||||
@@ -169,4 +153,34 @@ impl InputMessage {
|
|||||||
InputMessage::MessageWrapper { message, .. } => message.lane(),
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ impl MixTrafficController {
|
|||||||
let (message_sender, message_receiver) =
|
let (message_sender, message_receiver) =
|
||||||
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
|
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 {
|
MixTrafficController {
|
||||||
@@ -77,7 +77,7 @@ impl MixTrafficController {
|
|||||||
) {
|
) {
|
||||||
let (message_sender, message_receiver) =
|
let (message_sender, message_receiver) =
|
||||||
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
|
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 {
|
MixTrafficController {
|
||||||
gateway_transceiver,
|
gateway_transceiver,
|
||||||
|
|||||||
-3
@@ -222,9 +222,6 @@ impl ActionController {
|
|||||||
|
|
||||||
// note: when the entry expires it's automatically removed from pending_acks_timers
|
// note: when the entry expires it's automatically removed from pending_acks_timers
|
||||||
fn handle_expired_ack_timer(&mut self, expired_ack: Expired<FragmentIdentifier>) {
|
fn handle_expired_ack_timer(&mut self, expired_ack: Expired<FragmentIdentifier>) {
|
||||||
// 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();
|
let frag_id = expired_ack.into_inner();
|
||||||
|
|
||||||
trace!("{frag_id} has expired");
|
trace!("{frag_id} has expired");
|
||||||
|
|||||||
+59
-15
@@ -65,11 +65,12 @@ where
|
|||||||
recipient_tag: AnonymousSenderTag,
|
recipient_tag: AnonymousSenderTag,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
lane: TransmissionLane,
|
lane: TransmissionLane,
|
||||||
|
max_retransmissions: Option<u32>,
|
||||||
) {
|
) {
|
||||||
// offload reply handling to the dedicated task
|
// offload reply handling to the dedicated task
|
||||||
if let Err(err) = self
|
if let Err(err) =
|
||||||
.reply_controller_sender
|
self.reply_controller_sender
|
||||||
.send_reply(recipient_tag, data, lane)
|
.send_reply(recipient_tag, data, lane, max_retransmissions)
|
||||||
{
|
{
|
||||||
if !self.task_client.is_shutdown_poll() {
|
if !self.task_client.is_shutdown_poll() {
|
||||||
error!("failed to send a reply - {err}");
|
error!("failed to send a reply - {err}");
|
||||||
@@ -83,10 +84,11 @@ where
|
|||||||
content: Vec<u8>,
|
content: Vec<u8>,
|
||||||
lane: TransmissionLane,
|
lane: TransmissionLane,
|
||||||
packet_type: PacketType,
|
packet_type: PacketType,
|
||||||
|
max_retransmissions: Option<u32>,
|
||||||
) {
|
) {
|
||||||
if let Err(err) = self
|
if let Err(err) = self
|
||||||
.message_handler
|
.message_handler
|
||||||
.try_send_plain_message(recipient, content, lane, packet_type)
|
.try_send_plain_message(recipient, content, lane, packet_type, max_retransmissions)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
warn!("failed to send a plain message - {err}")
|
warn!("failed to send a plain message - {err}")
|
||||||
@@ -100,10 +102,18 @@ where
|
|||||||
reply_surbs: u32,
|
reply_surbs: u32,
|
||||||
lane: TransmissionLane,
|
lane: TransmissionLane,
|
||||||
packet_type: PacketType,
|
packet_type: PacketType,
|
||||||
|
max_retransmissions: Option<u32>,
|
||||||
) {
|
) {
|
||||||
if let Err(err) = self
|
if let Err(err) = self
|
||||||
.message_handler
|
.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
|
.await
|
||||||
{
|
{
|
||||||
warn!("failed to send a repliable message - {err}")
|
warn!("failed to send a repliable message - {err}")
|
||||||
@@ -116,25 +126,42 @@ where
|
|||||||
recipient,
|
recipient,
|
||||||
data,
|
data,
|
||||||
lane,
|
lane,
|
||||||
|
max_retransmissions,
|
||||||
} => {
|
} => {
|
||||||
self.handle_plain_message(recipient, data, lane, PacketType::Mix)
|
self.handle_plain_message(
|
||||||
.await
|
recipient,
|
||||||
|
data,
|
||||||
|
lane,
|
||||||
|
PacketType::Mix,
|
||||||
|
max_retransmissions,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
InputMessage::Anonymous {
|
InputMessage::Anonymous {
|
||||||
recipient,
|
recipient,
|
||||||
data,
|
data,
|
||||||
reply_surbs,
|
reply_surbs,
|
||||||
lane,
|
lane,
|
||||||
|
max_retransmissions,
|
||||||
} => {
|
} => {
|
||||||
self.handle_repliable_message(recipient, data, reply_surbs, lane, PacketType::Mix)
|
self.handle_repliable_message(
|
||||||
.await
|
recipient,
|
||||||
|
data,
|
||||||
|
reply_surbs,
|
||||||
|
lane,
|
||||||
|
PacketType::Mix,
|
||||||
|
max_retransmissions,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
InputMessage::Reply {
|
InputMessage::Reply {
|
||||||
recipient_tag,
|
recipient_tag,
|
||||||
data,
|
data,
|
||||||
lane,
|
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::Premade { msgs, lane } => self.handle_premade_packets(msgs, lane).await,
|
||||||
InputMessage::MessageWrapper {
|
InputMessage::MessageWrapper {
|
||||||
@@ -145,25 +172,42 @@ where
|
|||||||
recipient,
|
recipient,
|
||||||
data,
|
data,
|
||||||
lane,
|
lane,
|
||||||
|
max_retransmissions,
|
||||||
} => {
|
} => {
|
||||||
self.handle_plain_message(recipient, data, lane, packet_type)
|
self.handle_plain_message(
|
||||||
.await
|
recipient,
|
||||||
|
data,
|
||||||
|
lane,
|
||||||
|
packet_type,
|
||||||
|
max_retransmissions,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
InputMessage::Anonymous {
|
InputMessage::Anonymous {
|
||||||
recipient,
|
recipient,
|
||||||
data,
|
data,
|
||||||
reply_surbs,
|
reply_surbs,
|
||||||
lane,
|
lane,
|
||||||
|
max_retransmissions,
|
||||||
} => {
|
} => {
|
||||||
self.handle_repliable_message(recipient, data, reply_surbs, lane, packet_type)
|
self.handle_repliable_message(
|
||||||
.await
|
recipient,
|
||||||
|
data,
|
||||||
|
reply_surbs,
|
||||||
|
lane,
|
||||||
|
packet_type,
|
||||||
|
max_retransmissions,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
InputMessage::Reply {
|
InputMessage::Reply {
|
||||||
recipient_tag,
|
recipient_tag,
|
||||||
data,
|
data,
|
||||||
lane,
|
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 } => {
|
InputMessage::Premade { msgs, lane } => {
|
||||||
self.handle_premade_packets(msgs, lane).await
|
self.handle_premade_packets(msgs, lane).await
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ pub struct PendingAcknowledgement {
|
|||||||
delay: SphinxDelay,
|
delay: SphinxDelay,
|
||||||
destination: PacketDestination,
|
destination: PacketDestination,
|
||||||
retransmissions: u32,
|
retransmissions: u32,
|
||||||
|
max_retransmissions: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PendingAcknowledgement {
|
impl PendingAcknowledgement {
|
||||||
@@ -80,12 +81,14 @@ impl PendingAcknowledgement {
|
|||||||
message_chunk: Fragment,
|
message_chunk: Fragment,
|
||||||
delay: SphinxDelay,
|
delay: SphinxDelay,
|
||||||
recipient: Recipient,
|
recipient: Recipient,
|
||||||
|
max_retransmissions: Option<u32>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
PendingAcknowledgement {
|
PendingAcknowledgement {
|
||||||
message_chunk,
|
message_chunk,
|
||||||
delay,
|
delay,
|
||||||
destination: PacketDestination::KnownRecipient(recipient.into()),
|
destination: PacketDestination::KnownRecipient(recipient.into()),
|
||||||
retransmissions: 0,
|
retransmissions: 0,
|
||||||
|
max_retransmissions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +97,7 @@ impl PendingAcknowledgement {
|
|||||||
delay: SphinxDelay,
|
delay: SphinxDelay,
|
||||||
recipient_tag: AnonymousSenderTag,
|
recipient_tag: AnonymousSenderTag,
|
||||||
extra_surb_request: bool,
|
extra_surb_request: bool,
|
||||||
|
max_retransmissions: Option<u32>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
PendingAcknowledgement {
|
PendingAcknowledgement {
|
||||||
message_chunk,
|
message_chunk,
|
||||||
@@ -103,6 +107,7 @@ impl PendingAcknowledgement {
|
|||||||
extra_surb_request,
|
extra_surb_request,
|
||||||
},
|
},
|
||||||
retransmissions: 0,
|
retransmissions: 0,
|
||||||
|
max_retransmissions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +123,18 @@ impl PendingAcknowledgement {
|
|||||||
self.delay = new_delay;
|
self.delay = new_delay;
|
||||||
self.retransmissions += 1;
|
self.retransmissions += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn reached_max_retransmissions(
|
||||||
|
&self,
|
||||||
|
global_max_retransmissions: Option<u32>,
|
||||||
|
) -> 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
|
/// AcknowledgementControllerConnectors represents set of channels for communication with
|
||||||
|
|||||||
+8
-10
@@ -79,17 +79,15 @@ where
|
|||||||
|
|
||||||
let frag_id = timed_out_ack.message_chunk.fragment_identifier();
|
let frag_id = timed_out_ack.message_chunk.fragment_identifier();
|
||||||
|
|
||||||
if let Some(limit) = self.maximum_retransmissions {
|
if timed_out_ack.reached_max_retransmissions(self.maximum_retransmissions) {
|
||||||
if timed_out_ack.retransmissions >= limit {
|
debug!("reached maximum number of allowed retransmissions for the packet");
|
||||||
warn!("reached maximum number of allowed retransmissions for the packet");
|
if let Err(err) = self
|
||||||
if let Err(err) = self
|
.action_sender
|
||||||
.action_sender
|
.unbounded_send(Action::new_remove(frag_id))
|
||||||
.unbounded_send(Action::new_remove(frag_id))
|
{
|
||||||
{
|
error!("Failed to send remove action to the controller: {err}");
|
||||||
error!("Failed to send remove action to the controller: {err}");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let maybe_prepared_fragment = match &timed_out_ack.destination {
|
let maybe_prepared_fragment = match &timed_out_ack.destination {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::client::real_messages_control::real_traffic_stream::{
|
|||||||
BatchRealMessageSender, RealMessage,
|
BatchRealMessageSender, RealMessage,
|
||||||
};
|
};
|
||||||
use crate::client::real_messages_control::{AckActionSender, Action};
|
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::replies::reply_storage::{ReceivedReplySurbsMap, SentReplyKeys, UsedSenderTags};
|
||||||
use crate::client::topology_control::{TopologyAccessor, TopologyReadPermit};
|
use crate::client::topology_control::{TopologyAccessor, TopologyReadPermit};
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
@@ -33,10 +34,12 @@ pub enum PreparationError {
|
|||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
NymTopologyError(#[from] NymTopologyError),
|
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 },
|
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 },
|
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)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct MessageHandler<R> {
|
pub(crate) struct MessageHandler<R> {
|
||||||
config: Config,
|
config: Config,
|
||||||
@@ -196,10 +205,10 @@ where
|
|||||||
trace!("we already had sender tag for {recipient}");
|
trace!("we already had sender tag for {recipient}");
|
||||||
existing
|
existing
|
||||||
} else {
|
} else {
|
||||||
info!("creating new sender tag for {recipient}");
|
debug!("creating new sender tag for {recipient}");
|
||||||
let new_tag = AnonymousSenderTag::new_random(&mut self.rng);
|
let new_tag = AnonymousSenderTag::new_random(&mut self.rng);
|
||||||
self.tag_storage.insert_new(recipient, new_tag);
|
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
|
new_tag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,8 +301,14 @@ where
|
|||||||
Some(chunk.fragment_identifier()),
|
Some(chunk.fragment_identifier()),
|
||||||
);
|
);
|
||||||
let delay = prepared_fragment.total_delay;
|
let delay = prepared_fragment.total_delay;
|
||||||
let pending_ack =
|
let max_retransmissions = None;
|
||||||
PendingAcknowledgement::new_anonymous(chunk, delay, target, is_extra_surb_request);
|
let pending_ack = PendingAcknowledgement::new_anonymous(
|
||||||
|
chunk,
|
||||||
|
delay,
|
||||||
|
target,
|
||||||
|
is_extra_surb_request,
|
||||||
|
max_retransmissions,
|
||||||
|
);
|
||||||
|
|
||||||
let lane = if is_extra_surb_request {
|
let lane = if is_extra_surb_request {
|
||||||
TransmissionLane::ReplySurbRequest
|
TransmissionLane::ReplySurbRequest
|
||||||
@@ -348,7 +363,7 @@ where
|
|||||||
pub(crate) async fn try_send_reply_chunks_on_lane(
|
pub(crate) async fn try_send_reply_chunks_on_lane(
|
||||||
&mut self,
|
&mut self,
|
||||||
target: AnonymousSenderTag,
|
target: AnonymousSenderTag,
|
||||||
fragments: Vec<Fragment>,
|
fragments: Vec<FragmentWithMaxRetransmissions>,
|
||||||
reply_surbs: Vec<ReplySurb>,
|
reply_surbs: Vec<ReplySurb>,
|
||||||
lane: TransmissionLane,
|
lane: TransmissionLane,
|
||||||
) -> Result<(), SurbWrappedPreparationError> {
|
) -> Result<(), SurbWrappedPreparationError> {
|
||||||
@@ -365,12 +380,12 @@ where
|
|||||||
pub(crate) async fn try_send_reply_chunks(
|
pub(crate) async fn try_send_reply_chunks(
|
||||||
&mut self,
|
&mut self,
|
||||||
target: AnonymousSenderTag,
|
target: AnonymousSenderTag,
|
||||||
fragments: Vec<(TransmissionLane, Fragment)>,
|
fragments: Vec<(TransmissionLane, FragmentWithMaxRetransmissions)>,
|
||||||
reply_surbs: Vec<ReplySurb>,
|
reply_surbs: Vec<ReplySurb>,
|
||||||
) -> Result<(), SurbWrappedPreparationError> {
|
) -> Result<(), SurbWrappedPreparationError> {
|
||||||
let prepared_fragments = self
|
let prepared_fragments = self
|
||||||
.prepare_reply_chunks_for_sending(
|
.prepare_reply_chunks_for_sending(
|
||||||
fragments.iter().map(|(_, f)| f.clone()).collect(),
|
fragments.iter().map(|(_, f)| f.fragment.clone()).collect(),
|
||||||
reply_surbs,
|
reply_surbs,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -380,12 +395,21 @@ where
|
|||||||
|
|
||||||
for (raw, prepared) in fragments.into_iter().zip(prepared_fragments.into_iter()) {
|
for (raw, prepared) in fragments.into_iter().zip(prepared_fragments.into_iter()) {
|
||||||
let lane = raw.0;
|
let lane = raw.0;
|
||||||
let fragment = raw.1;
|
let FragmentWithMaxRetransmissions {
|
||||||
|
fragment,
|
||||||
|
max_retransmissions,
|
||||||
|
} = raw.1;
|
||||||
|
|
||||||
let real_message =
|
let real_message =
|
||||||
RealMessage::new(prepared.mix_packet, Some(prepared.fragment_identifier));
|
RealMessage::new(prepared.mix_packet, Some(prepared.fragment_identifier));
|
||||||
let delay = prepared.total_delay;
|
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();
|
let entry = to_forward.entry(lane).or_default();
|
||||||
entry.push(real_message);
|
entry.push(real_message);
|
||||||
@@ -414,10 +438,17 @@ where
|
|||||||
message: Vec<u8>,
|
message: Vec<u8>,
|
||||||
lane: TransmissionLane,
|
lane: TransmissionLane,
|
||||||
packet_type: PacketType,
|
packet_type: PacketType,
|
||||||
|
max_retransmissions: Option<u32>,
|
||||||
) -> Result<(), PreparationError> {
|
) -> Result<(), PreparationError> {
|
||||||
let message = NymMessage::new_plain(message);
|
let message = NymMessage::new_plain(message);
|
||||||
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
|
self.try_split_and_send_non_reply_message(
|
||||||
.await
|
message,
|
||||||
|
recipient,
|
||||||
|
lane,
|
||||||
|
packet_type,
|
||||||
|
max_retransmissions,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn try_split_and_send_non_reply_message(
|
pub(crate) async fn try_split_and_send_non_reply_message(
|
||||||
@@ -426,6 +457,7 @@ where
|
|||||||
recipient: Recipient,
|
recipient: Recipient,
|
||||||
lane: TransmissionLane,
|
lane: TransmissionLane,
|
||||||
packet_type: PacketType,
|
packet_type: PacketType,
|
||||||
|
max_retransmissions: Option<u32>,
|
||||||
) -> Result<(), PreparationError> {
|
) -> Result<(), PreparationError> {
|
||||||
debug!("Sending non-reply message with packet type {packet_type}");
|
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
|
// 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()),
|
Some(fragment.fragment_identifier()),
|
||||||
);
|
);
|
||||||
let delay = prepared_fragment.total_delay;
|
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);
|
real_messages.push(real_message);
|
||||||
pending_acks.push(pending_ack);
|
pending_acks.push(pending_ack);
|
||||||
@@ -493,11 +526,15 @@ where
|
|||||||
reply_surbs,
|
reply_surbs,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// When sending SURBs we want to retransmit
|
||||||
|
let max_retransmissions = None;
|
||||||
|
|
||||||
self.try_split_and_send_non_reply_message(
|
self.try_split_and_send_non_reply_message(
|
||||||
message,
|
message,
|
||||||
recipient,
|
recipient,
|
||||||
TransmissionLane::AdditionalReplySurbs,
|
TransmissionLane::AdditionalReplySurbs,
|
||||||
packet_type,
|
packet_type,
|
||||||
|
max_retransmissions,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -514,6 +551,7 @@ where
|
|||||||
num_reply_surbs: u32,
|
num_reply_surbs: u32,
|
||||||
lane: TransmissionLane,
|
lane: TransmissionLane,
|
||||||
packet_type: PacketType,
|
packet_type: PacketType,
|
||||||
|
max_retransmissions: Option<u32>,
|
||||||
) -> Result<(), SurbWrappedPreparationError> {
|
) -> Result<(), SurbWrappedPreparationError> {
|
||||||
debug!("Sending message with reply SURBs with packet type {packet_type}");
|
debug!("Sending message with reply SURBs with packet type {packet_type}");
|
||||||
let sender_tag = self.get_or_create_sender_tag(&recipient);
|
let sender_tag = self.get_or_create_sender_tag(&recipient);
|
||||||
@@ -524,8 +562,14 @@ where
|
|||||||
let message =
|
let message =
|
||||||
NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs));
|
NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs));
|
||||||
|
|
||||||
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
|
self.try_split_and_send_non_reply_message(
|
||||||
.await?;
|
message,
|
||||||
|
recipient,
|
||||||
|
lane,
|
||||||
|
packet_type,
|
||||||
|
max_retransmissions,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
log::trace!("storing {} reply keys", reply_keys.len());
|
log::trace!("storing {} reply keys", reply_keys.len());
|
||||||
self.reply_key_storage.insert_multiple(reply_keys);
|
self.reply_key_storage.insert_multiple(reply_keys);
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ impl RealMessagesController<OsRng> {
|
|||||||
let rng = OsRng;
|
let rng = OsRng;
|
||||||
|
|
||||||
// create channels for inter-task communication
|
// 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 (sent_notifier_tx, sent_notifier_rx) = mpsc::unbounded();
|
||||||
let (ack_action_tx, ack_action_rx) = mpsc::unbounded();
|
let (ack_action_tx, ack_action_rx) = mpsc::unbounded();
|
||||||
let ack_controller_connectors = AcknowledgementControllerConnectors::new(
|
let ack_controller_connectors = AcknowledgementControllerConnectors::new(
|
||||||
|
|||||||
@@ -517,17 +517,25 @@ where
|
|||||||
use crate::error::ClientCoreStatusMessage;
|
use crate::error::ClientCoreStatusMessage;
|
||||||
|
|
||||||
let packets = self.transmission_buffer.total_size();
|
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.lanes();
|
||||||
let lanes = self.transmission_buffer.num_lanes();
|
|
||||||
let mult = self.sending_delay_controller.current_multiplier();
|
let mult = self.sending_delay_controller.current_multiplier();
|
||||||
let delay = self.current_average_message_sending_delay().as_millis();
|
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::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
let status_str = if self.config.traffic.disable_main_poisson_packet_distribution {
|
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 {
|
} else {
|
||||||
format!(
|
format!("Packet backlog: {lane_status}, avg delay: {delay}ms ({mult})")
|
||||||
"Packet backlog: {backlog:.2} kiB ({packets}), {lanes} lanes, avg delay: {delay}ms ({mult})"
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if packets > 1000 {
|
if packets > 1000 {
|
||||||
log::warn!("{status_str}");
|
log::warn!("{status_str}");
|
||||||
} else if packets > 0 {
|
} else if packets > 0 {
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, C
|
|||||||
use nym_task::TaskClient;
|
use nym_task::TaskClient;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::Arc;
|
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"
|
// 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"
|
// 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<R: MessageReceiver> {
|
|||||||
recently_reconstructed: HashSet<i32>,
|
recently_reconstructed: HashSet<i32>,
|
||||||
|
|
||||||
stats_tx: ClientStatsSender,
|
stats_tx: ClientStatsSender,
|
||||||
|
|
||||||
|
// Periodically check for stale buffers to clean up
|
||||||
|
last_stale_check: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||||
@@ -96,9 +103,10 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
|||||||
}
|
}
|
||||||
None
|
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 {
|
Ok(reconstruction_result) => match reconstruction_result {
|
||||||
Some((reconstructed_message, used_sets)) => {
|
Some((reconstructed_message, used_sets)) => {
|
||||||
@@ -144,6 +152,16 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
|||||||
|
|
||||||
self.recover_from_fragment(fragment_data, raw_fragment_size)
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -172,6 +190,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
|||||||
message_sender: None,
|
message_sender: None,
|
||||||
recently_reconstructed: HashSet::new(),
|
recently_reconstructed: HashSet::new(),
|
||||||
stats_tx,
|
stats_tx,
|
||||||
|
last_stale_check: Instant::now(),
|
||||||
})),
|
})),
|
||||||
reply_key_storage,
|
reply_key_storage,
|
||||||
reply_controller_sender,
|
reply_controller_sender,
|
||||||
@@ -392,6 +411,11 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
drop(inner_guard);
|
||||||
|
|
||||||
if !completed_messages.is_empty() {
|
if !completed_messages.is_empty() {
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::client::real_messages_control::acknowledgement_control::PendingAcknowledgement;
|
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 crate::client::replies::reply_storage::CombinedReplyStorage;
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
@@ -10,7 +12,7 @@ use log::{debug, error, info, trace, warn};
|
|||||||
use nym_sphinx::addressing::clients::Recipient;
|
use nym_sphinx::addressing::clients::Recipient;
|
||||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||||
use nym_sphinx::anonymous_replies::ReplySurb;
|
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::connections::{ConnectionId, TransmissionLane};
|
||||||
use nym_task::TaskClient;
|
use nym_task::TaskClient;
|
||||||
use rand::{CryptoRng, Rng};
|
use rand::{CryptoRng, Rng};
|
||||||
@@ -49,6 +51,8 @@ impl Config {
|
|||||||
// - replies to "give additional surbs" requests
|
// - replies to "give additional surbs" requests
|
||||||
// - will reply to future heartbeats
|
// - will reply to future heartbeats
|
||||||
|
|
||||||
|
pub type MaxRetransmissions = Option<u32>;
|
||||||
|
|
||||||
// TODO: this should be split into ingress and egress controllers
|
// TODO: this should be split into ingress and egress controllers
|
||||||
// because currently its trying to perform two distinct jobs
|
// because currently its trying to perform two distinct jobs
|
||||||
pub struct ReplyController<R> {
|
pub struct ReplyController<R> {
|
||||||
@@ -59,7 +63,8 @@ pub struct ReplyController<R> {
|
|||||||
// of surbs required to send the message through
|
// of surbs required to send the message through
|
||||||
// expected_reliability: f32,
|
// expected_reliability: f32,
|
||||||
request_receiver: ReplyControllerReceiver,
|
request_receiver: ReplyControllerReceiver,
|
||||||
pending_replies: HashMap<AnonymousSenderTag, TransmissionBuffer<Fragment>>,
|
pending_replies:
|
||||||
|
HashMap<AnonymousSenderTag, TransmissionBuffer<FragmentWithMaxRetransmissions>>,
|
||||||
|
|
||||||
/// Retransmission packets that have already timed out and are waiting for additional reply SURBs
|
/// 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.
|
/// 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<I: IntoIterator<Item = Fragment>>(
|
fn insert_pending_replies<I: IntoIterator<Item = FragmentWithMaxRetransmissions>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
recipient: &AnonymousSenderTag,
|
recipient: &AnonymousSenderTag,
|
||||||
fragments: I,
|
fragments: I,
|
||||||
lane: TransmissionLane,
|
lane: TransmissionLane,
|
||||||
) {
|
) {
|
||||||
|
trace!("buffering pending replies for {recipient}");
|
||||||
self.pending_replies
|
self.pending_replies
|
||||||
.entry(*recipient)
|
.entry(*recipient)
|
||||||
.or_insert_with(TransmissionBuffer::new)
|
.or_insert_with(TransmissionBuffer::new)
|
||||||
@@ -111,8 +117,9 @@ where
|
|||||||
fn re_insert_pending_replies(
|
fn re_insert_pending_replies(
|
||||||
&mut self,
|
&mut self,
|
||||||
recipient: &AnonymousSenderTag,
|
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...
|
// the buffer should ALWAYS exist at this point, if it doesn't, it's a bug...
|
||||||
self.pending_replies
|
self.pending_replies
|
||||||
.entry(*recipient)
|
.entry(*recipient)
|
||||||
@@ -125,6 +132,7 @@ where
|
|||||||
recipient: &AnonymousSenderTag,
|
recipient: &AnonymousSenderTag,
|
||||||
data: Vec<Arc<PendingAcknowledgement>>,
|
data: Vec<Arc<PendingAcknowledgement>>,
|
||||||
) {
|
) {
|
||||||
|
trace!("re-inserting pending retransmissions for {recipient}");
|
||||||
// the underlying entry MUST exist as we've just got data from there
|
// the underlying entry MUST exist as we've just got data from there
|
||||||
let map_entry = self
|
let map_entry = self
|
||||||
.pending_retransmissions
|
.pending_retransmissions
|
||||||
@@ -142,7 +150,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn should_request_more_surbs(&self, target: &AnonymousSenderTag) -> bool {
|
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
|
let pending_queue_size = self
|
||||||
.pending_replies
|
.pending_replies
|
||||||
@@ -158,11 +166,6 @@ where
|
|||||||
|
|
||||||
let total_queue = pending_queue_size + retransmission_queue;
|
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
|
let available_surbs = self
|
||||||
.full_reply_storage
|
.full_reply_storage
|
||||||
.surbs_storage_ref()
|
.surbs_storage_ref()
|
||||||
@@ -179,11 +182,27 @@ where
|
|||||||
.full_reply_storage
|
.full_reply_storage
|
||||||
.surbs_storage_ref()
|
.surbs_storage_ref()
|
||||||
.max_surb_threshold();
|
.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
|
// Check if we have enough surbs to handle the total queue and maintain minimum thresholds
|
||||||
&& (pending_surbs + available_surbs) < (total_queue + min_surbs_threshold)
|
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(
|
async fn handle_send_reply(
|
||||||
@@ -191,6 +210,7 @@ where
|
|||||||
recipient_tag: AnonymousSenderTag,
|
recipient_tag: AnonymousSenderTag,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
lane: TransmissionLane,
|
lane: TransmissionLane,
|
||||||
|
max_retransmissions: Option<u32>,
|
||||||
) {
|
) {
|
||||||
if !self
|
if !self
|
||||||
.full_reply_storage
|
.full_reply_storage
|
||||||
@@ -228,7 +248,14 @@ where
|
|||||||
.get_reply_surbs(&recipient_tag, max_to_send);
|
.get_reply_surbs(&recipient_tag, max_to_send);
|
||||||
|
|
||||||
if let Some(reply_surbs) = surbs {
|
if let Some(reply_surbs) = surbs {
|
||||||
let to_send = fragments.drain(..max_to_send).collect::<Vec<_>>();
|
let to_send = fragments
|
||||||
|
.drain(..max_to_send)
|
||||||
|
.map(|f| FragmentWithMaxRetransmissions {
|
||||||
|
fragment: f,
|
||||||
|
max_retransmissions,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if let Err(err) = self
|
if let Err(err) = self
|
||||||
.message_handler
|
.message_handler
|
||||||
.try_send_reply_chunks_on_lane(
|
.try_send_reply_chunks_on_lane(
|
||||||
@@ -244,6 +271,10 @@ where
|
|||||||
&recipient_tag,
|
&recipient_tag,
|
||||||
);
|
);
|
||||||
warn!("failed to send reply to {recipient_tag}: {err}");
|
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);
|
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 there's leftover data we didn't send because we didn't have enough (or any) surbs - buffer it
|
||||||
if !fragments.is_empty() {
|
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);
|
self.insert_pending_replies(&recipient_tag, fragments, lane);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,6 +310,7 @@ where
|
|||||||
target: AnonymousSenderTag,
|
target: AnonymousSenderTag,
|
||||||
amount: u32,
|
amount: u32,
|
||||||
) -> Result<(), PreparationError> {
|
) -> Result<(), PreparationError> {
|
||||||
|
debug!("requesting {amount} additional reply surbs for {target}");
|
||||||
let reply_surb = self
|
let reply_surb = self
|
||||||
.full_reply_storage
|
.full_reply_storage
|
||||||
.surbs_storage_ref()
|
.surbs_storage_ref()
|
||||||
@@ -383,7 +429,7 @@ where
|
|||||||
&mut self,
|
&mut self,
|
||||||
from: &AnonymousSenderTag,
|
from: &AnonymousSenderTag,
|
||||||
amount: usize,
|
amount: usize,
|
||||||
) -> Option<Vec<(TransmissionLane, Fragment)>> {
|
) -> Option<Vec<(TransmissionLane, FragmentWithMaxRetransmissions)>> {
|
||||||
// if possible, pop all pending replies, if not, pop only entries for which we'd have a reply surb
|
// 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();
|
let total = self.pending_replies.get(from)?.total_size();
|
||||||
trace!("pending queue has {total} elements");
|
trace!("pending queue has {total} elements");
|
||||||
@@ -663,7 +709,11 @@ where
|
|||||||
recipient,
|
recipient,
|
||||||
message,
|
message,
|
||||||
lane,
|
lane,
|
||||||
} => self.handle_send_reply(recipient, message, lane).await,
|
max_retransmissions,
|
||||||
|
} => {
|
||||||
|
self.handle_send_reply(recipient, message, lane, max_retransmissions)
|
||||||
|
.await
|
||||||
|
}
|
||||||
ReplyControllerMessage::AdditionalSurbs {
|
ReplyControllerMessage::AdditionalSurbs {
|
||||||
sender_tag,
|
sender_tag,
|
||||||
reply_surbs,
|
reply_surbs,
|
||||||
@@ -686,7 +736,7 @@ where
|
|||||||
// it should take into consideration the average latency, sending rate and queue size.
|
// 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
|
// 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) {
|
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
|
let pending_queue_size = self
|
||||||
.pending_replies
|
.pending_replies
|
||||||
@@ -700,17 +750,18 @@ where
|
|||||||
.map(|pending_queue| pending_queue.len())
|
.map(|pending_queue| pending_queue.len())
|
||||||
.unwrap_or_default();
|
.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;
|
let total_queue = (pending_queue_size + retransmission_queue) as u32;
|
||||||
|
|
||||||
if total_queue == 0 {
|
// To proactively request additional surbs, we aim to have a buffer of extra surbs in our
|
||||||
trace!("the pending queues for {:?} are already empty", target);
|
// storage.
|
||||||
return;
|
let total_queue_with_buffer = total_queue + min_surbs_buffer;
|
||||||
}
|
|
||||||
|
|
||||||
let request_size = min(
|
let request_size = min(
|
||||||
self.config.reply_surbs.maximum_reply_surb_request_size,
|
self.config.reply_surbs.maximum_reply_surb_request_size,
|
||||||
max(
|
max(
|
||||||
total_queue,
|
total_queue_with_buffer,
|
||||||
self.config.reply_surbs.minimum_reply_surb_request_size,
|
self.config.reply_surbs.minimum_reply_surb_request_size,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -719,7 +770,7 @@ where
|
|||||||
.request_additional_reply_surbs(target, request_size)
|
.request_additional_reply_surbs(target, request_size)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
warn!("failed to request additional surbs... - {err}")
|
info!("{err}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,12 +66,14 @@ impl ReplyControllerSender {
|
|||||||
recipient: AnonymousSenderTag,
|
recipient: AnonymousSenderTag,
|
||||||
message: Vec<u8>,
|
message: Vec<u8>,
|
||||||
lane: TransmissionLane,
|
lane: TransmissionLane,
|
||||||
|
max_retransmissions: Option<u32>,
|
||||||
) -> Result<(), ReplyControllerSenderError> {
|
) -> Result<(), ReplyControllerSenderError> {
|
||||||
self.0
|
self.0
|
||||||
.unbounded_send(ReplyControllerMessage::SendReply {
|
.unbounded_send(ReplyControllerMessage::SendReply {
|
||||||
recipient,
|
recipient,
|
||||||
message,
|
message,
|
||||||
lane,
|
lane,
|
||||||
|
max_retransmissions,
|
||||||
})
|
})
|
||||||
.map_err(ReplyControllerSenderError::SendReply)
|
.map_err(ReplyControllerSenderError::SendReply)
|
||||||
}
|
}
|
||||||
@@ -160,6 +162,7 @@ pub enum ReplyControllerMessage {
|
|||||||
recipient: AnonymousSenderTag,
|
recipient: AnonymousSenderTag,
|
||||||
message: Vec<u8>,
|
message: Vec<u8>,
|
||||||
lane: TransmissionLane,
|
lane: TransmissionLane,
|
||||||
|
max_retransmissions: Option<u32>,
|
||||||
},
|
},
|
||||||
|
|
||||||
AdditionalSurbs {
|
AdditionalSurbs {
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ impl<T> TransmissionBuffer<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub(crate) fn num_lanes(&self) -> usize {
|
pub(crate) fn lanes(&self) -> Vec<TransmissionLane> {
|
||||||
self.buffer.keys().count()
|
self.buffer.keys().cloned().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn lane_length(&self, lane: &TransmissionLane) -> Option<usize> {
|
pub(crate) fn lane_length(&self, lane: &TransmissionLane) -> Option<usize> {
|
||||||
@@ -83,6 +83,7 @@ impl<T> TransmissionBuffer<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[allow(unused)]
|
||||||
pub(crate) fn total_size_in_bytes(&self) -> usize
|
pub(crate) fn total_size_in_bytes(&self) -> usize
|
||||||
where
|
where
|
||||||
T: SizedData,
|
T: SizedData,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use crate::{
|
|||||||
CombinedReplyStorage, ReceivedReplySurbsMap, ReplyStorageBackend, SentReplyKeys, UsedSenderTags,
|
CombinedReplyStorage, ReceivedReplySurbsMap, ReplyStorageBackend, SentReplyKeys, UsedSenderTags,
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use log::{error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -52,7 +52,10 @@ impl Backend {
|
|||||||
Ok(backend)
|
Ok(backend)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn try_load<P: AsRef<Path>>(database_path: P) -> Result<Self, StorageError> {
|
pub async fn try_load<P: AsRef<Path>>(
|
||||||
|
database_path: P,
|
||||||
|
fresh_sender_tags: bool,
|
||||||
|
) -> Result<Self, StorageError> {
|
||||||
let owned_path: PathBuf = database_path.as_ref().into();
|
let owned_path: PathBuf = database_path.as_ref().into();
|
||||||
if owned_path.file_name().is_none() {
|
if owned_path.file_name().is_none() {
|
||||||
return Err(StorageError::DatabasePathWithoutFilename {
|
return Err(StorageError::DatabasePathWithoutFilename {
|
||||||
@@ -118,6 +121,9 @@ impl Backend {
|
|||||||
if days > 2 {
|
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.");
|
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?;
|
manager.delete_all_tags().await?;
|
||||||
|
} else if fresh_sender_tags {
|
||||||
|
debug!("starting with fresh sender tags");
|
||||||
|
manager.delete_all_tags().await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Backend {
|
Ok(Backend {
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ use nym_credentials_interface::TicketType;
|
|||||||
use nym_crypto::asymmetric::identity;
|
use nym_crypto::asymmetric::identity;
|
||||||
use nym_gateway_requests::registration::handshake::client_handshake;
|
use nym_gateway_requests::registration::handshake::client_handshake;
|
||||||
use nym_gateway_requests::{
|
use nym_gateway_requests::{
|
||||||
BinaryRequest, ClientControlRequest, ClientRequest, SensitiveServerResponse, ServerResponse,
|
BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt,
|
||||||
SharedGatewayKey, SharedSymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
|
SensitiveServerResponse, ServerResponse, SharedGatewayKey, SharedSymmetricKey,
|
||||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
||||||
};
|
};
|
||||||
use nym_sphinx::forwarding::packet::MixPacket;
|
use nym_sphinx::forwarding::packet::MixPacket;
|
||||||
@@ -204,15 +204,15 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
"Attemting to establish connection to gateway at: {}",
|
"Attemting to establish connection to gateway at: {}",
|
||||||
self.gateway_address
|
self.gateway_address
|
||||||
);
|
);
|
||||||
let (ws_stream, _) = connect_async(&self.gateway_address).await?;
|
let (ws_stream, _) = connect_async(
|
||||||
|
&self.gateway_address,
|
||||||
|
#[cfg(unix)]
|
||||||
|
self.connection_fd_callback.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
self.connection = SocketState::Available(Box::new(ws_stream));
|
self.connection = SocketState::Available(Box::new(ws_stream));
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
if let (Some(callback), Some(fd)) = (self.connection_fd_callback.as_ref(), self.ws_fd()) {
|
|
||||||
callback.as_ref()(fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,28 +563,10 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
Ok(zeroizing_updated_key)
|
Ok(zeroizing_updated_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn authenticate(&mut self) -> Result<(), GatewayClientError> {
|
async fn send_authenticate_request_and_handle_response(
|
||||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
&mut self,
|
||||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
msg: ClientControlRequest,
|
||||||
};
|
) -> Result<(), GatewayClientError> {
|
||||||
|
|
||||||
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,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
match self.send_websocket_message(msg).await? {
|
match self.send_websocket_message(msg).await? {
|
||||||
ServerResponse::Authenticate {
|
ServerResponse::Authenticate {
|
||||||
protocol_version,
|
protocol_version,
|
||||||
@@ -608,6 +590,51 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
/// Helper method to either call register or authenticate based on self.shared_key value
|
||||||
#[instrument(skip_all,
|
#[instrument(skip_all,
|
||||||
fields(
|
fields(
|
||||||
@@ -623,19 +650,25 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1. check gateway's protocol version
|
// 1. check gateway's protocol version
|
||||||
let supports_aes_gcm_siv = match self.get_gateway_protocol().await {
|
let gw_protocol = match self.get_gateway_protocol().await {
|
||||||
Ok(protocol) => protocol >= AES_GCM_SIV_PROTOCOL_VERSION,
|
Ok(protocol) => Some(protocol),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// if we failed to send the request, it means the gateway is running the old binary,
|
// 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
|
// so it has reset our connection - we have to reconnect
|
||||||
self.establish_connection().await?;
|
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 {
|
if !supports_aes_gcm_siv {
|
||||||
warn!("this gateway is on an old version that doesn't support AES256-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 {
|
if self.authenticated {
|
||||||
debug!("Already authenticated");
|
debug!("Already authenticated");
|
||||||
@@ -650,7 +683,7 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.shared_key.is_some() {
|
if self.shared_key.is_some() {
|
||||||
self.authenticate().await?;
|
self.authenticate(supports_auth_v2).await?;
|
||||||
|
|
||||||
if self.authenticated {
|
if self.authenticated {
|
||||||
// if we are authenticated it means we MUST have an associated shared_key
|
// if we are authenticated it means we MUST have an associated shared_key
|
||||||
@@ -983,7 +1016,8 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if we're reconnecting, because we lost connection, we need to re-authenticate the connection
|
// 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
|
// this call is NON-blocking
|
||||||
self.start_listening_for_mixnet_messages()?;
|
self.start_listening_for_mixnet_messages()?;
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
use crate::error::GatewayClientError;
|
use crate::error::GatewayClientError;
|
||||||
|
|
||||||
use nym_http_api_client::HickoryDnsResolver;
|
use nym_http_api_client::HickoryDnsResolver;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::{
|
||||||
|
os::fd::{AsRawFd, RawFd},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||||
use tungstenite::handshake::client::Response;
|
use tungstenite::handshake::client::Response;
|
||||||
@@ -11,7 +16,10 @@ use std::net::SocketAddr;
|
|||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub(crate) async fn connect_async(
|
pub(crate) async fn connect_async(
|
||||||
endpoint: &str,
|
endpoint: &str,
|
||||||
|
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||||
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), GatewayClientError> {
|
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), GatewayClientError> {
|
||||||
|
use tokio::net::TcpSocket;
|
||||||
|
|
||||||
let resolver = HickoryDnsResolver::default();
|
let resolver = HickoryDnsResolver::default();
|
||||||
let uri =
|
let uri =
|
||||||
Url::parse(endpoint).map_err(|_| GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
|
Url::parse(endpoint).map_err(|_| GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
|
||||||
@@ -37,14 +45,41 @@ pub(crate) async fn connect_async(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let stream = TcpStream::connect(&sock_addrs[..]).await.map_err(|error| {
|
let mut stream = Err(GatewayClientError::NoEndpointForConnection {
|
||||||
GatewayClientError::NetworkConnectionFailed {
|
address: endpoint.to_owned(),
|
||||||
address: endpoint.to_owned(),
|
});
|
||||||
source: error.into(),
|
for sock_addr in sock_addrs {
|
||||||
|
let socket = if sock_addr.is_ipv4() {
|
||||||
|
TcpSocket::new_v4()
|
||||||
|
} else {
|
||||||
|
TcpSocket::new_v6()
|
||||||
}
|
}
|
||||||
})?;
|
.map_err(|err| GatewayClientError::NetworkConnectionFailed {
|
||||||
|
address: endpoint.to_owned(),
|
||||||
|
source: err.into(),
|
||||||
|
})?;
|
||||||
|
|
||||||
tokio_tungstenite::client_async_tls(endpoint, stream)
|
#[cfg(unix)]
|
||||||
|
if let Some(callback) = connection_fd_callback.as_ref() {
|
||||||
|
callback.as_ref()(socket.as_raw_fd());
|
||||||
|
}
|
||||||
|
|
||||||
|
match socket.connect(sock_addr).await {
|
||||||
|
Ok(s) => {
|
||||||
|
stream = Ok(s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
stream = Err(GatewayClientError::NetworkConnectionFailed {
|
||||||
|
address: endpoint.to_owned(),
|
||||||
|
source: err.into(),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio_tungstenite::client_async_tls(endpoint, stream?)
|
||||||
.await
|
.await
|
||||||
.map_err(|error| GatewayClientError::NetworkConnectionFailed {
|
.map_err(|error| GatewayClientError::NetworkConnectionFailed {
|
||||||
address: endpoint.to_owned(),
|
address: endpoint.to_owned(),
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ pub enum GatewayClientError {
|
|||||||
#[error("connection failed: {address}: {source}")]
|
#[error("connection failed: {address}: {source}")]
|
||||||
NetworkConnectionFailed { address: String, source: WsError },
|
NetworkConnectionFailed { address: String, source: WsError },
|
||||||
|
|
||||||
|
#[error("no socket address for endpoint: {address}")]
|
||||||
|
NoEndpointForConnection { address: String },
|
||||||
|
|
||||||
#[error("Invalid URL: {0}")]
|
#[error("Invalid URL: {0}")]
|
||||||
InvalidUrl(String),
|
InvalidUrl(String),
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ cw4 = { workspace = true }
|
|||||||
cw-controllers = { workspace = true }
|
cw-controllers = { workspace = true }
|
||||||
prost = { workspace = true, default-features = false }
|
prost = { workspace = true, default-features = false }
|
||||||
flate2 = { workspace = true }
|
flate2 = { workspace = true }
|
||||||
sha2 = { version = "0.9.5" }
|
sha2 = { workspace = true }
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
zeroize = { workspace = true, features = ["zeroize_derive"] }
|
zeroize = { workspace = true, features = ["zeroize_derive"] }
|
||||||
cosmwasm-std = { workspace = true }
|
cosmwasm-std = { workspace = true }
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ use nym_api_requests::models::{
|
|||||||
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
||||||
};
|
};
|
||||||
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
|
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_coconut_dkg_common::types::EpochId;
|
||||||
use nym_ecash_contract_common::deposit::DepositId;
|
use nym_ecash_contract_common::deposit::DepositId;
|
||||||
use nym_http_api_client::UserAgent;
|
use nym_http_api_client::UserAgent;
|
||||||
use nym_network_defaults::NymNetworkDetails;
|
use nym_network_defaults::NymNetworkDetails;
|
||||||
|
use std::net::IpAddr;
|
||||||
use time::Date;
|
use time::Date;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@@ -710,4 +711,11 @@ impl NymApiClient {
|
|||||||
.issued_ticketbooks_challenge(expiration_date, deposits)
|
.issued_ticketbooks_challenge(expiration_date, deposits)
|
||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn nodes_by_addresses(
|
||||||
|
&self,
|
||||||
|
addresses: Vec<IpAddr>,
|
||||||
|
) -> Result<NodesByAddressesResponse, ValidatorClientError> {
|
||||||
|
Ok(self.nym_api.nodes_by_addresses(addresses).await?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,6 +83,12 @@ impl TryFrom<ContractVKShare> for EcashApiClient {
|
|||||||
|
|
||||||
let url_address = Url::parse(&share.announce_address)?;
|
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 {
|
Ok(EcashApiClient {
|
||||||
api_client: NymApiClient::new(url_address),
|
api_client: NymApiClient::new(url_address),
|
||||||
verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?,
|
verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?,
|
||||||
|
|||||||
@@ -12,10 +12,13 @@ use nym_api_requests::ecash::models::{
|
|||||||
};
|
};
|
||||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||||
use nym_api_requests::models::{
|
use nym_api_requests::models::{
|
||||||
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
|
AnnotationResponse, ApiHealthResponse, BinaryBuildInformationOwned, ChainStatusResponse,
|
||||||
NodeRefreshBody, NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse,
|
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;
|
use nym_api_requests::pagination::PaginatedResponse;
|
||||||
pub use nym_api_requests::{
|
pub use nym_api_requests::{
|
||||||
ecash::{
|
ecash::{
|
||||||
@@ -40,6 +43,7 @@ pub use nym_http_api_client::Client;
|
|||||||
use nym_http_api_client::{ApiClient, NO_PARAMS};
|
use nym_http_api_client::{ApiClient, NO_PARAMS};
|
||||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId, NymNodeDetails};
|
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId, NymNodeDetails};
|
||||||
|
use std::net::IpAddr;
|
||||||
use time::format_description::BorrowedFormatItem;
|
use time::format_description::BorrowedFormatItem;
|
||||||
use time::Date;
|
use time::Date;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
@@ -66,6 +70,19 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", skip(self))]
|
||||||
|
async fn build_information(&self) -> Result<BinaryBuildInformationOwned, NymAPIError> {
|
||||||
|
self.get_json(
|
||||||
|
&[
|
||||||
|
routes::API_VERSION,
|
||||||
|
routes::API_STATUS_ROUTES,
|
||||||
|
routes::BUILD_INFORMATION,
|
||||||
|
],
|
||||||
|
NO_PARAMS,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
#[deprecated]
|
#[deprecated]
|
||||||
#[instrument(level = "debug", skip(self))]
|
#[instrument(level = "debug", skip(self))]
|
||||||
async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||||
@@ -1015,6 +1032,23 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn nodes_by_addresses(
|
||||||
|
&self,
|
||||||
|
addresses: Vec<IpAddr>,
|
||||||
|
) -> Result<NodesByAddressesResponse, NymAPIError> {
|
||||||
|
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))]
|
#[instrument(level = "debug", skip(self))]
|
||||||
async fn get_network_details(&self) -> Result<NymNetworkDetailsResponse, NymAPIError> {
|
async fn get_network_details(&self) -> Result<NymNetworkDetailsResponse, NymAPIError> {
|
||||||
self.get_json(
|
self.get_json(
|
||||||
@@ -1023,6 +1057,15 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", skip(self))]
|
||||||
|
async fn get_chain_status(&self) -> Result<ChainStatusResponse, NymAPIError> {
|
||||||
|
self.get_json(
|
||||||
|
&[routes::API_VERSION, routes::NETWORK, routes::CHAIN_STATUS],
|
||||||
|
NO_PARAMS,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||||
|
|||||||
@@ -43,11 +43,14 @@ pub mod nym_nodes {
|
|||||||
pub const NYM_NODES_BONDED: &str = "bonded";
|
pub const NYM_NODES_BONDED: &str = "bonded";
|
||||||
pub const NYM_NODES_REWARDED_SET: &str = "rewarded-set";
|
pub const NYM_NODES_REWARDED_SET: &str = "rewarded-set";
|
||||||
pub const NYM_NODES_REFRESH_DESCRIBED: &str = "refresh-described";
|
pub const NYM_NODES_REFRESH_DESCRIBED: &str = "refresh-described";
|
||||||
|
pub const BY_ADDRESSES: &str = "by-addresses";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const STATUS_ROUTES: &str = "status";
|
pub const STATUS_ROUTES: &str = "status";
|
||||||
pub const API_STATUS_ROUTES: &str = "api-status";
|
pub const API_STATUS_ROUTES: &str = "api-status";
|
||||||
pub const HEALTH: &str = "health";
|
pub const HEALTH: &str = "health";
|
||||||
|
pub const BUILD_INFORMATION: &str = "build-information";
|
||||||
|
|
||||||
pub const MIXNODE: &str = "mixnode";
|
pub const MIXNODE: &str = "mixnode";
|
||||||
pub const GATEWAY: &str = "gateway";
|
pub const GATEWAY: &str = "gateway";
|
||||||
pub const NYM_NODES: &str = "nym-nodes";
|
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 SERVICE_PROVIDERS: &str = "services";
|
||||||
|
|
||||||
pub const DETAILS: &str = "details";
|
pub const DETAILS: &str = "details";
|
||||||
|
pub const CHAIN_STATUS: &str = "chain-status";
|
||||||
pub const NETWORK: &str = "network";
|
pub const NETWORK: &str = "network";
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ use nym_network_defaults::{ChainDetails, NymNetworkDetails};
|
|||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use tendermint_rpc::endpoint::block::Response as BlockResponse;
|
|
||||||
use tendermint_rpc::endpoint::*;
|
use tendermint_rpc::endpoint::*;
|
||||||
use tendermint_rpc::{Error as TendermintRpcError, Order};
|
use tendermint_rpc::{Error as TendermintRpcError, Order};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@@ -63,6 +62,8 @@ pub use cw3;
|
|||||||
pub use cw4;
|
pub use cw4;
|
||||||
pub use cw_controllers;
|
pub use cw_controllers;
|
||||||
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
|
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::{
|
pub use tendermint_rpc::{
|
||||||
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
|
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
|
||||||
query::Query,
|
query::Query,
|
||||||
|
|||||||
@@ -25,15 +25,6 @@ pub fn in6addr_any_init() -> IpAddr {
|
|||||||
IpAddr::V6(Ipv6Addr::UNSPECIFIED)
|
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'?
|
// TODO: is it really part of 'Config'?
|
||||||
pub trait OptionalSet {
|
pub trait OptionalSet {
|
||||||
/// If the value is available (i.e. `Some`), the provided closure is applied.
|
/// If the value is available (i.e. `Some`), the provided closure is applied.
|
||||||
|
|||||||
@@ -43,4 +43,7 @@ serde = ["dep:serde", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"
|
|||||||
asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"]
|
asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"]
|
||||||
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array", "sha2"]
|
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array", "sha2"]
|
||||||
stream_cipher = ["aes", "ctr", "cipher", "generic-array"]
|
stream_cipher = ["aes", "ctr", "cipher", "generic-array"]
|
||||||
sphinx = ["nym-sphinx-types/sphinx"]
|
sphinx = ["nym-sphinx-types/sphinx"]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
@@ -16,8 +16,11 @@ pub fn compute_keyed_hmac<D>(key: &[u8], data: &[u8]) -> HmacOutput<D>
|
|||||||
where
|
where
|
||||||
D: Digest + BlockSizeUser,
|
D: Digest + BlockSizeUser,
|
||||||
{
|
{
|
||||||
let mut hmac = SimpleHmac::<D>::new_from_slice(key)
|
// SAFETY: hmac is fine with keys of any size; if they're smaller than the block size of the underlying
|
||||||
.expect("HMAC was instantiated with a key of an invalid size!");
|
// 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::<D>::new_from_slice(key).unwrap();
|
||||||
hmac.update(data);
|
hmac.update(data);
|
||||||
hmac.finalize()
|
hmac.finalize()
|
||||||
}
|
}
|
||||||
@@ -27,8 +30,11 @@ pub fn recompute_keyed_hmac_and_verify_tag<D>(key: &[u8], data: &[u8], tag: &[u8
|
|||||||
where
|
where
|
||||||
D: Digest + BlockSizeUser,
|
D: Digest + BlockSizeUser,
|
||||||
{
|
{
|
||||||
let mut hmac = SimpleHmac::<D>::new_from_slice(key)
|
// SAFETY: hmac is fine with keys of any size; if they're smaller than the block size of the underlying
|
||||||
.expect("HMAC was instantiated with a key of an invalid size!");
|
// 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::<D>::new_from_slice(key).unwrap();
|
||||||
hmac.update(data);
|
hmac.update(data);
|
||||||
|
|
||||||
let tag_arr = Output::<D>::from_slice(tag);
|
let tag_arr = Output::<D>::from_slice(tag);
|
||||||
|
|||||||
@@ -27,12 +27,16 @@ where
|
|||||||
// after performing diffie-hellman we don't care about the private component anymore
|
// after performing diffie-hellman we don't care about the private component anymore
|
||||||
let dh_result = ephemeral_keypair.private_key().diffie_hellman(remote_key);
|
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::<D>(None, &dh_result, None, C::key_size())
|
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::key_size())
|
||||||
.expect("somehow too long okm was provided");
|
.expect("somehow too long okm was provided");
|
||||||
|
|
||||||
let derived_shared_key =
|
// SAFETY: the generated okm has exactly `C::key_size()` elements,
|
||||||
Key::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!");
|
// so this call is safe
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let derived_shared_key = Key::<C>::from_exact_iter(okm).unwrap();
|
||||||
|
|
||||||
(ephemeral_keypair, derived_shared_key)
|
(ephemeral_keypair, derived_shared_key)
|
||||||
}
|
}
|
||||||
@@ -48,9 +52,14 @@ where
|
|||||||
{
|
{
|
||||||
let dh_result = local_key.diffie_hellman(remote_key);
|
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::<D>(None, &dh_result, None, C::key_size())
|
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::key_size())
|
||||||
.expect("somehow too long okm was provided");
|
.expect("somehow too long okm was provided");
|
||||||
|
|
||||||
Key::<C>::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::<C>::from_exact_iter(okm).unwrap()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,20 +60,15 @@ where
|
|||||||
Iv::<C>::default()
|
Iv::<C>::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iv_from_slice<C>(b: &[u8]) -> &IV<C>
|
pub fn try_iv_from_slice<C>(b: &[u8]) -> Option<&IV<C>>
|
||||||
where
|
where
|
||||||
C: IvSizeUser,
|
C: IvSizeUser,
|
||||||
{
|
{
|
||||||
if b.len() != C::iv_size() {
|
if b.len() != C::iv_size() {
|
||||||
// `from_slice` would have caused a panic about this issue anyway.
|
None
|
||||||
// Now we at least have slightly more information
|
} else {
|
||||||
panic!(
|
Some(IV::<C>::from_slice(b))
|
||||||
"Tried to convert {} bytes to IV. Expected {}",
|
|
||||||
b.len(),
|
|
||||||
C::iv_size()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
IV::<C>::from_slice(b)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: there's really no way to use more parts of the keystream if it was required at some point.
|
// TODO: there's really no way to use more parts of the keystream if it was required at some point.
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ lazy_static = { workspace = true }
|
|||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
rand_chacha = { workspace = true }
|
rand_chacha = { workspace = true }
|
||||||
rand_core = { workspace = true }
|
rand_core = { workspace = true }
|
||||||
sha2 = "0.9"
|
sha2 = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_derive = { workspace = true }
|
serde_derive = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|||||||
+96
-2
@@ -54,12 +54,12 @@ pub(crate) fn hash_to_scalar<M: AsRef<[u8]>>(msg: M, domain: &[u8]) -> Scalar {
|
|||||||
pub(crate) fn hash_to_scalars<M: AsRef<[u8]>>(msg: M, domain: &[u8], n: usize) -> Vec<Scalar> {
|
pub(crate) fn hash_to_scalars<M: AsRef<[u8]>>(msg: M, domain: &[u8], n: usize) -> Vec<Scalar> {
|
||||||
let mut output = vec![Scalar::zero(); n];
|
let mut output = vec![Scalar::zero(); n];
|
||||||
|
|
||||||
Scalar::hash_to_field::<ExpandMsgXmd<Sha256>>(msg.as_ref(), domain, &mut output);
|
Scalar::hash_to_field::<ExpandMsgXmd<Sha256>, _>([msg], domain, &mut output);
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn hash_g2<M: AsRef<[u8]>>(msg: M, domain: &[u8]) -> G2Projective {
|
pub(crate) fn hash_g2<M: AsRef<[u8]>>(msg: M, domain: &[u8]) -> G2Projective {
|
||||||
<G2Projective as HashToCurve<ExpandMsgXmd<Sha256>>>::hash_to_curve(msg, domain)
|
<G2Projective as HashToCurve<ExpandMsgXmd<Sha256>>>::hash_to_curve([msg], domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn combine_scalar_chunks(chunks: &[Scalar]) -> Scalar {
|
pub(crate) fn combine_scalar_chunks(chunks: &[Scalar]) -> Scalar {
|
||||||
@@ -112,3 +112,97 @@ pub(crate) fn deserialize_g2(b: &[u8]) -> Option<G2Projective> {
|
|||||||
G2Projective::from_bytes(&encoding).into()
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,11 +20,14 @@ serde_json = { workspace = true }
|
|||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tracing = { workspace = true, features = ["log"] }
|
tracing = { workspace = true, features = ["log"] }
|
||||||
|
time = { workspace = true }
|
||||||
|
subtle = { workspace = true }
|
||||||
zeroize = { workspace = true }
|
zeroize = { workspace = true }
|
||||||
|
|
||||||
nym-crypto = { path = "../crypto", features = ["aead", "hashing"] }
|
nym-crypto = { path = "../crypto", features = ["aead", "hashing"] }
|
||||||
nym-pemstore = { path = "../pemstore" }
|
nym-pemstore = { path = "../pemstore" }
|
||||||
nym-sphinx = { path = "../nymsphinx" }
|
nym-sphinx = { path = "../nymsphinx" }
|
||||||
|
nym-serde-helpers = { path = "../serde-helpers", features = ["base64"] }
|
||||||
nym-task = { path = "../task" }
|
nym-task = { path = "../task" }
|
||||||
|
|
||||||
nym-credentials = { path = "../credentials" }
|
nym-credentials = { path = "../credentials" }
|
||||||
|
|||||||
@@ -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)
|
// this is no longer constant size due to the differences in ciphertext between aes128ctr and aes256gcm-siv (inclusion of tag)
|
||||||
pub struct EncryptedAddressBytes(Vec<u8>);
|
pub struct EncryptedAddressBytes(Vec<u8>);
|
||||||
|
|
||||||
|
impl From<Vec<u8>> for EncryptedAddressBytes {
|
||||||
|
fn from(encrypted_address: Vec<u8>) -> Self {
|
||||||
|
EncryptedAddressBytes(encrypted_address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum EncryptedAddressConversionError {
|
pub enum EncryptedAddressConversionError {
|
||||||
#[error("Failed to decode the encrypted address - {0}")]
|
#[error("Failed to decode the encrypted address - {0}")]
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ pub use shared_key::{
|
|||||||
SharedGatewayKey, SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey,
|
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.
|
/// Defines the current version of the communication protocol between gateway and clients.
|
||||||
/// It has to be incremented for any breaking change.
|
/// 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
|
// 1 - initial release
|
||||||
// 2 - changes to client credentials structure
|
// 2 - changes to client credentials structure
|
||||||
// 3 - change to AES-GCM-SIV and non-zero IVs
|
// 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 INITIAL_PROTOCOL_VERSION: u8 = 1;
|
||||||
pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2;
|
pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2;
|
||||||
pub const AES_GCM_SIV_PROTOCOL_VERSION: u8 = 3;
|
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?
|
// TODO: could using `Mac` trait here for OutputSize backfire?
|
||||||
// Should hmac itself be exposed, imported and used instead?
|
// Should hmac itself be exposed, imported and used instead?
|
||||||
pub type LegacyGatewayMacSize = <GatewayIntegrityHmacAlgorithm as OutputSizeUser>::OutputSize;
|
pub type LegacyGatewayMacSize = <GatewayIntegrityHmacAlgorithm as OutputSizeUser>::OutputSize;
|
||||||
|
|
||||||
|
pub trait GatewayProtocolVersionExt {
|
||||||
|
fn supports_aes256_gcm_siv(&self) -> bool;
|
||||||
|
fn supports_authenticate_v2(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GatewayProtocolVersionExt for Option<u8> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
|
|
||||||
use crate::SharedKeyUsageError;
|
use crate::SharedKeyUsageError;
|
||||||
use nym_credentials_interface::CompactEcashError;
|
use nym_credentials_interface::CompactEcashError;
|
||||||
|
use nym_crypto::asymmetric::ed25519::SignatureError;
|
||||||
use nym_sphinx::addressing::nodes::NymNodeRoutingAddressError;
|
use nym_sphinx::addressing::nodes::NymNodeRoutingAddressError;
|
||||||
use nym_sphinx::forwarding::packet::MixPacketFormattingError;
|
use nym_sphinx::forwarding::packet::MixPacketFormattingError;
|
||||||
use nym_sphinx::params::packet_sizes::PacketSize;
|
use nym_sphinx::params::packet_sizes::PacketSize;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::string::FromUtf8Error;
|
use std::string::FromUtf8Error;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
// specific errors (that should not be nested!!) for clients to match on
|
// specific errors (that should not be nested!!) for clients to match on
|
||||||
#[derive(Debug, Copy, Clone, Error, Serialize, Deserialize)]
|
#[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}")]
|
#[error("the provided [v1] credential has invalid number of parameters - {0}")]
|
||||||
InvalidNumberOfEmbededParameters(u32),
|
InvalidNumberOfEmbededParameters(u32),
|
||||||
|
|
||||||
|
#[error("failed to authenticate the client: {0}")]
|
||||||
|
Authentication(#[from] AuthenticationFailure),
|
||||||
|
|
||||||
// variant to catch legacy errors
|
// variant to catch legacy errors
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Other(String),
|
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,
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// 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<AuthenticateRequest, GatewayRequestsError> {
|
||||||
|
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<u8>,
|
||||||
|
|
||||||
|
#[serde(with = "nym_serde_helpers::base64")]
|
||||||
|
pub nonce: Vec<u8>,
|
||||||
|
|
||||||
|
pub request_unix_timestamp: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthenticateRequestContent {
|
||||||
|
fn new(
|
||||||
|
protocol_version: u8,
|
||||||
|
shared_key: &SharedGatewayKey,
|
||||||
|
client_identity: ed25519::PublicKey,
|
||||||
|
) -> Result<AuthenticateRequestContent, GatewayRequestsError> {
|
||||||
|
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<u8> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
-1
@@ -2,16 +2,21 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::models::CredentialSpendingRequest;
|
use crate::models::CredentialSpendingRequest;
|
||||||
|
use crate::text_request::authenticate::AuthenticateRequest;
|
||||||
use crate::{
|
use crate::{
|
||||||
GatewayRequestsError, SharedGatewayKey, SymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
|
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_credentials_interface::CredentialSpendingData;
|
||||||
|
use nym_crypto::asymmetric::ed25519;
|
||||||
use nym_sphinx::DestinationAddressBytes;
|
use nym_sphinx::DestinationAddressBytes;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use tungstenite::Message;
|
use tungstenite::Message;
|
||||||
|
|
||||||
|
pub mod authenticate;
|
||||||
|
|
||||||
// wrapper for all encrypted requests for ease of use
|
// wrapper for all encrypted requests for ease of use
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
@@ -68,6 +73,9 @@ pub enum ClientControlRequest {
|
|||||||
enc_address: String,
|
enc_address: String,
|
||||||
iv: String,
|
iv: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
AuthenticateV2(Box<AuthenticateRequest>),
|
||||||
|
|
||||||
#[serde(alias = "handshakePayload")]
|
#[serde(alias = "handshakePayload")]
|
||||||
RegisterHandshakeInitRequest {
|
RegisterHandshakeInitRequest {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -123,9 +131,22 @@ impl ClientControlRequest {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_authenticate_v2(
|
||||||
|
shared_key: &SharedGatewayKey,
|
||||||
|
identity_keys: &ed25519::KeyPair,
|
||||||
|
) -> Result<Self, GatewayRequestsError> {
|
||||||
|
// 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 {
|
pub fn name(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
ClientControlRequest::Authenticate { .. } => "Authenticate".to_string(),
|
ClientControlRequest::Authenticate { .. } => "Authenticate".to_string(),
|
||||||
|
ClientControlRequest::AuthenticateV2(..) => "AuthenticateV2".to_string(),
|
||||||
ClientControlRequest::RegisterHandshakeInitRequest { .. } => {
|
ClientControlRequest::RegisterHandshakeInitRequest { .. } => {
|
||||||
"RegisterHandshakeInitRequest".to_string()
|
"RegisterHandshakeInitRequest".to_string()
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
ALTER TABLE shared_keys
|
||||||
|
ADD COLUMN last_used_authentication TIMESTAMP WITHOUT TIME ZONE;
|
||||||
@@ -200,6 +200,20 @@ impl GatewayStorage {
|
|||||||
Ok(())
|
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<Option<Client>, GatewayStorageError> {
|
pub async fn get_client(&self, client_id: i64) -> Result<Option<Client>, GatewayStorageError> {
|
||||||
let client = self.client_manager.get_client(client_id).await?;
|
let client = self.client_manager.get_client(client_id).await?;
|
||||||
Ok(client)
|
Ok(client)
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ pub struct Client {
|
|||||||
|
|
||||||
#[derive(FromRow)]
|
#[derive(FromRow)]
|
||||||
pub struct PersistedSharedKeys {
|
pub struct PersistedSharedKeys {
|
||||||
#[allow(dead_code)]
|
|
||||||
pub client_id: i64,
|
pub client_id: i64,
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub client_address_bs58: String,
|
pub client_address_bs58: String,
|
||||||
pub derived_aes128_ctr_blake3_hmac_keys_bs58: Option<String>,
|
pub derived_aes128_ctr_blake3_hmac_keys_bs58: Option<String>,
|
||||||
pub derived_aes256_gcm_siv_key: Option<Vec<u8>>,
|
pub derived_aes256_gcm_siv_key: Option<Vec<u8>>,
|
||||||
|
pub last_used_authentication: Option<OffsetDateTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<PersistedSharedKeys> for SharedGatewayKey {
|
impl TryFrom<PersistedSharedKeys> for SharedGatewayKey {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use crate::models::PersistedSharedKeys;
|
use crate::models::PersistedSharedKeys;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct SharedKeysManager {
|
pub(crate) struct SharedKeysManager {
|
||||||
@@ -68,6 +69,22 @@ impl SharedKeysManager {
|
|||||||
Ok(())
|
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.
|
/// Tries to retrieve shared keys stored for the particular client.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -77,13 +94,10 @@ impl SharedKeysManager {
|
|||||||
&self,
|
&self,
|
||||||
client_address_bs58: &str,
|
client_address_bs58: &str,
|
||||||
) -> Result<Option<PersistedSharedKeys>, sqlx::Error> {
|
) -> Result<Option<PersistedSharedKeys>, sqlx::Error> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as("SELECT * FROM shared_keys WHERE client_address_bs58 = ?")
|
||||||
PersistedSharedKeys,
|
.bind(client_address_bs58)
|
||||||
"SELECT * FROM shared_keys WHERE client_address_bs58 = ?",
|
.fetch_optional(&self.connection_pool)
|
||||||
client_address_bs58
|
.await
|
||||||
)
|
|
||||||
.fetch_optional(&self.connection_pool)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes from the database shared keys derived with the particular client.
|
/// Removes from the database shared keys derived with the particular client.
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ serde_json = { workspace = true }
|
|||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tracing = { 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" }
|
nym-bin-common = { path = "../bin-common" }
|
||||||
|
|
||||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||||
@@ -32,4 +38,4 @@ workspace = true
|
|||||||
features = ["tokio"]
|
features = ["tokio"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { workspace = true, features=["rt", "macros"] }
|
tokio = { workspace = true, features = ["rt", "macros"] }
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
//! DNS resolver configuration for internal lookups.
|
//! DNS resolver configuration for internal lookups.
|
||||||
//!
|
//!
|
||||||
//! The resolver itself is the set combination of the google, cloudflare, and quad9 endpoints
|
//! 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
|
//! Requires the `dns-over-https-rustls`, `webpki-roots` feature for the
|
||||||
//! `hickory-resolver` crate
|
//! `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)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use crate::ClientBuilder;
|
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::{
|
use hickory_resolver::{
|
||||||
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ResolverOpts},
|
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ResolverOpts},
|
||||||
error::ResolveError,
|
error::ResolveError,
|
||||||
lookup_ip::LookupIpIntoIter,
|
lookup_ip::LookupIpIntoIter,
|
||||||
TokioAsyncResolver,
|
TokioAsyncResolver,
|
||||||
};
|
};
|
||||||
|
use hickory_resolver::{error::ResolveErrorKind, lookup_ip::LookupIp};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
|
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
@@ -30,6 +49,13 @@ impl ClientBuilder {
|
|||||||
/// Override the DNS resolver implementation used by the underlying http client.
|
/// Override the DNS resolver implementation used by the underlying http client.
|
||||||
pub fn dns_resolver<R: Resolve + 'static>(mut self, resolver: Arc<R>) -> Self {
|
pub fn dns_resolver<R: Resolve + 'static>(mut self, resolver: Arc<R>) -> Self {
|
||||||
self.reqwest_client_builder = self.reqwest_client_builder.dns_resolver(resolver);
|
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
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,6 +64,14 @@ struct SocketAddrs {
|
|||||||
iter: LookupIpIntoIter,
|
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<HickoryDnsResolver> = LazyLock::new(|| {
|
||||||
|
tracing::debug!("Initializing shared DNS resolver");
|
||||||
|
HickoryDnsResolver::default()
|
||||||
|
});
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
#[error("hickory-dns resolver error: {hickory_error}")]
|
#[error("hickory-dns resolver error: {hickory_error}")]
|
||||||
/// Error occurring while resolving a hostname into an IP address.
|
/// 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.
|
/// 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)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct HickoryDnsResolver {
|
pub struct HickoryDnsResolver {
|
||||||
/// Since we might not have been called in the context of a
|
// Since we might not have been called in the context of a
|
||||||
/// Tokio Runtime in initialization, so we must delay the actual
|
// Tokio Runtime in initialization, so we must delay the actual
|
||||||
/// construction of the resolver.
|
// construction of the resolver.
|
||||||
state: Arc<OnceCell<TokioAsyncResolver>>,
|
state: Arc<OnceCell<TokioAsyncResolver>>,
|
||||||
fallback: Arc<OnceCell<TokioAsyncResolver>>,
|
fallback: Arc<OnceCell<TokioAsyncResolver>>,
|
||||||
|
dont_use_shared: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resolve for HickoryDnsResolver {
|
impl Resolve for HickoryDnsResolver {
|
||||||
fn resolve(&self, name: Name) -> Resolving {
|
fn resolve(&self, name: Name) -> Resolving {
|
||||||
let resolver = self.state.clone();
|
let resolver = self.state.clone();
|
||||||
let fallback = self.fallback.clone();
|
let fallback = self.fallback.clone();
|
||||||
|
let independent = self.dont_use_shared;
|
||||||
Box::pin(async move {
|
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)
|
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
|
||||||
let lookup = match resolver.lookup_ip(name.as_str()).await {
|
let lookup = match resolver.lookup_ip(name.as_str()).await {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// on failure use the fall back system configured DNS resolver
|
// on failure use the fall back system configured DNS resolver
|
||||||
warn!("primary DNS failed w/ error {e}: using system fallback");
|
match e.kind() {
|
||||||
let resolver = fallback.get_or_try_init(new_resolver_system)?;
|
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?
|
resolver.lookup_ip(name.as_str()).await?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -93,21 +160,55 @@ impl Iterator for SocketAddrs {
|
|||||||
impl HickoryDnsResolver {
|
impl HickoryDnsResolver {
|
||||||
/// Attempt to resolve a domain name to a set of ['IpAddr']s
|
/// Attempt to resolve a domain name to a set of ['IpAddr']s
|
||||||
pub async fn resolve_str(&self, name: &str) -> Result<LookupIp, HickoryDnsError> {
|
pub async fn resolve_str(&self, name: &str) -> Result<LookupIp, HickoryDnsError> {
|
||||||
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)
|
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
|
||||||
let lookup = match resolver.lookup_ip(name).await {
|
let lookup = match resolver.lookup_ip(name).await {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// on failure use the fall back system configured DNS resolver
|
// on failure use the fall back system configured DNS resolver
|
||||||
warn!("primary DNS failed w/ error {e}: using system fallback");
|
match e.kind() {
|
||||||
let resolver = self.fallback.get_or_try_init(new_resolver_system)?;
|
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?
|
resolver.lookup_ip(name).await?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(lookup)
|
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<TokioAsyncResolver, HickoryDnsError> {
|
||||||
|
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<TokioAsyncResolver, HickoryDnsError> {
|
||||||
|
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
|
/// Create a new resolver with a custom DoT based configuration. The options are overridden to look
|
||||||
|
|||||||
@@ -147,13 +147,13 @@ use thiserror::Error;
|
|||||||
use tracing::{instrument, warn};
|
use tracing::{instrument, warn};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
use http::HeaderMap;
|
||||||
|
pub use reqwest::IntoUrl;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub use reqwest::IntoUrl;
|
|
||||||
|
|
||||||
mod user_agent;
|
mod user_agent;
|
||||||
pub use user_agent::UserAgent;
|
pub use user_agent::UserAgent;
|
||||||
|
|
||||||
@@ -210,6 +210,12 @@ pub enum HttpClientError<E: Display = String> {
|
|||||||
#[error("failed to resolve request. status: '{status}', additional error message: {error}")]
|
#[error("failed to resolve request. status: '{status}', additional error message: {error}")]
|
||||||
EndpointFailure { status: StatusCode, error: E },
|
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")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
#[error("the request has timed out")]
|
#[error("the request has timed out")]
|
||||||
RequestTimeout,
|
RequestTimeout,
|
||||||
@@ -222,6 +228,8 @@ pub struct ClientBuilder {
|
|||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
custom_user_agent: bool,
|
custom_user_agent: bool,
|
||||||
reqwest_client_builder: reqwest::ClientBuilder,
|
reqwest_client_builder: reqwest::ClientBuilder,
|
||||||
|
#[allow(dead_code)] // not dead code, just unused in wasm
|
||||||
|
use_secure_dns: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientBuilder {
|
impl ClientBuilder {
|
||||||
@@ -233,37 +241,46 @@ impl ClientBuilder {
|
|||||||
U: IntoUrl,
|
U: IntoUrl,
|
||||||
E: Display,
|
E: Display,
|
||||||
{
|
{
|
||||||
// a naive check: if the provided URL does not start with http(s), add that scheme
|
|
||||||
let str_url = url.as_str();
|
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") {
|
if !str_url.starts_with("http") {
|
||||||
let alt = format!("http://{str_url}");
|
let alt = format!("http://{str_url}");
|
||||||
warn!("the provided url ('{str_url}') does not contain scheme information. Changing it to '{alt}' ...");
|
warn!("the provided url ('{str_url}') does not contain scheme information. Changing it to '{alt}' ...");
|
||||||
// TODO: or should we maybe default to https?
|
// TODO: or should we maybe default to https?
|
||||||
Self::new(alt)
|
Self::new(alt)
|
||||||
} else {
|
} else {
|
||||||
#[cfg(target_arch = "wasm32")]
|
Ok(Self::new_with_url(url.into_url()?))
|
||||||
let reqwest_client_builder = reqwest::ClientBuilder::new();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
/// Constructs a new http `ClientBuilder` from a valid url.
|
||||||
let reqwest_client_builder = {
|
pub fn new_with_url(url: Url) -> Self {
|
||||||
let r = reqwest::ClientBuilder::new()
|
if !url.scheme().starts_with("http") {
|
||||||
.dns_resolver(Arc::new(HickoryDnsResolver::default()));
|
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
|
#[cfg(target_arch = "wasm32")]
|
||||||
// `"Enable[s] auto gzip decompression by checking the Content-Encoding response header."`
|
let reqwest_client_builder = reqwest::ClientBuilder::new();
|
||||||
//
|
|
||||||
// I am going to leave it here anyways so that gzip decompression is attempted even if
|
|
||||||
// that feature is removed.
|
|
||||||
r.gzip(true)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(ClientBuilder {
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
url: url.into_url()?,
|
let reqwest_client_builder = {
|
||||||
timeout: None,
|
let r = reqwest::ClientBuilder::new();
|
||||||
custom_user_agent: false,
|
|
||||||
reqwest_client_builder,
|
// 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
|
let mut builder = self
|
||||||
.reqwest_client_builder
|
.reqwest_client_builder
|
||||||
.timeout(self.timeout.unwrap_or(DEFAULT_TIMEOUT));
|
.timeout(self.timeout.unwrap_or(DEFAULT_TIMEOUT));
|
||||||
|
|
||||||
|
// if no custom user agent was set, use a default
|
||||||
if !self.custom_user_agent {
|
if !self.custom_user_agent {
|
||||||
builder =
|
builder =
|
||||||
builder.user_agent(format!("nym-http-api-client/{}", env!("CARGO_PKG_VERSION")))
|
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()?
|
builder.build()?
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -349,6 +374,9 @@ pub struct Client {
|
|||||||
impl Client {
|
impl Client {
|
||||||
/// Create a new http `Client`
|
/// Create a new http `Client`
|
||||||
// no timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
|
// 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<Duration>) -> Self {
|
pub fn new(base_url: Url, timeout: Option<Duration>) -> Self {
|
||||||
Self::new_url::<_, String>(base_url, timeout).expect(
|
Self::new_url::<_, String>(base_url, timeout).expect(
|
||||||
"we provided valid url and we were unwrapping previous construction errors anyway",
|
"we provided valid url and we were unwrapping previous construction errors anyway",
|
||||||
@@ -849,6 +877,26 @@ fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
|
|||||||
url
|
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::<Mime>().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
|
/// Attempt to parse a json object from an HTTP response
|
||||||
#[instrument(level = "debug", skip_all)]
|
#[instrument(level = "debug", skip_all)]
|
||||||
pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
|
pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
|
||||||
@@ -864,21 +912,23 @@ where
|
|||||||
return Err(HttpClientError::EmptyResponse { status });
|
return Err(HttpClientError::EmptyResponse { status });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let headers = res.headers().clone();
|
||||||
|
tracing::trace!("headers: {:?}", headers);
|
||||||
|
|
||||||
if res.status().is_success() {
|
if res.status().is_success() {
|
||||||
#[cfg(debug_assertions)]
|
// internally reqwest is first retrieving bytes and then performing parsing via serde_json
|
||||||
{
|
// (and similarly does the same thing for text())
|
||||||
let text = res.text().await.inspect_err(|err| {
|
let full = res.bytes().await?;
|
||||||
tracing::error!("Couldn't even get response text: {err}");
|
match serde_json::from_slice(&full) {
|
||||||
})?;
|
Ok(data) => Ok(data),
|
||||||
tracing::trace!("Result:\n{:#?}", text);
|
Err(err) => {
|
||||||
|
let content = decode_as_text(&full, headers);
|
||||||
serde_json::from_str(&text)
|
Err(HttpClientError::ResponseDecodeFailure {
|
||||||
.map_err(|err| HttpClientError::GenericRequestFailure(err.to_string()))
|
source: err,
|
||||||
|
content,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
Ok(res.json().await?)
|
|
||||||
} else if res.status() == StatusCode::NOT_FOUND {
|
} else if res.status() == StatusCode::NOT_FOUND {
|
||||||
Err(HttpClientError::NotFound)
|
Err(HttpClientError::NotFound)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ mime = { workspace = true }
|
|||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
serde_yaml = { workspace = true }
|
serde_yaml = { workspace = true }
|
||||||
|
subtle.workspace = true
|
||||||
tower = { workspace = true }
|
tower = { workspace = true }
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
utoipa = { workspace = true, optional = true }
|
utoipa = { workspace = true, optional = true }
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use axum::{extract::Request, response::Response};
|
|||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
use subtle::ConstantTimeEq;
|
||||||
use tower::{Layer, Service};
|
use tower::{Layer, Service};
|
||||||
use tracing::{debug, instrument, trace};
|
use tracing::{debug, instrument, trace};
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
@@ -76,7 +77,7 @@ impl<S> RequireAuth<S> {
|
|||||||
return Err("`Authorization` header must contain non-empty `Bearer` token");
|
return Err("`Authorization` header must contain non-empty `Bearer` token");
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.bearer_token.as_str() != bearer_token {
|
if bool::from(self.bearer_token.as_bytes().ct_ne(bearer_token.as_bytes())) {
|
||||||
return Err("`Authorization` header does not contain the correct `Bearer` token");
|
return Err("`Authorization` header does not contain the correct `Bearer` token");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ bincode = { workspace = true }
|
|||||||
bytes = { workspace = true }
|
bytes = { workspace = true }
|
||||||
nym-bin-common = { path = "../bin-common" }
|
nym-bin-common = { path = "../bin-common" }
|
||||||
nym-crypto = { path = "../crypto" }
|
nym-crypto = { path = "../crypto" }
|
||||||
|
nym-service-provider-requests-common = { path = "../service-provider-requests-common" }
|
||||||
nym-sphinx = { path = "../nymsphinx" }
|
nym-sphinx = { path = "../nymsphinx" }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ const LENGTH_PREFIX_SIZE: usize = 2;
|
|||||||
// long for the buffer to fill up, since this kills latency.
|
// long for the buffer to fill up, since this kills latency.
|
||||||
pub struct MultiIpPacketCodec {
|
pub struct MultiIpPacketCodec {
|
||||||
buffer: BytesMut,
|
buffer: BytesMut,
|
||||||
buffer_timeout: tokio::time::Interval,
|
pub counter: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MultiIpPacketCodec {
|
impl MultiIpPacketCodec {
|
||||||
pub fn new(buffer_timeout: Duration) -> Self {
|
pub fn new() -> Self {
|
||||||
MultiIpPacketCodec {
|
MultiIpPacketCodec {
|
||||||
buffer: BytesMut::new(),
|
buffer: BytesMut::new(),
|
||||||
buffer_timeout: tokio::time::interval(buffer_timeout),
|
counter: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,56 +40,87 @@ impl MultiIpPacketCodec {
|
|||||||
bundled_packets.extend_from_slice(&packet);
|
bundled_packets.extend_from_slice(&packet);
|
||||||
bundled_packets.freeze()
|
bundled_packets.freeze()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Append a packet to the buffer and return the buffer if it's full
|
impl Default for MultiIpPacketCodec {
|
||||||
pub fn append_packet(&mut self, packet: Bytes) -> Option<Bytes> {
|
fn default() -> Self {
|
||||||
let mut bundled_packets = BytesMut::new();
|
Self::new()
|
||||||
self.encode(packet, &mut bundled_packets).unwrap();
|
}
|
||||||
if bundled_packets.is_empty() {
|
}
|
||||||
None
|
|
||||||
} else {
|
/// The packet that we encode and decode with the MultiIpPacketCodec into bundled multi-ip packets.
|
||||||
// log::info!("Sphinx packet utilization: {:.2}", self.buffer.len() as f64 / MAX_PACKET_SIZE as f64);
|
/// The data here is the actual IP packet that we want to send, not the bundled packets.
|
||||||
Some(bundled_packets.freeze())
|
#[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 into_bytes(self) -> Bytes {
|
||||||
pub fn flush_current_buffer(&mut self) -> Bytes {
|
match self {
|
||||||
let mut output_buffer = BytesMut::new();
|
IprPacket::Data(bytes) => bytes,
|
||||||
std::mem::swap(&mut output_buffer, &mut self.buffer);
|
IprPacket::Flush => Bytes::new(),
|
||||||
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<Bytes> {
|
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encoder<Bytes> for MultiIpPacketCodec {
|
impl From<Bytes> for IprPacket {
|
||||||
|
fn from(bytes: Bytes) -> Self {
|
||||||
|
IprPacket::Data(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<u8>> for IprPacket {
|
||||||
|
fn from(bytes: Vec<u8>) -> Self {
|
||||||
|
IprPacket::Data(Bytes::from(bytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encoder<IprPacket> for MultiIpPacketCodec {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn encode(&mut self, packet: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
fn encode(&mut self, packet: IprPacket, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
||||||
if self.buffer.is_empty() {
|
let packet = match packet {
|
||||||
self.buffer_timeout.reset();
|
IprPacket::Flush => {
|
||||||
}
|
dst.extend_from_slice(&self.buffer);
|
||||||
|
self.counter += 1;
|
||||||
|
println!("Encoding packet: {}", self.counter);
|
||||||
|
self.buffer = BytesMut::new();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
IprPacket::Data(packet) => packet,
|
||||||
|
};
|
||||||
|
|
||||||
let packet_size = packet.len();
|
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);
|
||||||
|
self.counter += 1;
|
||||||
|
println!("Encoding packet: {}", self.counter);
|
||||||
|
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 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);
|
dst.extend_from_slice(&self.buffer);
|
||||||
|
self.counter += 1;
|
||||||
|
println!("Encoding packet: {}", self.counter);
|
||||||
|
// Start a new buffer
|
||||||
self.buffer = BytesMut::new();
|
self.buffer = BytesMut::new();
|
||||||
self.buffer_timeout.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the packet size
|
// Add the packet size
|
||||||
@@ -103,7 +134,7 @@ impl Encoder<Bytes> for MultiIpPacketCodec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Decoder for MultiIpPacketCodec {
|
impl Decoder for MultiIpPacketCodec {
|
||||||
type Item = Bytes;
|
type Item = IprPacket;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||||
@@ -125,6 +156,86 @@ impl Decoder for MultiIpPacketCodec {
|
|||||||
// Read the packet
|
// Read the packet
|
||||||
let packet = src.split_to(packet_size);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,24 +2,18 @@ use serde::{Deserialize, Serialize};
|
|||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
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 codec;
|
||||||
|
pub mod sign;
|
||||||
pub mod v6;
|
pub mod v6;
|
||||||
pub mod v7;
|
pub mod v7;
|
||||||
|
pub mod v8;
|
||||||
|
|
||||||
// version 3: initial version
|
// version 3: initial version
|
||||||
// version 4: IPv6 support
|
// version 4: IPv6 support
|
||||||
// version 5: Add severity level to info response
|
// version 5: Add severity level to info response
|
||||||
// version 6: Increase the available IPs
|
// version 6: Increase the available IPs
|
||||||
// version 7: Add signature support (for the future)
|
// 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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct IpPair {
|
pub struct IpPair {
|
||||||
@@ -45,3 +39,9 @@ fn make_bincode_serializer() -> impl bincode::Options {
|
|||||||
.with_big_endian()
|
.with_big_endian()
|
||||||
.with_varint_encoding()
|
.with_varint_encoding()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_random() -> u64 {
|
||||||
|
use rand::RngCore;
|
||||||
|
let mut rng = rand::rngs::OsRng;
|
||||||
|
rng.next_u64()
|
||||||
|
}
|
||||||
|
|||||||
+18
-9
@@ -1,6 +1,7 @@
|
|||||||
use std::time::Duration;
|
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
|
// For reply protection, if a request is older than this, it will be rejected
|
||||||
const MAX_REQUEST_AGE: Duration = Duration::from_secs(10);
|
const MAX_REQUEST_AGE: Duration = Duration::from_secs(10);
|
||||||
@@ -22,29 +23,37 @@ pub enum SignatureError {
|
|||||||
#[error("signature verification failed")]
|
#[error("signature verification failed")]
|
||||||
VerificationFailed {
|
VerificationFailed {
|
||||||
message: String,
|
message: String,
|
||||||
error: identity::SignatureError,
|
error: ed25519::SignatureError,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SignedRequest {
|
pub trait SignedRequest {
|
||||||
fn identity(&self) -> &identity::PublicKey;
|
fn identity(&self) -> Option<&ed25519::PublicKey>;
|
||||||
|
|
||||||
fn request(&self) -> Result<Vec<u8>, SignatureError>;
|
fn request_as_bytes(&self) -> Result<Vec<u8>, 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> {
|
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() {
|
if let Some(signature) = self.signature() {
|
||||||
// First check that the request is recent enough
|
// 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);
|
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)
|
.verify(request_as_bytes, signature)
|
||||||
.map_err(|error| SignatureError::VerificationFailed {
|
.map_err(|error| SignatureError::VerificationFailed {
|
||||||
message: "signature verification failed".to_string(),
|
message: "signature verification failed".to_string(),
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use crate::{v6, v7};
|
|
||||||
|
|
||||||
impl From<v7::response::StaticConnectFailureReason> 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<v7::response::DynamicConnectFailureReason> 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<v7::response::InfoResponseReply> 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<v7::response::InfoLevel> 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
pub mod conversion;
|
|
||||||
pub mod request;
|
pub mod request;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
use time::OffsetDateTime;
|
|
||||||
|
|
||||||
use crate::{v6, v7};
|
|
||||||
|
|
||||||
impl From<v6::request::IpPacketRequest> for v7::request::IpPacketRequest {
|
|
||||||
fn from(ip_packet_request: v6::request::IpPacketRequest) -> Self {
|
|
||||||
Self {
|
|
||||||
version: 7,
|
|
||||||
data: ip_packet_request.data.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<v6::request::IpPacketRequestData> 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<v6::request::StaticConnectRequest> 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<v6::request::DynamicConnectRequest> 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<v6::request::DisconnectRequest> for v7::request::SignedDisconnectRequest {
|
|
||||||
fn from(disconnect_request: v6::request::DisconnectRequest) -> Self {
|
|
||||||
Self {
|
|
||||||
request: disconnect_request.into(),
|
|
||||||
signature: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<v6::request::DisconnectRequest> 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<v6::request::DataRequest> for v7::request::DataRequest {
|
|
||||||
fn from(data_request: v6::request::DataRequest) -> Self {
|
|
||||||
Self {
|
|
||||||
ip_packets: data_request.ip_packets,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<v6::request::PingRequest> 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<v6::request::HealthRequest> 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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
pub mod conversion;
|
|
||||||
pub mod request;
|
pub mod request;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
pub mod signature;
|
|
||||||
|
|
||||||
pub const VERSION: u8 = 7;
|
pub const VERSION: u8 = 7;
|
||||||
|
|||||||
@@ -1,22 +1,18 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
use nym_crypto::asymmetric::identity;
|
use nym_crypto::asymmetric::identity;
|
||||||
use nym_sphinx::addressing::clients::Recipient;
|
use nym_sphinx::addressing::clients::Recipient;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use crate::{make_bincode_serializer, IpPair};
|
use crate::{
|
||||||
|
sign::{SignatureError, SignedRequest},
|
||||||
use super::{
|
IpPair,
|
||||||
signature::{SignatureError, SignedRequest},
|
|
||||||
VERSION,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn generate_random() -> u64 {
|
use super::VERSION;
|
||||||
use rand::RngCore;
|
|
||||||
let mut rng = rand::rngs::OsRng;
|
|
||||||
rng.next_u64()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct IpPacketRequest {
|
pub struct IpPacketRequest {
|
||||||
pub version: u8,
|
pub version: u8,
|
||||||
pub data: IpPacketRequestData,
|
pub data: IpPacketRequestData,
|
||||||
@@ -30,7 +26,7 @@ impl IpPacketRequest {
|
|||||||
reply_to_avg_mix_delays: Option<f64>,
|
reply_to_avg_mix_delays: Option<f64>,
|
||||||
buffer_timeout: Option<u64>,
|
buffer_timeout: Option<u64>,
|
||||||
) -> (Self, u64) {
|
) -> (Self, u64) {
|
||||||
let request_id = generate_random();
|
let request_id = crate::generate_random();
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
version: VERSION,
|
version: VERSION,
|
||||||
@@ -58,7 +54,7 @@ impl IpPacketRequest {
|
|||||||
reply_to_avg_mix_delays: Option<f64>,
|
reply_to_avg_mix_delays: Option<f64>,
|
||||||
buffer_timeout: Option<u64>,
|
buffer_timeout: Option<u64>,
|
||||||
) -> (Self, u64) {
|
) -> (Self, u64) {
|
||||||
let request_id = generate_random();
|
let request_id = crate::generate_random();
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
version: VERSION,
|
version: VERSION,
|
||||||
@@ -79,7 +75,7 @@ impl IpPacketRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_disconnect_request(reply_to: Recipient) -> (Self, u64) {
|
pub fn new_disconnect_request(reply_to: Recipient) -> (Self, u64) {
|
||||||
let request_id = generate_random();
|
let request_id = crate::generate_random();
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
version: VERSION,
|
version: VERSION,
|
||||||
@@ -104,7 +100,7 @@ impl IpPacketRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_ping(reply_to: Recipient) -> (Self, u64) {
|
pub fn new_ping(reply_to: Recipient) -> (Self, u64) {
|
||||||
let request_id = generate_random();
|
let request_id = crate::generate_random();
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
version: VERSION,
|
version: VERSION,
|
||||||
@@ -119,7 +115,7 @@ impl IpPacketRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_health_request(reply_to: Recipient) -> (Self, u64) {
|
pub fn new_health_request(reply_to: Recipient) -> (Self, u64) {
|
||||||
let request_id = generate_random();
|
let request_id = crate::generate_random();
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
version: VERSION,
|
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<Vec<u8>, bincode::Error> {
|
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||||
use bincode::Options;
|
use bincode::Options;
|
||||||
make_bincode_serializer().serialize(self)
|
crate::make_bincode_serializer().serialize(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_reconstructed_message(
|
pub fn from_reconstructed_message(
|
||||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||||
) -> Result<Self, bincode::Error> {
|
) -> Result<Self, bincode::Error> {
|
||||||
use bincode::Options;
|
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),
|
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 {
|
impl IpPacketRequestData {
|
||||||
pub fn add_signature(&mut self, signature: identity::Signature) -> Option<identity::Signature> {
|
pub fn add_signature(&mut self, signature: identity::Signature) -> Option<identity::Signature> {
|
||||||
match self {
|
match self {
|
||||||
@@ -202,9 +222,9 @@ impl IpPacketRequestData {
|
|||||||
|
|
||||||
pub fn signable_request(&self) -> Option<Result<Vec<u8>, SignatureError>> {
|
pub fn signable_request(&self) -> Option<Result<Vec<u8>, SignatureError>> {
|
||||||
match self {
|
match self {
|
||||||
IpPacketRequestData::StaticConnect(request) => Some(request.request()),
|
IpPacketRequestData::StaticConnect(request) => Some(request.request_as_bytes()),
|
||||||
IpPacketRequestData::DynamicConnect(request) => Some(request.request()),
|
IpPacketRequestData::DynamicConnect(request) => Some(request.request_as_bytes()),
|
||||||
IpPacketRequestData::Disconnect(request) => Some(request.request()),
|
IpPacketRequestData::Disconnect(request) => Some(request.request_as_bytes()),
|
||||||
IpPacketRequestData::Data(_) => None,
|
IpPacketRequestData::Data(_) => None,
|
||||||
IpPacketRequestData::Ping(_) => None,
|
IpPacketRequestData::Ping(_) => None,
|
||||||
IpPacketRequestData::Health(_) => None,
|
IpPacketRequestData::Health(_) => None,
|
||||||
@@ -242,7 +262,7 @@ pub struct StaticConnectRequest {
|
|||||||
impl StaticConnectRequest {
|
impl StaticConnectRequest {
|
||||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||||
use bincode::Options;
|
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 {
|
impl SignedRequest for SignedStaticConnectRequest {
|
||||||
fn identity(&self) -> &identity::PublicKey {
|
fn identity(&self) -> Option<&identity::PublicKey> {
|
||||||
self.request.reply_to.identity()
|
Some(self.request.reply_to.identity())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request(&self) -> Result<Vec<u8>, SignatureError> {
|
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
|
||||||
self.request
|
self.request
|
||||||
.to_bytes()
|
.to_bytes()
|
||||||
.map_err(|error| SignatureError::RequestSerializationError {
|
.map_err(|error| SignatureError::RequestSerializationError {
|
||||||
@@ -306,7 +326,7 @@ pub struct DynamicConnectRequest {
|
|||||||
impl DynamicConnectRequest {
|
impl DynamicConnectRequest {
|
||||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||||
use bincode::Options;
|
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 {
|
impl SignedRequest for SignedDynamicConnectRequest {
|
||||||
fn identity(&self) -> &identity::PublicKey {
|
fn identity(&self) -> Option<&identity::PublicKey> {
|
||||||
self.request.reply_to.identity()
|
Some(self.request.reply_to.identity())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request(&self) -> Result<Vec<u8>, SignatureError> {
|
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
|
||||||
self.request
|
self.request
|
||||||
.to_bytes()
|
.to_bytes()
|
||||||
.map_err(|error| SignatureError::RequestSerializationError {
|
.map_err(|error| SignatureError::RequestSerializationError {
|
||||||
@@ -355,7 +375,7 @@ pub struct DisconnectRequest {
|
|||||||
impl DisconnectRequest {
|
impl DisconnectRequest {
|
||||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||||
use bincode::Options;
|
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 {
|
impl SignedRequest for SignedDisconnectRequest {
|
||||||
fn identity(&self) -> &identity::PublicKey {
|
fn identity(&self) -> Option<&identity::PublicKey> {
|
||||||
self.request.reply_to.identity()
|
Some(self.request.reply_to.identity())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request(&self) -> Result<Vec<u8>, SignatureError> {
|
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
|
||||||
self.request
|
self.request
|
||||||
.to_bytes()
|
.to_bytes()
|
||||||
.map_err(|error| SignatureError::RequestSerializationError {
|
.map_err(|error| SignatureError::RequestSerializationError {
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
pub mod request;
|
||||||
|
pub mod response;
|
||||||
|
|
||||||
|
pub const VERSION: u8 = 8;
|
||||||
@@ -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<ControlRequest>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<u64>,
|
||||||
|
|
||||||
|
// 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<u64>) -> (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<u64> {
|
||||||
|
match self.data {
|
||||||
|
IpPacketRequestData::Control(ref c) => Some(c.id()),
|
||||||
|
IpPacketRequestData::Data(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||||
|
use bincode::Options;
|
||||||
|
crate::make_bincode_serializer().serialize(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_reconstructed_message(
|
||||||
|
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||||
|
) -> Result<Self, bincode::Error> {
|
||||||
|
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<Vec<u8>, bincode::Error> {
|
||||||
|
use bincode::Options;
|
||||||
|
crate::make_bincode_serializer().serialize(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DisconnectRequest {
|
||||||
|
pub fn to_bytes(&self) -> Result<Vec<u8>, 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]),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ControlResponse>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<HealthResponse>),
|
||||||
|
|
||||||
|
// 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<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<u64> {
|
||||||
|
match &self.data {
|
||||||
|
IpPacketResponseData::Data(_) => None,
|
||||||
|
IpPacketResponseData::Control(response) => response.id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||||
|
use bincode::Options;
|
||||||
|
make_bincode_serializer().serialize(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_reconstructed_message(
|
||||||
|
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||||
|
) -> Result<Self, bincode::Error> {
|
||||||
|
use bincode::Options;
|
||||||
|
make_bincode_serializer().deserialize(&message.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IpPacketResponseData {
|
||||||
|
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||||
|
use bincode::Options;
|
||||||
|
make_bincode_serializer().serialize(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ControlResponse {
|
||||||
|
fn id(&self) -> Option<u64> {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,10 +15,10 @@ bls12_381 = { workspace = true, features = ["alloc", "pairings", "experimental",
|
|||||||
bincode.workspace = true
|
bincode.workspace = true
|
||||||
cfg-if.workspace = true
|
cfg-if.workspace = true
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
digest = "0.9"
|
digest = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
sha2 = "0.9"
|
sha2 = { workspace = true }
|
||||||
bs58 = { workspace = true }
|
bs58 = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
rayon = { workspace = true, optional = true }
|
rayon = { workspace = true, optional = true }
|
||||||
|
|||||||
@@ -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";
|
const SCALAR_HASH_DOMAIN: &[u8] = b"NYMECASH-V01-CS02-with-expander-SHA256";
|
||||||
|
|
||||||
pub fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
|
pub fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
|
||||||
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G1_HASH_DOMAIN)
|
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve([msg], G1_HASH_DOMAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
|
pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
|
||||||
let mut output = vec![Scalar::zero()];
|
let mut output = vec![Scalar::zero()];
|
||||||
|
|
||||||
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>>(
|
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>, _>([msg], SCALAR_HASH_DOMAIN, &mut output);
|
||||||
msg.as_ref(),
|
|
||||||
SCALAR_HASH_DOMAIN,
|
|
||||||
&mut output,
|
|
||||||
);
|
|
||||||
output[0]
|
output[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,4 +397,75 @@ mod tests {
|
|||||||
assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2));
|
assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2));
|
||||||
assert_ne!(hash_to_scalar(msg1), 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "nym-coconut"
|
|
||||||
version = "0.5.0"
|
|
||||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>", "Ania Piotrowska <ania@nymtech.net>"]
|
|
||||||
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"] }
|
|
||||||
@@ -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.
|
|
||||||
@@ -1,360 +0,0 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
|
||||||
// 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<Signature> = 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<SignatureShare> = 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<VerificationKey> = 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<u64> = (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);
|
|
||||||
@@ -1,354 +0,0 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
|
||||||
// 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<Ciphertext> {
|
|
||||||
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> {
|
|
||||||
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<PrivateKey> {
|
|
||||||
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<u8> {
|
|
||||||
self.to_bytes().to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
|
||||||
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<PublicKey> {
|
|
||||||
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<u8> {
|
|
||||||
self.to_bytes().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
|
||||||
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> {
|
|
||||||
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<Ciphertext>, Vec<EphemeralKey>) {
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
/// A `Result` alias where the `Err` case is `coconut_rs::Error`.
|
|
||||||
pub type Result<T> = std::result::Result<T, CoconutError>;
|
|
||||||
|
|
||||||
#[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,
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
mod clone;
|
|
||||||
mod serde;
|
|
||||||
@@ -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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
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<E: Error>(self, s: &str) -> Result<Self::Value, E> {
|
|
||||||
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<D>(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);
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user