From bccf0989b3b56e5bf0e3a51142756a6671f9dc24 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 15 Apr 2025 21:17:40 -0500 Subject: [PATCH 001/202] bump midnight-js --- package.json | 24 ++--- yarn.lock | 278 +++++++++++++++++++++++++++++---------------------- 2 files changed, 168 insertions(+), 134 deletions(-) diff --git a/package.json b/package.json index 24105ba7..bfe904a3 100644 --- a/package.json +++ b/package.json @@ -18,18 +18,18 @@ "dependencies": { "@midnight-ntwrk/compact-runtime": "^0.7.0", "@midnight-ntwrk/dapp-connector-api": "^1.2.2", - "@midnight-ntwrk/ledger": "^3.0.2", - "@midnight-ntwrk/midnight-js-contracts": "0.2.5", - "@midnight-ntwrk/midnight-js-fetch-zk-config-provider": "0.2.5", - "@midnight-ntwrk/midnight-js-http-client-proof-provider": "0.2.5", - "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "0.2.5", - "@midnight-ntwrk/midnight-js-level-private-state-provider": "0.2.5", - "@midnight-ntwrk/midnight-js-network-id": "0.2.5", - "@midnight-ntwrk/midnight-js-node-zk-config-provider": "0.2.5", - "@midnight-ntwrk/midnight-js-types": "0.2.5", - "@midnight-ntwrk/midnight-js-utils": "0.2.5", - "@midnight-ntwrk/wallet": "^3.7.3", - "@midnight-ntwrk/wallet-api": "^3.5.0", + "@midnight-ntwrk/ledger": "^3.0.6", + "@midnight-ntwrk/midnight-js-contracts": "^1.0.0", + "@midnight-ntwrk/midnight-js-fetch-zk-config-provider": "^1.0.0", + "@midnight-ntwrk/midnight-js-http-client-proof-provider": "^1.0.0", + "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "^1.0.0", + "@midnight-ntwrk/midnight-js-level-private-state-provider": "^1.0.0", + "@midnight-ntwrk/midnight-js-network-id": "^1.0.0", + "@midnight-ntwrk/midnight-js-node-zk-config-provider": "^1.0.0", + "@midnight-ntwrk/midnight-js-types": "^1.0.0", + "@midnight-ntwrk/midnight-js-utils": "^1.0.0", + "@midnight-ntwrk/wallet": "^4.0.0", + "@midnight-ntwrk/wallet-api": "^4.0.0", "fp-ts": "^2.16.1", "io-ts": "^2.2.20", "pino": "^8.16.0", diff --git a/yarn.lock b/yarn.lock index 64ac2456..7eb3114a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -888,54 +888,55 @@ __metadata: languageName: node linkType: hard -"@midnight-ntwrk/ledger@npm:^3.0.2": +"@midnight-ntwrk/ledger@npm:^3.0.6": version: 3.0.6 resolution: "@midnight-ntwrk/ledger@npm:3.0.6" checksum: 10/644fac9ea7b47c8d0c8ea437e18e2362746701e2fedba3f6fa9a08dcea1f283c05336d9def60403e72055baa5a1e841f971a858ae3133b577a3f12518a48c753 languageName: node linkType: hard -"@midnight-ntwrk/midnight-js-contracts@npm:0.2.5": - version: 0.2.5 - resolution: "@midnight-ntwrk/midnight-js-contracts@npm:0.2.5" +"@midnight-ntwrk/midnight-js-contracts@npm:^1.0.0": + version: 1.0.0 + resolution: "@midnight-ntwrk/midnight-js-contracts@npm:1.0.0" dependencies: - "@midnight-ntwrk/midnight-js-network-id": "npm:0.2.5" - "@midnight-ntwrk/midnight-js-types": "npm:0.2.5" - "@midnight-ntwrk/midnight-js-utils": "npm:0.2.5" - checksum: 10/ad584d7874f7539c6ce2fc8057ab86bf4da8021c935c0e6be569218d2abd64f7e44ef68e7c98da86a87f1d8389fb14676b30a9c15e4e1b0a5daba3fe92cf9b29 + "@midnight-ntwrk/midnight-js-network-id": "npm:1.0.0" + "@midnight-ntwrk/midnight-js-types": "npm:1.0.0" + "@midnight-ntwrk/midnight-js-utils": "npm:1.0.0" + checksum: 10/41a33e55788f881222ebf764b70206b420986b56c911f9534a261dec4ad4554e79871ef751a5fddcf68e552f5248a5462cf410edc5bb4a88f991140eed5942df languageName: node linkType: hard -"@midnight-ntwrk/midnight-js-fetch-zk-config-provider@npm:0.2.5": - version: 0.2.5 - resolution: "@midnight-ntwrk/midnight-js-fetch-zk-config-provider@npm:0.2.5" +"@midnight-ntwrk/midnight-js-fetch-zk-config-provider@npm:^1.0.0": + version: 1.0.0 + resolution: "@midnight-ntwrk/midnight-js-fetch-zk-config-provider@npm:1.0.0" dependencies: - "@midnight-ntwrk/midnight-js-types": "npm:0.2.5" + "@midnight-ntwrk/midnight-js-types": "npm:1.0.0" cross-fetch: "npm:^4.0.0" - checksum: 10/aa867a7e57b1a854d7aa70fbdf6e27d3ccda6388da9e4c27d5430344b7fe0d10c6ae357a47e20e61c559539378114addba562cf24c503ee25f93048c416d5942 + checksum: 10/8756a60a59d92411239274bb2532c2344a3bba962faae87dab2193c237ee3499f3adbecfe7b8f06e3a2e85a6a435fda90b8a77523f92d936cd0101721d6151bc languageName: node linkType: hard -"@midnight-ntwrk/midnight-js-http-client-proof-provider@npm:0.2.5": - version: 0.2.5 - resolution: "@midnight-ntwrk/midnight-js-http-client-proof-provider@npm:0.2.5" +"@midnight-ntwrk/midnight-js-http-client-proof-provider@npm:^1.0.0": + version: 1.0.0 + resolution: "@midnight-ntwrk/midnight-js-http-client-proof-provider@npm:1.0.0" dependencies: "@dao-xyz/borsh": "npm:^5.1.5" - "@midnight-ntwrk/midnight-js-network-id": "npm:0.2.5" - "@midnight-ntwrk/midnight-js-types": "npm:0.2.5" + "@midnight-ntwrk/midnight-js-network-id": "npm:1.0.0" + "@midnight-ntwrk/midnight-js-types": "npm:1.0.0" cross-fetch: "npm:^4.0.0" + fetch-retry: "npm:^6.0.0" lodash: "npm:^4.17.21" - checksum: 10/8e51e01363f506e67001944f17c249fac8ac16756c2405002431d7591dba1a84442a4c086a92e3ffc3a9c68028ad54d28902e22f4399e16942214f0c95b1fb69 + checksum: 10/7469fa5b66b540aceca22ef5f4069e7dc0c5c5f776510fde798dcf2638a4a79dc188aec78fa378a50ca6d8e09a5efcff939696e86de339dcb3f2587e56530f9c languageName: node linkType: hard -"@midnight-ntwrk/midnight-js-indexer-public-data-provider@npm:0.2.5": - version: 0.2.5 - resolution: "@midnight-ntwrk/midnight-js-indexer-public-data-provider@npm:0.2.5" +"@midnight-ntwrk/midnight-js-indexer-public-data-provider@npm:^1.0.0": + version: 1.0.0 + resolution: "@midnight-ntwrk/midnight-js-indexer-public-data-provider@npm:1.0.0" dependencies: "@apollo/client": "npm:^3.8.2" - "@midnight-ntwrk/midnight-js-network-id": "npm:0.2.5" - "@midnight-ntwrk/midnight-js-types": "npm:0.2.5" + "@midnight-ntwrk/midnight-js-network-id": "npm:1.0.0" + "@midnight-ntwrk/midnight-js-types": "npm:1.0.0" buffer: "npm:^6.0.3" cross-fetch: "npm:^4.0.0" graphql: "npm:^16.8.0" @@ -944,55 +945,55 @@ __metadata: rxjs: "npm:^7.5.0" ws: "npm:^8.14.2" zen-observable-ts: "npm:^1.1.0" - checksum: 10/cc31fb2cd3f0940cc0d87c2acaf8da513a0255d703dc20e71d0c7f94cf4eafb4ff03980036983a9d19ca1a32cf10b2ff1ad2eb5f74b74b3bd8a297f3c6f3b3a3 + checksum: 10/a179af0cc0bf7d9ce2709193d399a54f9b6a6b78062acedd72059acedcd7630400e69d4b39d1a385d34c6faa558c77cf6813ea4956155c3bdc1c046ee33cfcd7 languageName: node linkType: hard -"@midnight-ntwrk/midnight-js-level-private-state-provider@npm:0.2.5": - version: 0.2.5 - resolution: "@midnight-ntwrk/midnight-js-level-private-state-provider@npm:0.2.5" +"@midnight-ntwrk/midnight-js-level-private-state-provider@npm:^1.0.0": + version: 1.0.0 + resolution: "@midnight-ntwrk/midnight-js-level-private-state-provider@npm:1.0.0" dependencies: - "@midnight-ntwrk/midnight-js-types": "npm:0.2.5" - abstract-level: "npm:^1.0.3" + "@midnight-ntwrk/midnight-js-types": "npm:1.0.0" + abstract-level: "npm:^2.0.0" buffer: "npm:^6.0.3" fp-ts: "npm:^2.16.1" io-ts: "npm:^2.2.20" - level: "npm:^8.0.0" + level: "npm:^9.0.0" lodash: "npm:^4.17.21" - superjson: "npm:^1.13.1" - checksum: 10/f58dc30fc8894afde7cdf23ed25cdf2b4c7de1fe8408b3163e40a86e4dda5e2b9d40741a38194915f33216f77f98d0a9250ca78f2287e251ad44481106e57027 + superjson: "npm:^2.0.0" + checksum: 10/cf909ff1ed397888abc06e4c282b9f522742264b3952c5ac7abbfbbb82f4c5d791e6eeea1fc8f849cb17519d468235c114def877f5079df96efd5a8cb6cf7885 languageName: node linkType: hard -"@midnight-ntwrk/midnight-js-network-id@npm:0.2.5": - version: 0.2.5 - resolution: "@midnight-ntwrk/midnight-js-network-id@npm:0.2.5" - checksum: 10/1cc6736f381eb2b4b368d2ac048f332dc935f4147dfa2e6065cf525ce3a43dd4cf24328117e64479007f35323904b952f9a39f28810318b630b92e6e8954b002 +"@midnight-ntwrk/midnight-js-network-id@npm:1.0.0, @midnight-ntwrk/midnight-js-network-id@npm:^1.0.0": + version: 1.0.0 + resolution: "@midnight-ntwrk/midnight-js-network-id@npm:1.0.0" + checksum: 10/3edcdb713737368150c122b4315324f9294ba4c06a8adb1e5582a598cacdd4cca004c06e8bbeca2035fe72bfcb31abc9f0906a4328972985f085c857f3df70e3 languageName: node linkType: hard -"@midnight-ntwrk/midnight-js-node-zk-config-provider@npm:0.2.5": - version: 0.2.5 - resolution: "@midnight-ntwrk/midnight-js-node-zk-config-provider@npm:0.2.5" +"@midnight-ntwrk/midnight-js-node-zk-config-provider@npm:^1.0.0": + version: 1.0.0 + resolution: "@midnight-ntwrk/midnight-js-node-zk-config-provider@npm:1.0.0" dependencies: - "@midnight-ntwrk/midnight-js-types": "npm:0.2.5" - checksum: 10/b34768defa91c3aae7275644fffbd9f502fe1fa2a6b6ed8fe9620eb1a03b420becf516544137a4dd46271159aed5b25865f978c3bb92741ce77034540ae26771 + "@midnight-ntwrk/midnight-js-types": "npm:1.0.0" + checksum: 10/1a4a5dd6539a6288d1920d88f6d0edc985d19029246ddbf5eff6e01f5cefa54582b515631ce6c736d06aea7c32de5f9db95447788df7af23ad15994491a2b813 languageName: node linkType: hard -"@midnight-ntwrk/midnight-js-types@npm:0.2.5": - version: 0.2.5 - resolution: "@midnight-ntwrk/midnight-js-types@npm:0.2.5" +"@midnight-ntwrk/midnight-js-types@npm:1.0.0, @midnight-ntwrk/midnight-js-types@npm:^1.0.0": + version: 1.0.0 + resolution: "@midnight-ntwrk/midnight-js-types@npm:1.0.0" dependencies: rxjs: "npm:^7.5.0" - checksum: 10/0c113dfe8ef8c707f2cab1e1bd9c0350c0aa31d50030b3c1a0ce00804ef1db9d5d652ba238bb4c7a5d24507e7d8c13b0d0a1ac926d55abb62c7c94a7e2083707 + checksum: 10/a295476b6a921c87e69e844c4a4dfbe82c640c4af6a19899bcb2b3ec6b30b5b734862f248c1d22dfff061ab3980b3de1cecfb4370e32fe73ee77a6596cdd2a97 languageName: node linkType: hard -"@midnight-ntwrk/midnight-js-utils@npm:0.2.5": - version: 0.2.5 - resolution: "@midnight-ntwrk/midnight-js-utils@npm:0.2.5" - checksum: 10/5449becaf8c39f732430b01e26fb8906281113f78441c93f9a9b17231aa9ae304da1244cea30ac36ec1bef6a597124b0ba59689b4557456ce958115538bd2a23 +"@midnight-ntwrk/midnight-js-utils@npm:1.0.0, @midnight-ntwrk/midnight-js-utils@npm:^1.0.0": + version: 1.0.0 + resolution: "@midnight-ntwrk/midnight-js-utils@npm:1.0.0" + checksum: 10/f56d42a8a927fc2e174b15ed72202e00be79950345ec54ca3a4665352ea76f05d4b01c58cf5afb522a227889526bdeac0181279cd2bcd735b38a3051dcca1a75 languageName: node linkType: hard @@ -1003,7 +1004,7 @@ __metadata: languageName: node linkType: hard -"@midnight-ntwrk/wallet-api@npm:^3.4.2, @midnight-ntwrk/wallet-api@npm:^3.5.0": +"@midnight-ntwrk/wallet-api@npm:^3.4.2": version: 3.5.0 resolution: "@midnight-ntwrk/wallet-api@npm:3.5.0" dependencies: @@ -1014,18 +1015,51 @@ __metadata: languageName: node linkType: hard -"@midnight-ntwrk/wallet@npm:^3.7.3": - version: 3.7.5 - resolution: "@midnight-ntwrk/wallet@npm:3.7.5" +"@midnight-ntwrk/wallet-api@npm:^4.0.0": + version: 4.0.0 + resolution: "@midnight-ntwrk/wallet-api@npm:4.0.0" + dependencies: + "@midnight-ntwrk/zswap": "npm:^3.0.2" + peerDependencies: + rxjs: 7.x + checksum: 10/9056b6a3e53680bd8dc1639798984509dda3e66bca1a6ce43c2aa2ab0ba282fa3d89c4e91887396868b80b41038ddce7e4225782c2f5e1253cc15216804fb400 + languageName: node + linkType: hard + +"@midnight-ntwrk/wallet-sdk-address-format@npm:^1.0.0": + version: 1.0.0 + resolution: "@midnight-ntwrk/wallet-sdk-address-format@npm:1.0.0" dependencies: - "@midnight-ntwrk/wallet-api": "npm:^3.5.0" + "@scure/base": "npm:^1.1.9" + peerDependencies: + "@midnight-ntwrk/zswap": ^3.0.6 + checksum: 10/53813ac256d5e38d91e3b0b0bf47392246e027072f021f5cfe6c96daf3316ee6b61f7d45fdad627a1fce0d4ae4b4ec5c7eb5712ad5399f4b26dfd52c6203971c + languageName: node + linkType: hard + +"@midnight-ntwrk/wallet-sdk-capabilities@npm:^1.0.0": + version: 1.0.0 + resolution: "@midnight-ntwrk/wallet-sdk-capabilities@npm:1.0.0" + peerDependencies: + "@midnight-ntwrk/zswap": ^3.0.6 + checksum: 10/1d733bb60fc35161e47fe503aa85de8c63b807d3bfc479e5a25e6764dd527725e5e4b2ee46ae568083f330c3c5a1e5919934f4fc9ab66c0f01ca7d0249b88a69 + languageName: node + linkType: hard + +"@midnight-ntwrk/wallet@npm:^4.0.0": + version: 4.0.0 + resolution: "@midnight-ntwrk/wallet@npm:4.0.0" + dependencies: + "@midnight-ntwrk/wallet-api": "npm:^4.0.0" + "@midnight-ntwrk/wallet-sdk-address-format": "npm:^1.0.0" + "@midnight-ntwrk/wallet-sdk-capabilities": "npm:^1.0.0" "@midnight-ntwrk/zswap": "npm:^3.0.6" isomorphic-ws: "npm:^5.0.0" node-fetch: "npm:3.3.2" rxjs: "npm:^7.5" scale-ts: "npm:^1.1.0" ws: "npm:^8.8.1" - checksum: 10/60f57e17dc84cd980d22f6842cd82e4d1c02af20c5435833e2d8ab57559b89ac22e2fa5028f4a9d0d9da76bfcd2aa24efa2d87eef047967bbecb2c4772b9c3a0 + checksum: 10/3edf549f88c2b528b1107c9c18ca2d56beb80e201f7d3b000cc89f46d7c45c76961daa09c16b64062d7d9b6d9fc6a44ed9d59e9fb0deda6401cbf400521ba5b1 languageName: node linkType: hard @@ -1154,6 +1188,13 @@ __metadata: languageName: node linkType: hard +"@scure/base@npm:^1.1.9": + version: 1.2.4 + resolution: "@scure/base@npm:1.2.4" + checksum: 10/4b61679209af40143b49ce7b7570e1d9157c19df311ea6f57cd212d764b0b82222dbe3707334f08bec181caf1f047aca31aa91193c678d6548312cb3f9c82ab1 + languageName: node + linkType: hard + "@sinclair/typebox@npm:^0.27.8": version: 0.27.8 resolution: "@sinclair/typebox@npm:0.27.8" @@ -1592,18 +1633,17 @@ __metadata: languageName: node linkType: hard -"abstract-level@npm:^1.0.2, abstract-level@npm:^1.0.3, abstract-level@npm:^1.0.4": - version: 1.0.4 - resolution: "abstract-level@npm:1.0.4" +"abstract-level@npm:^2.0.0, abstract-level@npm:^2.0.1": + version: 2.0.2 + resolution: "abstract-level@npm:2.0.2" dependencies: buffer: "npm:^6.0.3" - catering: "npm:^2.1.0" is-buffer: "npm:^2.0.5" - level-supports: "npm:^4.0.0" + level-supports: "npm:^6.0.0" level-transcoder: "npm:^1.0.1" + maybe-combine-errors: "npm:^1.0.0" module-error: "npm:^1.0.1" - queue-microtask: "npm:^1.2.3" - checksum: 10/8edf4cf55b7b66b653296f53a643bcf1501074be099d8c44351595cd33f769b7b2aed216d5fffe1c99ebea4acf14f5ae093e98baa60ea1d236ea8a3387350ebb + checksum: 10/b41de35219ec70accc693be4c61f2b006891a51d408d328d489e745ea465022f5a5ee6afd4773ed91545668457f7e84530918bce5044953928977f7fccbe44bc languageName: node linkType: hard @@ -2091,15 +2131,12 @@ __metadata: languageName: node linkType: hard -"browser-level@npm:^1.0.1": - version: 1.0.1 - resolution: "browser-level@npm:1.0.1" +"browser-level@npm:^2.0.0": + version: 2.0.0 + resolution: "browser-level@npm:2.0.0" dependencies: - abstract-level: "npm:^1.0.2" - catering: "npm:^2.1.1" - module-error: "npm:^1.0.2" - run-parallel-limit: "npm:^1.1.0" - checksum: 10/e712569111782da76853fecf648b43ff878ff2301c2830a9e7399685b646824a85f304dea5f023e02ee41a63a972f9aad734bd411069095adc9c79784fc649a5 + abstract-level: "npm:^2.0.1" + checksum: 10/47b82677b533717386c176f59c2ef3dd5032d083933808c6614f8122f31ce13cecb20d538130d0d47ac573e848e505163378f01e8962c43fa7e70739b3e6951a languageName: node linkType: hard @@ -2279,13 +2316,6 @@ __metadata: languageName: node linkType: hard -"catering@npm:^2.1.0, catering@npm:^2.1.1": - version: 2.1.1 - resolution: "catering@npm:2.1.1" - checksum: 10/4669c9fa5f3a73273535fb458a964d8aba12dc5102d8487049cf03623bef3cdff4b5d9f92ff04c00f1001057a7cc7df6e700752ac622c2a7baf7bcff34166683 - languageName: node - linkType: hard - "chalk@npm:^4.0.0, chalk@npm:^4.0.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" @@ -2331,17 +2361,16 @@ __metadata: languageName: node linkType: hard -"classic-level@npm:^1.2.0": - version: 1.4.1 - resolution: "classic-level@npm:1.4.1" +"classic-level@npm:^2.0.0": + version: 2.0.0 + resolution: "classic-level@npm:2.0.0" dependencies: - abstract-level: "npm:^1.0.2" - catering: "npm:^2.1.0" + abstract-level: "npm:^2.0.0" module-error: "npm:^1.0.1" napi-macros: "npm:^2.2.2" node-gyp: "npm:latest" node-gyp-build: "npm:^4.3.0" - checksum: 10/11f9362301477cb5cf3b147e5846754e0e4296231e265145101403f4a5cb797a685b6a9b6b4c880a42b05772f846a222a5a7a563262ca15b5ca03e25e9a805db + checksum: 10/730efa24dfcfba769c0c4c4100e04fae76587e08ab7ac5e93811d01c7307fa2e96ade054cf3c00cf125f4dc4a1f6e5bdfb77d0b3196b786afac2607a1fdbe4b8 languageName: node linkType: hard @@ -3365,6 +3394,13 @@ __metadata: languageName: node linkType: hard +"fetch-retry@npm:^6.0.0": + version: 6.0.0 + resolution: "fetch-retry@npm:6.0.0" + checksum: 10/0c8d3082e2d76fff2df75adef6280bc854bc36fd3ef38506674f0216d0d819e2efd14da7477d3f1732415aea1d2cfde7cd3e1aeae46f45f2adbfc5133296e8de + languageName: node + linkType: hard + "file-entry-cache@npm:^6.0.1": version: 6.0.1 resolution: "file-entry-cache@npm:6.0.1" @@ -5027,10 +5063,10 @@ __metadata: languageName: node linkType: hard -"level-supports@npm:^4.0.0": - version: 4.0.1 - resolution: "level-supports@npm:4.0.1" - checksum: 10/e2f177af813a25af29d15406a14240e2e10e5efb1c35b03643c885ac5931af760b9337826506b6395f98cf6b1e68ba294bfc345a248a1ae3f9c69e08e81824b2 +"level-supports@npm:^6.0.0": + version: 6.2.0 + resolution: "level-supports@npm:6.2.0" + checksum: 10/450c04839cf42ac7c73085b4928f1c1c51d9ab179aac9102cc8ef2389faf2d06cebaf57df2d025da89d78465004ccf29bfd972a04b0b35d5d423fa3f4516f906 languageName: node linkType: hard @@ -5044,14 +5080,14 @@ __metadata: languageName: node linkType: hard -"level@npm:^8.0.0": - version: 8.0.1 - resolution: "level@npm:8.0.1" +"level@npm:^9.0.0": + version: 9.0.0 + resolution: "level@npm:9.0.0" dependencies: - abstract-level: "npm:^1.0.4" - browser-level: "npm:^1.0.1" - classic-level: "npm:^1.2.0" - checksum: 10/a9c6d1fc50e30b2cc80b3c975b34de0eb12daab7fb4f8a546a28303705a45685340a904544fcd32e9a380fae7c62474ebd9cdb0108021ddbc7b88dd9c913f126 + abstract-level: "npm:^2.0.1" + browser-level: "npm:^2.0.0" + classic-level: "npm:^2.0.0" + checksum: 10/6d9dc32300dc6d2680ab3f68cd6862c7db48840070f99ccd51a1f5a6999bf41ccb2e20eeb9fe99bcf9052ff4370d783e01522fdbebe8c981cb9afbc72a8e70f0 languageName: node linkType: hard @@ -5196,6 +5232,13 @@ __metadata: languageName: node linkType: hard +"maybe-combine-errors@npm:^1.0.0": + version: 1.0.0 + resolution: "maybe-combine-errors@npm:1.0.0" + checksum: 10/16bb6d3dcf79fc61f5a04abe948c4c81cae0da6ee5da9a1d8196f1723b069d6ab60f752bc208e18481e2b82de146e068bc462558c65ecdf96fed0d021a1aa6ab + languageName: node + linkType: hard + "merge-stream@npm:^2.0.0": version: 2.0.0 resolution: "merge-stream@npm:2.0.0" @@ -5372,7 +5415,7 @@ __metadata: languageName: node linkType: hard -"module-error@npm:^1.0.1, module-error@npm:^1.0.2": +"module-error@npm:^1.0.1": version: 1.0.2 resolution: "module-error@npm:1.0.2" checksum: 10/5d653e35bd55b3e95f8aee2cdac108082ea892e71b8f651be92cde43e4ee86abee4fa8bd7fc3fe5e68b63926d42f63c54cd17b87a560c31f18739295575a3962 @@ -6024,7 +6067,7 @@ __metadata: languageName: node linkType: hard -"queue-microtask@npm:^1.2.2, queue-microtask@npm:^1.2.3": +"queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" checksum: 10/72900df0616e473e824202113c3df6abae59150dfb73ed13273503127235320e9c8ca4aaaaccfd58cf417c6ca92a6e68ee9a5c3182886ae949a768639b388a7b @@ -6264,18 +6307,18 @@ __metadata: dependencies: "@midnight-ntwrk/compact-runtime": "npm:^0.7.0" "@midnight-ntwrk/dapp-connector-api": "npm:^1.2.2" - "@midnight-ntwrk/ledger": "npm:^3.0.2" - "@midnight-ntwrk/midnight-js-contracts": "npm:0.2.5" - "@midnight-ntwrk/midnight-js-fetch-zk-config-provider": "npm:0.2.5" - "@midnight-ntwrk/midnight-js-http-client-proof-provider": "npm:0.2.5" - "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "npm:0.2.5" - "@midnight-ntwrk/midnight-js-level-private-state-provider": "npm:0.2.5" - "@midnight-ntwrk/midnight-js-network-id": "npm:0.2.5" - "@midnight-ntwrk/midnight-js-node-zk-config-provider": "npm:0.2.5" - "@midnight-ntwrk/midnight-js-types": "npm:0.2.5" - "@midnight-ntwrk/midnight-js-utils": "npm:0.2.5" - "@midnight-ntwrk/wallet": "npm:^3.7.3" - "@midnight-ntwrk/wallet-api": "npm:^3.5.0" + "@midnight-ntwrk/ledger": "npm:^3.0.6" + "@midnight-ntwrk/midnight-js-contracts": "npm:^1.0.0" + "@midnight-ntwrk/midnight-js-fetch-zk-config-provider": "npm:^1.0.0" + "@midnight-ntwrk/midnight-js-http-client-proof-provider": "npm:^1.0.0" + "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "npm:^1.0.0" + "@midnight-ntwrk/midnight-js-level-private-state-provider": "npm:^1.0.0" + "@midnight-ntwrk/midnight-js-network-id": "npm:^1.0.0" + "@midnight-ntwrk/midnight-js-node-zk-config-provider": "npm:^1.0.0" + "@midnight-ntwrk/midnight-js-types": "npm:^1.0.0" + "@midnight-ntwrk/midnight-js-utils": "npm:^1.0.0" + "@midnight-ntwrk/wallet": "npm:^4.0.0" + "@midnight-ntwrk/wallet-api": "npm:^4.0.0" "@types/jest": "npm:^29.5.6" "@types/node": "npm:^18.18.6" "@typescript-eslint/eslint-plugin": "npm:^6.8.0" @@ -6306,15 +6349,6 @@ __metadata: languageName: unknown linkType: soft -"run-parallel-limit@npm:^1.1.0": - version: 1.1.0 - resolution: "run-parallel-limit@npm:1.1.0" - dependencies: - queue-microtask: "npm:^1.2.2" - checksum: 10/672c3b87e7f939c684b9965222b361421db0930223ed1e43ebf0e7e48ccc1a022ea4de080bef4d5468434e2577c33b7681e3f03b7593fdc49ad250a55381123c - languageName: node - linkType: hard - "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -6831,12 +6865,12 @@ __metadata: languageName: node linkType: hard -"superjson@npm:^1.13.1": - version: 1.13.3 - resolution: "superjson@npm:1.13.3" +"superjson@npm:^2.0.0": + version: 2.2.2 + resolution: "superjson@npm:2.2.2" dependencies: copy-anything: "npm:^3.0.2" - checksum: 10/71a186c513a9821e58264c0563cd1b3cf07d3b5ba53a09cc5c1a604d8ffeacac976a6ba1b5d5b3c71b6ab5a1941dfba5a15e3f106ad3ef22fe8d5eee3e2be052 + checksum: 10/6fdc709db4f69d586a18379948e0ade8268c851c791701fea960e29cea12672d7561b4ca89c4049c2e787eb1cec08a51df51d357aa6852078bc0d71d7e17b401 languageName: node linkType: hard From 780132e79168952b773116a134d060285f0c47a5 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 15 Apr 2025 23:20:32 -0500 Subject: [PATCH 002/202] remove unused deps --- package.json | 19 +- yarn.lock | 711 +-------------------------------------------------- 2 files changed, 9 insertions(+), 721 deletions(-) diff --git a/package.json b/package.json index bfe904a3..aef03893 100644 --- a/package.json +++ b/package.json @@ -11,25 +11,8 @@ "build": "turbo run build", "lint": "turbo run lint" }, - "resolutions": { - "@midnight-ntwrk/compact-runtime/@midnight-ntwrk/onchain-runtime": "^0.2.2", - "@midnight-ntwrk/zswap": "^3.0.2" - }, "dependencies": { "@midnight-ntwrk/compact-runtime": "^0.7.0", - "@midnight-ntwrk/dapp-connector-api": "^1.2.2", - "@midnight-ntwrk/ledger": "^3.0.6", - "@midnight-ntwrk/midnight-js-contracts": "^1.0.0", - "@midnight-ntwrk/midnight-js-fetch-zk-config-provider": "^1.0.0", - "@midnight-ntwrk/midnight-js-http-client-proof-provider": "^1.0.0", - "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "^1.0.0", - "@midnight-ntwrk/midnight-js-level-private-state-provider": "^1.0.0", - "@midnight-ntwrk/midnight-js-network-id": "^1.0.0", - "@midnight-ntwrk/midnight-js-node-zk-config-provider": "^1.0.0", - "@midnight-ntwrk/midnight-js-types": "^1.0.0", - "@midnight-ntwrk/midnight-js-utils": "^1.0.0", - "@midnight-ntwrk/wallet": "^4.0.0", - "@midnight-ntwrk/wallet-api": "^4.0.0", "fp-ts": "^2.16.1", "io-ts": "^2.2.20", "pino": "^8.16.0", @@ -37,6 +20,8 @@ "rxjs": "^7.8.1" }, "devDependencies": { + "@midnight-ntwrk/ledger": "^3.0.6", + "@midnight-ntwrk/zswap": "^3.0.6", "@types/jest": "^29.5.6", "@types/node": "^18.18.6", "@typescript-eslint/eslint-plugin": "^6.8.0", diff --git a/yarn.lock b/yarn.lock index 7eb3114a..64965d1e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,42 +15,6 @@ __metadata: languageName: node linkType: hard -"@apollo/client@npm:^3.8.2": - version: 3.13.1 - resolution: "@apollo/client@npm:3.13.1" - dependencies: - "@graphql-typed-document-node/core": "npm:^3.1.1" - "@wry/caches": "npm:^1.0.0" - "@wry/equality": "npm:^0.5.6" - "@wry/trie": "npm:^0.5.0" - graphql-tag: "npm:^2.12.6" - hoist-non-react-statics: "npm:^3.3.2" - optimism: "npm:^0.18.0" - prop-types: "npm:^15.7.2" - rehackt: "npm:^0.1.0" - symbol-observable: "npm:^4.0.0" - ts-invariant: "npm:^0.10.3" - tslib: "npm:^2.3.0" - zen-observable-ts: "npm:^1.2.5" - peerDependencies: - graphql: ^15.0.0 || ^16.0.0 - graphql-ws: ^5.5.5 || ^6.0.3 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc - subscriptions-transport-ws: ^0.9.0 || ^0.11.0 - peerDependenciesMeta: - graphql-ws: - optional: true - react: - optional: true - react-dom: - optional: true - subscriptions-transport-ws: - optional: true - checksum: 10/d3744a5416c7ba33057b1ed247fa4b30da167a6b490898968e6e03870424906c3b4b1910829dc5b26622393e3f203b6ad26e7f6a2c2e9505dc0f9e915432482a - languageName: node - linkType: hard - "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.26.2": version: 7.26.2 resolution: "@babel/code-frame@npm:7.26.2" @@ -436,16 +400,6 @@ __metadata: languageName: node linkType: hard -"@dao-xyz/borsh@npm:^5.1.5": - version: 5.2.3 - resolution: "@dao-xyz/borsh@npm:5.2.3" - dependencies: - "@protobufjs/float": "npm:^1.0.2" - "@protobufjs/utf8": "npm:^1.1.0" - checksum: 10/87480526dd501ee5726aa39dccb27018e82e00a0d21ec7eaed6f23dd80eb94662f2395b17b09b44cd34ff06ffde9458d7734940e42cf5e7b5daf2c7bc03cfe3a - languageName: node - linkType: hard - "@eslint-community/eslint-utils@npm:^4.1.2, @eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": version: 4.4.1 resolution: "@eslint-community/eslint-utils@npm:4.4.1" @@ -495,15 +449,6 @@ __metadata: languageName: node linkType: hard -"@graphql-typed-document-node/core@npm:^3.1.1": - version: 3.2.0 - resolution: "@graphql-typed-document-node/core@npm:3.2.0" - peerDependencies: - graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/fa44443accd28c8cf4cb96aaaf39d144a22e8b091b13366843f4e97d19c7bfeaf609ce3c7603a4aeffe385081eaf8ea245d078633a7324c11c5ec4b2011bb76d - languageName: node - linkType: hard - "@humanwhocodes/config-array@npm:^0.13.0": version: 0.13.0 resolution: "@humanwhocodes/config-array@npm:0.13.0" @@ -877,17 +822,6 @@ __metadata: languageName: unknown linkType: soft -"@midnight-ntwrk/dapp-connector-api@npm:^1.2.2": - version: 1.2.3 - resolution: "@midnight-ntwrk/dapp-connector-api@npm:1.2.3" - dependencies: - "@midnight-ntwrk/wallet-api": "npm:^3.4.2" - "@midnight-ntwrk/zswap": "npm:^3.0.2" - ts-custom-error: "npm:^3.3.1" - checksum: 10/2ecc68a486fa535b42361ba89e1703485ed25c533ee4e312bcd16bde83e990ad37c4f928f08531954da172a9773d0561473154c4feb034915779a491f8816ce0 - languageName: node - linkType: hard - "@midnight-ntwrk/ledger@npm:^3.0.6": version: 3.0.6 resolution: "@midnight-ntwrk/ledger@npm:3.0.6" @@ -895,175 +829,14 @@ __metadata: languageName: node linkType: hard -"@midnight-ntwrk/midnight-js-contracts@npm:^1.0.0": - version: 1.0.0 - resolution: "@midnight-ntwrk/midnight-js-contracts@npm:1.0.0" - dependencies: - "@midnight-ntwrk/midnight-js-network-id": "npm:1.0.0" - "@midnight-ntwrk/midnight-js-types": "npm:1.0.0" - "@midnight-ntwrk/midnight-js-utils": "npm:1.0.0" - checksum: 10/41a33e55788f881222ebf764b70206b420986b56c911f9534a261dec4ad4554e79871ef751a5fddcf68e552f5248a5462cf410edc5bb4a88f991140eed5942df - languageName: node - linkType: hard - -"@midnight-ntwrk/midnight-js-fetch-zk-config-provider@npm:^1.0.0": - version: 1.0.0 - resolution: "@midnight-ntwrk/midnight-js-fetch-zk-config-provider@npm:1.0.0" - dependencies: - "@midnight-ntwrk/midnight-js-types": "npm:1.0.0" - cross-fetch: "npm:^4.0.0" - checksum: 10/8756a60a59d92411239274bb2532c2344a3bba962faae87dab2193c237ee3499f3adbecfe7b8f06e3a2e85a6a435fda90b8a77523f92d936cd0101721d6151bc - languageName: node - linkType: hard - -"@midnight-ntwrk/midnight-js-http-client-proof-provider@npm:^1.0.0": - version: 1.0.0 - resolution: "@midnight-ntwrk/midnight-js-http-client-proof-provider@npm:1.0.0" - dependencies: - "@dao-xyz/borsh": "npm:^5.1.5" - "@midnight-ntwrk/midnight-js-network-id": "npm:1.0.0" - "@midnight-ntwrk/midnight-js-types": "npm:1.0.0" - cross-fetch: "npm:^4.0.0" - fetch-retry: "npm:^6.0.0" - lodash: "npm:^4.17.21" - checksum: 10/7469fa5b66b540aceca22ef5f4069e7dc0c5c5f776510fde798dcf2638a4a79dc188aec78fa378a50ca6d8e09a5efcff939696e86de339dcb3f2587e56530f9c - languageName: node - linkType: hard - -"@midnight-ntwrk/midnight-js-indexer-public-data-provider@npm:^1.0.0": - version: 1.0.0 - resolution: "@midnight-ntwrk/midnight-js-indexer-public-data-provider@npm:1.0.0" - dependencies: - "@apollo/client": "npm:^3.8.2" - "@midnight-ntwrk/midnight-js-network-id": "npm:1.0.0" - "@midnight-ntwrk/midnight-js-types": "npm:1.0.0" - buffer: "npm:^6.0.3" - cross-fetch: "npm:^4.0.0" - graphql: "npm:^16.8.0" - graphql-ws: "npm:^5.14.0" - isomorphic-ws: "npm:^5.0.0" - rxjs: "npm:^7.5.0" - ws: "npm:^8.14.2" - zen-observable-ts: "npm:^1.1.0" - checksum: 10/a179af0cc0bf7d9ce2709193d399a54f9b6a6b78062acedd72059acedcd7630400e69d4b39d1a385d34c6faa558c77cf6813ea4956155c3bdc1c046ee33cfcd7 - languageName: node - linkType: hard - -"@midnight-ntwrk/midnight-js-level-private-state-provider@npm:^1.0.0": - version: 1.0.0 - resolution: "@midnight-ntwrk/midnight-js-level-private-state-provider@npm:1.0.0" - dependencies: - "@midnight-ntwrk/midnight-js-types": "npm:1.0.0" - abstract-level: "npm:^2.0.0" - buffer: "npm:^6.0.3" - fp-ts: "npm:^2.16.1" - io-ts: "npm:^2.2.20" - level: "npm:^9.0.0" - lodash: "npm:^4.17.21" - superjson: "npm:^2.0.0" - checksum: 10/cf909ff1ed397888abc06e4c282b9f522742264b3952c5ac7abbfbbb82f4c5d791e6eeea1fc8f849cb17519d468235c114def877f5079df96efd5a8cb6cf7885 - languageName: node - linkType: hard - -"@midnight-ntwrk/midnight-js-network-id@npm:1.0.0, @midnight-ntwrk/midnight-js-network-id@npm:^1.0.0": - version: 1.0.0 - resolution: "@midnight-ntwrk/midnight-js-network-id@npm:1.0.0" - checksum: 10/3edcdb713737368150c122b4315324f9294ba4c06a8adb1e5582a598cacdd4cca004c06e8bbeca2035fe72bfcb31abc9f0906a4328972985f085c857f3df70e3 - languageName: node - linkType: hard - -"@midnight-ntwrk/midnight-js-node-zk-config-provider@npm:^1.0.0": - version: 1.0.0 - resolution: "@midnight-ntwrk/midnight-js-node-zk-config-provider@npm:1.0.0" - dependencies: - "@midnight-ntwrk/midnight-js-types": "npm:1.0.0" - checksum: 10/1a4a5dd6539a6288d1920d88f6d0edc985d19029246ddbf5eff6e01f5cefa54582b515631ce6c736d06aea7c32de5f9db95447788df7af23ad15994491a2b813 - languageName: node - linkType: hard - -"@midnight-ntwrk/midnight-js-types@npm:1.0.0, @midnight-ntwrk/midnight-js-types@npm:^1.0.0": - version: 1.0.0 - resolution: "@midnight-ntwrk/midnight-js-types@npm:1.0.0" - dependencies: - rxjs: "npm:^7.5.0" - checksum: 10/a295476b6a921c87e69e844c4a4dfbe82c640c4af6a19899bcb2b3ec6b30b5b734862f248c1d22dfff061ab3980b3de1cecfb4370e32fe73ee77a6596cdd2a97 - languageName: node - linkType: hard - -"@midnight-ntwrk/midnight-js-utils@npm:1.0.0, @midnight-ntwrk/midnight-js-utils@npm:^1.0.0": - version: 1.0.0 - resolution: "@midnight-ntwrk/midnight-js-utils@npm:1.0.0" - checksum: 10/f56d42a8a927fc2e174b15ed72202e00be79950345ec54ca3a4665352ea76f05d4b01c58cf5afb522a227889526bdeac0181279cd2bcd735b38a3051dcca1a75 - languageName: node - linkType: hard - -"@midnight-ntwrk/onchain-runtime@npm:^0.2.2": +"@midnight-ntwrk/onchain-runtime@npm:^0.2.0": version: 0.2.6 resolution: "@midnight-ntwrk/onchain-runtime@npm:0.2.6" checksum: 10/6c7bf8a6d9dfd4560f1da67a0b0a2a89331eecb8110b07f7d2eab1c311cadc8f5fc42e46118649766490e534b504c0e7cad94cfdcccdbb1b09d9f69877707ebd languageName: node linkType: hard -"@midnight-ntwrk/wallet-api@npm:^3.4.2": - version: 3.5.0 - resolution: "@midnight-ntwrk/wallet-api@npm:3.5.0" - dependencies: - "@midnight-ntwrk/zswap": "npm:^3.0.2" - peerDependencies: - rxjs: 7.x - checksum: 10/56777aa3d5df442767c88c4839ae397b50c09057542635a52f276999e1c831fd138bd0baf519cd339948c420e040cd49908086599fa1417f8d61123aafb514ad - languageName: node - linkType: hard - -"@midnight-ntwrk/wallet-api@npm:^4.0.0": - version: 4.0.0 - resolution: "@midnight-ntwrk/wallet-api@npm:4.0.0" - dependencies: - "@midnight-ntwrk/zswap": "npm:^3.0.2" - peerDependencies: - rxjs: 7.x - checksum: 10/9056b6a3e53680bd8dc1639798984509dda3e66bca1a6ce43c2aa2ab0ba282fa3d89c4e91887396868b80b41038ddce7e4225782c2f5e1253cc15216804fb400 - languageName: node - linkType: hard - -"@midnight-ntwrk/wallet-sdk-address-format@npm:^1.0.0": - version: 1.0.0 - resolution: "@midnight-ntwrk/wallet-sdk-address-format@npm:1.0.0" - dependencies: - "@scure/base": "npm:^1.1.9" - peerDependencies: - "@midnight-ntwrk/zswap": ^3.0.6 - checksum: 10/53813ac256d5e38d91e3b0b0bf47392246e027072f021f5cfe6c96daf3316ee6b61f7d45fdad627a1fce0d4ae4b4ec5c7eb5712ad5399f4b26dfd52c6203971c - languageName: node - linkType: hard - -"@midnight-ntwrk/wallet-sdk-capabilities@npm:^1.0.0": - version: 1.0.0 - resolution: "@midnight-ntwrk/wallet-sdk-capabilities@npm:1.0.0" - peerDependencies: - "@midnight-ntwrk/zswap": ^3.0.6 - checksum: 10/1d733bb60fc35161e47fe503aa85de8c63b807d3bfc479e5a25e6764dd527725e5e4b2ee46ae568083f330c3c5a1e5919934f4fc9ab66c0f01ca7d0249b88a69 - languageName: node - linkType: hard - -"@midnight-ntwrk/wallet@npm:^4.0.0": - version: 4.0.0 - resolution: "@midnight-ntwrk/wallet@npm:4.0.0" - dependencies: - "@midnight-ntwrk/wallet-api": "npm:^4.0.0" - "@midnight-ntwrk/wallet-sdk-address-format": "npm:^1.0.0" - "@midnight-ntwrk/wallet-sdk-capabilities": "npm:^1.0.0" - "@midnight-ntwrk/zswap": "npm:^3.0.6" - isomorphic-ws: "npm:^5.0.0" - node-fetch: "npm:3.3.2" - rxjs: "npm:^7.5" - scale-ts: "npm:^1.1.0" - ws: "npm:^8.8.1" - checksum: 10/3edf549f88c2b528b1107c9c18ca2d56beb80e201f7d3b000cc89f46d7c45c76961daa09c16b64062d7d9b6d9fc6a44ed9d59e9fb0deda6401cbf400521ba5b1 - languageName: node - linkType: hard - -"@midnight-ntwrk/zswap@npm:^3.0.2": +"@midnight-ntwrk/zswap@npm:^3.0.6": version: 3.0.6 resolution: "@midnight-ntwrk/zswap@npm:3.0.6" checksum: 10/d095b9380d1ca9f0f94286ce31bd8aaee0ee6458d8681d5b021198c1125e9e9276ebf7bd6ca5221c800ec1d8ed27909faf866268e3673d36a5126c2c9b99b595 @@ -1167,20 +940,6 @@ __metadata: languageName: node linkType: hard -"@protobufjs/float@npm:^1.0.2": - version: 1.0.2 - resolution: "@protobufjs/float@npm:1.0.2" - checksum: 10/634c2c989da0ef2f4f19373d64187e2a79f598c5fb7991afb689d29a2ea17c14b796b29725945fa34b9493c17fb799e08ac0a7ccaae460ee1757d3083ed35187 - languageName: node - linkType: hard - -"@protobufjs/utf8@npm:^1.1.0": - version: 1.1.0 - resolution: "@protobufjs/utf8@npm:1.1.0" - checksum: 10/131e289c57534c1d73a0e55782d6751dd821db1583cb2f7f7e017c9d6747addaebe79f28120b2e0185395d990aad347fb14ffa73ef4096fa38508d61a0e64602 - languageName: node - linkType: hard - "@rtsao/scc@npm:^1.1.0": version: 1.1.0 resolution: "@rtsao/scc@npm:1.1.0" @@ -1188,13 +947,6 @@ __metadata: languageName: node linkType: hard -"@scure/base@npm:^1.1.9": - version: 1.2.4 - resolution: "@scure/base@npm:1.2.4" - checksum: 10/4b61679209af40143b49ce7b7570e1d9157c19df311ea6f57cd212d764b0b82222dbe3707334f08bec181caf1f047aca31aa91193c678d6548312cb3f9c82ab1 - languageName: node - linkType: hard - "@sinclair/typebox@npm:^0.27.8": version: 0.27.8 resolution: "@sinclair/typebox@npm:0.27.8" @@ -1581,42 +1333,6 @@ __metadata: languageName: node linkType: hard -"@wry/caches@npm:^1.0.0": - version: 1.0.1 - resolution: "@wry/caches@npm:1.0.1" - dependencies: - tslib: "npm:^2.3.0" - checksum: 10/055f592ee52b5fd9aa86e274e54e4a8b2650f619000bf6f61880ce14aaf47eb2ab34f3ada2eab964fe8b2f19bf8097ecacddcea4638fcc64c3d3a0a512aaa07c - languageName: node - linkType: hard - -"@wry/context@npm:^0.7.0": - version: 0.7.4 - resolution: "@wry/context@npm:0.7.4" - dependencies: - tslib: "npm:^2.3.0" - checksum: 10/70d648949a97a035b2be2d6ddb716d4162113e850ab2c4c86331b2da94a7e826204080ce04eee2a95665bd3a0b245bf2ea3aae9adfa57b004ae0d2d49bdb5c8f - languageName: node - linkType: hard - -"@wry/equality@npm:^0.5.6": - version: 0.5.7 - resolution: "@wry/equality@npm:0.5.7" - dependencies: - tslib: "npm:^2.3.0" - checksum: 10/69dccf33c0c41fd7ec5550f5703b857c6484a949412ad747001da941270ea436648c3ab988a2091765304249585ac30c7b417fad8be9a7ce19c1221f71548e35 - languageName: node - linkType: hard - -"@wry/trie@npm:^0.5.0": - version: 0.5.0 - resolution: "@wry/trie@npm:0.5.0" - dependencies: - tslib: "npm:^2.3.0" - checksum: 10/578a08f3a96256c9b163230337183d9511fd775bdfe147a30561ccaacedc9ce33b9731ee6e591bb1f5f53e41b26789e519b47dff5100c7bf4e1cd2df3062f797 - languageName: node - linkType: hard - "abbrev@npm:^3.0.0": version: 3.0.0 resolution: "abbrev@npm:3.0.0" @@ -1633,20 +1349,6 @@ __metadata: languageName: node linkType: hard -"abstract-level@npm:^2.0.0, abstract-level@npm:^2.0.1": - version: 2.0.2 - resolution: "abstract-level@npm:2.0.2" - dependencies: - buffer: "npm:^6.0.3" - is-buffer: "npm:^2.0.5" - level-supports: "npm:^6.0.0" - level-transcoder: "npm:^1.0.1" - maybe-combine-errors: "npm:^1.0.0" - module-error: "npm:^1.0.1" - checksum: 10/b41de35219ec70accc693be4c61f2b006891a51d408d328d489e745ea465022f5a5ee6afd4773ed91545668457f7e84530918bce5044953928977f7fccbe44bc - languageName: node - linkType: hard - "acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -2131,15 +1833,6 @@ __metadata: languageName: node linkType: hard -"browser-level@npm:^2.0.0": - version: 2.0.0 - resolution: "browser-level@npm:2.0.0" - dependencies: - abstract-level: "npm:^2.0.1" - checksum: 10/47b82677b533717386c176f59c2ef3dd5032d083933808c6614f8122f31ce13cecb20d538130d0d47ac573e848e505163378f01e8962c43fa7e70739b3e6951a - languageName: node - linkType: hard - "browserslist@npm:^4.24.0": version: 4.24.4 resolution: "browserslist@npm:4.24.4" @@ -2361,19 +2054,6 @@ __metadata: languageName: node linkType: hard -"classic-level@npm:^2.0.0": - version: 2.0.0 - resolution: "classic-level@npm:2.0.0" - dependencies: - abstract-level: "npm:^2.0.0" - module-error: "npm:^1.0.1" - napi-macros: "npm:^2.2.2" - node-gyp: "npm:latest" - node-gyp-build: "npm:^4.3.0" - checksum: 10/730efa24dfcfba769c0c4c4100e04fae76587e08ab7ac5e93811d01c7307fa2e96ade054cf3c00cf125f4dc4a1f6e5bdfb77d0b3196b786afac2607a1fdbe4b8 - languageName: node - linkType: hard - "cliui@npm:^8.0.1": version: 8.0.1 resolution: "cliui@npm:8.0.1" @@ -2449,15 +2129,6 @@ __metadata: languageName: node linkType: hard -"copy-anything@npm:^3.0.2": - version: 3.0.5 - resolution: "copy-anything@npm:3.0.5" - dependencies: - is-what: "npm:^4.1.8" - checksum: 10/4c41385a94a1cff6352a954f9b1c05b6bb1b70713a2d31f4c7b188ae7187ce00ddcc9c09bd58d24cd35b67fc6dd84df5954c0be86ea10700ff74e677db3cb09c - languageName: node - linkType: hard - "core-util-is@npm:~1.0.0": version: 1.0.3 resolution: "core-util-is@npm:1.0.3" @@ -2519,15 +2190,6 @@ __metadata: languageName: node linkType: hard -"cross-fetch@npm:^4.0.0": - version: 4.1.0 - resolution: "cross-fetch@npm:4.1.0" - dependencies: - node-fetch: "npm:^2.7.0" - checksum: 10/07624940607b64777d27ec9c668ddb6649e8c59ee0a5a10e63a51ce857e2bbb1294a45854a31c10eccb91b65909a5b199fcb0217339b44156f85900a7384f489 - languageName: node - linkType: hard - "cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" @@ -2539,13 +2201,6 @@ __metadata: languageName: node linkType: hard -"data-uri-to-buffer@npm:^4.0.0": - version: 4.0.1 - resolution: "data-uri-to-buffer@npm:4.0.1" - checksum: 10/0d0790b67ffec5302f204c2ccca4494f70b4e2d940fea3d36b09f0bb2b8539c2e86690429eb1f1dc4bcc9e4df0644193073e63d9ee48ac9fce79ec1506e4aa4c - languageName: node - linkType: hard - "data-view-buffer@npm:^1.0.2": version: 1.0.2 resolution: "data-view-buffer@npm:1.0.2" @@ -3384,23 +3039,6 @@ __metadata: languageName: node linkType: hard -"fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4": - version: 3.2.0 - resolution: "fetch-blob@npm:3.2.0" - dependencies: - node-domexception: "npm:^1.0.0" - web-streams-polyfill: "npm:^3.0.3" - checksum: 10/5264ecceb5fdc19eb51d1d0359921f12730941e333019e673e71eb73921146dceabcb0b8f534582be4497312d656508a439ad0f5edeec2b29ab2e10c72a1f86b - languageName: node - linkType: hard - -"fetch-retry@npm:^6.0.0": - version: 6.0.0 - resolution: "fetch-retry@npm:6.0.0" - checksum: 10/0c8d3082e2d76fff2df75adef6280bc854bc36fd3ef38506674f0216d0d819e2efd14da7477d3f1732415aea1d2cfde7cd3e1aeae46f45f2adbfc5133296e8de - languageName: node - linkType: hard - "file-entry-cache@npm:^6.0.1": version: 6.0.1 resolution: "file-entry-cache@npm:6.0.1" @@ -3485,15 +3123,6 @@ __metadata: languageName: node linkType: hard -"formdata-polyfill@npm:^4.0.10": - version: 4.0.10 - resolution: "formdata-polyfill@npm:4.0.10" - dependencies: - fetch-blob: "npm:^3.1.2" - checksum: 10/9b5001d2edef3c9449ac3f48bd4f8cc92e7d0f2e7c1a5c8ba555ad4e77535cc5cf621fabe49e97f304067037282dd9093b9160a3cb533e46420b446c4e6bc06f - languageName: node - linkType: hard - "fp-ts@npm:^2.16.1": version: 2.16.9 resolution: "fp-ts@npm:2.16.9" @@ -3774,33 +3403,6 @@ __metadata: languageName: node linkType: hard -"graphql-tag@npm:^2.12.6": - version: 2.12.6 - resolution: "graphql-tag@npm:2.12.6" - dependencies: - tslib: "npm:^2.1.0" - peerDependencies: - graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - checksum: 10/23a2bc1d3fbeae86444204e0ac08522e09dc369559ba75768e47421a7321b59f352fb5b2c9a5c37d3cf6de890dca4e5ac47e740c7cc622e728572ecaa649089e - languageName: node - linkType: hard - -"graphql-ws@npm:^5.14.0": - version: 5.16.2 - resolution: "graphql-ws@npm:5.16.2" - peerDependencies: - graphql: ">=0.11 <=16" - checksum: 10/6647bfe640b467f27aaf5ee044c1d114fe266e82cda4ebbb4368d5a4e98df5d2de9d6be70d28eb5e821d87fbf8964c3a8a18abf87c76d4f148800fd8e0488c3d - languageName: node - linkType: hard - -"graphql@npm:^16.8.0": - version: 16.10.0 - resolution: "graphql@npm:16.10.0" - checksum: 10/d42cf81ddcf3a61dfb213217576bf33c326f15b02c4cee369b373dc74100cbdcdc4479b3b797e79b654dabd8fddf50ef65ff75420e9ce5596c02e21f24c9126a - languageName: node - linkType: hard - "has-bigints@npm:^1.0.2": version: 1.1.0 resolution: "has-bigints@npm:1.1.0" @@ -3865,15 +3467,6 @@ __metadata: languageName: node linkType: hard -"hoist-non-react-statics@npm:^3.3.2": - version: 3.3.2 - resolution: "hoist-non-react-statics@npm:3.3.2" - dependencies: - react-is: "npm:^16.7.0" - checksum: 10/1acbe85f33e5a39f90c822ad4d28b24daeb60f71c545279431dc98c312cd28a54f8d64788e477fe21dc502b0e3cf58589ebe5c1ad22af27245370391c2d24ea6 - languageName: node - linkType: hard - "html-escaper@npm:^2.0.0": version: 2.0.2 resolution: "html-escaper@npm:2.0.2" @@ -4064,13 +3657,6 @@ __metadata: languageName: node linkType: hard -"is-buffer@npm:^2.0.5": - version: 2.0.5 - resolution: "is-buffer@npm:2.0.5" - checksum: 10/3261a8b858edcc6c9566ba1694bf829e126faa88911d1c0a747ea658c5d81b14b6955e3a702d59dabadd58fdd440c01f321aa71d6547105fd21d03f94d0597e7 - languageName: node - linkType: hard - "is-builtin-module@npm:^3.2.1": version: 3.2.1 resolution: "is-builtin-module@npm:3.2.1" @@ -4299,13 +3885,6 @@ __metadata: languageName: node linkType: hard -"is-what@npm:^4.1.8": - version: 4.1.16 - resolution: "is-what@npm:4.1.16" - checksum: 10/f6400634bae77be6903365dc53817292e1c4d8db1b467515d0c842505b8388ee8e558326d5e6952cb2a9d74116eca2af0c6adb8aa7e9d5c845a130ce9328bf13 - languageName: node - linkType: hard - "is-wsl@npm:^2.2.0": version: 2.2.0 resolution: "is-wsl@npm:2.2.0" @@ -4343,15 +3922,6 @@ __metadata: languageName: node linkType: hard -"isomorphic-ws@npm:^5.0.0": - version: 5.0.0 - resolution: "isomorphic-ws@npm:5.0.0" - peerDependencies: - ws: "*" - checksum: 10/e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398 - languageName: node - linkType: hard - "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.2 resolution: "istanbul-lib-coverage@npm:3.2.2" @@ -4931,7 +4501,7 @@ __metadata: languageName: node linkType: hard -"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": +"js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" checksum: 10/af37d0d913fb56aec6dc0074c163cc71cd23c0b8aad5c2350747b6721d37ba118af35abdd8b33c47ec2800de07dedb16a527ca9c530ee004093e04958bd0cbf2 @@ -5063,34 +4633,6 @@ __metadata: languageName: node linkType: hard -"level-supports@npm:^6.0.0": - version: 6.2.0 - resolution: "level-supports@npm:6.2.0" - checksum: 10/450c04839cf42ac7c73085b4928f1c1c51d9ab179aac9102cc8ef2389faf2d06cebaf57df2d025da89d78465004ccf29bfd972a04b0b35d5d423fa3f4516f906 - languageName: node - linkType: hard - -"level-transcoder@npm:^1.0.1": - version: 1.0.1 - resolution: "level-transcoder@npm:1.0.1" - dependencies: - buffer: "npm:^6.0.3" - module-error: "npm:^1.0.1" - checksum: 10/2fb41a1d8037fc279f851ead8cdc3852b738f1f935ac2895183cd606aae3e57008e085c7c2bd2b2d43cfd057333108cfaed604092e173ac2abdf5ab1b8333f9e - languageName: node - linkType: hard - -"level@npm:^9.0.0": - version: 9.0.0 - resolution: "level@npm:9.0.0" - dependencies: - abstract-level: "npm:^2.0.1" - browser-level: "npm:^2.0.0" - classic-level: "npm:^2.0.0" - checksum: 10/6d9dc32300dc6d2680ab3f68cd6862c7db48840070f99ccd51a1f5a6999bf41ccb2e20eeb9fe99bcf9052ff4370d783e01522fdbebe8c981cb9afbc72a8e70f0 - languageName: node - linkType: hard - "leven@npm:^3.1.0": version: 3.1.0 resolution: "leven@npm:3.1.0" @@ -5147,24 +4689,13 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.15, lodash@npm:^4.17.21": +"lodash@npm:^4.17.15": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: 10/c08619c038846ea6ac754abd6dd29d2568aa705feb69339e836dfa8d8b09abbb2f859371e86863eda41848221f9af43714491467b5b0299122431e202bb0c532 languageName: node linkType: hard -"loose-envify@npm:^1.4.0": - version: 1.4.0 - resolution: "loose-envify@npm:1.4.0" - dependencies: - js-tokens: "npm:^3.0.0 || ^4.0.0" - bin: - loose-envify: cli.js - checksum: 10/6517e24e0cad87ec9888f500c5b5947032cdfe6ef65e1c1936a0c48a524b81e65542c9c3edc91c97d5bddc806ee2a985dbc79be89215d613b1de5db6d1cfe6f4 - languageName: node - linkType: hard - "lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": version: 10.4.3 resolution: "lru-cache@npm:10.4.3" @@ -5232,13 +4763,6 @@ __metadata: languageName: node linkType: hard -"maybe-combine-errors@npm:^1.0.0": - version: 1.0.0 - resolution: "maybe-combine-errors@npm:1.0.0" - checksum: 10/16bb6d3dcf79fc61f5a04abe948c4c81cae0da6ee5da9a1d8196f1723b069d6ab60f752bc208e18481e2b82de146e068bc462558c65ecdf96fed0d021a1aa6ab - languageName: node - linkType: hard - "merge-stream@npm:^2.0.0": version: 2.0.0 resolution: "merge-stream@npm:2.0.0" @@ -5415,13 +4939,6 @@ __metadata: languageName: node linkType: hard -"module-error@npm:^1.0.1": - version: 1.0.2 - resolution: "module-error@npm:1.0.2" - checksum: 10/5d653e35bd55b3e95f8aee2cdac108082ea892e71b8f651be92cde43e4ee86abee4fa8bd7fc3fe5e68b63926d42f63c54cd17b87a560c31f18739295575a3962 - languageName: node - linkType: hard - "ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" @@ -5438,13 +4955,6 @@ __metadata: languageName: node linkType: hard -"napi-macros@npm:^2.2.2": - version: 2.2.2 - resolution: "napi-macros@npm:2.2.2" - checksum: 10/2cdb9c40ad4b424b14fbe5e13c5329559e2b511665acf41cdcda172fd2270202dc747a2d288b687c72bc70f654c797bc24a93adb67631128d62461588d7cc070 - languageName: node - linkType: hard - "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -5459,49 +4969,6 @@ __metadata: languageName: node linkType: hard -"node-domexception@npm:^1.0.0": - version: 1.0.0 - resolution: "node-domexception@npm:1.0.0" - checksum: 10/e332522f242348c511640c25a6fc7da4f30e09e580c70c6b13cb0be83c78c3e71c8d4665af2527e869fc96848924a4316ae7ec9014c091e2156f41739d4fa233 - languageName: node - linkType: hard - -"node-fetch@npm:3.3.2": - version: 3.3.2 - resolution: "node-fetch@npm:3.3.2" - dependencies: - data-uri-to-buffer: "npm:^4.0.0" - fetch-blob: "npm:^3.1.4" - formdata-polyfill: "npm:^4.0.10" - checksum: 10/24207ca8c81231c7c59151840e3fded461d67a31cf3e3b3968e12201a42f89ce4a0b5fb7079b1fa0a4655957b1ca9257553200f03a9f668b45ebad265ca5593d - languageName: node - linkType: hard - -"node-fetch@npm:^2.7.0": - version: 2.7.0 - resolution: "node-fetch@npm:2.7.0" - dependencies: - whatwg-url: "npm:^5.0.0" - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - checksum: 10/b24f8a3dc937f388192e59bcf9d0857d7b6940a2496f328381641cb616efccc9866e89ec43f2ec956bbd6c3d3ee05524ce77fe7b29ccd34692b3a16f237d6676 - languageName: node - linkType: hard - -"node-gyp-build@npm:^4.3.0": - version: 4.8.4 - resolution: "node-gyp-build@npm:4.8.4" - bin: - node-gyp-build: bin.js - node-gyp-build-optional: optional.js - node-gyp-build-test: build-test.js - checksum: 10/6a7d62289d1afc419fc8fc9bd00aa4e554369e50ca0acbc215cb91446148b75ff7e2a3b53c2c5b2c09a39d416d69f3d3237937860373104b5fe429bf30ad9ac5 - languageName: node - linkType: hard - "node-gyp@npm:latest": version: 11.1.0 resolution: "node-gyp@npm:11.1.0" @@ -5563,13 +5030,6 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:^4.1.1": - version: 4.1.1 - resolution: "object-assign@npm:4.1.1" - checksum: 10/fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f - languageName: node - linkType: hard - "object-inspect@npm:^1.12.3, object-inspect@npm:^1.13.3": version: 1.13.4 resolution: "object-inspect@npm:1.13.4" @@ -5669,18 +5129,6 @@ __metadata: languageName: node linkType: hard -"optimism@npm:^0.18.0": - version: 0.18.1 - resolution: "optimism@npm:0.18.1" - dependencies: - "@wry/caches": "npm:^1.0.0" - "@wry/context": "npm:^0.7.0" - "@wry/trie": "npm:^0.5.0" - tslib: "npm:^2.3.0" - checksum: 10/d805f5995d61a417d4fd49a923749db1aa310d1ae8de084ec3a5f589f8b185d9a41b7b4422d33ee75ce43115c264e14bca086f8be2bb182c76448ad08997213a - languageName: node - linkType: hard - "optionator@npm:^0.9.3": version: 0.9.4 resolution: "optionator@npm:0.9.4" @@ -6012,17 +5460,6 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.7.2": - version: 15.8.1 - resolution: "prop-types@npm:15.8.1" - dependencies: - loose-envify: "npm:^1.4.0" - object-assign: "npm:^4.1.1" - react-is: "npm:^16.13.1" - checksum: 10/7d959caec002bc964c86cdc461ec93108b27337dabe6192fb97d69e16a0c799a03462713868b40749bfc1caf5f57ef80ac3e4ffad3effa636ee667582a75e2c0 - languageName: node - linkType: hard - "proper-lockfile@npm:^4.1.2": version: 4.1.2 resolution: "proper-lockfile@npm:4.1.2" @@ -6081,13 +5518,6 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.13.1, react-is@npm:^16.7.0": - version: 16.13.1 - resolution: "react-is@npm:16.13.1" - checksum: 10/5aa564a1cde7d391ac980bedee21202fc90bdea3b399952117f54fb71a932af1e5902020144fb354b4690b2414a0c7aafe798eb617b76a3d441d956db7726fdf - languageName: node - linkType: hard - "react-is@npm:^18.0.0": version: 18.3.1 resolution: "react-is@npm:18.3.1" @@ -6180,21 +5610,6 @@ __metadata: languageName: node linkType: hard -"rehackt@npm:^0.1.0": - version: 0.1.0 - resolution: "rehackt@npm:0.1.0" - peerDependencies: - "@types/react": "*" - react: "*" - peerDependenciesMeta: - "@types/react": - optional: true - react: - optional: true - checksum: 10/c81adead82c165dffc574cbf9e1de3605522782a56b48df48b68d53d45c4d8c9253df3790109335bf97072424e54ad2423bb9544ca3a985fa91995dda43452fc - languageName: node - linkType: hard - "require-directory@npm:^2.1.1": version: 2.1.1 resolution: "require-directory@npm:2.1.1" @@ -6306,19 +5721,8 @@ __metadata: resolution: "root-workspace-0b6124@workspace:." dependencies: "@midnight-ntwrk/compact-runtime": "npm:^0.7.0" - "@midnight-ntwrk/dapp-connector-api": "npm:^1.2.2" "@midnight-ntwrk/ledger": "npm:^3.0.6" - "@midnight-ntwrk/midnight-js-contracts": "npm:^1.0.0" - "@midnight-ntwrk/midnight-js-fetch-zk-config-provider": "npm:^1.0.0" - "@midnight-ntwrk/midnight-js-http-client-proof-provider": "npm:^1.0.0" - "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "npm:^1.0.0" - "@midnight-ntwrk/midnight-js-level-private-state-provider": "npm:^1.0.0" - "@midnight-ntwrk/midnight-js-network-id": "npm:^1.0.0" - "@midnight-ntwrk/midnight-js-node-zk-config-provider": "npm:^1.0.0" - "@midnight-ntwrk/midnight-js-types": "npm:^1.0.0" - "@midnight-ntwrk/midnight-js-utils": "npm:^1.0.0" - "@midnight-ntwrk/wallet": "npm:^4.0.0" - "@midnight-ntwrk/wallet-api": "npm:^4.0.0" + "@midnight-ntwrk/zswap": "npm:^3.0.6" "@types/jest": "npm:^29.5.6" "@types/node": "npm:^18.18.6" "@typescript-eslint/eslint-plugin": "npm:^6.8.0" @@ -6358,7 +5762,7 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:^7.5, rxjs@npm:^7.5.0, rxjs@npm:^7.8.1": +"rxjs@npm:^7.8.1": version: 7.8.2 resolution: "rxjs@npm:7.8.2" dependencies: @@ -6429,13 +5833,6 @@ __metadata: languageName: node linkType: hard -"scale-ts@npm:^1.1.0": - version: 1.6.1 - resolution: "scale-ts@npm:1.6.1" - checksum: 10/f1f9bf1d9abfcfcaf8ae2ae326270beca5c2456cc72f6b6b8230aa175a30bdcd6387678746a4d873c834efbba9c8e015698d42ee67bd71b70f7adfe2e0ba1d39 - languageName: node - linkType: hard - "secure-json-parse@npm:^2.4.0": version: 2.7.0 resolution: "secure-json-parse@npm:2.7.0" @@ -6865,15 +6262,6 @@ __metadata: languageName: node linkType: hard -"superjson@npm:^2.0.0": - version: 2.2.2 - resolution: "superjson@npm:2.2.2" - dependencies: - copy-anything: "npm:^3.0.2" - checksum: 10/6fdc709db4f69d586a18379948e0ade8268c851c791701fea960e29cea12672d7561b4ca89c4049c2e787eb1cec08a51df51d357aa6852078bc0d71d7e17b401 - languageName: node - linkType: hard - "supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" @@ -6899,13 +6287,6 @@ __metadata: languageName: node linkType: hard -"symbol-observable@npm:^4.0.0": - version: 4.0.0 - resolution: "symbol-observable@npm:4.0.0" - checksum: 10/983aef3912ad080fc834b9ad115d44bc2994074c57cea4fb008e9f7ab9bb4118b908c63d9edc861f51257bc0595025510bdf7263bb09d8953a6929f240165c24 - languageName: node - linkType: hard - "synckit@npm:^0.9.1": version: 0.9.2 resolution: "synckit@npm:0.9.2" @@ -7065,13 +6446,6 @@ __metadata: languageName: node linkType: hard -"tr46@npm:~0.0.3": - version: 0.0.3 - resolution: "tr46@npm:0.0.3" - checksum: 10/8f1f5aa6cb232f9e1bdc86f485f916b7aa38caee8a778b378ffec0b70d9307873f253f5cbadbe2955ece2ac5c83d0dc14a77513166ccd0a0c7fe197e21396695 - languageName: node - linkType: hard - "ts-api-utils@npm:^1.0.1": version: 1.4.3 resolution: "ts-api-utils@npm:1.4.3" @@ -7081,22 +6455,6 @@ __metadata: languageName: node linkType: hard -"ts-custom-error@npm:^3.3.1": - version: 3.3.1 - resolution: "ts-custom-error@npm:3.3.1" - checksum: 10/92e3a2c426bf6049579aeb889b6f9787e0cfb6bb715a1457e2571708be7fe739662ca9eb2a8c61b72a2d32189645f4fbcf1a370087e030d922e9e2a7b7c1c994 - languageName: node - linkType: hard - -"ts-invariant@npm:^0.10.3": - version: 0.10.3 - resolution: "ts-invariant@npm:0.10.3" - dependencies: - tslib: "npm:^2.1.0" - checksum: 10/bb07d56fe4aae69d8860e0301dfdee2d375281159054bc24bf1e49e513fb0835bf7f70a11351344d213a79199c5e695f37ebbf5a447188a377ce0cd81d91ddb5 - languageName: node - linkType: hard - "ts-jest@npm:^29.1.1": version: 29.2.6 resolution: "ts-jest@npm:29.2.6" @@ -7184,7 +6542,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.6.2": +"tslib@npm:^2.1.0, tslib@npm:^2.6.2": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 @@ -7498,30 +6856,6 @@ __metadata: languageName: node linkType: hard -"web-streams-polyfill@npm:^3.0.3": - version: 3.3.3 - resolution: "web-streams-polyfill@npm:3.3.3" - checksum: 10/8e7e13501b3834094a50abe7c0b6456155a55d7571312b89570012ef47ec2a46d766934768c50aabad10a9c30dd764a407623e8bfcc74fcb58495c29130edea9 - languageName: node - linkType: hard - -"webidl-conversions@npm:^3.0.0": - version: 3.0.1 - resolution: "webidl-conversions@npm:3.0.1" - checksum: 10/b65b9f8d6854572a84a5c69615152b63371395f0c5dcd6729c45789052296df54314db2bc3e977df41705eacb8bc79c247cee139a63fa695192f95816ed528ad - languageName: node - linkType: hard - -"whatwg-url@npm:^5.0.0": - version: 5.0.0 - resolution: "whatwg-url@npm:5.0.0" - dependencies: - tr46: "npm:~0.0.3" - webidl-conversions: "npm:^3.0.0" - checksum: 10/f95adbc1e80820828b45cc671d97da7cd5e4ef9deb426c31bcd5ab00dc7103042291613b3ef3caec0a2335ed09e0d5ed026c940755dbb6d404e2b27f940fdf07 - languageName: node - linkType: hard - "which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": version: 1.1.1 resolution: "which-boxed-primitive@npm:1.1.1" @@ -7650,21 +6984,6 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.14.2, ws@npm:^8.8.1": - version: 8.18.1 - resolution: "ws@npm:8.18.1" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ">=5.0.2" - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 10/3f38e9594f2af5b6324138e86b74df7d77bbb8e310bf8188679dd80bac0d1f47e51536a1923ac3365f31f3d8b25ea0b03e4ade466aa8292a86cd5defca64b19b - languageName: node - linkType: hard - "xml@npm:^1.0.1": version: 1.0.1 resolution: "xml@npm:1.0.1" @@ -7745,22 +7064,6 @@ __metadata: languageName: node linkType: hard -"zen-observable-ts@npm:^1.1.0, zen-observable-ts@npm:^1.2.5": - version: 1.2.5 - resolution: "zen-observable-ts@npm:1.2.5" - dependencies: - zen-observable: "npm:0.8.15" - checksum: 10/2384cf92a60e39e7b9735a0696f119684fee0f8bcc81d71474c92d656eca1bc3e87b484a04e97546e56bd539f8756bf97cf21a28a933ff7a94b35a8d217848eb - languageName: node - linkType: hard - -"zen-observable@npm:0.8.15": - version: 0.8.15 - resolution: "zen-observable@npm:0.8.15" - checksum: 10/30eac3f4055d33f446b4cd075d3543da347c2c8e68fbc35c3f5a19fb43be67c6ed27ee136bc8f8933efa547be7ce04957809ad00ee7f1b00a964f199ae6fb514 - languageName: node - linkType: hard - "zip-stream@npm:^6.0.1": version: 6.0.1 resolution: "zip-stream@npm:6.0.1" From 3419a25f2106b3904aa0a269e7b2a2cdfd2c4e4e Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 16 Apr 2025 17:56:00 -0500 Subject: [PATCH 003/202] remove eslint and prettier, add biome --- .dockerignore | 21 - biome.json | 38 + compact/src/run-compactc.cjs | 2 +- contracts/erc20/.eslintignore | 1 - contracts/erc20/.eslintrc.cjs | 30 - contracts/erc20/package.json | 1 - contracts/initializable/.eslintignore | 1 - contracts/initializable/.eslintrc.cjs | 30 - contracts/utils/.eslintignore | 1 - contracts/utils/.eslintrc.cjs | 30 - contracts/utils/package.json | 1 - package.json | 10 +- prettierrc.json | 6 - yarn.lock | 1674 ++----------------------- 14 files changed, 148 insertions(+), 1698 deletions(-) delete mode 100644 .dockerignore create mode 100644 biome.json delete mode 100644 contracts/erc20/.eslintignore delete mode 100644 contracts/erc20/.eslintrc.cjs delete mode 100644 contracts/initializable/.eslintignore delete mode 100644 contracts/initializable/.eslintrc.cjs delete mode 100644 contracts/utils/.eslintignore delete mode 100644 contracts/utils/.eslintrc.cjs delete mode 100644 prettierrc.json diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 8ebba54f..00000000 --- a/.dockerignore +++ /dev/null @@ -1,21 +0,0 @@ -*.tsbuildinfo -result/ -.direnv/ -.idea/ -.env -*.log -examples/ -coverage/ -.github/ -.vscode/ -.yarn -flake.nix -flake.lock -renovate.json -*.Dockerfile -COMMIT_EDITMSG -FETCH_HEAD -ORIG_HEAD -NOTICE -LICENSE -HEAD diff --git a/biome.json b/biome.json new file mode 100644 index 00000000..a06c8fa6 --- /dev/null +++ b/biome.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": [ + "tsconfig.base.json", + "tsconfig*.json", + "*.compact", + "artifacts/*", + "coverage/*", + "dist/*", + "reports/*" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + } +} diff --git a/compact/src/run-compactc.cjs b/compact/src/run-compactc.cjs index 69ca0eb4..89a5929a 100755 --- a/compact/src/run-compactc.cjs +++ b/compact/src/run-compactc.cjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -const childProcess = require('child_process'); +const childProcess = require('node:child_process'); const path = require('path'); const [_node, _script, ...args] = process.argv; diff --git a/contracts/erc20/.eslintignore b/contracts/erc20/.eslintignore deleted file mode 100644 index 327555cd..00000000 --- a/contracts/erc20/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -src/artifacts diff --git a/contracts/erc20/.eslintrc.cjs b/contracts/erc20/.eslintrc.cjs deleted file mode 100644 index 581f1d49..00000000 --- a/contracts/erc20/.eslintrc.cjs +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = { - env: { - browser: true, - es2021: true, - node: true, - jest: true, - }, - extends: [ - 'standard-with-typescript', - 'plugin:prettier/recommended', - 'plugin:@typescript-eslint/recommended-requiring-type-checking', - ], - overrides: [], - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - project: ['tsconfig.json'], - }, - rules: { - '@typescript-eslint/no-misused-promises': 'off', // https://github.com/typescript-eslint/typescript-eslint/issues/5807 - '@typescript-eslint/no-floating-promises': 'warn', - '@typescript-eslint/promise-function-async': 'off', - '@typescript-eslint/no-redeclare': 'off', - '@typescript-eslint/no-invalid-void-type': 'off', - '@typescript-eslint/no-unsafe-call': 'off', - '@typescript-eslint/no-unsafe-member-access': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/consistent-type-definitions': 'off' - }, -}; diff --git a/contracts/erc20/package.json b/contracts/erc20/package.json index d914daf0..77d0921d 100644 --- a/contracts/erc20/package.json +++ b/contracts/erc20/package.json @@ -25,7 +25,6 @@ }, "devDependencies": { "@midnight-ntwrk/compact": "workspace:*", - "eslint": "^8.52.0", "jest": "^29.7.0", "typescript": "^5.2.2" } diff --git a/contracts/initializable/.eslintignore b/contracts/initializable/.eslintignore deleted file mode 100644 index 327555cd..00000000 --- a/contracts/initializable/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -src/artifacts diff --git a/contracts/initializable/.eslintrc.cjs b/contracts/initializable/.eslintrc.cjs deleted file mode 100644 index 581f1d49..00000000 --- a/contracts/initializable/.eslintrc.cjs +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = { - env: { - browser: true, - es2021: true, - node: true, - jest: true, - }, - extends: [ - 'standard-with-typescript', - 'plugin:prettier/recommended', - 'plugin:@typescript-eslint/recommended-requiring-type-checking', - ], - overrides: [], - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - project: ['tsconfig.json'], - }, - rules: { - '@typescript-eslint/no-misused-promises': 'off', // https://github.com/typescript-eslint/typescript-eslint/issues/5807 - '@typescript-eslint/no-floating-promises': 'warn', - '@typescript-eslint/promise-function-async': 'off', - '@typescript-eslint/no-redeclare': 'off', - '@typescript-eslint/no-invalid-void-type': 'off', - '@typescript-eslint/no-unsafe-call': 'off', - '@typescript-eslint/no-unsafe-member-access': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/consistent-type-definitions': 'off' - }, -}; diff --git a/contracts/utils/.eslintignore b/contracts/utils/.eslintignore deleted file mode 100644 index 327555cd..00000000 --- a/contracts/utils/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -src/artifacts diff --git a/contracts/utils/.eslintrc.cjs b/contracts/utils/.eslintrc.cjs deleted file mode 100644 index 581f1d49..00000000 --- a/contracts/utils/.eslintrc.cjs +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = { - env: { - browser: true, - es2021: true, - node: true, - jest: true, - }, - extends: [ - 'standard-with-typescript', - 'plugin:prettier/recommended', - 'plugin:@typescript-eslint/recommended-requiring-type-checking', - ], - overrides: [], - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - project: ['tsconfig.json'], - }, - rules: { - '@typescript-eslint/no-misused-promises': 'off', // https://github.com/typescript-eslint/typescript-eslint/issues/5807 - '@typescript-eslint/no-floating-promises': 'warn', - '@typescript-eslint/promise-function-async': 'off', - '@typescript-eslint/no-redeclare': 'off', - '@typescript-eslint/no-invalid-void-type': 'off', - '@typescript-eslint/no-unsafe-call': 'off', - '@typescript-eslint/no-unsafe-member-access': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/consistent-type-definitions': 'off' - }, -}; diff --git a/contracts/utils/package.json b/contracts/utils/package.json index 40c57157..fc0fe810 100644 --- a/contracts/utils/package.json +++ b/contracts/utils/package.json @@ -22,7 +22,6 @@ }, "devDependencies": { "@midnight-ntwrk/compact": "workspace:*", - "eslint": "^8.52.0", "jest": "^29.7.0", "typescript": "^5.2.2" } diff --git a/package.json b/package.json index aef03893..128470a1 100644 --- a/package.json +++ b/package.json @@ -20,25 +20,17 @@ "rxjs": "^7.8.1" }, "devDependencies": { + "@biomejs/biome": "1.9.4", "@midnight-ntwrk/ledger": "^3.0.6", "@midnight-ntwrk/zswap": "^3.0.6", "@types/jest": "^29.5.6", "@types/node": "^18.18.6", - "@typescript-eslint/eslint-plugin": "^6.8.0", - "@typescript-eslint/parser": "^6.8.0", - "eslint": "^8.52.0", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-import": "^2.28.1", - "eslint-plugin-n": "^16.2.0", - "eslint-plugin-prettier": "^5.0.1", - "eslint-plugin-promise": "^6.1.1", "fast-check": "^3.15.0", "jest": "^29.7.0", "jest-fast-check": "^2.0.0", "jest-gh-md-reporter": "^0.0.2", "jest-html-reporters": "^3.1.4", "jest-junit": "^16.0.0", - "prettier": "^3.0.3", "testcontainers": "^10.3.2", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", diff --git a/prettierrc.json b/prettierrc.json deleted file mode 100644 index 4eb15403..00000000 --- a/prettierrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "semi": true, - "trailingComma": "all", - "singleQuote": true, - "printWidth": 130 - } diff --git a/yarn.lock b/yarn.lock index 64965d1e..4d7515ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -391,6 +391,97 @@ __metadata: languageName: node linkType: hard +"@biomejs/biome@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/biome@npm:1.9.4" + dependencies: + "@biomejs/cli-darwin-arm64": "npm:1.9.4" + "@biomejs/cli-darwin-x64": "npm:1.9.4" + "@biomejs/cli-linux-arm64": "npm:1.9.4" + "@biomejs/cli-linux-arm64-musl": "npm:1.9.4" + "@biomejs/cli-linux-x64": "npm:1.9.4" + "@biomejs/cli-linux-x64-musl": "npm:1.9.4" + "@biomejs/cli-win32-arm64": "npm:1.9.4" + "@biomejs/cli-win32-x64": "npm:1.9.4" + dependenciesMeta: + "@biomejs/cli-darwin-arm64": + optional: true + "@biomejs/cli-darwin-x64": + optional: true + "@biomejs/cli-linux-arm64": + optional: true + "@biomejs/cli-linux-arm64-musl": + optional: true + "@biomejs/cli-linux-x64": + optional: true + "@biomejs/cli-linux-x64-musl": + optional: true + "@biomejs/cli-win32-arm64": + optional: true + "@biomejs/cli-win32-x64": + optional: true + bin: + biome: bin/biome + checksum: 10/bd8ff8fb4dc0581bd60a9b9ac28d0cd03ba17c6a1de2ab6228b7fda582079594ceee774f47e41aac2fc6d35de1637def2e32ef2e58fa24e22d1b24ef9ee5cefa + languageName: node + linkType: hard + +"@biomejs/cli-darwin-arm64@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/cli-darwin-arm64@npm:1.9.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@biomejs/cli-darwin-x64@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/cli-darwin-x64@npm:1.9.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@biomejs/cli-linux-arm64-musl@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/cli-linux-arm64-musl@npm:1.9.4" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@biomejs/cli-linux-arm64@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/cli-linux-arm64@npm:1.9.4" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@biomejs/cli-linux-x64-musl@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/cli-linux-x64-musl@npm:1.9.4" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@biomejs/cli-linux-x64@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/cli-linux-x64@npm:1.9.4" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@biomejs/cli-win32-arm64@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/cli-win32-arm64@npm:1.9.4" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@biomejs/cli-win32-x64@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/cli-win32-x64@npm:1.9.4" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@cspotcode/source-map-support@npm:^0.8.0": version: 0.8.1 resolution: "@cspotcode/source-map-support@npm:0.8.1" @@ -400,7 +491,7 @@ __metadata: languageName: node linkType: hard -"@eslint-community/eslint-utils@npm:^4.1.2, @eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": +"@eslint-community/eslint-utils@npm:^4.2.0": version: 4.4.1 resolution: "@eslint-community/eslint-utils@npm:4.4.1" dependencies: @@ -411,7 +502,7 @@ __metadata: languageName: node linkType: hard -"@eslint-community/regexpp@npm:^4.11.0, @eslint-community/regexpp@npm:^4.5.1, @eslint-community/regexpp@npm:^4.6.1": +"@eslint-community/regexpp@npm:^4.6.1": version: 4.12.1 resolution: "@eslint-community/regexpp@npm:4.12.1" checksum: 10/c08f1dd7dd18fbb60bdd0d85820656d1374dd898af9be7f82cb00451313402a22d5e30569c150315b4385907cdbca78c22389b2a72ab78883b3173be317620cc @@ -853,14 +944,14 @@ __metadata: languageName: node linkType: hard -"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": +"@nodelib/fs.stat@npm:2.0.5": version: 2.0.5 resolution: "@nodelib/fs.stat@npm:2.0.5" checksum: 10/012480b5ca9d97bff9261571dbbec7bbc6033f69cc92908bc1ecfad0792361a5a1994bc48674b9ef76419d056a03efadfce5a6cf6dbc0a36559571a7a483f6f0 languageName: node linkType: hard -"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": +"@nodelib/fs.walk@npm:^1.2.8": version: 1.2.8 resolution: "@nodelib/fs.walk@npm:1.2.8" dependencies: @@ -898,7 +989,6 @@ __metadata: dependencies: "@midnight-ntwrk/compact": "workspace:*" "@openzeppelin-midnight-contracts/utils-contract": "workspace:^" - eslint: "npm:^8.52.0" jest: "npm:^29.7.0" typescript: "npm:^5.2.2" languageName: unknown @@ -920,7 +1010,6 @@ __metadata: resolution: "@openzeppelin-midnight-contracts/utils-contract@workspace:contracts/utils" dependencies: "@midnight-ntwrk/compact": "workspace:*" - eslint: "npm:^8.52.0" jest: "npm:^29.7.0" typescript: "npm:^5.2.2" languageName: unknown @@ -933,20 +1022,6 @@ __metadata: languageName: node linkType: hard -"@pkgr/core@npm:^0.1.0": - version: 0.1.1 - resolution: "@pkgr/core@npm:0.1.1" - checksum: 10/6f25fd2e3008f259c77207ac9915b02f1628420403b2630c92a07ff963129238c9262afc9e84344c7a23b5cc1f3965e2cd17e3798219f5fd78a63d144d3cceba - languageName: node - linkType: hard - -"@rtsao/scc@npm:^1.1.0": - version: 1.1.0 - resolution: "@rtsao/scc@npm:1.1.0" - checksum: 10/17d04adf404e04c1e61391ed97bca5117d4c2767a76ae3e879390d6dec7b317fcae68afbf9e98badee075d0b64fa60f287729c4942021b4d19cd01db77385c01 - languageName: node - linkType: hard - "@sinclair/typebox@npm:^0.27.8": version: 0.27.8 resolution: "@sinclair/typebox@npm:0.27.8" @@ -1106,20 +1181,6 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:^7.0.12": - version: 7.0.15 - resolution: "@types/json-schema@npm:7.0.15" - checksum: 10/1a3c3e06236e4c4aab89499c428d585527ce50c24fe8259e8b3926d3df4cfbbbcf306cfc73ddfb66cbafc973116efd15967020b0f738f63e09e64c7d260519e7 - languageName: node - linkType: hard - -"@types/json5@npm:^0.0.29": - version: 0.0.29 - resolution: "@types/json5@npm:0.0.29" - checksum: 10/4e5aed58cabb2bbf6f725da13421aa50a49abb6bc17bfab6c31b8774b073fa7b50d557c61f961a09a85f6056151190f8ac95f13f5b48136ba5841f7d4484ec56 - languageName: node - linkType: hard - "@types/node@npm:*": version: 22.13.8 resolution: "@types/node@npm:22.13.8" @@ -1145,13 +1206,6 @@ __metadata: languageName: node linkType: hard -"@types/semver@npm:^7.5.0": - version: 7.5.8 - resolution: "@types/semver@npm:7.5.8" - checksum: 10/3496808818ddb36deabfe4974fd343a78101fa242c4690044ccdc3b95dcf8785b494f5d628f2f47f38a702f8db9c53c67f47d7818f2be1b79f2efb09692e1178 - languageName: node - linkType: hard - "@types/ssh2-streams@npm:*": version: 0.1.12 resolution: "@types/ssh2-streams@npm:0.1.12" @@ -1203,129 +1257,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^6.8.0": - version: 6.21.0 - resolution: "@typescript-eslint/eslint-plugin@npm:6.21.0" - dependencies: - "@eslint-community/regexpp": "npm:^4.5.1" - "@typescript-eslint/scope-manager": "npm:6.21.0" - "@typescript-eslint/type-utils": "npm:6.21.0" - "@typescript-eslint/utils": "npm:6.21.0" - "@typescript-eslint/visitor-keys": "npm:6.21.0" - debug: "npm:^4.3.4" - graphemer: "npm:^1.4.0" - ignore: "npm:^5.2.4" - natural-compare: "npm:^1.4.0" - semver: "npm:^7.5.4" - ts-api-utils: "npm:^1.0.1" - peerDependencies: - "@typescript-eslint/parser": ^6.0.0 || ^6.0.0-alpha - eslint: ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10/a57de0f630789330204cc1531f86cfc68b391cafb1ba67c8992133f1baa2a09d629df66e71260b040de4c9a3ff1252952037093c4128b0d56c4dbb37720b4c1d - languageName: node - linkType: hard - -"@typescript-eslint/parser@npm:^6.8.0": - version: 6.21.0 - resolution: "@typescript-eslint/parser@npm:6.21.0" - dependencies: - "@typescript-eslint/scope-manager": "npm:6.21.0" - "@typescript-eslint/types": "npm:6.21.0" - "@typescript-eslint/typescript-estree": "npm:6.21.0" - "@typescript-eslint/visitor-keys": "npm:6.21.0" - debug: "npm:^4.3.4" - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10/4d51cdbc170e72275efc5ef5fce48a81ec431e4edde8374f4d0213d8d370a06823e1a61ae31d502a5f1b0d1f48fc4d29a1b1b5c2dcf809d66d3872ccf6e46ac7 - languageName: node - linkType: hard - -"@typescript-eslint/scope-manager@npm:6.21.0": - version: 6.21.0 - resolution: "@typescript-eslint/scope-manager@npm:6.21.0" - dependencies: - "@typescript-eslint/types": "npm:6.21.0" - "@typescript-eslint/visitor-keys": "npm:6.21.0" - checksum: 10/fe91ac52ca8e09356a71dc1a2f2c326480f3cccfec6b2b6d9154c1a90651ab8ea270b07c67df5678956c3bbf0bbe7113ab68f68f21b20912ea528b1214197395 - languageName: node - linkType: hard - -"@typescript-eslint/type-utils@npm:6.21.0": - version: 6.21.0 - resolution: "@typescript-eslint/type-utils@npm:6.21.0" - dependencies: - "@typescript-eslint/typescript-estree": "npm:6.21.0" - "@typescript-eslint/utils": "npm:6.21.0" - debug: "npm:^4.3.4" - ts-api-utils: "npm:^1.0.1" - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10/d03fb3ee1caa71f3ce053505f1866268d7ed79ffb7fed18623f4a1253f5b8f2ffc92636d6fd08fcbaf5bd265a6de77bf192c53105131e4724643dfc910d705fc - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:6.21.0": - version: 6.21.0 - resolution: "@typescript-eslint/types@npm:6.21.0" - checksum: 10/e26da86d6f36ca5b6ef6322619f8ec55aabcd7d43c840c977ae13ae2c964c3091fc92eb33730d8be08927c9de38466c5323e78bfb270a9ff1d3611fe821046c5 - languageName: node - linkType: hard - -"@typescript-eslint/typescript-estree@npm:6.21.0": - version: 6.21.0 - resolution: "@typescript-eslint/typescript-estree@npm:6.21.0" - dependencies: - "@typescript-eslint/types": "npm:6.21.0" - "@typescript-eslint/visitor-keys": "npm:6.21.0" - debug: "npm:^4.3.4" - globby: "npm:^11.1.0" - is-glob: "npm:^4.0.3" - minimatch: "npm:9.0.3" - semver: "npm:^7.5.4" - ts-api-utils: "npm:^1.0.1" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10/b32fa35fca2a229e0f5f06793e5359ff9269f63e9705e858df95d55ca2cd7fdb5b3e75b284095a992c48c5fc46a1431a1a4b6747ede2dd08929dc1cbacc589b8 - languageName: node - linkType: hard - -"@typescript-eslint/utils@npm:6.21.0": - version: 6.21.0 - resolution: "@typescript-eslint/utils@npm:6.21.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.4.0" - "@types/json-schema": "npm:^7.0.12" - "@types/semver": "npm:^7.5.0" - "@typescript-eslint/scope-manager": "npm:6.21.0" - "@typescript-eslint/types": "npm:6.21.0" - "@typescript-eslint/typescript-estree": "npm:6.21.0" - semver: "npm:^7.5.4" - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - checksum: 10/b404a2c55a425a79d054346ae123087d30c7ecf7ed7abcf680c47bf70c1de4fabadc63434f3f460b2fa63df76bc9e4a0b9fa2383bb8a9fcd62733fb5c4e4f3e3 - languageName: node - linkType: hard - -"@typescript-eslint/visitor-keys@npm:6.21.0": - version: 6.21.0 - resolution: "@typescript-eslint/visitor-keys@npm:6.21.0" - dependencies: - "@typescript-eslint/types": "npm:6.21.0" - eslint-visitor-keys: "npm:^3.4.1" - checksum: 10/30422cdc1e2ffad203df40351a031254b272f9c6f2b7e02e9bfa39e3fc2c7b1c6130333b0057412968deda17a3a68a578a78929a8139c6acef44d9d841dc72e1 - languageName: node - linkType: hard - "@ungap/structured-clone@npm:^1.2.0": version: 1.3.0 resolution: "@ungap/structured-clone@npm:1.3.0" @@ -1504,90 +1435,6 @@ __metadata: languageName: node linkType: hard -"array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2": - version: 1.0.2 - resolution: "array-buffer-byte-length@npm:1.0.2" - dependencies: - call-bound: "npm:^1.0.3" - is-array-buffer: "npm:^3.0.5" - checksum: 10/0ae3786195c3211b423e5be8dd93357870e6fb66357d81da968c2c39ef43583ef6eece1f9cb1caccdae4806739c65dea832b44b8593414313cd76a89795fca63 - languageName: node - linkType: hard - -"array-includes@npm:^3.1.8": - version: 3.1.8 - resolution: "array-includes@npm:3.1.8" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - es-object-atoms: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.4" - is-string: "npm:^1.0.7" - checksum: 10/290b206c9451f181fb2b1f79a3bf1c0b66bb259791290ffbada760c79b284eef6f5ae2aeb4bcff450ebc9690edd25732c4c73a3c2b340fcc0f4563aed83bf488 - languageName: node - linkType: hard - -"array-union@npm:^2.1.0": - version: 2.1.0 - resolution: "array-union@npm:2.1.0" - checksum: 10/5bee12395cba82da674931df6d0fea23c4aa4660cb3b338ced9f828782a65caa232573e6bf3968f23e0c5eb301764a382cef2f128b170a9dc59de0e36c39f98d - languageName: node - linkType: hard - -"array.prototype.findlastindex@npm:^1.2.5": - version: 1.2.5 - resolution: "array.prototype.findlastindex@npm:1.2.5" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10/7c5c821f357cd53ab6cc305de8086430dd8d7a2485db87b13f843e868055e9582b1fd338f02338f67fc3a1603ceaf9610dd2a470b0b506f9d18934780f95b246 - languageName: node - linkType: hard - -"array.prototype.flat@npm:^1.3.2": - version: 1.3.3 - resolution: "array.prototype.flat@npm:1.3.3" - dependencies: - call-bind: "npm:^1.0.8" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.5" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10/f9b992fa0775d8f7c97abc91eb7f7b2f0ed8430dd9aeb9fdc2967ac4760cdd7fc2ef7ead6528fef40c7261e4d790e117808ce0d3e7e89e91514d4963a531cd01 - languageName: node - linkType: hard - -"array.prototype.flatmap@npm:^1.3.2": - version: 1.3.3 - resolution: "array.prototype.flatmap@npm:1.3.3" - dependencies: - call-bind: "npm:^1.0.8" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.5" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10/473534573aa4b37b1d80705d0ce642f5933cccf5617c9f3e8a56686e9815ba93d469138e86a1f25d2fe8af999c3d24f54d703ec1fc2db2e6778d46d0f4ac951e - languageName: node - linkType: hard - -"arraybuffer.prototype.slice@npm:^1.0.4": - version: 1.0.4 - resolution: "arraybuffer.prototype.slice@npm:1.0.4" - dependencies: - array-buffer-byte-length: "npm:^1.0.1" - call-bind: "npm:^1.0.8" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.5" - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.6" - is-array-buffer: "npm:^3.0.4" - checksum: 10/4821ebdfe7d699f910c7f09bc9fa996f09b96b80bccb4f5dd4b59deae582f6ad6e505ecef6376f8beac1eda06df2dbc89b70e82835d104d6fcabd33c1aed1ae9 - languageName: node - linkType: hard - "asn1@npm:^0.2.6": version: 0.2.6 resolution: "asn1@npm:0.2.6" @@ -1597,13 +1444,6 @@ __metadata: languageName: node linkType: hard -"async-function@npm:^1.0.0": - version: 1.0.0 - resolution: "async-function@npm:1.0.0" - checksum: 10/1a09379937d846f0ce7614e75071c12826945d4e417db634156bf0e4673c495989302f52186dfa9767a1d9181794554717badd193ca2bbab046ef1da741d8efd - languageName: node - linkType: hard - "async-lock@npm:^1.4.1": version: 1.4.1 resolution: "async-lock@npm:1.4.1" @@ -1625,15 +1465,6 @@ __metadata: languageName: node linkType: hard -"available-typed-arrays@npm:^1.0.7": - version: 1.0.7 - resolution: "available-typed-arrays@npm:1.0.7" - dependencies: - possible-typed-array-names: "npm:^1.0.0" - checksum: 10/6c9da3a66caddd83c875010a1ca8ef11eac02ba15fb592dc9418b2b5e7b77b645fa7729380a92d9835c2f05f2ca1b6251f39b993e0feb3f1517c74fa1af02cab - languageName: node - linkType: hard - "b4a@npm:^1.6.4": version: 1.6.7 resolution: "b4a@npm:1.6.7" @@ -1906,22 +1737,6 @@ __metadata: languageName: node linkType: hard -"builtin-modules@npm:^3.3.0": - version: 3.3.0 - resolution: "builtin-modules@npm:3.3.0" - checksum: 10/62e063ab40c0c1efccbfa9ffa31873e4f9d57408cb396a2649981a0ecbce56aabc93c28feaccbc5658c95aab2703ad1d11980e62ec2e5e72637404e1eb60f39e - languageName: node - linkType: hard - -"builtins@npm:^5.0.1": - version: 5.1.0 - resolution: "builtins@npm:5.1.0" - dependencies: - semver: "npm:^7.0.0" - checksum: 10/60aa9969f69656bf6eab82cd74b23ab805f112ae46a54b912bccc1533875760f2d2ce95e0a7d13144e35ada9f0386f17ed4961908bc9434b5a5e21375b1902b2 - languageName: node - linkType: hard - "byline@npm:^5.0.0": version: 5.0.0 resolution: "byline@npm:5.0.0" @@ -1949,38 +1764,6 @@ __metadata: languageName: node linkType: hard -"call-bind-apply-helpers@npm:^1.0.0, call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": - version: 1.0.2 - resolution: "call-bind-apply-helpers@npm:1.0.2" - dependencies: - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - checksum: 10/00482c1f6aa7cfb30fb1dbeb13873edf81cfac7c29ed67a5957d60635a56b2a4a480f1016ddbdb3395cc37900d46037fb965043a51c5c789ffeab4fc535d18b5 - languageName: node - linkType: hard - -"call-bind@npm:^1.0.7, call-bind@npm:^1.0.8": - version: 1.0.8 - resolution: "call-bind@npm:1.0.8" - dependencies: - call-bind-apply-helpers: "npm:^1.0.0" - es-define-property: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.4" - set-function-length: "npm:^1.2.2" - checksum: 10/659b03c79bbfccf0cde3a79e7d52570724d7290209823e1ca5088f94b52192dc1836b82a324d0144612f816abb2f1734447438e38d9dafe0b3f82c2a1b9e3bce - languageName: node - linkType: hard - -"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3": - version: 1.0.3 - resolution: "call-bound@npm:1.0.3" - dependencies: - call-bind-apply-helpers: "npm:^1.0.1" - get-intrinsic: "npm:^1.2.6" - checksum: 10/c39a8245f68cdb7c1f5eea7b3b1e3a7a90084ea6efebb78ebc454d698ade2c2bb42ec033abc35f1e596d62496b6100e9f4cdfad1956476c510130e2cda03266d - languageName: node - linkType: hard - "callsites@npm:^3.0.0": version: 3.1.0 resolution: "callsites@npm:3.1.0" @@ -2201,39 +1984,6 @@ __metadata: languageName: node linkType: hard -"data-view-buffer@npm:^1.0.2": - version: 1.0.2 - resolution: "data-view-buffer@npm:1.0.2" - dependencies: - call-bound: "npm:^1.0.3" - es-errors: "npm:^1.3.0" - is-data-view: "npm:^1.0.2" - checksum: 10/c10b155a4e93999d3a215d08c23eea95f865e1f510b2e7748fcae1882b776df1afe8c99f483ace7fc0e5a3193ab08da138abebc9829d12003746c5a338c4d644 - languageName: node - linkType: hard - -"data-view-byte-length@npm:^1.0.2": - version: 1.0.2 - resolution: "data-view-byte-length@npm:1.0.2" - dependencies: - call-bound: "npm:^1.0.3" - es-errors: "npm:^1.3.0" - is-data-view: "npm:^1.0.2" - checksum: 10/2a47055fcf1ab3ec41b00b6f738c6461a841391a643c9ed9befec1117c1765b4d492661d97fb7cc899200c328949dca6ff189d2c6537d96d60e8a02dfe3c95f7 - languageName: node - linkType: hard - -"data-view-byte-offset@npm:^1.0.1": - version: 1.0.1 - resolution: "data-view-byte-offset@npm:1.0.1" - dependencies: - call-bound: "npm:^1.0.2" - es-errors: "npm:^1.3.0" - is-data-view: "npm:^1.0.1" - checksum: 10/fa3bdfa0968bea6711ee50375094b39f561bce3f15f9e558df59de9c25f0bdd4cddc002d9c1d70ac7772ebd36854a7e22d1761e7302a934e6f1c2263bcf44aa2 - languageName: node - linkType: hard - "dateformat@npm:^4.6.3": version: 4.6.3 resolution: "dateformat@npm:4.6.3" @@ -2253,15 +2003,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:^3.2.7": - version: 3.2.7 - resolution: "debug@npm:3.2.7" - dependencies: - ms: "npm:^2.1.1" - checksum: 10/d86fd7be2b85462297ea16f1934dc219335e802f629ca9a69b63ed8ed041dda492389bb2ee039217c02e5b54792b1c51aa96ae954cf28634d363a2360c7a1639 - languageName: node - linkType: hard - "dedent@npm:^1.0.0": version: 1.5.3 resolution: "dedent@npm:1.5.3" @@ -2288,17 +2029,6 @@ __metadata: languageName: node linkType: hard -"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.4": - version: 1.1.4 - resolution: "define-data-property@npm:1.1.4" - dependencies: - es-define-property: "npm:^1.0.0" - es-errors: "npm:^1.3.0" - gopd: "npm:^1.0.1" - checksum: 10/abdcb2505d80a53524ba871273e5da75e77e52af9e15b3aa65d8aad82b8a3a424dad7aee2cc0b71470ac7acf501e08defac362e8b6a73cdb4309f028061df4ae - languageName: node - linkType: hard - "define-lazy-prop@npm:^2.0.0": version: 2.0.0 resolution: "define-lazy-prop@npm:2.0.0" @@ -2306,17 +2036,6 @@ __metadata: languageName: node linkType: hard -"define-properties@npm:^1.2.1": - version: 1.2.1 - resolution: "define-properties@npm:1.2.1" - dependencies: - define-data-property: "npm:^1.0.1" - has-property-descriptors: "npm:^1.0.0" - object-keys: "npm:^1.1.1" - checksum: 10/b4ccd00597dd46cb2d4a379398f5b19fca84a16f3374e2249201992f36b30f6835949a9429669ee6b41b6e837205a163eadd745e472069e70dfc10f03e5fcc12 - languageName: node - linkType: hard - "detect-newline@npm:^3.0.0": version: 3.1.0 resolution: "detect-newline@npm:3.1.0" @@ -2338,15 +2057,6 @@ __metadata: languageName: node linkType: hard -"dir-glob@npm:^3.0.1": - version: 3.0.1 - resolution: "dir-glob@npm:3.0.1" - dependencies: - path-type: "npm:^4.0.0" - checksum: 10/fa05e18324510d7283f55862f3161c6759a3f2f8dbce491a2fc14c8324c498286c54282c1f0e933cb930da8419b30679389499b919122952a4f8592362ef4615 - languageName: node - linkType: hard - "docker-compose@npm:^0.24.8": version: 0.24.8 resolution: "docker-compose@npm:0.24.8" @@ -2379,15 +2089,6 @@ __metadata: languageName: node linkType: hard -"doctrine@npm:^2.1.0": - version: 2.1.0 - resolution: "doctrine@npm:2.1.0" - dependencies: - esutils: "npm:^2.0.2" - checksum: 10/555684f77e791b17173ea86e2eea45ef26c22219cb64670669c4f4bebd26dbc95cd90ec1f4159e9349a6bb9eb892ce4dde8cd0139e77bedd8bf4518238618474 - languageName: node - linkType: hard - "doctrine@npm:^3.0.0": version: 3.0.0 resolution: "doctrine@npm:3.0.0" @@ -2397,17 +2098,6 @@ __metadata: languageName: node linkType: hard -"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": - version: 1.0.1 - resolution: "dunder-proto@npm:1.0.1" - dependencies: - call-bind-apply-helpers: "npm:^1.0.1" - es-errors: "npm:^1.3.0" - gopd: "npm:^1.2.0" - checksum: 10/5add88a3d68d42d6e6130a0cac450b7c2edbe73364bbd2fc334564418569bea97c6943a8fcd70e27130bf32afc236f30982fc4905039b703f23e9e0433c29934 - languageName: node - linkType: hard - "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0" @@ -2495,120 +2185,6 @@ __metadata: languageName: node linkType: hard -"es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.9": - version: 1.23.9 - resolution: "es-abstract@npm:1.23.9" - dependencies: - array-buffer-byte-length: "npm:^1.0.2" - arraybuffer.prototype.slice: "npm:^1.0.4" - available-typed-arrays: "npm:^1.0.7" - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - data-view-buffer: "npm:^1.0.2" - data-view-byte-length: "npm:^1.0.2" - data-view-byte-offset: "npm:^1.0.1" - es-define-property: "npm:^1.0.1" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - es-set-tostringtag: "npm:^2.1.0" - es-to-primitive: "npm:^1.3.0" - function.prototype.name: "npm:^1.1.8" - get-intrinsic: "npm:^1.2.7" - get-proto: "npm:^1.0.0" - get-symbol-description: "npm:^1.1.0" - globalthis: "npm:^1.0.4" - gopd: "npm:^1.2.0" - has-property-descriptors: "npm:^1.0.2" - has-proto: "npm:^1.2.0" - has-symbols: "npm:^1.1.0" - hasown: "npm:^2.0.2" - internal-slot: "npm:^1.1.0" - is-array-buffer: "npm:^3.0.5" - is-callable: "npm:^1.2.7" - is-data-view: "npm:^1.0.2" - is-regex: "npm:^1.2.1" - is-shared-array-buffer: "npm:^1.0.4" - is-string: "npm:^1.1.1" - is-typed-array: "npm:^1.1.15" - is-weakref: "npm:^1.1.0" - math-intrinsics: "npm:^1.1.0" - object-inspect: "npm:^1.13.3" - object-keys: "npm:^1.1.1" - object.assign: "npm:^4.1.7" - own-keys: "npm:^1.0.1" - regexp.prototype.flags: "npm:^1.5.3" - safe-array-concat: "npm:^1.1.3" - safe-push-apply: "npm:^1.0.0" - safe-regex-test: "npm:^1.1.0" - set-proto: "npm:^1.0.0" - string.prototype.trim: "npm:^1.2.10" - string.prototype.trimend: "npm:^1.0.9" - string.prototype.trimstart: "npm:^1.0.8" - typed-array-buffer: "npm:^1.0.3" - typed-array-byte-length: "npm:^1.0.3" - typed-array-byte-offset: "npm:^1.0.4" - typed-array-length: "npm:^1.0.7" - unbox-primitive: "npm:^1.1.0" - which-typed-array: "npm:^1.1.18" - checksum: 10/31a321966d760d88fc2ed984104841b42f4f24fc322b246002b9be0af162e03803ee41fcc3cf8be89e07a27ba3033168f877dd983703cb81422ffe5322a27582 - languageName: node - linkType: hard - -"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": - version: 1.0.1 - resolution: "es-define-property@npm:1.0.1" - checksum: 10/f8dc9e660d90919f11084db0a893128f3592b781ce967e4fccfb8f3106cb83e400a4032c559184ec52ee1dbd4b01e7776c7cd0b3327b1961b1a4a7008920fe78 - languageName: node - linkType: hard - -"es-errors@npm:^1.3.0": - version: 1.3.0 - resolution: "es-errors@npm:1.3.0" - checksum: 10/96e65d640156f91b707517e8cdc454dd7d47c32833aa3e85d79f24f9eb7ea85f39b63e36216ef0114996581969b59fe609a94e30316b08f5f4df1d44134cf8d5 - languageName: node - linkType: hard - -"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": - version: 1.1.1 - resolution: "es-object-atoms@npm:1.1.1" - dependencies: - es-errors: "npm:^1.3.0" - checksum: 10/54fe77de288451dae51c37bfbfe3ec86732dc3778f98f3eb3bdb4bf48063b2c0b8f9c93542656986149d08aa5be3204286e2276053d19582b76753f1a2728867 - languageName: node - linkType: hard - -"es-set-tostringtag@npm:^2.1.0": - version: 2.1.0 - resolution: "es-set-tostringtag@npm:2.1.0" - dependencies: - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.6" - has-tostringtag: "npm:^1.0.2" - hasown: "npm:^2.0.2" - checksum: 10/86814bf8afbcd8966653f731415888019d4bc4aca6b6c354132a7a75bb87566751e320369654a101d23a91c87a85c79b178bcf40332839bd347aff437c4fb65f - languageName: node - linkType: hard - -"es-shim-unscopables@npm:^1.0.2": - version: 1.1.0 - resolution: "es-shim-unscopables@npm:1.1.0" - dependencies: - hasown: "npm:^2.0.2" - checksum: 10/c351f586c30bbabc62355be49564b2435468b52c3532b8a1663672e3d10dc300197e69c247869dd173e56d86423ab95fc0c10b0939cdae597094e0fdca078cba - languageName: node - linkType: hard - -"es-to-primitive@npm:^1.3.0": - version: 1.3.0 - resolution: "es-to-primitive@npm:1.3.0" - dependencies: - is-callable: "npm:^1.2.7" - is-date-object: "npm:^1.0.5" - is-symbol: "npm:^1.0.4" - checksum: 10/17faf35c221aad59a16286cbf58ef6f080bf3c485dff202c490d074d8e74da07884e29b852c245d894eac84f73c58330ec956dfd6d02c0b449d75eb1012a3f9b - languageName: node - linkType: hard - "escalade@npm:^3.1.1, escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" @@ -2630,143 +2206,6 @@ __metadata: languageName: node linkType: hard -"eslint-compat-utils@npm:^0.5.1": - version: 0.5.1 - resolution: "eslint-compat-utils@npm:0.5.1" - dependencies: - semver: "npm:^7.5.4" - peerDependencies: - eslint: ">=6.0.0" - checksum: 10/ac65ac1c6107cf19f63f5fc17cea361c9cb1336be7356f23dbb0fac10979974b4622e13e950be43cbf431801f2c07f7dab448573181ccf6edc0b86d5b5304511 - languageName: node - linkType: hard - -"eslint-config-prettier@npm:^9.0.0": - version: 9.1.0 - resolution: "eslint-config-prettier@npm:9.1.0" - peerDependencies: - eslint: ">=7.0.0" - bin: - eslint-config-prettier: bin/cli.js - checksum: 10/411e3b3b1c7aa04e3e0f20d561271b3b909014956c4dba51c878bf1a23dbb8c800a3be235c46c4732c70827276e540b6eed4636d9b09b444fd0a8e07f0fcd830 - languageName: node - linkType: hard - -"eslint-import-resolver-node@npm:^0.3.9": - version: 0.3.9 - resolution: "eslint-import-resolver-node@npm:0.3.9" - dependencies: - debug: "npm:^3.2.7" - is-core-module: "npm:^2.13.0" - resolve: "npm:^1.22.4" - checksum: 10/d52e08e1d96cf630957272e4f2644dcfb531e49dcfd1edd2e07e43369eb2ec7a7d4423d417beee613201206ff2efa4eb9a582b5825ee28802fc7c71fcd53ca83 - languageName: node - linkType: hard - -"eslint-module-utils@npm:^2.12.0": - version: 2.12.0 - resolution: "eslint-module-utils@npm:2.12.0" - dependencies: - debug: "npm:^3.2.7" - peerDependenciesMeta: - eslint: - optional: true - checksum: 10/dd27791147eca17366afcb83f47d6825b6ce164abb256681e5de4ec1d7e87d8605641eb869298a0dbc70665e2446dbcc2f40d3e1631a9475dd64dd23d4ca5dee - languageName: node - linkType: hard - -"eslint-plugin-es-x@npm:^7.5.0": - version: 7.8.0 - resolution: "eslint-plugin-es-x@npm:7.8.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.1.2" - "@eslint-community/regexpp": "npm:^4.11.0" - eslint-compat-utils: "npm:^0.5.1" - peerDependencies: - eslint: ">=8" - checksum: 10/1df8d52c4fadc06854ce801af05b05f2642aa2deb918fb7d37738596eabd70b7f21a22b150b78ec9104bac6a1b6b4fb796adea2364ede91b01d20964849ce5f7 - languageName: node - linkType: hard - -"eslint-plugin-import@npm:^2.28.1": - version: 2.31.0 - resolution: "eslint-plugin-import@npm:2.31.0" - dependencies: - "@rtsao/scc": "npm:^1.1.0" - array-includes: "npm:^3.1.8" - array.prototype.findlastindex: "npm:^1.2.5" - array.prototype.flat: "npm:^1.3.2" - array.prototype.flatmap: "npm:^1.3.2" - debug: "npm:^3.2.7" - doctrine: "npm:^2.1.0" - eslint-import-resolver-node: "npm:^0.3.9" - eslint-module-utils: "npm:^2.12.0" - hasown: "npm:^2.0.2" - is-core-module: "npm:^2.15.1" - is-glob: "npm:^4.0.3" - minimatch: "npm:^3.1.2" - object.fromentries: "npm:^2.0.8" - object.groupby: "npm:^1.0.3" - object.values: "npm:^1.2.0" - semver: "npm:^6.3.1" - string.prototype.trimend: "npm:^1.0.8" - tsconfig-paths: "npm:^3.15.0" - peerDependencies: - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 - checksum: 10/6b76bd009ac2db0615d9019699d18e2a51a86cb8c1d0855a35fb1b418be23b40239e6debdc6e8c92c59f1468ed0ea8d7b85c817117a113d5cc225be8a02ad31c - languageName: node - linkType: hard - -"eslint-plugin-n@npm:^16.2.0": - version: 16.6.2 - resolution: "eslint-plugin-n@npm:16.6.2" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.4.0" - builtins: "npm:^5.0.1" - eslint-plugin-es-x: "npm:^7.5.0" - get-tsconfig: "npm:^4.7.0" - globals: "npm:^13.24.0" - ignore: "npm:^5.2.4" - is-builtin-module: "npm:^3.2.1" - is-core-module: "npm:^2.12.1" - minimatch: "npm:^3.1.2" - resolve: "npm:^1.22.2" - semver: "npm:^7.5.3" - peerDependencies: - eslint: ">=7.0.0" - checksum: 10/e0f600d03d3a3df57e9a811648b1b534a6d67c90ea9406340ddf3763c2b87cf5ef910b390f787ca5cb27c8d8ff36aad42d70209b54e2a1cb4cc2507ca417229a - languageName: node - linkType: hard - -"eslint-plugin-prettier@npm:^5.0.1": - version: 5.2.3 - resolution: "eslint-plugin-prettier@npm:5.2.3" - dependencies: - prettier-linter-helpers: "npm:^1.0.0" - synckit: "npm:^0.9.1" - peerDependencies: - "@types/eslint": ">=8.0.0" - eslint: ">=8.0.0" - eslint-config-prettier: "*" - prettier: ">=3.0.0" - peerDependenciesMeta: - "@types/eslint": - optional: true - eslint-config-prettier: - optional: true - checksum: 10/6444a0b89f3e2a6b38adce69761133f8539487d797f1655b3fa24f93a398be132c4f68f87041a14740b79202368d5782aa1dffd2bd7a3ea659f263d6796acf15 - languageName: node - linkType: hard - -"eslint-plugin-promise@npm:^6.1.1": - version: 6.6.0 - resolution: "eslint-plugin-promise@npm:6.6.0" - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - checksum: 10/c2b5604efd7e1390c132fcbf06cb2f072c956ffa65c14a991cb74ba1e2327357797239cb5b9b292d5e4010301bb897bd85a6273d7873fb157edc46aa2d95cbd9 - languageName: node - linkType: hard - "eslint-scope@npm:^7.2.2": version: 7.2.2 resolution: "eslint-scope@npm:7.2.2" @@ -2966,13 +2405,6 @@ __metadata: languageName: node linkType: hard -"fast-diff@npm:^1.1.2": - version: 1.3.0 - resolution: "fast-diff@npm:1.3.0" - checksum: 10/9e57415bc69cd6efcc720b3b8fe9fdaf42dcfc06f86f0f45378b1fa512598a8aac48aa3928c8751d58e2f01bb4ba4f07e4f3d9bc0d57586d45f1bd1e872c6cde - languageName: node - linkType: hard - "fast-fifo@npm:^1.2.0, fast-fifo@npm:^1.3.2": version: 1.3.2 resolution: "fast-fifo@npm:1.3.2" @@ -2980,19 +2412,6 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.2.9": - version: 3.3.3 - resolution: "fast-glob@npm:3.3.3" - dependencies: - "@nodelib/fs.stat": "npm:^2.0.2" - "@nodelib/fs.walk": "npm:^1.2.3" - glob-parent: "npm:^5.1.2" - merge2: "npm:^1.3.0" - micromatch: "npm:^4.0.8" - checksum: 10/dcc6432b269762dd47381d8b8358bf964d8f4f60286ac6aa41c01ade70bda459ff2001b516690b96d5365f68a49242966112b5d5cc9cd82395fa8f9d017c90ad - languageName: node - linkType: hard - "fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" @@ -3104,15 +2523,6 @@ __metadata: languageName: node linkType: hard -"for-each@npm:^0.3.3": - version: 0.3.5 - resolution: "for-each@npm:0.3.5" - dependencies: - is-callable: "npm:^1.2.7" - checksum: 10/330cc2439f85c94f4609de3ee1d32c5693ae15cdd7fe3d112c4fd9efd4ce7143f2c64ef6c2c9e0cfdb0058437f33ef05b5bdae5b98fcc903fb2143fbaf0fea0f - languageName: node - linkType: hard - "foreground-child@npm:^3.1.0": version: 3.3.1 resolution: "foreground-child@npm:3.3.1" @@ -3190,27 +2600,6 @@ __metadata: languageName: node linkType: hard -"function.prototype.name@npm:^1.1.6, function.prototype.name@npm:^1.1.8": - version: 1.1.8 - resolution: "function.prototype.name@npm:1.1.8" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - define-properties: "npm:^1.2.1" - functions-have-names: "npm:^1.2.3" - hasown: "npm:^2.0.2" - is-callable: "npm:^1.2.7" - checksum: 10/25b9e5bea936732a6f0c0c08db58cc0d609ac1ed458c6a07ead46b32e7b9bf3fe5887796c3f83d35994efbc4fdde81c08ac64135b2c399b8f2113968d44082bc - languageName: node - linkType: hard - -"functions-have-names@npm:^1.2.3": - version: 1.2.3 - resolution: "functions-have-names@npm:1.2.3" - checksum: 10/0ddfd3ed1066a55984aaecebf5419fbd9344a5c38dd120ffb0739fac4496758dcf371297440528b115e4367fc46e3abc86a2cc0ff44612181b175ae967a11a05 - languageName: node - linkType: hard - "gensync@npm:^1.0.0-beta.2": version: 1.0.0-beta.2 resolution: "gensync@npm:1.0.0-beta.2" @@ -3225,24 +2614,6 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7": - version: 1.3.0 - resolution: "get-intrinsic@npm:1.3.0" - dependencies: - call-bind-apply-helpers: "npm:^1.0.2" - es-define-property: "npm:^1.0.1" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.1.1" - function-bind: "npm:^1.1.2" - get-proto: "npm:^1.0.1" - gopd: "npm:^1.2.0" - has-symbols: "npm:^1.1.0" - hasown: "npm:^2.0.2" - math-intrinsics: "npm:^1.1.0" - checksum: 10/6e9dd920ff054147b6f44cb98104330e87caafae051b6d37b13384a45ba15e71af33c3baeac7cb630a0aaa23142718dcf25b45cfdd86c184c5dcb4e56d953a10 - languageName: node - linkType: hard - "get-package-type@npm:^0.1.0": version: 0.1.0 resolution: "get-package-type@npm:0.1.0" @@ -3257,16 +2628,6 @@ __metadata: languageName: node linkType: hard -"get-proto@npm:^1.0.0, get-proto@npm:^1.0.1": - version: 1.0.1 - resolution: "get-proto@npm:1.0.1" - dependencies: - dunder-proto: "npm:^1.0.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10/4fc96afdb58ced9a67558698b91433e6b037aaa6f1493af77498d7c85b141382cf223c0e5946f334fb328ee85dfe6edd06d218eaf09556f4bc4ec6005d7f5f7b - languageName: node - linkType: hard - "get-stream@npm:^6.0.0": version: 6.0.1 resolution: "get-stream@npm:6.0.1" @@ -3274,35 +2635,6 @@ __metadata: languageName: node linkType: hard -"get-symbol-description@npm:^1.1.0": - version: 1.1.0 - resolution: "get-symbol-description@npm:1.1.0" - dependencies: - call-bound: "npm:^1.0.3" - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.6" - checksum: 10/a353e3a9595a74720b40fb5bae3ba4a4f826e186e83814d93375182384265676f59e49998b9cdfac4a2225ce95a3d32a68f502a2c5619303987f1c183ab80494 - languageName: node - linkType: hard - -"get-tsconfig@npm:^4.7.0": - version: 4.10.0 - resolution: "get-tsconfig@npm:4.10.0" - dependencies: - resolve-pkg-maps: "npm:^1.0.0" - checksum: 10/5259b5c99a1957114337d9d0603b4a305ec9e29fa6cac7d2fbf634ba6754a0cc88bfd281a02416ce64e604b637d3cb239185381a79a5842b17fb55c097b38c4b - languageName: node - linkType: hard - -"glob-parent@npm:^5.1.2": - version: 5.1.2 - resolution: "glob-parent@npm:5.1.2" - dependencies: - is-glob: "npm:^4.0.1" - checksum: 10/32cd106ce8c0d83731966d31517adb766d02c3812de49c30cfe0675c7c0ae6630c11214c54a5ae67aca882cf738d27fd7768f21aa19118b9245950554be07247 - languageName: node - linkType: hard - "glob-parent@npm:^6.0.2": version: 6.0.2 resolution: "glob-parent@npm:6.0.2" @@ -3349,7 +2681,7 @@ __metadata: languageName: node linkType: hard -"globals@npm:^13.19.0, globals@npm:^13.24.0": +"globals@npm:^13.19.0": version: 13.24.0 resolution: "globals@npm:13.24.0" dependencies: @@ -3358,37 +2690,6 @@ __metadata: languageName: node linkType: hard -"globalthis@npm:^1.0.4": - version: 1.0.4 - resolution: "globalthis@npm:1.0.4" - dependencies: - define-properties: "npm:^1.2.1" - gopd: "npm:^1.0.1" - checksum: 10/1f1fd078fb2f7296306ef9dd51019491044ccf17a59ed49d375b576ca108ff37e47f3d29aead7add40763574a992f16a5367dd1e2173b8634ef18556ab719ac4 - languageName: node - linkType: hard - -"globby@npm:^11.1.0": - version: 11.1.0 - resolution: "globby@npm:11.1.0" - dependencies: - array-union: "npm:^2.1.0" - dir-glob: "npm:^3.0.1" - fast-glob: "npm:^3.2.9" - ignore: "npm:^5.2.0" - merge2: "npm:^1.4.1" - slash: "npm:^3.0.0" - checksum: 10/288e95e310227bbe037076ea81b7c2598ccbc3122d87abc6dab39e1eec309aa14f0e366a98cdc45237ffcfcbad3db597778c0068217dcb1950fef6249104e1b1 - languageName: node - linkType: hard - -"gopd@npm:^1.0.1, gopd@npm:^1.2.0": - version: 1.2.0 - resolution: "gopd@npm:1.2.0" - checksum: 10/94e296d69f92dc1c0768fcfeecfb3855582ab59a7c75e969d5f96ce50c3d201fd86d5a2857c22565764d5bb8a816c7b1e58f133ec318cd56274da36c5e3fb1a1 - languageName: node - linkType: hard - "graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" @@ -3403,13 +2704,6 @@ __metadata: languageName: node linkType: hard -"has-bigints@npm:^1.0.2": - version: 1.1.0 - resolution: "has-bigints@npm:1.1.0" - checksum: 10/90fb1b24d40d2472bcd1c8bd9dd479037ec240215869bdbff97b2be83acef57d28f7e96bdd003a21bed218d058b49097f4acc8821c05b1629cc5d48dd7bfcccd - languageName: node - linkType: hard - "has-flag@npm:^4.0.0": version: 4.0.0 resolution: "has-flag@npm:4.0.0" @@ -3417,40 +2711,6 @@ __metadata: languageName: node linkType: hard -"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2": - version: 1.0.2 - resolution: "has-property-descriptors@npm:1.0.2" - dependencies: - es-define-property: "npm:^1.0.0" - checksum: 10/2d8c9ab8cebb572e3362f7d06139a4592105983d4317e68f7adba320fe6ddfc8874581e0971e899e633fd5f72e262830edce36d5a0bc863dad17ad20572484b2 - languageName: node - linkType: hard - -"has-proto@npm:^1.2.0": - version: 1.2.0 - resolution: "has-proto@npm:1.2.0" - dependencies: - dunder-proto: "npm:^1.0.0" - checksum: 10/7eaed07728eaa28b77fadccabce53f30de467ff186a766872669a833ac2e87d8922b76a22cc58339d7e0277aefe98d6d00762113b27a97cdf65adcf958970935 - languageName: node - linkType: hard - -"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": - version: 1.1.0 - resolution: "has-symbols@npm:1.1.0" - checksum: 10/959385c98696ebbca51e7534e0dc723ada325efa3475350951363cce216d27373e0259b63edb599f72eb94d6cde8577b4b2375f080b303947e560f85692834fa - languageName: node - linkType: hard - -"has-tostringtag@npm:^1.0.2": - version: 1.0.2 - resolution: "has-tostringtag@npm:1.0.2" - dependencies: - has-symbols: "npm:^1.0.3" - checksum: 10/c74c5f5ceee3c8a5b8bc37719840dc3749f5b0306d818974141dda2471a1a2ca6c8e46b9d6ac222c5345df7a901c9b6f350b1e6d62763fec877e26609a401bfe - languageName: node - linkType: hard - "hasown@npm:^2.0.2": version: 2.0.2 resolution: "hasown@npm:2.0.2" @@ -3524,7 +2784,7 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^5.2.0, ignore@npm:^5.2.4": +"ignore@npm:^5.2.0": version: 5.3.2 resolution: "ignore@npm:5.3.2" checksum: 10/cceb6a457000f8f6a50e1196429750d782afce5680dd878aa4221bd79972d68b3a55b4b1458fc682be978f4d3c6a249046aa0880637367216444ab7b014cfc98 @@ -3577,17 +2837,6 @@ __metadata: languageName: node linkType: hard -"internal-slot@npm:^1.1.0": - version: 1.1.0 - resolution: "internal-slot@npm:1.1.0" - dependencies: - es-errors: "npm:^1.3.0" - hasown: "npm:^2.0.2" - side-channel: "npm:^1.1.0" - checksum: 10/1d5219273a3dab61b165eddf358815eefc463207db33c20fcfca54717da02e3f492003757721f972fd0bf21e4b426cab389c5427b99ceea4b8b670dc88ee6d4a - languageName: node - linkType: hard - "io-ts@npm:^2.2.20": version: 2.2.22 resolution: "io-ts@npm:2.2.22" @@ -3607,17 +2856,6 @@ __metadata: languageName: node linkType: hard -"is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5": - version: 3.0.5 - resolution: "is-array-buffer@npm:3.0.5" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - get-intrinsic: "npm:^1.2.6" - checksum: 10/ef1095c55b963cd0dcf6f88a113e44a0aeca91e30d767c475e7d746d28d1195b10c5076b94491a7a0cd85020ca6a4923070021d74651d093dc909e9932cf689b - languageName: node - linkType: hard - "is-arrayish@npm:^0.2.1": version: 0.2.1 resolution: "is-arrayish@npm:0.2.1" @@ -3625,55 +2863,7 @@ __metadata: languageName: node linkType: hard -"is-async-function@npm:^2.0.0": - version: 2.1.1 - resolution: "is-async-function@npm:2.1.1" - dependencies: - async-function: "npm:^1.0.0" - call-bound: "npm:^1.0.3" - get-proto: "npm:^1.0.1" - has-tostringtag: "npm:^1.0.2" - safe-regex-test: "npm:^1.1.0" - checksum: 10/7c2ac7efdf671e03265e74a043bcb1c0a32e226bc2a42dfc5ec8644667df668bbe14b91c08e6c1414f392f8cf86cd1d489b3af97756e2c7a49dd1ba63fd40ca6 - languageName: node - linkType: hard - -"is-bigint@npm:^1.1.0": - version: 1.1.0 - resolution: "is-bigint@npm:1.1.0" - dependencies: - has-bigints: "npm:^1.0.2" - checksum: 10/10cf327310d712fe227cfaa32d8b11814c214392b6ac18c827f157e1e85363cf9c8e2a22df526689bd5d25e53b58cc110894787afb54e138e7c504174dba15fd - languageName: node - linkType: hard - -"is-boolean-object@npm:^1.2.1": - version: 1.2.2 - resolution: "is-boolean-object@npm:1.2.2" - dependencies: - call-bound: "npm:^1.0.3" - has-tostringtag: "npm:^1.0.2" - checksum: 10/051fa95fdb99d7fbf653165a7e6b2cba5d2eb62f7ffa81e793a790f3fb5366c91c1b7b6af6820aa2937dd86c73aa3ca9d9ca98f500988457b1c59692c52ba911 - languageName: node - linkType: hard - -"is-builtin-module@npm:^3.2.1": - version: 3.2.1 - resolution: "is-builtin-module@npm:3.2.1" - dependencies: - builtin-modules: "npm:^3.3.0" - checksum: 10/e8f0ffc19a98240bda9c7ada84d846486365af88d14616e737d280d378695c8c448a621dcafc8332dbf0fcd0a17b0763b845400709963fa9151ddffece90ae88 - languageName: node - linkType: hard - -"is-callable@npm:^1.2.7": - version: 1.2.7 - resolution: "is-callable@npm:1.2.7" - checksum: 10/48a9297fb92c99e9df48706241a189da362bff3003354aea4048bd5f7b2eb0d823cd16d0a383cece3d76166ba16d85d9659165ac6fcce1ac12e6c649d66dbdb9 - languageName: node - linkType: hard - -"is-core-module@npm:^2.12.1, is-core-module@npm:^2.13.0, is-core-module@npm:^2.15.1, is-core-module@npm:^2.16.0": +"is-core-module@npm:^2.16.0": version: 2.16.1 resolution: "is-core-module@npm:2.16.1" dependencies: @@ -3682,27 +2872,6 @@ __metadata: languageName: node linkType: hard -"is-data-view@npm:^1.0.1, is-data-view@npm:^1.0.2": - version: 1.0.2 - resolution: "is-data-view@npm:1.0.2" - dependencies: - call-bound: "npm:^1.0.2" - get-intrinsic: "npm:^1.2.6" - is-typed-array: "npm:^1.1.13" - checksum: 10/357e9a48fa38f369fd6c4c3b632a3ab2b8adca14997db2e4b3fe94c4cd0a709af48e0fb61b02c64a90c0dd542fd489d49c2d03157b05ae6c07f5e4dec9e730a8 - languageName: node - linkType: hard - -"is-date-object@npm:^1.0.5, is-date-object@npm:^1.1.0": - version: 1.1.0 - resolution: "is-date-object@npm:1.1.0" - dependencies: - call-bound: "npm:^1.0.2" - has-tostringtag: "npm:^1.0.2" - checksum: 10/3a811b2c3176fb31abee1d23d3dc78b6c65fd9c07d591fcb67553cab9e7f272728c3dd077d2d738b53f9a2103255b0a6e8dfc9568a7805c56a78b2563e8d1dec - languageName: node - linkType: hard - "is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": version: 2.2.1 resolution: "is-docker@npm:2.2.1" @@ -3719,15 +2888,6 @@ __metadata: languageName: node linkType: hard -"is-finalizationregistry@npm:^1.1.0": - version: 1.1.1 - resolution: "is-finalizationregistry@npm:1.1.1" - dependencies: - call-bound: "npm:^1.0.3" - checksum: 10/0bfb145e9a1ba852ddde423b0926d2169ae5fe9e37882cde9e8f69031281a986308df4d982283e152396e88b86562ed2256cbaa5e6390fb840a4c25ab54b8a80 - languageName: node - linkType: hard - "is-fullwidth-code-point@npm:^3.0.0": version: 3.0.0 resolution: "is-fullwidth-code-point@npm:3.0.0" @@ -3742,19 +2902,7 @@ __metadata: languageName: node linkType: hard -"is-generator-function@npm:^1.0.10": - version: 1.1.0 - resolution: "is-generator-function@npm:1.1.0" - dependencies: - call-bound: "npm:^1.0.3" - get-proto: "npm:^1.0.0" - has-tostringtag: "npm:^1.0.2" - safe-regex-test: "npm:^1.1.0" - checksum: 10/5906ff51a856a5fbc6b90a90fce32040b0a6870da905f98818f1350f9acadfc9884f7c3dec833fce04b83dd883937b86a190b6593ede82e8b1af8b6c4ecf7cbd - languageName: node - linkType: hard - -"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3": +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.3": version: 4.0.3 resolution: "is-glob@npm:4.0.3" dependencies: @@ -3763,23 +2911,6 @@ __metadata: languageName: node linkType: hard -"is-map@npm:^2.0.3": - version: 2.0.3 - resolution: "is-map@npm:2.0.3" - checksum: 10/8de7b41715b08bcb0e5edb0fb9384b80d2d5bcd10e142188f33247d19ff078abaf8e9b6f858e2302d8d05376a26a55cd23a3c9f8ab93292b02fcd2cc9e4e92bb - languageName: node - linkType: hard - -"is-number-object@npm:^1.1.1": - version: 1.1.1 - resolution: "is-number-object@npm:1.1.1" - dependencies: - call-bound: "npm:^1.0.3" - has-tostringtag: "npm:^1.0.2" - checksum: 10/a5922fb8779ab1ea3b8a9c144522b3d0bea5d9f8f23f7a72470e61e1e4df47714e28e0154ac011998b709cce260c3c9447ad3cd24a96c2f2a0abfdb2cbdc76c8 - languageName: node - linkType: hard - "is-number@npm:^7.0.0": version: 7.0.0 resolution: "is-number@npm:7.0.0" @@ -3794,34 +2925,6 @@ __metadata: languageName: node linkType: hard -"is-regex@npm:^1.2.1": - version: 1.2.1 - resolution: "is-regex@npm:1.2.1" - dependencies: - call-bound: "npm:^1.0.2" - gopd: "npm:^1.2.0" - has-tostringtag: "npm:^1.0.2" - hasown: "npm:^2.0.2" - checksum: 10/c42b7efc5868a5c9a4d8e6d3e9816e8815c611b09535c00fead18a1138455c5cb5e1887f0023a467ad3f9c419d62ba4dc3d9ba8bafe55053914d6d6454a945d2 - languageName: node - linkType: hard - -"is-set@npm:^2.0.3": - version: 2.0.3 - resolution: "is-set@npm:2.0.3" - checksum: 10/5685df33f0a4a6098a98c72d94d67cad81b2bc72f1fb2091f3d9283c4a1c582123cd709145b02a9745f0ce6b41e3e43f1c944496d1d74d4ea43358be61308669 - languageName: node - linkType: hard - -"is-shared-array-buffer@npm:^1.0.4": - version: 1.0.4 - resolution: "is-shared-array-buffer@npm:1.0.4" - dependencies: - call-bound: "npm:^1.0.3" - checksum: 10/0380d7c60cc692856871526ffcd38a8133818a2ee42d47bb8008248a0cd2121d8c8b5f66b6da3cac24bc5784553cacb6faaf678f66bc88c6615b42af2825230e - languageName: node - linkType: hard - "is-stream@npm:^2.0.0, is-stream@npm:^2.0.1": version: 2.0.1 resolution: "is-stream@npm:2.0.1" @@ -3829,62 +2932,6 @@ __metadata: languageName: node linkType: hard -"is-string@npm:^1.0.7, is-string@npm:^1.1.1": - version: 1.1.1 - resolution: "is-string@npm:1.1.1" - dependencies: - call-bound: "npm:^1.0.3" - has-tostringtag: "npm:^1.0.2" - checksum: 10/5277cb9e225a7cc8a368a72623b44a99f2cfa139659c6b203553540681ad4276bfc078420767aad0e73eef5f0bd07d4abf39a35d37ec216917879d11cebc1f8b - languageName: node - linkType: hard - -"is-symbol@npm:^1.0.4, is-symbol@npm:^1.1.1": - version: 1.1.1 - resolution: "is-symbol@npm:1.1.1" - dependencies: - call-bound: "npm:^1.0.2" - has-symbols: "npm:^1.1.0" - safe-regex-test: "npm:^1.1.0" - checksum: 10/db495c0d8cd0a7a66b4f4ef7fccee3ab5bd954cb63396e8ac4d32efe0e9b12fdfceb851d6c501216a71f4f21e5ff20fc2ee845a3d52d455e021c466ac5eb2db2 - languageName: node - linkType: hard - -"is-typed-array@npm:^1.1.13, is-typed-array@npm:^1.1.14, is-typed-array@npm:^1.1.15": - version: 1.1.15 - resolution: "is-typed-array@npm:1.1.15" - dependencies: - which-typed-array: "npm:^1.1.16" - checksum: 10/e8cf60b9ea85667097a6ad68c209c9722cfe8c8edf04d6218366469e51944c5cc25bae45ffb845c23f811d262e4314d3b0168748eb16711aa34d12724cdf0735 - languageName: node - linkType: hard - -"is-weakmap@npm:^2.0.2": - version: 2.0.2 - resolution: "is-weakmap@npm:2.0.2" - checksum: 10/a7b7e23206c542dcf2fa0abc483142731788771527e90e7e24f658c0833a0d91948a4f7b30d78f7a65255a48512e41a0288b778ba7fc396137515c12e201fd11 - languageName: node - linkType: hard - -"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.0": - version: 1.1.1 - resolution: "is-weakref@npm:1.1.1" - dependencies: - call-bound: "npm:^1.0.3" - checksum: 10/543506fd8259038b371bb083aac25b16cb4fd8b12fc58053aa3d45ac28dfd001cd5c6dffbba7aeea4213c74732d46b6cb2cfb5b412eed11f2db524f3f97d09a0 - languageName: node - linkType: hard - -"is-weakset@npm:^2.0.3": - version: 2.0.4 - resolution: "is-weakset@npm:2.0.4" - dependencies: - call-bound: "npm:^1.0.3" - get-intrinsic: "npm:^1.2.6" - checksum: 10/1d5e1d0179beeed3661125a6faa2e59bfb48afda06fc70db807f178aa0ebebc3758fb6358d76b3d528090d5ef85148c345dcfbf90839592fe293e3e5e82f2134 - languageName: node - linkType: hard - "is-wsl@npm:^2.2.0": version: 2.2.0 resolution: "is-wsl@npm:2.2.0" @@ -3894,13 +2941,6 @@ __metadata: languageName: node linkType: hard -"isarray@npm:^2.0.5": - version: 2.0.5 - resolution: "isarray@npm:2.0.5" - checksum: 10/1d8bc7911e13bb9f105b1b3e0b396c787a9e63046af0b8fe0ab1414488ab06b2b099b87a2d8a9e31d21c9a6fad773c7fc8b257c4880f2d957274479d28ca3414 - languageName: node - linkType: hard - "isarray@npm:~1.0.0": version: 1.0.0 resolution: "isarray@npm:1.0.0" @@ -4575,17 +3615,6 @@ __metadata: languageName: node linkType: hard -"json5@npm:^1.0.2": - version: 1.0.2 - resolution: "json5@npm:1.0.2" - dependencies: - minimist: "npm:^1.2.0" - bin: - json5: lib/cli.js - checksum: 10/a78d812dbbd5642c4f637dd130954acfd231b074965871c3e28a5bbd571f099d623ecf9161f1960c4ddf68e0cc98dee8bebfdb94a71ad4551f85a1afc94b63f6 - languageName: node - linkType: hard - "json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" @@ -4756,13 +3785,6 @@ __metadata: languageName: node linkType: hard -"math-intrinsics@npm:^1.1.0": - version: 1.1.0 - resolution: "math-intrinsics@npm:1.1.0" - checksum: 10/11df2eda46d092a6035479632e1ec865b8134bdfc4bd9e571a656f4191525404f13a283a515938c3a8de934dbfd9c09674d9da9fa831e6eb7e22b50b197d2edd - languageName: node - linkType: hard - "merge-stream@npm:^2.0.0": version: 2.0.0 resolution: "merge-stream@npm:2.0.0" @@ -4770,14 +3792,7 @@ __metadata: languageName: node linkType: hard -"merge2@npm:^1.3.0, merge2@npm:^1.4.1": - version: 1.4.1 - resolution: "merge2@npm:1.4.1" - checksum: 10/7268db63ed5169466540b6fb947aec313200bcf6d40c5ab722c22e242f651994619bcd85601602972d3c85bd2cc45a358a4c61937e9f11a061919a1da569b0c2 - languageName: node - linkType: hard - -"micromatch@npm:^4.0.4, micromatch@npm:^4.0.8": +"micromatch@npm:^4.0.4": version: 4.0.8 resolution: "micromatch@npm:4.0.8" dependencies: @@ -4794,15 +3809,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:9.0.3": - version: 9.0.3 - resolution: "minimatch@npm:9.0.3" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10/c81b47d28153e77521877649f4bab48348d10938df9e8147a58111fe00ef89559a2938de9f6632910c4f7bf7bb5cd81191a546167e58d357f0cfb1e18cecc1c5 - languageName: node - linkType: hard - "minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -4830,7 +3836,7 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.2.0, minimist@npm:^1.2.6": +"minimist@npm:^1.2.6": version: 1.2.8 resolution: "minimist@npm:1.2.8" checksum: 10/908491b6cc15a6c440ba5b22780a0ba89b9810e1aea684e253e43c4e3b8d56ec1dcdd7ea96dde119c29df59c936cde16062159eae4225c691e19c70b432b6e6f @@ -4939,7 +3945,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:^2.1.1, ms@npm:^2.1.3": +"ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10/aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d @@ -5030,69 +4036,13 @@ __metadata: languageName: node linkType: hard -"object-inspect@npm:^1.12.3, object-inspect@npm:^1.13.3": +"object-inspect@npm:^1.12.3": version: 1.13.4 resolution: "object-inspect@npm:1.13.4" checksum: 10/aa13b1190ad3e366f6c83ad8a16ed37a19ed57d267385aa4bfdccda833d7b90465c057ff6c55d035a6b2e52c1a2295582b294217a0a3a1ae7abdd6877ef781fb languageName: node linkType: hard -"object-keys@npm:^1.1.1": - version: 1.1.1 - resolution: "object-keys@npm:1.1.1" - checksum: 10/3d81d02674115973df0b7117628ea4110d56042e5326413e4b4313f0bcdf7dd78d4a3acef2c831463fa3796a66762c49daef306f4a0ea1af44877d7086d73bde - languageName: node - linkType: hard - -"object.assign@npm:^4.1.7": - version: 4.1.7 - resolution: "object.assign@npm:4.1.7" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - define-properties: "npm:^1.2.1" - es-object-atoms: "npm:^1.0.0" - has-symbols: "npm:^1.1.0" - object-keys: "npm:^1.1.1" - checksum: 10/3fe28cdd779f2a728a9a66bd688679ba231a2b16646cd1e46b528fe7c947494387dda4bc189eff3417f3717ef4f0a8f2439347cf9a9aa3cef722fbfd9f615587 - languageName: node - linkType: hard - -"object.fromentries@npm:^2.0.8": - version: 2.0.8 - resolution: "object.fromentries@npm:2.0.8" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - es-object-atoms: "npm:^1.0.0" - checksum: 10/5b2e80f7af1778b885e3d06aeb335dcc86965e39464671adb7167ab06ac3b0f5dd2e637a90d8ebd7426d69c6f135a4753ba3dd7d0fe2a7030cf718dcb910fd92 - languageName: node - linkType: hard - -"object.groupby@npm:^1.0.3": - version: 1.0.3 - resolution: "object.groupby@npm:1.0.3" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - checksum: 10/44cb86dd2c660434be65f7585c54b62f0425b0c96b5c948d2756be253ef06737da7e68d7106e35506ce4a44d16aa85a413d11c5034eb7ce5579ec28752eb42d0 - languageName: node - linkType: hard - -"object.values@npm:^1.2.0": - version: 1.2.1 - resolution: "object.values@npm:1.2.1" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - define-properties: "npm:^1.2.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10/f5ec9eccdefeaaa834b089c525663436812a65ff13de7964a1c3a9110f32054f2d58aa476a645bb14f75a79f3fe1154fb3e7bfdae7ac1e80affe171b2ef74bce - languageName: node - linkType: hard - "on-exit-leak-free@npm:^2.1.0": version: 2.1.2 resolution: "on-exit-leak-free@npm:2.1.2" @@ -5143,17 +4093,6 @@ __metadata: languageName: node linkType: hard -"own-keys@npm:^1.0.1": - version: 1.0.1 - resolution: "own-keys@npm:1.0.1" - dependencies: - get-intrinsic: "npm:^1.2.6" - object-keys: "npm:^1.1.1" - safe-push-apply: "npm:^1.0.0" - checksum: 10/ab4bb3b8636908554fc19bf899e225444195092864cb61503a0d048fdaf662b04be2605b636a4ffeaf6e8811f6fcfa8cbb210ec964c0eb1a41eb853e1d5d2f41 - languageName: node - linkType: hard - "p-limit@npm:^2.2.0": version: 2.3.0 resolution: "p-limit@npm:2.3.0" @@ -5270,13 +4209,6 @@ __metadata: languageName: node linkType: hard -"path-type@npm:^4.0.0": - version: 4.0.0 - resolution: "path-type@npm:4.0.0" - checksum: 10/5b1e2daa247062061325b8fdbfd1fb56dde0a448fb1455453276ea18c60685bdad23a445dc148cf87bc216be1573357509b7d4060494a6fd768c7efad833ee45 - languageName: node - linkType: hard - "picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" @@ -5369,13 +4301,6 @@ __metadata: languageName: node linkType: hard -"possible-typed-array-names@npm:^1.0.0": - version: 1.1.0 - resolution: "possible-typed-array-names@npm:1.1.0" - checksum: 10/2f44137b8d3dd35f4a7ba7469eec1cd9cfbb46ec164b93a5bc1f4c3d68599c9910ee3b91da1d28b4560e9cc8414c3cd56fedc07259c67e52cc774476270d3302 - languageName: node - linkType: hard - "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -5383,24 +4308,6 @@ __metadata: languageName: node linkType: hard -"prettier-linter-helpers@npm:^1.0.0": - version: 1.0.0 - resolution: "prettier-linter-helpers@npm:1.0.0" - dependencies: - fast-diff: "npm:^1.1.2" - checksum: 10/00ce8011cf6430158d27f9c92cfea0a7699405633f7f1d4a45f07e21bf78e99895911cbcdc3853db3a824201a7c745bd49bfea8abd5fb9883e765a90f74f8392 - languageName: node - linkType: hard - -"prettier@npm:^3.0.3": - version: 3.5.2 - resolution: "prettier@npm:3.5.2" - bin: - prettier: bin/prettier.cjs - checksum: 10/ac7a157c8ec76459b13d81a03ff65d228015992cb926b676b0f1c83edd47e5db8ba257336b400be20942fc671816f1afde377cffe94d9e4368762a3d3acbffe5 - languageName: node - linkType: hard - "pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0": version: 29.7.0 resolution: "pretty-format@npm:29.7.0" @@ -5580,36 +4487,6 @@ __metadata: languageName: node linkType: hard -"reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.9": - version: 1.0.10 - resolution: "reflect.getprototypeof@npm:1.0.10" - dependencies: - call-bind: "npm:^1.0.8" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.9" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.7" - get-proto: "npm:^1.0.1" - which-builtin-type: "npm:^1.2.1" - checksum: 10/80a4e2be716f4fe46a89a08ccad0863b47e8ce0f49616cab2d65dab0fbd53c6fdba0f52935fd41d37a2e4e22355c272004f920d63070de849f66eea7aeb4a081 - languageName: node - linkType: hard - -"regexp.prototype.flags@npm:^1.5.3": - version: 1.5.4 - resolution: "regexp.prototype.flags@npm:1.5.4" - dependencies: - call-bind: "npm:^1.0.8" - define-properties: "npm:^1.2.1" - es-errors: "npm:^1.3.0" - get-proto: "npm:^1.0.1" - gopd: "npm:^1.2.0" - set-function-name: "npm:^2.0.2" - checksum: 10/8ab897ca445968e0b96f6237641510f3243e59c180ee2ee8d83889c52ff735dd1bf3657fcd36db053e35e1d823dd53f2565d0b8021ea282c9fe62401c6c3bd6d - languageName: node - linkType: hard - "require-directory@npm:^2.1.1": version: 2.1.1 resolution: "require-directory@npm:2.1.1" @@ -5640,13 +4517,6 @@ __metadata: languageName: node linkType: hard -"resolve-pkg-maps@npm:^1.0.0": - version: 1.0.0 - resolution: "resolve-pkg-maps@npm:1.0.0" - checksum: 10/0763150adf303040c304009231314d1e84c6e5ebfa2d82b7d94e96a6e82bacd1dcc0b58ae257315f3c8adb89a91d8d0f12928241cba2df1680fbe6f60bf99b0e - languageName: node - linkType: hard - "resolve.exports@npm:^2.0.0": version: 2.0.3 resolution: "resolve.exports@npm:2.0.3" @@ -5654,7 +4524,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.20.0, resolve@npm:^1.22.2, resolve@npm:^1.22.4": +"resolve@npm:^1.20.0": version: 1.22.10 resolution: "resolve@npm:1.22.10" dependencies: @@ -5667,7 +4537,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.2#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin": +"resolve@patch:resolve@npm%3A^1.20.0#optional!builtin": version: 1.22.10 resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" dependencies: @@ -5720,19 +4590,12 @@ __metadata: version: 0.0.0-use.local resolution: "root-workspace-0b6124@workspace:." dependencies: + "@biomejs/biome": "npm:1.9.4" "@midnight-ntwrk/compact-runtime": "npm:^0.7.0" "@midnight-ntwrk/ledger": "npm:^3.0.6" "@midnight-ntwrk/zswap": "npm:^3.0.6" "@types/jest": "npm:^29.5.6" "@types/node": "npm:^18.18.6" - "@typescript-eslint/eslint-plugin": "npm:^6.8.0" - "@typescript-eslint/parser": "npm:^6.8.0" - eslint: "npm:^8.52.0" - eslint-config-prettier: "npm:^9.0.0" - eslint-plugin-import: "npm:^2.28.1" - eslint-plugin-n: "npm:^16.2.0" - eslint-plugin-prettier: "npm:^5.0.1" - eslint-plugin-promise: "npm:^6.1.1" fast-check: "npm:^3.15.0" fp-ts: "npm:^2.16.1" io-ts: "npm:^2.2.20" @@ -5743,7 +4606,6 @@ __metadata: jest-junit: "npm:^16.0.0" pino: "npm:^8.16.0" pino-pretty: "npm:^10.2.3" - prettier: "npm:^3.0.3" rxjs: "npm:^7.8.1" testcontainers: "npm:^10.3.2" ts-jest: "npm:^29.1.1" @@ -5771,19 +4633,6 @@ __metadata: languageName: node linkType: hard -"safe-array-concat@npm:^1.1.3": - version: 1.1.3 - resolution: "safe-array-concat@npm:1.1.3" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.2" - get-intrinsic: "npm:^1.2.6" - has-symbols: "npm:^1.1.0" - isarray: "npm:^2.0.5" - checksum: 10/fac4f40f20a3f7da024b54792fcc61059e814566dcbb04586bfefef4d3b942b2408933f25b7b3dd024affd3f2a6bbc916bef04807855e4f192413941369db864 - languageName: node - linkType: hard - "safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": version: 5.1.2 resolution: "safe-buffer@npm:5.1.2" @@ -5798,27 +4647,6 @@ __metadata: languageName: node linkType: hard -"safe-push-apply@npm:^1.0.0": - version: 1.0.0 - resolution: "safe-push-apply@npm:1.0.0" - dependencies: - es-errors: "npm:^1.3.0" - isarray: "npm:^2.0.5" - checksum: 10/2bd4e53b6694f7134b9cf93631480e7fafc8637165f0ee91d5a4af5e7f33d37de9562d1af5021178dd4217d0230cde8d6530fa28cfa1ebff9a431bf8fff124b4 - languageName: node - linkType: hard - -"safe-regex-test@npm:^1.1.0": - version: 1.1.0 - resolution: "safe-regex-test@npm:1.1.0" - dependencies: - call-bound: "npm:^1.0.2" - es-errors: "npm:^1.3.0" - is-regex: "npm:^1.2.1" - checksum: 10/ebdb61f305bf4756a5b023ad86067df5a11b26898573afe9e52a548a63c3bd594825d9b0e2dde2eb3c94e57e0e04ac9929d4107c394f7b8e56a4613bed46c69a - languageName: node - linkType: hard - "safe-stable-stringify@npm:^2.3.1": version: 2.5.0 resolution: "safe-stable-stringify@npm:2.5.0" @@ -5849,7 +4677,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.0.0, semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.7.1": +"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.7.1": version: 7.7.1 resolution: "semver@npm:7.7.1" bin: @@ -5858,43 +4686,6 @@ __metadata: languageName: node linkType: hard -"set-function-length@npm:^1.2.2": - version: 1.2.2 - resolution: "set-function-length@npm:1.2.2" - dependencies: - define-data-property: "npm:^1.1.4" - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.4" - gopd: "npm:^1.0.1" - has-property-descriptors: "npm:^1.0.2" - checksum: 10/505d62b8e088468917ca4e3f8f39d0e29f9a563b97dbebf92f4bd2c3172ccfb3c5b8e4566d5fcd00784a00433900e7cb8fbc404e2dbd8c3818ba05bb9d4a8a6d - languageName: node - linkType: hard - -"set-function-name@npm:^2.0.2": - version: 2.0.2 - resolution: "set-function-name@npm:2.0.2" - dependencies: - define-data-property: "npm:^1.1.4" - es-errors: "npm:^1.3.0" - functions-have-names: "npm:^1.2.3" - has-property-descriptors: "npm:^1.0.2" - checksum: 10/c7614154a53ebf8c0428a6c40a3b0b47dac30587c1a19703d1b75f003803f73cdfa6a93474a9ba678fa565ef5fbddc2fae79bca03b7d22ab5fd5163dbe571a74 - languageName: node - linkType: hard - -"set-proto@npm:^1.0.0": - version: 1.0.0 - resolution: "set-proto@npm:1.0.0" - dependencies: - dunder-proto: "npm:^1.0.1" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - checksum: 10/b87f8187bca595ddc3c0721ece4635015fd9d7cb294e6dd2e394ce5186a71bbfa4dc8a35010958c65e43ad83cde09642660e61a952883c24fd6b45ead15f045c - languageName: node - linkType: hard - "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -5911,54 +4702,6 @@ __metadata: languageName: node linkType: hard -"side-channel-list@npm:^1.0.0": - version: 1.0.0 - resolution: "side-channel-list@npm:1.0.0" - dependencies: - es-errors: "npm:^1.3.0" - object-inspect: "npm:^1.13.3" - checksum: 10/603b928997abd21c5a5f02ae6b9cc36b72e3176ad6827fab0417ead74580cc4fb4d5c7d0a8a2ff4ead34d0f9e35701ed7a41853dac8a6d1a664fcce1a044f86f - languageName: node - linkType: hard - -"side-channel-map@npm:^1.0.1": - version: 1.0.1 - resolution: "side-channel-map@npm:1.0.1" - dependencies: - call-bound: "npm:^1.0.2" - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.5" - object-inspect: "npm:^1.13.3" - checksum: 10/5771861f77feefe44f6195ed077a9e4f389acc188f895f570d56445e251b861754b547ea9ef73ecee4e01fdada6568bfe9020d2ec2dfc5571e9fa1bbc4a10615 - languageName: node - linkType: hard - -"side-channel-weakmap@npm:^1.0.2": - version: 1.0.2 - resolution: "side-channel-weakmap@npm:1.0.2" - dependencies: - call-bound: "npm:^1.0.2" - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.5" - object-inspect: "npm:^1.13.3" - side-channel-map: "npm:^1.0.1" - checksum: 10/a815c89bc78c5723c714ea1a77c938377ea710af20d4fb886d362b0d1f8ac73a17816a5f6640f354017d7e292a43da9c5e876c22145bac00b76cfb3468001736 - languageName: node - linkType: hard - -"side-channel@npm:^1.1.0": - version: 1.1.0 - resolution: "side-channel@npm:1.1.0" - dependencies: - es-errors: "npm:^1.3.0" - object-inspect: "npm:^1.13.3" - side-channel-list: "npm:^1.0.0" - side-channel-map: "npm:^1.0.1" - side-channel-weakmap: "npm:^1.0.2" - checksum: 10/7d53b9db292c6262f326b6ff3bc1611db84ece36c2c7dc0e937954c13c73185b0406c56589e2bb8d071d6fee468e14c39fb5d203ee39be66b7b8174f179afaba - languageName: node - linkType: hard - "signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -6160,44 +4903,6 @@ __metadata: languageName: node linkType: hard -"string.prototype.trim@npm:^1.2.10": - version: 1.2.10 - resolution: "string.prototype.trim@npm:1.2.10" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.2" - define-data-property: "npm:^1.1.4" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.5" - es-object-atoms: "npm:^1.0.0" - has-property-descriptors: "npm:^1.0.2" - checksum: 10/47bb63cd2470a64bc5e2da1e570d369c016ccaa85c918c3a8bb4ab5965120f35e66d1f85ea544496fac84b9207a6b722adf007e6c548acd0813e5f8a82f9712a - languageName: node - linkType: hard - -"string.prototype.trimend@npm:^1.0.8, string.prototype.trimend@npm:^1.0.9": - version: 1.0.9 - resolution: "string.prototype.trimend@npm:1.0.9" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.2" - define-properties: "npm:^1.2.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10/140c73899b6747de9e499c7c2e7a83d549c47a26fa06045b69492be9cfb9e2a95187499a373983a08a115ecff8bc3bd7b0fb09b8ff72fb2172abe766849272ef - languageName: node - linkType: hard - -"string.prototype.trimstart@npm:^1.0.8": - version: 1.0.8 - resolution: "string.prototype.trimstart@npm:1.0.8" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10/160167dfbd68e6f7cb9f51a16074eebfce1571656fc31d40c3738ca9e30e35496f2c046fe57b6ad49f65f238a152be8c86fd9a2dd58682b5eba39dad995b3674 - languageName: node - linkType: hard - "string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" @@ -6234,13 +4939,6 @@ __metadata: languageName: node linkType: hard -"strip-bom@npm:^3.0.0": - version: 3.0.0 - resolution: "strip-bom@npm:3.0.0" - checksum: 10/8d50ff27b7ebe5ecc78f1fe1e00fcdff7af014e73cf724b46fb81ef889eeb1015fc5184b64e81a2efe002180f3ba431bdd77e300da5c6685d702780fbf0c8d5b - languageName: node - linkType: hard - "strip-bom@npm:^4.0.0": version: 4.0.0 resolution: "strip-bom@npm:4.0.0" @@ -6287,16 +4985,6 @@ __metadata: languageName: node linkType: hard -"synckit@npm:^0.9.1": - version: 0.9.2 - resolution: "synckit@npm:0.9.2" - dependencies: - "@pkgr/core": "npm:^0.1.0" - tslib: "npm:^2.6.2" - checksum: 10/d45c4288be9c0232343650643892a7edafb79152c0c08d7ae5d33ca2c296b67a0e15f8cb5c9153969612c4ea5cd5686297542384aab977db23cfa6653fe02027 - languageName: node - linkType: hard - "tar-fs@npm:^3.0.6": version: 3.0.8 resolution: "tar-fs@npm:3.0.8" @@ -6446,15 +5134,6 @@ __metadata: languageName: node linkType: hard -"ts-api-utils@npm:^1.0.1": - version: 1.4.3 - resolution: "ts-api-utils@npm:1.4.3" - peerDependencies: - typescript: ">=4.2.0" - checksum: 10/713c51e7392323305bd4867422ba130fbf70873ef6edbf80ea6d7e9c8f41eeeb13e40e8e7fe7cd321d74e4864777329797077268c9f570464303a1723f1eed39 - languageName: node - linkType: hard - "ts-jest@npm:^29.1.1": version: 29.2.6 resolution: "ts-jest@npm:29.2.6" @@ -6530,19 +5209,7 @@ __metadata: languageName: node linkType: hard -"tsconfig-paths@npm:^3.15.0": - version: 3.15.0 - resolution: "tsconfig-paths@npm:3.15.0" - dependencies: - "@types/json5": "npm:^0.0.29" - json5: "npm:^1.0.2" - minimist: "npm:^1.2.6" - strip-bom: "npm:^3.0.0" - checksum: 10/2041beaedc6c271fc3bedd12e0da0cc553e65d030d4ff26044b771fac5752d0460944c0b5e680f670c2868c95c664a256cec960ae528888db6ded83524e33a14 - languageName: node - linkType: hard - -"tslib@npm:^2.1.0, tslib@npm:^2.6.2": +"tslib@npm:^2.1.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 @@ -6657,59 +5324,6 @@ __metadata: languageName: node linkType: hard -"typed-array-buffer@npm:^1.0.3": - version: 1.0.3 - resolution: "typed-array-buffer@npm:1.0.3" - dependencies: - call-bound: "npm:^1.0.3" - es-errors: "npm:^1.3.0" - is-typed-array: "npm:^1.1.14" - checksum: 10/3fb91f0735fb413b2bbaaca9fabe7b8fc14a3fa5a5a7546bab8a57e755be0e3788d893195ad9c2b842620592de0e68d4c077d4c2c41f04ec25b8b5bb82fa9a80 - languageName: node - linkType: hard - -"typed-array-byte-length@npm:^1.0.3": - version: 1.0.3 - resolution: "typed-array-byte-length@npm:1.0.3" - dependencies: - call-bind: "npm:^1.0.8" - for-each: "npm:^0.3.3" - gopd: "npm:^1.2.0" - has-proto: "npm:^1.2.0" - is-typed-array: "npm:^1.1.14" - checksum: 10/269dad101dda73e3110117a9b84db86f0b5c07dad3a9418116fd38d580cab7fc628a4fc167e29b6d7c39da2f53374b78e7cb578b3c5ec7a556689d985d193519 - languageName: node - linkType: hard - -"typed-array-byte-offset@npm:^1.0.4": - version: 1.0.4 - resolution: "typed-array-byte-offset@npm:1.0.4" - dependencies: - available-typed-arrays: "npm:^1.0.7" - call-bind: "npm:^1.0.8" - for-each: "npm:^0.3.3" - gopd: "npm:^1.2.0" - has-proto: "npm:^1.2.0" - is-typed-array: "npm:^1.1.15" - reflect.getprototypeof: "npm:^1.0.9" - checksum: 10/c2869aa584cdae24ecfd282f20a0f556b13a49a9d5bca1713370bb3c89dff0ccbc5ceb45cb5b784c98f4579e5e3e2a07e438c3a5b8294583e2bd4abbd5104fb5 - languageName: node - linkType: hard - -"typed-array-length@npm:^1.0.7": - version: 1.0.7 - resolution: "typed-array-length@npm:1.0.7" - dependencies: - call-bind: "npm:^1.0.7" - for-each: "npm:^0.3.3" - gopd: "npm:^1.0.1" - is-typed-array: "npm:^1.1.13" - possible-typed-array-names: "npm:^1.0.0" - reflect.getprototypeof: "npm:^1.0.6" - checksum: 10/d6b2f0e81161682d2726eb92b1dc2b0890890f9930f33f9bcf6fc7272895ce66bc368066d273e6677776de167608adc53fcf81f1be39a146d64b630edbf2081c - languageName: node - linkType: hard - "typescript@npm:^5.2.2": version: 5.8.2 resolution: "typescript@npm:5.8.2" @@ -6730,18 +5344,6 @@ __metadata: languageName: node linkType: hard -"unbox-primitive@npm:^1.1.0": - version: 1.1.0 - resolution: "unbox-primitive@npm:1.1.0" - dependencies: - call-bound: "npm:^1.0.3" - has-bigints: "npm:^1.0.2" - has-symbols: "npm:^1.1.0" - which-boxed-primitive: "npm:^1.1.1" - checksum: 10/fadb347020f66b2c8aeacf8b9a79826fa34cc5e5457af4eb0bbc4e79bd87fed0fa795949825df534320f7c13f199259516ad30abc55a6e7b91d8d996ca069e50 - languageName: node - linkType: hard - "undici-types@npm:~5.26.4": version: 5.26.5 resolution: "undici-types@npm:5.26.5" @@ -6856,66 +5458,6 @@ __metadata: languageName: node linkType: hard -"which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": - version: 1.1.1 - resolution: "which-boxed-primitive@npm:1.1.1" - dependencies: - is-bigint: "npm:^1.1.0" - is-boolean-object: "npm:^1.2.1" - is-number-object: "npm:^1.1.1" - is-string: "npm:^1.1.1" - is-symbol: "npm:^1.1.1" - checksum: 10/a877c0667bc089518c83ad4d845cf8296b03efe3565c1de1940c646e00a2a1ae9ed8a185bcfa27cbf352de7906f0616d83b9d2f19ca500ee02a551fb5cf40740 - languageName: node - linkType: hard - -"which-builtin-type@npm:^1.2.1": - version: 1.2.1 - resolution: "which-builtin-type@npm:1.2.1" - dependencies: - call-bound: "npm:^1.0.2" - function.prototype.name: "npm:^1.1.6" - has-tostringtag: "npm:^1.0.2" - is-async-function: "npm:^2.0.0" - is-date-object: "npm:^1.1.0" - is-finalizationregistry: "npm:^1.1.0" - is-generator-function: "npm:^1.0.10" - is-regex: "npm:^1.2.1" - is-weakref: "npm:^1.0.2" - isarray: "npm:^2.0.5" - which-boxed-primitive: "npm:^1.1.0" - which-collection: "npm:^1.0.2" - which-typed-array: "npm:^1.1.16" - checksum: 10/22c81c5cb7a896c5171742cd30c90d992ff13fb1ea7693e6cf80af077791613fb3f89aa9b4b7f890bd47b6ce09c6322c409932359580a2a2a54057f7b52d1cbe - languageName: node - linkType: hard - -"which-collection@npm:^1.0.2": - version: 1.0.2 - resolution: "which-collection@npm:1.0.2" - dependencies: - is-map: "npm:^2.0.3" - is-set: "npm:^2.0.3" - is-weakmap: "npm:^2.0.2" - is-weakset: "npm:^2.0.3" - checksum: 10/674bf659b9bcfe4055f08634b48a8588e879161b9fefed57e9ec4ff5601e4d50a05ccd76cf10f698ef5873784e5df3223336d56c7ce88e13bcf52ebe582fc8d7 - languageName: node - linkType: hard - -"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18": - version: 1.1.18 - resolution: "which-typed-array@npm:1.1.18" - dependencies: - available-typed-arrays: "npm:^1.0.7" - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - for-each: "npm:^0.3.3" - gopd: "npm:^1.2.0" - has-tostringtag: "npm:^1.0.2" - checksum: 10/11eed801b2bd08cdbaecb17aff381e0fb03526532f61acc06e6c7b9370e08062c33763a51f27825f13fdf34aabd0df6104007f4e8f96e6eaef7db0ce17a26d6e - languageName: node - linkType: hard - "which@npm:^2.0.1": version: 2.0.2 resolution: "which@npm:2.0.2" From 45a2563f60e18c3404057765ec56ee8000f061f4 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 16 Apr 2025 18:03:47 -0500 Subject: [PATCH 004/202] run fmt --- biome.json | 72 +++++------ compact/.eslintrc.cjs | 2 +- compact/src/run-compactc.cjs | 12 +- contracts/erc20/jest.config.ts | 20 +-- contracts/erc20/js-resolver.cjs | 18 +-- contracts/erc20/src/test/erc20.test.ts | 60 ++++++--- .../src/test/simulators/ERC20Simulator.ts | 118 +++++++++++++----- contracts/erc20/src/test/types/index.ts | 6 +- contracts/erc20/src/test/types/test.ts | 5 +- contracts/erc20/src/test/utils/address.ts | 33 ++--- contracts/erc20/src/test/utils/test.ts | 21 ++-- contracts/initializable/jest.config.ts | 20 +-- contracts/initializable/js-resolver.cjs | 18 +-- .../src/test/InitializableSimulator.ts | 34 +++-- .../src/test/initializable.test.ts | 24 ++-- .../initializable/src/test/types/test.ts | 5 +- .../initializable/src/test/utils/test.ts | 9 +- contracts/utils/jest.config.ts | 20 +-- contracts/utils/js-resolver.cjs | 18 +-- contracts/utils/src/test/UtilsSimulator.ts | 17 +-- contracts/utils/src/test/types/test.ts | 5 +- contracts/utils/src/test/utils.test.ts | 7 +- contracts/utils/src/test/utils/address.ts | 33 ++--- contracts/utils/src/test/utils/index.ts | 8 +- contracts/utils/src/test/utils/test.ts | 21 ++-- package.json | 76 +++++------ turbo.json | 45 +++++-- 27 files changed, 451 insertions(+), 276 deletions(-) diff --git a/biome.json b/biome.json index a06c8fa6..656beeee 100644 --- a/biome.json +++ b/biome.json @@ -1,38 +1,38 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", - "vcs": { - "enabled": false, - "clientKind": "git", - "useIgnoreFile": false - }, - "files": { - "ignoreUnknown": false, - "ignore": [ - "tsconfig.base.json", - "tsconfig*.json", - "*.compact", - "artifacts/*", - "coverage/*", - "dist/*", - "reports/*" - ] - }, - "formatter": { - "enabled": true, - "indentStyle": "tab" - }, - "organizeImports": { - "enabled": true - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true - } - }, - "javascript": { - "formatter": { - "quoteStyle": "single" - } - } + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": [ + "tsconfig.base.json", + "tsconfig*.json", + "*.compact", + "artifacts/*", + "coverage/*", + "dist/*", + "reports/*" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "space" + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + } } diff --git a/compact/.eslintrc.cjs b/compact/.eslintrc.cjs index fad1d320..32fa19b8 100644 --- a/compact/.eslintrc.cjs +++ b/compact/.eslintrc.cjs @@ -19,6 +19,6 @@ module.exports = { '@typescript-eslint/no-misused-promises': 'off', // https://github.com/typescript-eslint/typescript-eslint/issues/5807 '@typescript-eslint/no-floating-promises': 'warn', '@typescript-eslint/promise-function-async': 'off', - '@typescript-eslint/no-redeclare': 'off' + '@typescript-eslint/no-redeclare': 'off', }, }; diff --git a/compact/src/run-compactc.cjs b/compact/src/run-compactc.cjs index 89a5929a..0361d51d 100755 --- a/compact/src/run-compactc.cjs +++ b/compact/src/run-compactc.cjs @@ -9,15 +9,19 @@ const COMPACT_HOME_ENV = process.env.COMPACT_HOME; let compactPath; if (COMPACT_HOME_ENV != null) { compactPath = COMPACT_HOME_ENV; - console.log(`COMPACT_HOME env variable is set; using Compact from ${compactPath}`); + console.log( + `COMPACT_HOME env variable is set; using Compact from ${compactPath}`, + ); } else { compactPath = path.resolve(__dirname, '..', 'compactc'); - console.log(`COMPACT_HOME env variable is not set; using fetched compact from ${compactPath}`); + console.log( + `COMPACT_HOME env variable is not set; using fetched compact from ${compactPath}`, + ); } // yarn runs everything with node... const child = childProcess.spawn(path.resolve(compactPath, 'compactc'), args, { - stdio: 'inherit' + stdio: 'inherit', }); child.on('exit', (code, signal) => { if (code === 0) { @@ -25,4 +29,4 @@ child.on('exit', (code, signal) => { } else { process.exit(code ?? signal); } -}) +}); diff --git a/contracts/erc20/jest.config.ts b/contracts/erc20/jest.config.ts index 3cbccc1b..5d1dbd14 100644 --- a/contracts/erc20/jest.config.ts +++ b/contracts/erc20/jest.config.ts @@ -1,14 +1,14 @@ -import type { Config } from "@jest/types"; +import type { Config } from '@jest/types'; const config: Config.InitialOptions = { - preset: "ts-jest/presets/default-esm", - testEnvironment: "node", + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', verbose: true, - roots: [""], - modulePaths: [""], + roots: [''], + modulePaths: [''], passWithNoTests: false, - testMatch: ["**/*.test.ts"], - extensionsToTreatAsEsm: [".ts"], + testMatch: ['**/*.test.ts'], + extensionsToTreatAsEsm: ['.ts'], collectCoverage: true, resolver: '/js-resolver.cjs', coverageThreshold: { @@ -19,9 +19,9 @@ const config: Config.InitialOptions = { }, }, reporters: [ - "default", - ["jest-junit", { outputDirectory: "reports", outputName: "report.xml" }], - ["jest-html-reporters", { publicPath: "reports", filename: "report.html" }], + 'default', + ['jest-junit', { outputDirectory: 'reports', outputName: 'report.xml' }], + ['jest-html-reporters', { publicPath: 'reports', filename: 'report.html' }], ], }; diff --git a/contracts/erc20/js-resolver.cjs b/contracts/erc20/js-resolver.cjs index cc9ed285..19b6f50c 100644 --- a/contracts/erc20/js-resolver.cjs +++ b/contracts/erc20/js-resolver.cjs @@ -1,16 +1,20 @@ const jsResolver = (path, options) => { - const jsExtRegex = /\.js$/i - const resolver = options.defaultResolver - if (jsExtRegex.test(path) && !options.basedir.includes('node_modules') && !path.includes('node_modules')) { + const jsExtRegex = /\.js$/i; + const resolver = options.defaultResolver; + if ( + jsExtRegex.test(path) && + !options.basedir.includes('node_modules') && + !path.includes('node_modules') + ) { const newPath = path.replace(jsExtRegex, '.ts'); try { - return resolver(newPath, options) + return resolver(newPath, options); } catch { // use default resolver } } - return resolver(path, options) -} + return resolver(path, options); +}; -module.exports = jsResolver +module.exports = jsResolver; diff --git a/contracts/erc20/src/test/erc20.test.ts b/contracts/erc20/src/test/erc20.test.ts index 939f1c2b..a31d04eb 100644 --- a/contracts/erc20/src/test/erc20.test.ts +++ b/contracts/erc20/src/test/erc20.test.ts @@ -5,24 +5,31 @@ import * as utils from './utils'; const NO_STRING: MaybeString = { is_some: false, - value: '' + value: '', }; const NAME: MaybeString = { is_some: true, - value: "NAME" + value: 'NAME', }; const SYMBOL: MaybeString = { is_some: true, - value: "SYMBOL" + value: 'SYMBOL', }; const DECIMALS: bigint = 18n; const AMOUNT: bigint = BigInt(250); -const MAX_UINT128 = BigInt(2**128) - BigInt(1); - -const OWNER = String(Buffer.from("OWNER", 'ascii').toString('hex')).padStart(64, '0'); -const SPENDER = String(Buffer.from("SPENDER", 'ascii').toString('hex')).padStart(64, '0'); -const UNAUTHORIZED = String(Buffer.from("UNAUTHORIZED", 'ascii').toString('hex')).padStart(64, '0'); +const MAX_UINT128 = BigInt(2 ** 128) - BigInt(1); + +const OWNER = String(Buffer.from('OWNER', 'ascii').toString('hex')).padStart( + 64, + '0', +); +const SPENDER = String( + Buffer.from('SPENDER', 'ascii').toString('hex'), +).padStart(64, '0'); +const UNAUTHORIZED = String( + Buffer.from('UNAUTHORIZED', 'ascii').toString('hex'), +).padStart(64, '0'); const ZERO = String().padStart(64, '0'); const Z_OWNER = utils.createEitherTestUser('OWNER'); const Z_RECIPIENT = utils.createEitherTestUser('RECIPIENT'); @@ -65,8 +72,8 @@ describe('ERC20', () => { it('returns the amount of existing tokens when there is a supply', () => { token._mint(Z_OWNER, AMOUNT); expect(token.totalSupply()).toEqual(AMOUNT); - }) - }) + }); + }); describe('balanceOf', () => { it('should return zero when requested account has no balance', () => { @@ -232,7 +239,12 @@ describe('ERC20', () => { caller = SPENDER; const partialAmt = AMOUNT - 1n; - const txSuccess = token.transferFrom(Z_OWNER, Z_RECIPIENT, partialAmt, caller); + const txSuccess = token.transferFrom( + Z_OWNER, + Z_RECIPIENT, + partialAmt, + caller, + ); expect(txSuccess).toBeTruthy(); // Check balances @@ -245,7 +257,12 @@ describe('ERC20', () => { it('should transferFrom spender (full)', () => { caller = SPENDER; - const txSuccess = token.transferFrom(Z_OWNER, Z_RECIPIENT, AMOUNT, caller); + const txSuccess = token.transferFrom( + Z_OWNER, + Z_RECIPIENT, + AMOUNT, + caller, + ); expect(txSuccess).toBeTruthy(); // Check balances @@ -260,7 +277,12 @@ describe('ERC20', () => { token.approve(Z_SPENDER, MAX_UINT128, caller); caller = SPENDER; - const txSuccess = token.transferFrom(Z_OWNER, Z_RECIPIENT, AMOUNT, caller); + const txSuccess = token.transferFrom( + Z_OWNER, + Z_RECIPIENT, + AMOUNT, + caller, + ); expect(txSuccess).toBeTruthy(); // Check balances @@ -270,7 +292,7 @@ describe('ERC20', () => { expect(token.allowance(Z_OWNER, Z_SPENDER)).toEqual(MAX_UINT128); }); - it ('should fail when transfer amount exceeds allowance', () => { + it('should fail when transfer amount exceeds allowance', () => { caller = SPENDER; expect(() => { @@ -278,7 +300,7 @@ describe('ERC20', () => { }).toThrow('ERC20: insufficient allowance'); }); - it ('should fail when transfer amount exceeds balance', () => { + it('should fail when transfer amount exceeds balance', () => { caller = OWNER; // Increase allowance > balance token.approve(Z_SPENDER, AMOUNT + 1n, caller); @@ -294,7 +316,7 @@ describe('ERC20', () => { expect(() => { token.transferFrom(Z_OWNER, Z_RECIPIENT, AMOUNT, caller); - }).toThrow("ERC20: insufficient allowance"); + }).toThrow('ERC20: insufficient allowance'); }); it('should fail to transferFrom zero address', () => { @@ -302,7 +324,7 @@ describe('ERC20', () => { expect(() => { token.transferFrom(Z_OWNER, Z_RECIPIENT, AMOUNT, caller); - }).toThrow("ERC20: insufficient allowance"); + }).toThrow('ERC20: insufficient allowance'); }); it('should fail to transferFrom to the zero address', () => { @@ -310,7 +332,7 @@ describe('ERC20', () => { expect(() => { token.transferFrom(Z_OWNER, utils.ZERO_ADDRESS, AMOUNT, caller); - }).toThrow("ERC20: invalid receiver"); + }).toThrow('ERC20: invalid receiver'); }); }); @@ -330,7 +352,7 @@ describe('ERC20', () => { expect(token.balanceOf(Z_OWNER)).toEqual(1n); expect(token.balanceOf(Z_RECIPIENT)).toEqual(partialAmt); }); - }) + }); describe('_mint', () => { it('should mint and update supply', () => { diff --git a/contracts/erc20/src/test/simulators/ERC20Simulator.ts b/contracts/erc20/src/test/simulators/ERC20Simulator.ts index a8e324da..2a5d01a6 100644 --- a/contracts/erc20/src/test/simulators/ERC20Simulator.ts +++ b/contracts/erc20/src/test/simulators/ERC20Simulator.ts @@ -40,15 +40,16 @@ export class ERC20Simulator * @description Initializes the mock contract. */ constructor(name: MaybeString, symbol: MaybeString, decimals: bigint) { - this.contract = new MockERC20( - ERC20Witnesses, - ); + this.contract = new MockERC20(ERC20Witnesses); const { currentPrivateState, currentContractState, currentZswapLocalState, } = this.contract.initialState( - constructorContext({}, '0'.repeat(64)), name, symbol, decimals, + constructorContext({}, '0'.repeat(64)), + name, + symbol, + decimals, ); this.circuitContext = { currentPrivateState, @@ -123,8 +124,11 @@ export class ERC20Simulator * @param account The public key or contract address to query. * @returns The account's token balance. */ - public balanceOf(account: Either): bigint { - return this.contract.impureCircuits.balanceOf(this.circuitContext, account).result; + public balanceOf( + account: Either, + ): bigint { + return this.contract.impureCircuits.balanceOf(this.circuitContext, account) + .result; } /** @@ -136,9 +140,13 @@ export class ERC20Simulator */ public allowance( owner: Either, - spender: Either + spender: Either, ): bigint { - return this.contract.impureCircuits.allowance(this.circuitContext, owner, spender).result; + return this.contract.impureCircuits.allowance( + this.circuitContext, + owner, + spender, + ).result; } /** @@ -148,13 +156,20 @@ export class ERC20Simulator * @param sender The simulated caller. * @returns As per the IERC20 spec, this MUST return true. */ - public transfer(to: Either, value: bigint, sender?: CoinPublicKey): boolean { - const res = this.contract.impureCircuits.transfer({ + public transfer( + to: Either, + value: bigint, + sender?: CoinPublicKey, + ): boolean { + const res = this.contract.impureCircuits.transfer( + { ...this.circuitContext, currentZswapLocalState: sender ? emptyZswapLocalState(sender) : this.circuitContext.currentZswapLocalState, - }, to, value + }, + to, + value, ); this.circuitContext = res.context; @@ -174,15 +189,18 @@ export class ERC20Simulator from: Either, to: Either, value: bigint, - sender?: CoinPublicKey + sender?: CoinPublicKey, ): boolean { - const res = this.contract.impureCircuits.transferFrom({ + const res = this.contract.impureCircuits.transferFrom( + { ...this.circuitContext, currentZswapLocalState: sender ? emptyZswapLocalState(sender) : this.circuitContext.currentZswapLocalState, - }, - from, to, value + }, + from, + to, + value, ); this.circuitContext = res.context; @@ -196,14 +214,20 @@ export class ERC20Simulator * @param sender The simulated caller. * @returns Returns a boolean value indicating whether the operation succeeded. */ - public approve(spender: Either, value: bigint, sender?: CoinPublicKey): boolean { - const res = this.contract.impureCircuits.approve({ + public approve( + spender: Either, + value: bigint, + sender?: CoinPublicKey, + ): boolean { + const res = this.contract.impureCircuits.approve( + { ...this.circuitContext, currentZswapLocalState: sender ? emptyZswapLocalState(sender) : this.circuitContext.currentZswapLocalState, - }, - spender, value + }, + spender, + value, ); this.circuitContext = res.context; @@ -226,9 +250,14 @@ export class ERC20Simulator public _approve( owner: Either, spender: Either, - value: bigint + value: bigint, ) { - this.circuitContext = this.contract.impureCircuits._approve(this.circuitContext, owner, spender, value).context; + this.circuitContext = this.contract.impureCircuits._approve( + this.circuitContext, + owner, + spender, + value, + ).context; } /** @@ -244,7 +273,12 @@ export class ERC20Simulator to: Either, value: bigint, ) { - this.circuitContext = this.contract.impureCircuits._transfer(this.circuitContext, from, to, value).context; + this.circuitContext = this.contract.impureCircuits._transfer( + this.circuitContext, + from, + to, + value, + ).context; } /** @@ -253,8 +287,15 @@ export class ERC20Simulator * @param account The recipient of tokens minted. * @param value The amount of tokens minted. */ - public _mint(account: Either, value: bigint) { - this.circuitContext = this.contract.impureCircuits._mint(this.circuitContext, account, value).context; + public _mint( + account: Either, + value: bigint, + ) { + this.circuitContext = this.contract.impureCircuits._mint( + this.circuitContext, + account, + value, + ).context; } /** @@ -263,8 +304,15 @@ export class ERC20Simulator * @param account The target owner of tokens to burn. * @param value The amount of tokens to burn. */ - public _burn(account: Either, value: bigint) { - this.circuitContext = this.contract.impureCircuits._burn(this.circuitContext, account, value).context; + public _burn( + account: Either, + value: bigint, + ) { + this.circuitContext = this.contract.impureCircuits._burn( + this.circuitContext, + account, + value, + ).context; } /** @@ -277,9 +325,14 @@ export class ERC20Simulator public _update( from: Either, to: Either, - value: bigint + value: bigint, ) { - this.circuitContext = this.contract.impureCircuits._update(this.circuitContext, from, to, value).context; + this.circuitContext = this.contract.impureCircuits._update( + this.circuitContext, + from, + to, + value, + ).context; } /** @@ -292,8 +345,13 @@ export class ERC20Simulator public _spendAllowance( owner: Either, spender: Either, - value: bigint + value: bigint, ) { - this.circuitContext = this.contract.impureCircuits._spendAllowance(this.circuitContext, owner, spender, value).context; + this.circuitContext = this.contract.impureCircuits._spendAllowance( + this.circuitContext, + owner, + spender, + value, + ).context; } } diff --git a/contracts/erc20/src/test/types/index.ts b/contracts/erc20/src/test/types/index.ts index dac4e694..6e704746 100644 --- a/contracts/erc20/src/test/types/index.ts +++ b/contracts/erc20/src/test/types/index.ts @@ -1,6 +1,6 @@ export type { IContractSimulator } from './test'; export type MaybeString = { - is_some: boolean, - value: string -} + is_some: boolean; + value: string; +}; diff --git a/contracts/erc20/src/test/types/test.ts b/contracts/erc20/src/test/types/test.ts index 10fb6c98..7a909543 100644 --- a/contracts/erc20/src/test/types/test.ts +++ b/contracts/erc20/src/test/types/test.ts @@ -1,4 +1,7 @@ -import type { CircuitContext, ContractState } from '@midnight-ntwrk/compact-runtime'; +import type { + CircuitContext, + ContractState, +} from '@midnight-ntwrk/compact-runtime'; /** * Generic interface for mock contract implementations. diff --git a/contracts/erc20/src/test/utils/address.ts b/contracts/erc20/src/test/utils/address.ts index ef9a2842..032f6d4e 100644 --- a/contracts/erc20/src/test/utils/address.ts +++ b/contracts/erc20/src/test/utils/address.ts @@ -1,8 +1,11 @@ import { encodeContractAddress } from '@midnight-ntwrk/ledger'; import * as Compact from '../../artifacts/MockERC20/contract/index.cjs'; -import { convert_bigint_to_Uint8Array, encodeCoinPublicKey } from '@midnight-ntwrk/compact-runtime'; +import { + convert_bigint_to_Uint8Array, + encodeCoinPublicKey, +} from '@midnight-ntwrk/compact-runtime'; -const PREFIX_ADDRESS = "0200"; +const PREFIX_ADDRESS = '0200'; export const pad = (s: string, n: number): Uint8Array => { const encoder = new TextEncoder(); @@ -13,7 +16,7 @@ export const pad = (s: string, n: number): Uint8Array => { const paddedArray = new Uint8Array(n); paddedArray.set(utf8Bytes); return paddedArray; -} +}; /** * @description Generates ZswapCoinPublicKey from `str` for testing purposes. @@ -23,7 +26,7 @@ export const pad = (s: string, n: number): Uint8Array => { export const encodeToPK = (str: string): Compact.ZswapCoinPublicKey => { const toHex = Buffer.from(str, 'ascii').toString('hex'); return { bytes: encodeCoinPublicKey(String(toHex).padStart(64, '0')) }; -} +}; /** * @description Generates ContractAddress from `str` for testing purposes. @@ -35,7 +38,7 @@ export const encodeToAddress = (str: string): Compact.ContractAddress => { const toHex = Buffer.from(str, 'ascii').toString('hex'); const fullAddress = PREFIX_ADDRESS + String(toHex).padStart(64, '0'); return { bytes: encodeContractAddress(fullAddress) }; -} +}; /** * @description Generates an Either object for ZswapCoinPublicKey for testing. @@ -47,9 +50,9 @@ export const createEitherTestUser = (str: string) => { return { is_left: true, left: encodeToPK(str), - right: encodeToAddress('') - } -} + right: encodeToAddress(''), + }; +}; /** * @description Generates an Either object for ContractAddress for testing. @@ -61,18 +64,18 @@ export const createEitherTestContractAddress = (str: string) => { return { is_left: false, left: encodeToPK(''), - right: encodeToAddress(str) - } -} + right: encodeToAddress(str), + }; +}; export const ZERO_KEY = { is_left: true, left: { bytes: convert_bigint_to_Uint8Array(32, BigInt(0)) }, - right: encodeToAddress('') -} + right: encodeToAddress(''), +}; export const ZERO_ADDRESS = { is_left: false, left: encodeToPK(''), - right: { bytes: convert_bigint_to_Uint8Array(32, BigInt(0)) } -} + right: { bytes: convert_bigint_to_Uint8Array(32, BigInt(0)) }, +}; diff --git a/contracts/erc20/src/test/utils/test.ts b/contracts/erc20/src/test/utils/test.ts index 940ed612..d5479fcc 100644 --- a/contracts/erc20/src/test/utils/test.ts +++ b/contracts/erc20/src/test/utils/test.ts @@ -1,10 +1,10 @@ import { - type CircuitContext, - type CoinPublicKey, - type ContractAddress, - type ContractState, - QueryContext, - emptyZswapLocalState, + type CircuitContext, + type CoinPublicKey, + type ContractAddress, + type ContractState, + QueryContext, + emptyZswapLocalState, } from '@midnight-ntwrk/compact-runtime'; import type { IContractSimulator } from '../types'; @@ -50,10 +50,11 @@ export function useCircuitContext

( * @returns A new `CircuitContext` with the sender and updated context values. * @todo TODO: Move this utility to a generic package for broader reuse across contracts. */ -export function useCircuitContextSender>( - contract: C, - sender: CoinPublicKey, -): CircuitContext

{ +export function useCircuitContextSender< + P, + L, + C extends IContractSimulator, +>(contract: C, sender: CoinPublicKey): CircuitContext

{ const currentPrivateState = contract.getCurrentPrivateState(); const originalState = contract.getCurrentContractState(); const contractAddress = contract.contractAddress; diff --git a/contracts/initializable/jest.config.ts b/contracts/initializable/jest.config.ts index 3cbccc1b..5d1dbd14 100644 --- a/contracts/initializable/jest.config.ts +++ b/contracts/initializable/jest.config.ts @@ -1,14 +1,14 @@ -import type { Config } from "@jest/types"; +import type { Config } from '@jest/types'; const config: Config.InitialOptions = { - preset: "ts-jest/presets/default-esm", - testEnvironment: "node", + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', verbose: true, - roots: [""], - modulePaths: [""], + roots: [''], + modulePaths: [''], passWithNoTests: false, - testMatch: ["**/*.test.ts"], - extensionsToTreatAsEsm: [".ts"], + testMatch: ['**/*.test.ts'], + extensionsToTreatAsEsm: ['.ts'], collectCoverage: true, resolver: '/js-resolver.cjs', coverageThreshold: { @@ -19,9 +19,9 @@ const config: Config.InitialOptions = { }, }, reporters: [ - "default", - ["jest-junit", { outputDirectory: "reports", outputName: "report.xml" }], - ["jest-html-reporters", { publicPath: "reports", filename: "report.html" }], + 'default', + ['jest-junit', { outputDirectory: 'reports', outputName: 'report.xml' }], + ['jest-html-reporters', { publicPath: 'reports', filename: 'report.html' }], ], }; diff --git a/contracts/initializable/js-resolver.cjs b/contracts/initializable/js-resolver.cjs index cc9ed285..19b6f50c 100644 --- a/contracts/initializable/js-resolver.cjs +++ b/contracts/initializable/js-resolver.cjs @@ -1,16 +1,20 @@ const jsResolver = (path, options) => { - const jsExtRegex = /\.js$/i - const resolver = options.defaultResolver - if (jsExtRegex.test(path) && !options.basedir.includes('node_modules') && !path.includes('node_modules')) { + const jsExtRegex = /\.js$/i; + const resolver = options.defaultResolver; + if ( + jsExtRegex.test(path) && + !options.basedir.includes('node_modules') && + !path.includes('node_modules') + ) { const newPath = path.replace(jsExtRegex, '.ts'); try { - return resolver(newPath, options) + return resolver(newPath, options); } catch { // use default resolver } } - return resolver(path, options) -} + return resolver(path, options); +}; -module.exports = jsResolver +module.exports = jsResolver; diff --git a/contracts/initializable/src/test/InitializableSimulator.ts b/contracts/initializable/src/test/InitializableSimulator.ts index 89402a19..9b8b5687 100644 --- a/contracts/initializable/src/test/InitializableSimulator.ts +++ b/contracts/initializable/src/test/InitializableSimulator.ts @@ -1,7 +1,20 @@ -import { type CircuitContext, type ContractState, QueryContext, sampleContractAddress, constructorContext } from '@midnight-ntwrk/compact-runtime'; -import { Contract as MockInitializable, type Ledger, ledger } from '../artifacts/MockInitializable/contract/index.cjs'; +import { + type CircuitContext, + type ContractState, + QueryContext, + sampleContractAddress, + constructorContext, +} from '@midnight-ntwrk/compact-runtime'; +import { + Contract as MockInitializable, + type Ledger, + ledger, +} from '../artifacts/MockInitializable/contract/index.cjs'; import type { IContractSimulator } from './types'; -import { InitializablePrivateState, InitializableWitnesses } from '../witnesses'; +import { + InitializablePrivateState, + InitializableWitnesses, +} from '../witnesses'; /** * @description A simulator implementation of an utils contract for testing purposes. @@ -31,9 +44,7 @@ export class InitializableSimulator currentPrivateState, currentContractState, currentZswapLocalState, - } = this.contract.initialState( - constructorContext({}, '0'.repeat(64)) - ); + } = this.contract.initialState(constructorContext({}, '0'.repeat(64))); this.circuitContext = { currentPrivateState, currentZswapLocalState, @@ -70,19 +81,22 @@ export class InitializableSimulator return this.circuitContext.originalState; } - /** + /** * @description Initializes the state. * @returns None. */ public initialize() { - this.circuitContext = this.contract.impureCircuits.initialize(this.circuitContext).context; + this.circuitContext = this.contract.impureCircuits.initialize( + this.circuitContext, + ).context; } - /** + /** * @description Returns true if the state is initialized. * @returns Whether the contract has been initialized. */ public isInitialized(): boolean { - return this.contract.impureCircuits.isInitialized(this.circuitContext).result; + return this.contract.impureCircuits.isInitialized(this.circuitContext) + .result; } } diff --git a/contracts/initializable/src/test/initializable.test.ts b/contracts/initializable/src/test/initializable.test.ts index 15ee059c..96d5271e 100644 --- a/contracts/initializable/src/test/initializable.test.ts +++ b/contracts/initializable/src/test/initializable.test.ts @@ -7,26 +7,32 @@ const contract = new InitializableSimulator(); describe('Initializable', () => { it('should generate the initial ledger state deterministically', () => { const contract2 = new InitializableSimulator(); - expect(contract.getCurrentPublicState()).toEqual(contract2.getCurrentPublicState()); + expect(contract.getCurrentPublicState()).toEqual( + contract2.getCurrentPublicState(), + ); }); describe('initialize', () => { it('should not be initialized', () => { expect(contract.isInitialized()).toEqual(false); - expect(contract.getCurrentPublicState().initializableState).toEqual(STATE.uninitialized); + expect(contract.getCurrentPublicState().initializableState).toEqual( + STATE.uninitialized, + ); }); it('should initialize', () => { contract.initialize(); expect(contract.isInitialized()).toEqual(true); - expect(contract.getCurrentPublicState().initializableState).toEqual(STATE.initialized); - }); + expect(contract.getCurrentPublicState().initializableState).toEqual( + STATE.initialized, + ); }); + }); - it('should fail when re-initialized', () => { - expect(() => { - contract.initialize(); - contract.initialize(); - }).toThrow('Contract already initialized'); + it('should fail when re-initialized', () => { + expect(() => { + contract.initialize(); + contract.initialize(); + }).toThrow('Contract already initialized'); }); }); diff --git a/contracts/initializable/src/test/types/test.ts b/contracts/initializable/src/test/types/test.ts index 10fb6c98..7a909543 100644 --- a/contracts/initializable/src/test/types/test.ts +++ b/contracts/initializable/src/test/types/test.ts @@ -1,4 +1,7 @@ -import type { CircuitContext, ContractState } from '@midnight-ntwrk/compact-runtime'; +import type { + CircuitContext, + ContractState, +} from '@midnight-ntwrk/compact-runtime'; /** * Generic interface for mock contract implementations. diff --git a/contracts/initializable/src/test/utils/test.ts b/contracts/initializable/src/test/utils/test.ts index 5a9e5837..d5479fcc 100644 --- a/contracts/initializable/src/test/utils/test.ts +++ b/contracts/initializable/src/test/utils/test.ts @@ -50,10 +50,11 @@ export function useCircuitContext

( * @returns A new `CircuitContext` with the sender and updated context values. * @todo TODO: Move this utility to a generic package for broader reuse across contracts. */ -export function useCircuitContextSender>( - contract: C, - sender: CoinPublicKey, -): CircuitContext

{ +export function useCircuitContextSender< + P, + L, + C extends IContractSimulator, +>(contract: C, sender: CoinPublicKey): CircuitContext

{ const currentPrivateState = contract.getCurrentPrivateState(); const originalState = contract.getCurrentContractState(); const contractAddress = contract.contractAddress; diff --git a/contracts/utils/jest.config.ts b/contracts/utils/jest.config.ts index edbdaeba..f15dd79d 100644 --- a/contracts/utils/jest.config.ts +++ b/contracts/utils/jest.config.ts @@ -1,14 +1,14 @@ -import type { Config } from "@jest/types"; +import type { Config } from '@jest/types'; const config: Config.InitialOptions = { - preset: "ts-jest/presets/default-esm", - testEnvironment: "node", + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', verbose: true, - roots: [""], - modulePaths: [""], + roots: [''], + modulePaths: [''], passWithNoTests: false, - testMatch: ["**/*.test.ts"], - extensionsToTreatAsEsm: [".ts"], + testMatch: ['**/*.test.ts'], + extensionsToTreatAsEsm: ['.ts'], collectCoverage: true, resolver: '/js-resolver.cjs', coverageThreshold: { @@ -19,9 +19,9 @@ const config: Config.InitialOptions = { }, }, reporters: [ - "default", - ["jest-junit", { outputDirectory: "reports", outputName: "report.xml" }], - ["jest-html-reporters", { publicPath: "reports", filename: "report.html" }], + 'default', + ['jest-junit', { outputDirectory: 'reports', outputName: 'report.xml' }], + ['jest-html-reporters', { publicPath: 'reports', filename: 'report.html' }], ], }; diff --git a/contracts/utils/js-resolver.cjs b/contracts/utils/js-resolver.cjs index cc9ed285..19b6f50c 100644 --- a/contracts/utils/js-resolver.cjs +++ b/contracts/utils/js-resolver.cjs @@ -1,16 +1,20 @@ const jsResolver = (path, options) => { - const jsExtRegex = /\.js$/i - const resolver = options.defaultResolver - if (jsExtRegex.test(path) && !options.basedir.includes('node_modules') && !path.includes('node_modules')) { + const jsExtRegex = /\.js$/i; + const resolver = options.defaultResolver; + if ( + jsExtRegex.test(path) && + !options.basedir.includes('node_modules') && + !path.includes('node_modules') + ) { const newPath = path.replace(jsExtRegex, '.ts'); try { - return resolver(newPath, options) + return resolver(newPath, options); } catch { // use default resolver } } - return resolver(path, options) -} + return resolver(path, options); +}; -module.exports = jsResolver +module.exports = jsResolver; diff --git a/contracts/utils/src/test/UtilsSimulator.ts b/contracts/utils/src/test/UtilsSimulator.ts index c55c802f..d0bb53e0 100644 --- a/contracts/utils/src/test/UtilsSimulator.ts +++ b/contracts/utils/src/test/UtilsSimulator.ts @@ -37,16 +37,12 @@ export class UtilsContractSimulator * @description Initializes the mock contract. */ constructor() { - this.contract = new MockUtils( - UtilsWitnesses, - ); + this.contract = new MockUtils(UtilsWitnesses); const { currentPrivateState, currentContractState, currentZswapLocalState, - } = this.contract.initialState( - constructorContext({}, '0'.repeat(64)) - ); + } = this.contract.initialState(constructorContext({}, '0'.repeat(64))); this.circuitContext = { currentPrivateState, currentZswapLocalState, @@ -88,7 +84,12 @@ export class UtilsContractSimulator * @param keyOrAddress The target value to check, either a ZswapCoinPublicKey or a ContractAddress. * @returns Returns true if `keyOrAddress` is zero. */ - public isKeyOrAddressZero(keyOrAddress: Either): boolean { - return this.contract.circuits.isKeyOrAddressZero(this.circuitContext, keyOrAddress).result; + public isKeyOrAddressZero( + keyOrAddress: Either, + ): boolean { + return this.contract.circuits.isKeyOrAddressZero( + this.circuitContext, + keyOrAddress, + ).result; } } diff --git a/contracts/utils/src/test/types/test.ts b/contracts/utils/src/test/types/test.ts index 10fb6c98..7a909543 100644 --- a/contracts/utils/src/test/types/test.ts +++ b/contracts/utils/src/test/types/test.ts @@ -1,4 +1,7 @@ -import type { CircuitContext, ContractState } from '@midnight-ntwrk/compact-runtime'; +import type { + CircuitContext, + ContractState, +} from '@midnight-ntwrk/compact-runtime'; /** * Generic interface for mock contract implementations. diff --git a/contracts/utils/src/test/utils.test.ts b/contracts/utils/src/test/utils.test.ts index 3f38952b..da0a8d37 100644 --- a/contracts/utils/src/test/utils.test.ts +++ b/contracts/utils/src/test/utils.test.ts @@ -2,7 +2,8 @@ import { UtilsContractSimulator } from './UtilsSimulator'; import * as contractUtils from './utils'; const Z_SOME_KEY = contractUtils.createEitherTestUser('SOME_KEY'); -const SOME_CONTRACT = contractUtils.createEitherTestContractAddress('SOME_CONTRACT'); +const SOME_CONTRACT = + contractUtils.createEitherTestContractAddress('SOME_CONTRACT'); let contract: UtilsContractSimulator; @@ -12,7 +13,9 @@ describe('Utils', () => { describe('isKeyOrAddressZero', () => { it('should return zero for the zero address', () => { expect(contract.isKeyOrAddressZero(contractUtils.ZERO_KEY)).toBeTruthy(); - expect(contract.isKeyOrAddressZero(contractUtils.ZERO_ADDRESS)).toBeTruthy(); + expect( + contract.isKeyOrAddressZero(contractUtils.ZERO_ADDRESS), + ).toBeTruthy(); }); it('should not return zero for nonzero addresses', () => { diff --git a/contracts/utils/src/test/utils/address.ts b/contracts/utils/src/test/utils/address.ts index f6255d60..218db5de 100644 --- a/contracts/utils/src/test/utils/address.ts +++ b/contracts/utils/src/test/utils/address.ts @@ -1,8 +1,11 @@ import { encodeContractAddress } from '@midnight-ntwrk/ledger'; import * as Compact from '../../artifacts/MockUtils/contract/index.cjs'; -import { convert_bigint_to_Uint8Array, encodeCoinPublicKey } from '@midnight-ntwrk/compact-runtime'; +import { + convert_bigint_to_Uint8Array, + encodeCoinPublicKey, +} from '@midnight-ntwrk/compact-runtime'; -const PREFIX_ADDRESS = "0200"; +const PREFIX_ADDRESS = '0200'; export const pad = (s: string, n: number): Uint8Array => { const encoder = new TextEncoder(); @@ -13,7 +16,7 @@ export const pad = (s: string, n: number): Uint8Array => { const paddedArray = new Uint8Array(n); paddedArray.set(utf8Bytes); return paddedArray; -} +}; /** * @description Generates ZswapCoinPublicKey from `str` for testing purposes. @@ -23,7 +26,7 @@ export const pad = (s: string, n: number): Uint8Array => { export const encodeToPK = (str: string): Compact.ZswapCoinPublicKey => { const toHex = Buffer.from(str, 'ascii').toString('hex'); return { bytes: encodeCoinPublicKey(String(toHex).padStart(64, '0')) }; -} +}; /** * @description Generates ContractAddress from `str` for testing purposes. @@ -35,7 +38,7 @@ export const encodeToAddress = (str: string): Compact.ContractAddress => { const toHex = Buffer.from(str, 'ascii').toString('hex'); const fullAddress = PREFIX_ADDRESS + String(toHex).padStart(64, '0'); return { bytes: encodeContractAddress(fullAddress) }; -} +}; /** * @description Generates an Either object for ZswapCoinPublicKey for testing. @@ -47,9 +50,9 @@ export const createEitherTestUser = (str: string) => { return { is_left: true, left: encodeToPK(str), - right: encodeToAddress('') - } -} + right: encodeToAddress(''), + }; +}; /** * @description Generates an Either object for ContractAddress for testing. @@ -61,18 +64,18 @@ export const createEitherTestContractAddress = (str: string) => { return { is_left: false, left: encodeToPK(''), - right: encodeToAddress(str) - } -} + right: encodeToAddress(str), + }; +}; export const ZERO_KEY = { is_left: true, left: { bytes: convert_bigint_to_Uint8Array(32, BigInt(0)) }, - right: encodeToAddress('') -} + right: encodeToAddress(''), +}; export const ZERO_ADDRESS = { is_left: false, left: encodeToPK(''), - right: { bytes: convert_bigint_to_Uint8Array(32, BigInt(0)) } -} + right: { bytes: convert_bigint_to_Uint8Array(32, BigInt(0)) }, +}; diff --git a/contracts/utils/src/test/utils/index.ts b/contracts/utils/src/test/utils/index.ts index 2668cc79..b8e9585d 100644 --- a/contracts/utils/src/test/utils/index.ts +++ b/contracts/utils/src/test/utils/index.ts @@ -1,4 +1,10 @@ export { useCircuitContext as circuitContext } from './test'; export { - pad, encodeToPK, encodeToAddress, createEitherTestUser, createEitherTestContractAddress, ZERO_KEY, ZERO_ADDRESS + pad, + encodeToPK, + encodeToAddress, + createEitherTestUser, + createEitherTestContractAddress, + ZERO_KEY, + ZERO_ADDRESS, } from './address'; diff --git a/contracts/utils/src/test/utils/test.ts b/contracts/utils/src/test/utils/test.ts index 940ed612..d5479fcc 100644 --- a/contracts/utils/src/test/utils/test.ts +++ b/contracts/utils/src/test/utils/test.ts @@ -1,10 +1,10 @@ import { - type CircuitContext, - type CoinPublicKey, - type ContractAddress, - type ContractState, - QueryContext, - emptyZswapLocalState, + type CircuitContext, + type CoinPublicKey, + type ContractAddress, + type ContractState, + QueryContext, + emptyZswapLocalState, } from '@midnight-ntwrk/compact-runtime'; import type { IContractSimulator } from '../types'; @@ -50,10 +50,11 @@ export function useCircuitContext

( * @returns A new `CircuitContext` with the sender and updated context values. * @todo TODO: Move this utility to a generic package for broader reuse across contracts. */ -export function useCircuitContextSender>( - contract: C, - sender: CoinPublicKey, -): CircuitContext

{ +export function useCircuitContextSender< + P, + L, + C extends IContractSimulator, +>(contract: C, sender: CoinPublicKey): CircuitContext

{ const currentPrivateState = contract.getCurrentPrivateState(); const originalState = contract.getCurrentContractState(); const contractAddress = contract.contractAddress; diff --git a/package.json b/package.json index 128470a1..18a86574 100644 --- a/package.json +++ b/package.json @@ -1,40 +1,40 @@ { - "packageManager": "yarn@4.1.0", - "workspaces": [ - "compact", - "contracts/erc20/", - "contracts/initializable/", - "contracts/utils/" - ], - "scripts": { - "compact": "turbo run compact", - "build": "turbo run build", - "lint": "turbo run lint" - }, - "dependencies": { - "@midnight-ntwrk/compact-runtime": "^0.7.0", - "fp-ts": "^2.16.1", - "io-ts": "^2.2.20", - "pino": "^8.16.0", - "pino-pretty": "^10.2.3", - "rxjs": "^7.8.1" - }, - "devDependencies": { - "@biomejs/biome": "1.9.4", - "@midnight-ntwrk/ledger": "^3.0.6", - "@midnight-ntwrk/zswap": "^3.0.6", - "@types/jest": "^29.5.6", - "@types/node": "^18.18.6", - "fast-check": "^3.15.0", - "jest": "^29.7.0", - "jest-fast-check": "^2.0.0", - "jest-gh-md-reporter": "^0.0.2", - "jest-html-reporters": "^3.1.4", - "jest-junit": "^16.0.0", - "testcontainers": "^10.3.2", - "ts-jest": "^29.1.1", - "ts-node": "^10.9.1", - "turbo": "^1.10.16", - "typescript": "^5.2.2" - } + "packageManager": "yarn@4.1.0", + "workspaces": [ + "compact", + "contracts/erc20/", + "contracts/initializable/", + "contracts/utils/" + ], + "scripts": { + "compact": "turbo run compact", + "build": "turbo run build", + "lint": "turbo run lint" + }, + "dependencies": { + "@midnight-ntwrk/compact-runtime": "^0.7.0", + "fp-ts": "^2.16.1", + "io-ts": "^2.2.20", + "pino": "^8.16.0", + "pino-pretty": "^10.2.3", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@biomejs/biome": "1.9.4", + "@midnight-ntwrk/ledger": "^3.0.6", + "@midnight-ntwrk/zswap": "^3.0.6", + "@types/jest": "^29.5.6", + "@types/node": "^18.18.6", + "fast-check": "^3.15.0", + "jest": "^29.7.0", + "jest-fast-check": "^2.0.0", + "jest-gh-md-reporter": "^0.0.2", + "jest-html-reporters": "^3.1.4", + "jest-junit": "^16.0.0", + "testcontainers": "^10.3.2", + "ts-jest": "^29.1.1", + "ts-node": "^10.9.1", + "turbo": "^1.10.16", + "typescript": "^5.2.2" + } } diff --git a/turbo.json b/turbo.json index b539bd83..770d717d 100644 --- a/turbo.json +++ b/turbo.json @@ -1,12 +1,15 @@ { "$schema": "https://turbo.build/schema.json", - "globalDependencies": [ - ".prettierrc.json" - ], + "globalDependencies": [".prettierrc.json"], "pipeline": { "typecheck": { "dependsOn": ["^build", "compact"], - "inputs": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.mts", "tsconfig.json"], + "inputs": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.mts", + "tsconfig.json" + ], "outputMode": "new-only", "outputs": [] }, @@ -20,13 +23,33 @@ "build": { "dependsOn": ["^build", "compact", "typecheck"], "outputMode": "new-only", - "inputs": ["src/**/*.ts", "src/**/*.mts", "src/**/*.tsx", "!src/**/*.test.ts", "!tests/**/*.ts", "tsconfig.json", "tsconfig.build.json", ".env"], + "inputs": [ + "src/**/*.ts", + "src/**/*.mts", + "src/**/*.tsx", + "!src/**/*.test.ts", + "!tests/**/*.ts", + "tsconfig.json", + "tsconfig.build.json", + ".env" + ], "outputs": ["dist/**"] }, "build-storybook": { "dependsOn": ["^build", "typecheck"], "outputMode": "new-only", - "inputs": ["src/**/*.ts", "src/**/*.mts", "src/**/*.tsx", "!src/**/*.test.ts", "!tests/**/*.ts", "tsconfig.json", "tsconfig.build.json", ".env", "vite.config.ts", ".storybook/**"], + "inputs": [ + "src/**/*.ts", + "src/**/*.mts", + "src/**/*.tsx", + "!src/**/*.test.ts", + "!tests/**/*.ts", + "tsconfig.json", + "tsconfig.build.json", + ".env", + "vite.config.ts", + ".storybook/**" + ], "outputs": ["storybook-static/**"] }, "lint": { @@ -37,7 +60,15 @@ "test": { "outputMode": "new-only", "dependsOn": ["^build", "compact", "typecheck"], - "inputs": ["src/**/*.ts", "src/**/*.mts", "src/**/*.tsx", "jest.config.ts", "tsconfig.json", "tsconfig.test.json", "test-compose.yml"], + "inputs": [ + "src/**/*.ts", + "src/**/*.mts", + "src/**/*.tsx", + "jest.config.ts", + "tsconfig.json", + "tsconfig.test.json", + "test-compose.yml" + ], "outputs": ["reports/**"] }, "check": { From fec71f584835232853eafcd50b470f234f51001b Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 16 Apr 2025 18:05:17 -0500 Subject: [PATCH 005/202] run lint --- compact/src/run-compactc.cjs | 2 +- contracts/erc20/src/test/erc20.test.ts | 4 ++-- .../erc20/src/test/simulators/ERC20Simulator.ts | 12 ++++++------ contracts/erc20/src/test/utils/address.ts | 2 +- .../initializable/src/test/InitializableSimulator.ts | 2 +- contracts/utils/src/test/UtilsSimulator.ts | 8 ++++---- contracts/utils/src/test/utils/address.ts | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/compact/src/run-compactc.cjs b/compact/src/run-compactc.cjs index 0361d51d..dca2891f 100755 --- a/compact/src/run-compactc.cjs +++ b/compact/src/run-compactc.cjs @@ -1,7 +1,7 @@ #!/usr/bin/env node const childProcess = require('node:child_process'); -const path = require('path'); +const path = require('node:path'); const [_node, _script, ...args] = process.argv; const COMPACT_HOME_ENV = process.env.COMPACT_HOME; diff --git a/contracts/erc20/src/test/erc20.test.ts b/contracts/erc20/src/test/erc20.test.ts index a31d04eb..8942dc55 100644 --- a/contracts/erc20/src/test/erc20.test.ts +++ b/contracts/erc20/src/test/erc20.test.ts @@ -1,6 +1,6 @@ -import { CoinPublicKey } from '@midnight-ntwrk/compact-runtime'; +import type { CoinPublicKey } from '@midnight-ntwrk/compact-runtime'; import { ERC20Simulator } from './simulators'; -import { MaybeString } from './types'; +import type { MaybeString } from './types'; import * as utils from './utils'; const NO_STRING: MaybeString = { diff --git a/contracts/erc20/src/test/simulators/ERC20Simulator.ts b/contracts/erc20/src/test/simulators/ERC20Simulator.ts index 2a5d01a6..f2e7257a 100644 --- a/contracts/erc20/src/test/simulators/ERC20Simulator.ts +++ b/contracts/erc20/src/test/simulators/ERC20Simulator.ts @@ -1,6 +1,6 @@ import { type CircuitContext, - CoinPublicKey, + type CoinPublicKey, type ContractState, QueryContext, constructorContext, @@ -11,13 +11,13 @@ import { type Ledger, Contract as MockERC20, ledger, - Either, - ZswapCoinPublicKey, - ContractAddress, + type Either, + type ZswapCoinPublicKey, + type ContractAddress, } from '../../artifacts/MockERC20/contract/index.cjs'; // Combined imports -import { MaybeString } from '../types'; +import type { MaybeString } from '../types'; import type { IContractSimulator } from './../types'; -import { ERC20PrivateState, ERC20Witnesses } from '../../witnesses'; +import { type ERC20PrivateState, ERC20Witnesses } from '../../witnesses'; /** * @description A simulator implementation of an erc20 contract for testing purposes. diff --git a/contracts/erc20/src/test/utils/address.ts b/contracts/erc20/src/test/utils/address.ts index 032f6d4e..e775fee6 100644 --- a/contracts/erc20/src/test/utils/address.ts +++ b/contracts/erc20/src/test/utils/address.ts @@ -1,5 +1,5 @@ import { encodeContractAddress } from '@midnight-ntwrk/ledger'; -import * as Compact from '../../artifacts/MockERC20/contract/index.cjs'; +import type * as Compact from '../../artifacts/MockERC20/contract/index.cjs'; import { convert_bigint_to_Uint8Array, encodeCoinPublicKey, diff --git a/contracts/initializable/src/test/InitializableSimulator.ts b/contracts/initializable/src/test/InitializableSimulator.ts index 9b8b5687..783b17f4 100644 --- a/contracts/initializable/src/test/InitializableSimulator.ts +++ b/contracts/initializable/src/test/InitializableSimulator.ts @@ -12,7 +12,7 @@ import { } from '../artifacts/MockInitializable/contract/index.cjs'; import type { IContractSimulator } from './types'; import { - InitializablePrivateState, + type InitializablePrivateState, InitializableWitnesses, } from '../witnesses'; diff --git a/contracts/utils/src/test/UtilsSimulator.ts b/contracts/utils/src/test/UtilsSimulator.ts index d0bb53e0..abaff99e 100644 --- a/contracts/utils/src/test/UtilsSimulator.ts +++ b/contracts/utils/src/test/UtilsSimulator.ts @@ -9,12 +9,12 @@ import { type Ledger, Contract as MockUtils, ledger, - Either, - ZswapCoinPublicKey, - ContractAddress, + type Either, + type ZswapCoinPublicKey, + type ContractAddress, } from '../artifacts/MockUtils/contract/index.cjs'; // Combined imports import type { IContractSimulator } from './types'; -import { UtilsPrivateState, UtilsWitnesses } from '../witnesses'; +import { type UtilsPrivateState, UtilsWitnesses } from '../witnesses'; /** * @description A simulator implementation of an utils contract for testing purposes. diff --git a/contracts/utils/src/test/utils/address.ts b/contracts/utils/src/test/utils/address.ts index 218db5de..9690fd48 100644 --- a/contracts/utils/src/test/utils/address.ts +++ b/contracts/utils/src/test/utils/address.ts @@ -1,5 +1,5 @@ import { encodeContractAddress } from '@midnight-ntwrk/ledger'; -import * as Compact from '../../artifacts/MockUtils/contract/index.cjs'; +import type * as Compact from '../../artifacts/MockUtils/contract/index.cjs'; import { convert_bigint_to_Uint8Array, encodeCoinPublicKey, From 86565b361a2effe34b8ba0c2b61beef7bd077b76 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 16 Apr 2025 18:06:25 -0500 Subject: [PATCH 006/202] organize imports --- contracts/erc20/src/test/simulators/ERC20Simulator.ts | 8 ++++---- contracts/erc20/src/test/utils/address.ts | 4 ++-- .../initializable/src/test/InitializableSimulator.ts | 6 +++--- contracts/initializable/src/test/initializable.test.ts | 4 ++-- contracts/utils/src/test/UtilsSimulator.ts | 8 ++++---- contracts/utils/src/test/utils/address.ts | 4 ++-- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/contracts/erc20/src/test/simulators/ERC20Simulator.ts b/contracts/erc20/src/test/simulators/ERC20Simulator.ts index f2e7257a..6814f878 100644 --- a/contracts/erc20/src/test/simulators/ERC20Simulator.ts +++ b/contracts/erc20/src/test/simulators/ERC20Simulator.ts @@ -8,16 +8,16 @@ import { } from '@midnight-ntwrk/compact-runtime'; import { sampleContractAddress } from '@midnight-ntwrk/zswap'; import { + type ContractAddress, + type Either, type Ledger, Contract as MockERC20, - ledger, - type Either, type ZswapCoinPublicKey, - type ContractAddress, + ledger, } from '../../artifacts/MockERC20/contract/index.cjs'; // Combined imports +import { type ERC20PrivateState, ERC20Witnesses } from '../../witnesses'; import type { MaybeString } from '../types'; import type { IContractSimulator } from './../types'; -import { type ERC20PrivateState, ERC20Witnesses } from '../../witnesses'; /** * @description A simulator implementation of an erc20 contract for testing purposes. diff --git a/contracts/erc20/src/test/utils/address.ts b/contracts/erc20/src/test/utils/address.ts index e775fee6..3580e196 100644 --- a/contracts/erc20/src/test/utils/address.ts +++ b/contracts/erc20/src/test/utils/address.ts @@ -1,9 +1,9 @@ -import { encodeContractAddress } from '@midnight-ntwrk/ledger'; -import type * as Compact from '../../artifacts/MockERC20/contract/index.cjs'; import { convert_bigint_to_Uint8Array, encodeCoinPublicKey, } from '@midnight-ntwrk/compact-runtime'; +import { encodeContractAddress } from '@midnight-ntwrk/ledger'; +import type * as Compact from '../../artifacts/MockERC20/contract/index.cjs'; const PREFIX_ADDRESS = '0200'; diff --git a/contracts/initializable/src/test/InitializableSimulator.ts b/contracts/initializable/src/test/InitializableSimulator.ts index 783b17f4..6388989f 100644 --- a/contracts/initializable/src/test/InitializableSimulator.ts +++ b/contracts/initializable/src/test/InitializableSimulator.ts @@ -2,19 +2,19 @@ import { type CircuitContext, type ContractState, QueryContext, - sampleContractAddress, constructorContext, + sampleContractAddress, } from '@midnight-ntwrk/compact-runtime'; import { - Contract as MockInitializable, type Ledger, + Contract as MockInitializable, ledger, } from '../artifacts/MockInitializable/contract/index.cjs'; -import type { IContractSimulator } from './types'; import { type InitializablePrivateState, InitializableWitnesses, } from '../witnesses'; +import type { IContractSimulator } from './types'; /** * @description A simulator implementation of an utils contract for testing purposes. diff --git a/contracts/initializable/src/test/initializable.test.ts b/contracts/initializable/src/test/initializable.test.ts index 96d5271e..2760fbbd 100644 --- a/contracts/initializable/src/test/initializable.test.ts +++ b/contracts/initializable/src/test/initializable.test.ts @@ -1,6 +1,6 @@ -import { it, describe, expect } from '@jest/globals'; -import { InitializableSimulator } from './InitializableSimulator.js'; +import { describe, expect, it } from '@jest/globals'; import { Initializable_STATE as STATE } from '../artifacts/MockInitializable/contract/index.cjs'; +import { InitializableSimulator } from './InitializableSimulator.js'; const contract = new InitializableSimulator(); diff --git a/contracts/utils/src/test/UtilsSimulator.ts b/contracts/utils/src/test/UtilsSimulator.ts index abaff99e..894058c9 100644 --- a/contracts/utils/src/test/UtilsSimulator.ts +++ b/contracts/utils/src/test/UtilsSimulator.ts @@ -6,15 +6,15 @@ import { } from '@midnight-ntwrk/compact-runtime'; import { sampleContractAddress } from '@midnight-ntwrk/zswap'; import { + type ContractAddress, + type Either, type Ledger, Contract as MockUtils, - ledger, - type Either, type ZswapCoinPublicKey, - type ContractAddress, + ledger, } from '../artifacts/MockUtils/contract/index.cjs'; // Combined imports -import type { IContractSimulator } from './types'; import { type UtilsPrivateState, UtilsWitnesses } from '../witnesses'; +import type { IContractSimulator } from './types'; /** * @description A simulator implementation of an utils contract for testing purposes. diff --git a/contracts/utils/src/test/utils/address.ts b/contracts/utils/src/test/utils/address.ts index 9690fd48..d4ac78a7 100644 --- a/contracts/utils/src/test/utils/address.ts +++ b/contracts/utils/src/test/utils/address.ts @@ -1,9 +1,9 @@ -import { encodeContractAddress } from '@midnight-ntwrk/ledger'; -import type * as Compact from '../../artifacts/MockUtils/contract/index.cjs'; import { convert_bigint_to_Uint8Array, encodeCoinPublicKey, } from '@midnight-ntwrk/compact-runtime'; +import { encodeContractAddress } from '@midnight-ntwrk/ledger'; +import type * as Compact from '../../artifacts/MockUtils/contract/index.cjs'; const PREFIX_ADDRESS = '0200'; From 24c2e784ecf8872e47df0d6d734d0eacc8e8b658 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 17 Apr 2025 02:31:42 -0500 Subject: [PATCH 007/202] remove dev deps from contracts/ --- contracts/erc20/package.json | 5 ----- contracts/initializable/package.json | 6 ------ contracts/utils/package.json | 5 ----- yarn.lock | 14 +------------- 4 files changed, 1 insertion(+), 29 deletions(-) diff --git a/contracts/erc20/package.json b/contracts/erc20/package.json index 77d0921d..67cbfe99 100644 --- a/contracts/erc20/package.json +++ b/contracts/erc20/package.json @@ -22,10 +22,5 @@ }, "dependencies": { "@openzeppelin-midnight-contracts/utils-contract": "workspace:^" - }, - "devDependencies": { - "@midnight-ntwrk/compact": "workspace:*", - "jest": "^29.7.0", - "typescript": "^5.2.2" } } diff --git a/contracts/initializable/package.json b/contracts/initializable/package.json index a04f1d3a..e6868ab2 100644 --- a/contracts/initializable/package.json +++ b/contracts/initializable/package.json @@ -19,11 +19,5 @@ "build": "rm -rf dist && tsc --project tsconfig.build.json && cp -Rf ./src/artifacts ./dist/artifacts && cp ./src/Initializable.compact ./dist", "lint": "eslint src", "typecheck": "tsc -p tsconfig.json --noEmit" - }, - "devDependencies": { - "@midnight-ntwrk/compact": "workspace:*", - "eslint": "^8.52.0", - "jest": "^29.7.0", - "typescript": "^5.2.2" } } diff --git a/contracts/utils/package.json b/contracts/utils/package.json index fc0fe810..3fdcfcf5 100644 --- a/contracts/utils/package.json +++ b/contracts/utils/package.json @@ -19,10 +19,5 @@ "build": "rm -rf dist && tsc --project tsconfig.build.json && cp -Rf ./src/artifacts ./dist/artifacts && cp ./src/Utils.compact ./dist", "lint": "eslint src", "typecheck": "tsc -p tsconfig.json --noEmit" - }, - "devDependencies": { - "@midnight-ntwrk/compact": "workspace:*", - "jest": "^29.7.0", - "typescript": "^5.2.2" } } diff --git a/yarn.lock b/yarn.lock index 4d7515ad..21ea5a9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -901,7 +901,7 @@ __metadata: languageName: node linkType: hard -"@midnight-ntwrk/compact@workspace:*, @midnight-ntwrk/compact@workspace:compact": +"@midnight-ntwrk/compact@workspace:compact": version: 0.0.0-use.local resolution: "@midnight-ntwrk/compact@workspace:compact" dependencies: @@ -987,31 +987,19 @@ __metadata: version: 0.0.0-use.local resolution: "@openzeppelin-midnight-contracts/erc20-contract@workspace:contracts/erc20" dependencies: - "@midnight-ntwrk/compact": "workspace:*" "@openzeppelin-midnight-contracts/utils-contract": "workspace:^" - jest: "npm:^29.7.0" - typescript: "npm:^5.2.2" languageName: unknown linkType: soft "@openzeppelin-midnight-contracts/initializable-contract@workspace:contracts/initializable": version: 0.0.0-use.local resolution: "@openzeppelin-midnight-contracts/initializable-contract@workspace:contracts/initializable" - dependencies: - "@midnight-ntwrk/compact": "workspace:*" - eslint: "npm:^8.52.0" - jest: "npm:^29.7.0" - typescript: "npm:^5.2.2" languageName: unknown linkType: soft "@openzeppelin-midnight-contracts/utils-contract@workspace:^, @openzeppelin-midnight-contracts/utils-contract@workspace:contracts/utils": version: 0.0.0-use.local resolution: "@openzeppelin-midnight-contracts/utils-contract@workspace:contracts/utils" - dependencies: - "@midnight-ntwrk/compact": "workspace:*" - jest: "npm:^29.7.0" - typescript: "npm:^5.2.2" languageName: unknown linkType: soft From 82e64e508984c51826b055c36d3676b573130b72 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 17 Apr 2025 15:27:48 -0500 Subject: [PATCH 008/202] remove unused deps --- package.json | 9 +- yarn.lock | 992 +-------------------------------------------------- 2 files changed, 13 insertions(+), 988 deletions(-) diff --git a/package.json b/package.json index 18a86574..5d3ea810 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "packageManager": "yarn@4.1.0", "workspaces": [ "compact", - "contracts/erc20/", "contracts/initializable/", "contracts/utils/" ], @@ -12,12 +11,7 @@ "lint": "turbo run lint" }, "dependencies": { - "@midnight-ntwrk/compact-runtime": "^0.7.0", - "fp-ts": "^2.16.1", - "io-ts": "^2.2.20", - "pino": "^8.16.0", - "pino-pretty": "^10.2.3", - "rxjs": "^7.8.1" + "@midnight-ntwrk/compact-runtime": "^0.7.0" }, "devDependencies": { "@biomejs/biome": "1.9.4", @@ -31,7 +25,6 @@ "jest-gh-md-reporter": "^0.0.2", "jest-html-reporters": "^3.1.4", "jest-junit": "^16.0.0", - "testcontainers": "^10.3.2", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", "turbo": "^1.10.16", diff --git a/yarn.lock b/yarn.lock index 21ea5a9c..2be4f8ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -377,13 +377,6 @@ __metadata: languageName: node linkType: hard -"@balena/dockerignore@npm:^1.0.2": - version: 1.0.2 - resolution: "@balena/dockerignore@npm:1.0.2" - checksum: 10/13d654fdd725008577d32e721c720275bdc48f72bce612326363d5bed449febbed856c517a0b23c7c40d87cb531e63432804550b4ecc13e365d26fee38fb6c8a - languageName: node - linkType: hard - "@bcoe/v8-coverage@npm:^0.2.3": version: 0.2.3 resolution: "@bcoe/v8-coverage@npm:0.2.3" @@ -533,13 +526,6 @@ __metadata: languageName: node linkType: hard -"@fastify/busboy@npm:^2.0.0": - version: 2.1.1 - resolution: "@fastify/busboy@npm:2.1.1" - checksum: 10/2bb8a7eca8289ed14c9eb15239bc1019797454624e769b39a0b90ed204d032403adc0f8ed0d2aef8a18c772205fa7808cf5a1b91f21c7bfc7b6032150b1062c5 - languageName: node - linkType: hard - "@humanwhocodes/config-array@npm:^0.13.0": version: 0.13.0 resolution: "@humanwhocodes/config-array@npm:0.13.0" @@ -983,21 +969,13 @@ __metadata: languageName: node linkType: hard -"@openzeppelin-midnight-contracts/erc20-contract@workspace:contracts/erc20": - version: 0.0.0-use.local - resolution: "@openzeppelin-midnight-contracts/erc20-contract@workspace:contracts/erc20" - dependencies: - "@openzeppelin-midnight-contracts/utils-contract": "workspace:^" - languageName: unknown - linkType: soft - "@openzeppelin-midnight-contracts/initializable-contract@workspace:contracts/initializable": version: 0.0.0-use.local resolution: "@openzeppelin-midnight-contracts/initializable-contract@workspace:contracts/initializable" languageName: unknown linkType: soft -"@openzeppelin-midnight-contracts/utils-contract@workspace:^, @openzeppelin-midnight-contracts/utils-contract@workspace:contracts/utils": +"@openzeppelin-midnight-contracts/utils-contract@workspace:contracts/utils": version: 0.0.0-use.local resolution: "@openzeppelin-midnight-contracts/utils-contract@workspace:contracts/utils" languageName: unknown @@ -1104,27 +1082,6 @@ __metadata: languageName: node linkType: hard -"@types/docker-modem@npm:*": - version: 3.0.6 - resolution: "@types/docker-modem@npm:3.0.6" - dependencies: - "@types/node": "npm:*" - "@types/ssh2": "npm:*" - checksum: 10/cc58e8189f6ec5a2b8ca890207402178a97ddac8c80d125dc65d8ab29034b5db736de15e99b91b2d74e66d14e26e73b6b8b33216613dd15fd3aa6b82c11a83ed - languageName: node - linkType: hard - -"@types/dockerode@npm:^3.3.29": - version: 3.3.35 - resolution: "@types/dockerode@npm:3.3.35" - dependencies: - "@types/docker-modem": "npm:*" - "@types/node": "npm:*" - "@types/ssh2": "npm:*" - checksum: 10/9b1bc6ffc032c5fd76564c4b2c80724eddcba4c0deb885105b811f0a843464f3152e44ea850d91b614f234e35fa70002aa7350d109517460a7fc339800833ade - languageName: node - linkType: hard - "@types/graceful-fs@npm:^4.1.3": version: 4.1.9 resolution: "@types/graceful-fs@npm:4.1.9" @@ -1178,7 +1135,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^18.11.18, @types/node@npm:^18.18.6": +"@types/node@npm:^18.18.6": version: 18.19.78 resolution: "@types/node@npm:18.19.78" dependencies: @@ -1194,34 +1151,6 @@ __metadata: languageName: node linkType: hard -"@types/ssh2-streams@npm:*": - version: 0.1.12 - resolution: "@types/ssh2-streams@npm:0.1.12" - dependencies: - "@types/node": "npm:*" - checksum: 10/377bfff70e6c13e42f7bf832209c916b9a80491bba611c21f4cbdc8c9f99553794e5583ee933fd02bb1b056dd9b97433195452f119104f592a5a2440806f3087 - languageName: node - linkType: hard - -"@types/ssh2@npm:*": - version: 1.15.4 - resolution: "@types/ssh2@npm:1.15.4" - dependencies: - "@types/node": "npm:^18.11.18" - checksum: 10/a4d37e28bf81c6bc41c785d78ee0208163af86294411f9662097f72bf91bb14647d4786f7a01a5c8e74594cfc1ccedcf9495bfdfb5541f2262a2cf433c94c5d9 - languageName: node - linkType: hard - -"@types/ssh2@npm:^0.5.48": - version: 0.5.52 - resolution: "@types/ssh2@npm:0.5.52" - dependencies: - "@types/node": "npm:*" - "@types/ssh2-streams": "npm:*" - checksum: 10/fc2584af091da49da9d6628dd8a5e851b217bb9b1b732b0361903894f2730ab3fdf8634f954be34c5a513f7eb0b2772d059d64062bcf6b4a0eb73bfc83c4b858 - languageName: node - linkType: hard - "@types/stack-utils@npm:^2.0.0": version: 2.0.3 resolution: "@types/stack-utils@npm:2.0.3" @@ -1259,15 +1188,6 @@ __metadata: languageName: node linkType: hard -"abort-controller@npm:^3.0.0": - version: 3.0.0 - resolution: "abort-controller@npm:3.0.0" - dependencies: - event-target-shim: "npm:^5.0.0" - checksum: 10/ed84af329f1828327798229578b4fe03a4dd2596ba304083ebd2252666bdc1d7647d66d0b18704477e1f8aa315f055944aa6e859afebd341f12d0a53c37b4b40 - languageName: node - linkType: hard - "acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -1370,36 +1290,6 @@ __metadata: languageName: node linkType: hard -"archiver-utils@npm:^5.0.0, archiver-utils@npm:^5.0.2": - version: 5.0.2 - resolution: "archiver-utils@npm:5.0.2" - dependencies: - glob: "npm:^10.0.0" - graceful-fs: "npm:^4.2.0" - is-stream: "npm:^2.0.1" - lazystream: "npm:^1.0.0" - lodash: "npm:^4.17.15" - normalize-path: "npm:^3.0.0" - readable-stream: "npm:^4.0.0" - checksum: 10/9dde4aa3f0cb1bdfe0b3d4c969f82e6cca9ae76338b7fee6f0071a14a2a38c0cdd1c41ecd3e362466585aa6cc5d07e9e435abea8c94fd9c7ace35f184abef9e4 - languageName: node - linkType: hard - -"archiver@npm:^7.0.1": - version: 7.0.1 - resolution: "archiver@npm:7.0.1" - dependencies: - archiver-utils: "npm:^5.0.2" - async: "npm:^3.2.4" - buffer-crc32: "npm:^1.0.0" - readable-stream: "npm:^4.0.0" - readdir-glob: "npm:^1.1.2" - tar-stream: "npm:^3.0.0" - zip-stream: "npm:^6.0.1" - checksum: 10/81c6102db99d7ffd5cb2aed02a678f551c6603991a059ca66ef59249942b835a651a3d3b5240af4f8bec4e61e13790357c9d1ad4a99982bd2cc4149575c31d67 - languageName: node - linkType: hard - "arg@npm:^4.1.0": version: 4.1.3 resolution: "arg@npm:4.1.3" @@ -1423,43 +1313,13 @@ __metadata: languageName: node linkType: hard -"asn1@npm:^0.2.6": - version: 0.2.6 - resolution: "asn1@npm:0.2.6" - dependencies: - safer-buffer: "npm:~2.1.0" - checksum: 10/cf629291fee6c1a6f530549939433ebf32200d7849f38b810ff26ee74235e845c0c12b2ed0f1607ac17383d19b219b69cefa009b920dab57924c5c544e495078 - languageName: node - linkType: hard - -"async-lock@npm:^1.4.1": - version: 1.4.1 - resolution: "async-lock@npm:1.4.1" - checksum: 10/80d55ac95f920e880a865968b799963014f6d987dd790dd08173fae6e1af509d8cd0ab45a25daaca82e3ef8e7c939f5d128cd1facfcc5c647da8ac2409e20ef9 - languageName: node - linkType: hard - -"async@npm:^3.2.3, async@npm:^3.2.4": +"async@npm:^3.2.3": version: 3.2.6 resolution: "async@npm:3.2.6" checksum: 10/cb6e0561a3c01c4b56a799cc8bab6ea5fef45f069ab32500b6e19508db270ef2dffa55e5aed5865c5526e9907b1f8be61b27530823b411ffafb5e1538c86c368 languageName: node linkType: hard -"atomic-sleep@npm:^1.0.0": - version: 1.0.0 - resolution: "atomic-sleep@npm:1.0.0" - checksum: 10/3ab6d2cf46b31394b4607e935ec5c1c3c4f60f3e30f0913d35ea74b51b3585e84f590d09e58067f11762eec71c87d25314ce859030983dc0e4397eed21daa12e - languageName: node - linkType: hard - -"b4a@npm:^1.6.4": - version: 1.6.7 - resolution: "b4a@npm:1.6.7" - checksum: 10/1ac056e3bce378d4d3e570e57319360a9d3125ab6916a1921b95bea33d9ee646698ebc75467561fd6fcc80ff697612124c89bb9b95e80db94c6dc23fcb977705 - languageName: node - linkType: hard - "babel-jest@npm:^29.7.0": version: 29.7.0 resolution: "babel-jest@npm:29.7.0" @@ -1546,84 +1406,6 @@ __metadata: languageName: node linkType: hard -"bare-events@npm:^2.0.0, bare-events@npm:^2.2.0": - version: 2.5.4 - resolution: "bare-events@npm:2.5.4" - checksum: 10/135ef380b13f554ca2c6905bdbcfac8edae08fce85b7f953fa01f09a9f5b0da6a25e414111659bc9a6118216f0dd1f732016acd11ce91517f2afb26ebeb4b721 - languageName: node - linkType: hard - -"bare-fs@npm:^4.0.1": - version: 4.0.1 - resolution: "bare-fs@npm:4.0.1" - dependencies: - bare-events: "npm:^2.0.0" - bare-path: "npm:^3.0.0" - bare-stream: "npm:^2.0.0" - checksum: 10/70951cf7d7522f0b6780bdfaf7969226db85370fa107b1eee71c58272573463388b40203595a8826cd55ca34e6359ca4b1ee91fd5d0b8ea64ab0d1f9979de262 - languageName: node - linkType: hard - -"bare-os@npm:^3.0.1": - version: 3.5.1 - resolution: "bare-os@npm:3.5.1" - checksum: 10/ff65328cb83bf8ed1f527f1bf46ec0a9990d76575bc3ab464f0299f9e94fea551af7c044c1471967fc220be5f0ddd420e8580bc4e7a1be9a1a16bf944b45f89e - languageName: node - linkType: hard - -"bare-path@npm:^3.0.0": - version: 3.0.0 - resolution: "bare-path@npm:3.0.0" - dependencies: - bare-os: "npm:^3.0.1" - checksum: 10/712d90e9cd8c3263cc11b0e0d386d1531a452706d7840c081ee586b34b00d72544e65df7a40013d47c1b177277495225deeede65cb2984db88a979cb65aaa2ff - languageName: node - linkType: hard - -"bare-stream@npm:^2.0.0": - version: 2.6.5 - resolution: "bare-stream@npm:2.6.5" - dependencies: - streamx: "npm:^2.21.0" - peerDependencies: - bare-buffer: "*" - bare-events: "*" - peerDependenciesMeta: - bare-buffer: - optional: true - bare-events: - optional: true - checksum: 10/0f5ca2167fbbccc118157bce7c53a933e21726268e03d751461211550d72b2d01c296b767ccf96aae8ab28e106b126407c6fe0d29f915734b844ffe6057f0a08 - languageName: node - linkType: hard - -"base64-js@npm:^1.3.1": - version: 1.5.1 - resolution: "base64-js@npm:1.5.1" - checksum: 10/669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 - languageName: node - linkType: hard - -"bcrypt-pbkdf@npm:^1.0.2": - version: 1.0.2 - resolution: "bcrypt-pbkdf@npm:1.0.2" - dependencies: - tweetnacl: "npm:^0.14.3" - checksum: 10/13a4cde058250dbf1fa77a4f1b9a07d32ae2e3b9e28e88a0c7a1827835bc3482f3e478c4a0cfd4da6ff0c46dae07da1061123a995372b32cc563d9975f975404 - languageName: node - linkType: hard - -"bl@npm:^4.0.3": - version: 4.1.0 - resolution: "bl@npm:4.1.0" - dependencies: - buffer: "npm:^5.5.0" - inherits: "npm:^2.0.4" - readable-stream: "npm:^3.4.0" - checksum: 10/b7904e66ed0bdfc813c06ea6c3e35eafecb104369dbf5356d0f416af90c1546de3b74e5b63506f0629acf5e16a6f87c3798f16233dcff086e9129383aa02ab55 - languageName: node - linkType: hard - "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -1684,13 +1466,6 @@ __metadata: languageName: node linkType: hard -"buffer-crc32@npm:^1.0.0": - version: 1.0.0 - resolution: "buffer-crc32@npm:1.0.0" - checksum: 10/ef3b7c07622435085c04300c9a51e850ec34a27b2445f758eef69b859c7827848c2282f3840ca6c1eef3829145a1580ce540cab03ccf4433827a2b95d3b09ca7 - languageName: node - linkType: hard - "buffer-from@npm:^1.0.0": version: 1.1.2 resolution: "buffer-from@npm:1.1.2" @@ -1698,40 +1473,6 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^5.5.0": - version: 5.7.1 - resolution: "buffer@npm:5.7.1" - dependencies: - base64-js: "npm:^1.3.1" - ieee754: "npm:^1.1.13" - checksum: 10/997434d3c6e3b39e0be479a80288875f71cd1c07d75a3855e6f08ef848a3c966023f79534e22e415ff3a5112708ce06127277ab20e527146d55c84566405c7c6 - languageName: node - linkType: hard - -"buffer@npm:^6.0.3": - version: 6.0.3 - resolution: "buffer@npm:6.0.3" - dependencies: - base64-js: "npm:^1.3.1" - ieee754: "npm:^1.2.1" - checksum: 10/b6bc68237ebf29bdacae48ce60e5e28fc53ae886301f2ad9496618efac49427ed79096750033e7eab1897a4f26ae374ace49106a5758f38fb70c78c9fda2c3b1 - languageName: node - linkType: hard - -"buildcheck@npm:~0.0.6": - version: 0.0.6 - resolution: "buildcheck@npm:0.0.6" - checksum: 10/194ee8d3b0926fd6f3e799732130ad7ab194882c56900b8670ad43c81326f64871f49b7d9f1e9baad91ca3070eb4e8b678797fe9ae78cf87dde86d8916eb25d2 - languageName: node - linkType: hard - -"byline@npm:^5.0.0": - version: 5.0.0 - resolution: "byline@npm:5.0.0" - checksum: 10/737ca83e8eda2976728dae62e68bc733aea095fab08db4c6f12d3cee3cf45b6f97dce45d1f6b6ff9c2c947736d10074985b4425b31ce04afa1985a4ef3d334a7 - languageName: node - linkType: hard - "cacache@npm:^19.0.1": version: 19.0.1 resolution: "cacache@npm:19.0.1" @@ -1797,13 +1538,6 @@ __metadata: languageName: node linkType: hard -"chownr@npm:^1.1.1": - version: 1.1.4 - resolution: "chownr@npm:1.1.4" - checksum: 10/115648f8eb38bac5e41c3857f3e663f9c39ed6480d1349977c4d96c95a47266fcacc5a5aabf3cb6c481e22d72f41992827db47301851766c4fd77ac21a4f081d - languageName: node - linkType: hard - "chownr@npm:^3.0.0": version: 3.0.0 resolution: "chownr@npm:3.0.0" @@ -1866,26 +1600,6 @@ __metadata: languageName: node linkType: hard -"colorette@npm:^2.0.7": - version: 2.0.20 - resolution: "colorette@npm:2.0.20" - checksum: 10/0b8de48bfa5d10afc160b8eaa2b9938f34a892530b2f7d7897e0458d9535a066e3998b49da9d21161c78225b272df19ae3a64d6df28b4c9734c0e55bbd02406f - languageName: node - linkType: hard - -"compress-commons@npm:^6.0.2": - version: 6.0.2 - resolution: "compress-commons@npm:6.0.2" - dependencies: - crc-32: "npm:^1.2.0" - crc32-stream: "npm:^6.0.0" - is-stream: "npm:^2.0.1" - normalize-path: "npm:^3.0.0" - readable-stream: "npm:^4.0.0" - checksum: 10/78e3ba10aeef919a1c5bbac21e120f3e1558a31b2defebbfa1635274fc7f7e8a3a0ee748a06249589acd0b33a0d58144b8238ff77afc3220f8d403a96fcc13aa - languageName: node - linkType: hard - "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1" @@ -1900,43 +1614,6 @@ __metadata: languageName: node linkType: hard -"core-util-is@npm:~1.0.0": - version: 1.0.3 - resolution: "core-util-is@npm:1.0.3" - checksum: 10/9de8597363a8e9b9952491ebe18167e3b36e7707569eed0ebf14f8bba773611376466ae34575bca8cfe3c767890c859c74056084738f09d4e4a6f902b2ad7d99 - languageName: node - linkType: hard - -"cpu-features@npm:~0.0.10": - version: 0.0.10 - resolution: "cpu-features@npm:0.0.10" - dependencies: - buildcheck: "npm:~0.0.6" - nan: "npm:^2.19.0" - node-gyp: "npm:latest" - checksum: 10/941b828ffe77582b2bdc03e894c913e2e2eeb5c6043ccb01338c34446d026f6888dc480ecb85e684809f9c3889d245f3648c7907eb61a92bdfc6aed039fcda8d - languageName: node - linkType: hard - -"crc-32@npm:^1.2.0": - version: 1.2.2 - resolution: "crc-32@npm:1.2.2" - bin: - crc32: bin/crc32.njs - checksum: 10/824f696a5baaf617809aa9cd033313c8f94f12d15ebffa69f10202480396be44aef9831d900ab291638a8022ed91c360696dd5b1ba691eb3f34e60be8835b7c3 - languageName: node - linkType: hard - -"crc32-stream@npm:^6.0.0": - version: 6.0.0 - resolution: "crc32-stream@npm:6.0.0" - dependencies: - crc-32: "npm:^1.2.0" - readable-stream: "npm:^4.0.0" - checksum: 10/e6edc2f81bc387daef6d18b2ac18c2ffcb01b554d3b5c7d8d29b177505aafffba574658fdd23922767e8dab1183d1962026c98c17e17fb272794c33293ef607c - languageName: node - linkType: hard - "create-jest@npm:^29.7.0": version: 29.7.0 resolution: "create-jest@npm:29.7.0" @@ -1972,14 +1649,7 @@ __metadata: languageName: node linkType: hard -"dateformat@npm:^4.6.3": - version: 4.6.3 - resolution: "dateformat@npm:4.6.3" - checksum: 10/5c149c91bf9ce2142c89f84eee4c585f0cb1f6faf2536b1af89873f862666a28529d1ccafc44750aa01384da2197c4f76f4e149a3cc0c1cb2c46f5cc45f2bcb5 - languageName: node - linkType: hard - -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4": version: 4.4.0 resolution: "debug@npm:4.4.0" dependencies: @@ -2045,38 +1715,6 @@ __metadata: languageName: node linkType: hard -"docker-compose@npm:^0.24.8": - version: 0.24.8 - resolution: "docker-compose@npm:0.24.8" - dependencies: - yaml: "npm:^2.2.2" - checksum: 10/2b8526f9797a55c819ff2d7dcea57085b012b3a3d77bc2e1a6b45c3fc9e82196312f5298cbe8299966462454a5ac8f68814bb407736b4385e0d226a2a39e877a - languageName: node - linkType: hard - -"docker-modem@npm:^3.0.0": - version: 3.0.8 - resolution: "docker-modem@npm:3.0.8" - dependencies: - debug: "npm:^4.1.1" - readable-stream: "npm:^3.5.0" - split-ca: "npm:^1.0.1" - ssh2: "npm:^1.11.0" - checksum: 10/a731d057b3da5a9da3dd9aff7e25bc33f2d29f3e0af947bd823d1361350071afb5b7cb0582af5bf012b08fca356520685bcff87bfcba08e85725576b32f264a2 - languageName: node - linkType: hard - -"dockerode@npm:^3.3.5": - version: 3.3.5 - resolution: "dockerode@npm:3.3.5" - dependencies: - "@balena/dockerignore": "npm:^1.0.2" - docker-modem: "npm:^3.0.0" - tar-fs: "npm:~2.0.1" - checksum: 10/1748e8d96f88fe71bb165a4c05726904937f5863b69eaeb4a3c1bb3bbf66940c7bef13b349ff757dc43664b4367611aab76f35c1ba468f07dcbaba567e6acd88 - languageName: node - linkType: hard - "doctrine@npm:^3.0.0": version: 3.0.0 resolution: "doctrine@npm:3.0.0" @@ -2141,15 +1779,6 @@ __metadata: languageName: node linkType: hard -"end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": - version: 1.4.4 - resolution: "end-of-stream@npm:1.4.4" - dependencies: - once: "npm:^1.4.0" - checksum: 10/530a5a5a1e517e962854a31693dbb5c0b2fc40b46dad2a56a2deec656ca040631124f4795823acc68238147805f8b021abbe221f4afed5ef3c8e8efc2024908b - languageName: node - linkType: hard - "env-paths@npm:^2.2.0": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -2312,20 +1941,6 @@ __metadata: languageName: node linkType: hard -"event-target-shim@npm:^5.0.0": - version: 5.0.1 - resolution: "event-target-shim@npm:5.0.1" - checksum: 10/49ff46c3a7facbad3decb31f597063e761785d7fdb3920d4989d7b08c97a61c2f51183e2f3a03130c9088df88d4b489b1b79ab632219901f184f85158508f4c8 - languageName: node - linkType: hard - -"events@npm:^3.3.0": - version: 3.3.0 - resolution: "events@npm:3.3.0" - checksum: 10/a3d47e285e28d324d7180f1e493961a2bbb4cad6412090e4dec114f4db1f5b560c7696ee8e758f55e23913ede856e3689cd3aa9ae13c56b5d8314cd3b3ddd1be - languageName: node - linkType: hard - "execa@npm:^5.0.0": version: 5.1.1 resolution: "execa@npm:5.1.1" @@ -2379,13 +1994,6 @@ __metadata: languageName: node linkType: hard -"fast-copy@npm:^3.0.0": - version: 3.0.2 - resolution: "fast-copy@npm:3.0.2" - checksum: 10/97e1022e2aaa27acf4a986d679310bfd66bfb87fe8da9dd33b698e3e50189484001cf1eeb9670e19b59d9d299828ed86c8da354c954f125995ab2a6331c5f290 - languageName: node - linkType: hard - "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -2393,13 +2001,6 @@ __metadata: languageName: node linkType: hard -"fast-fifo@npm:^1.2.0, fast-fifo@npm:^1.3.2": - version: 1.3.2 - resolution: "fast-fifo@npm:1.3.2" - checksum: 10/6bfcba3e4df5af7be3332703b69a7898a8ed7020837ec4395bb341bd96cc3a6d86c3f6071dd98da289618cf2234c70d84b2a6f09a33dd6f988b1ff60d8e54275 - languageName: node - linkType: hard - "fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" @@ -2414,20 +2015,6 @@ __metadata: languageName: node linkType: hard -"fast-redact@npm:^3.1.1": - version: 3.5.0 - resolution: "fast-redact@npm:3.5.0" - checksum: 10/24b27e2023bd5a62f908d97a753b1adb8d89206b260f97727728e00b693197dea2fc2aa3711147a385d0ec6e713569fd533df37a4ef947e08cb65af3019c7ad5 - languageName: node - linkType: hard - -"fast-safe-stringify@npm:^2.1.1": - version: 2.1.1 - resolution: "fast-safe-stringify@npm:2.1.1" - checksum: 10/dc1f063c2c6ac9533aee14d406441f86783a8984b2ca09b19c2fe281f9ff59d315298bc7bc22fd1f83d26fe19ef2f20e2ddb68e96b15040292e555c5ced0c1e4 - languageName: node - linkType: hard - "fastq@npm:^1.6.0": version: 1.19.1 resolution: "fastq@npm:1.19.1" @@ -2521,20 +2108,6 @@ __metadata: languageName: node linkType: hard -"fp-ts@npm:^2.16.1": - version: 2.16.9 - resolution: "fp-ts@npm:2.16.9" - checksum: 10/af5c3fa829456da60ed63b5288907868f0df01a7215acfc5697f621a21f76b0a0f7b7c08ac81f2bcf7ecae13a6c6d41047e739a79cf239db19cd49afd7e8e015 - languageName: node - linkType: hard - -"fs-constants@npm:^1.0.0": - version: 1.0.0 - resolution: "fs-constants@npm:1.0.0" - checksum: 10/18f5b718371816155849475ac36c7d0b24d39a11d91348cfcb308b4494824413e03572c403c86d3a260e049465518c4f0d5bd00f0371cdfcad6d4f30a85b350d - languageName: node - linkType: hard - "fs-extra@npm:^10.0.0": version: 10.1.0 resolution: "fs-extra@npm:10.1.0" @@ -2609,13 +2182,6 @@ __metadata: languageName: node linkType: hard -"get-port@npm:^5.1.1": - version: 5.1.1 - resolution: "get-port@npm:5.1.1" - checksum: 10/0162663ffe5c09e748cd79d97b74cd70e5a5c84b760a475ce5767b357fb2a57cb821cee412d646aa8a156ed39b78aab88974eddaa9e5ee926173c036c0713787 - languageName: node - linkType: hard - "get-stream@npm:^6.0.0": version: 6.0.1 resolution: "get-stream@npm:6.0.1" @@ -2632,7 +2198,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7": +"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7": version: 10.4.5 resolution: "glob@npm:10.4.5" dependencies: @@ -2678,7 +2244,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 @@ -2708,13 +2274,6 @@ __metadata: languageName: node linkType: hard -"help-me@npm:^5.0.0": - version: 5.0.0 - resolution: "help-me@npm:5.0.0" - checksum: 10/5f99bd91dae93d02867175c3856c561d7e3a24f16999b08f5fc79689044b938d7ed58457f4d8c8744c01403e6e0470b7896baa344d112b2355842fd935a75d69 - languageName: node - linkType: hard - "html-escaper@npm:^2.0.0": version: 2.0.2 resolution: "html-escaper@npm:2.0.2" @@ -2765,13 +2324,6 @@ __metadata: languageName: node linkType: hard -"ieee754@npm:^1.1.13, ieee754@npm:^1.2.1": - version: 1.2.1 - resolution: "ieee754@npm:1.2.1" - checksum: 10/d9f2557a59036f16c282aaeb107832dc957a93d73397d89bbad4eb1130560560eb695060145e8e6b3b498b15ab95510226649a0b8f52ae06583575419fe10fc4 - languageName: node - linkType: hard - "ignore@npm:^5.2.0": version: 5.3.2 resolution: "ignore@npm:5.3.2" @@ -2818,22 +2370,13 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3": +"inherits@npm:2": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 languageName: node linkType: hard -"io-ts@npm:^2.2.20": - version: 2.2.22 - resolution: "io-ts@npm:2.2.22" - peerDependencies: - fp-ts: ^2.5.0 - checksum: 10/c5eb8ca848f6e9586b5430773c62c8577902a6ca621349339e4d238c9ac4aba8df8de3e4d4317ff6593dcf38eb804445e0a5ba87afd7a2b8d29344ea9b6dc151 - languageName: node - linkType: hard - "ip-address@npm:^9.0.5": version: 9.0.5 resolution: "ip-address@npm:9.0.5" @@ -2913,7 +2456,7 @@ __metadata: languageName: node linkType: hard -"is-stream@npm:^2.0.0, is-stream@npm:^2.0.1": +"is-stream@npm:^2.0.0": version: 2.0.1 resolution: "is-stream@npm:2.0.1" checksum: 10/b8e05ccdf96ac330ea83c12450304d4a591f9958c11fd17bed240af8d5ffe08aedafa4c0f4cfccd4d28dc9d4d129daca1023633d5c11601a6cbc77521f6fae66 @@ -2929,13 +2472,6 @@ __metadata: languageName: node linkType: hard -"isarray@npm:~1.0.0": - version: 1.0.0 - resolution: "isarray@npm:1.0.0" - checksum: 10/f032df8e02dce8ec565cf2eb605ea939bdccea528dbcf565cdf92bfa2da9110461159d86a537388ef1acef8815a330642d7885b29010e8f7eac967c9993b65ab - languageName: node - linkType: hard - "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -3522,13 +3058,6 @@ __metadata: languageName: node linkType: hard -"joycon@npm:^3.1.1": - version: 3.1.1 - resolution: "joycon@npm:3.1.1" - checksum: 10/4b36e3479144ec196425f46b3618f8a96ce7e1b658f091a309cd4906215f5b7a402d7df331a3e0a09681381a658d0c5f039cb3cf6907e0a1e17ed847f5d37775 - languageName: node - linkType: hard - "js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -3641,15 +3170,6 @@ __metadata: languageName: node linkType: hard -"lazystream@npm:^1.0.0": - version: 1.0.1 - resolution: "lazystream@npm:1.0.1" - dependencies: - readable-stream: "npm:^2.0.5" - checksum: 10/35f8cf8b5799c76570b211b079d4d706a20cbf13a4936d44cc7dbdacab1de6b346ab339ed3e3805f4693155ee5bbebbda4050fa2b666d61956e89a573089e3d4 - languageName: node - linkType: hard - "leven@npm:^3.1.0": version: 3.1.0 resolution: "leven@npm:3.1.0" @@ -3706,13 +3226,6 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.15": - version: 4.17.21 - resolution: "lodash@npm:4.17.21" - checksum: 10/c08619c038846ea6ac754abd6dd29d2568aa705feb69339e836dfa8d8b09abbb2f859371e86863eda41848221f9af43714491467b5b0299122431e202bb0c532 - languageName: node - linkType: hard - "lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": version: 10.4.3 resolution: "lru-cache@npm:10.4.3" @@ -3806,7 +3319,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^5.0.1, minimatch@npm:^5.1.0": +"minimatch@npm:^5.0.1": version: 5.1.6 resolution: "minimatch@npm:5.1.6" dependencies: @@ -3824,13 +3337,6 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.2.6": - version: 1.2.8 - resolution: "minimist@npm:1.2.8" - checksum: 10/908491b6cc15a6c440ba5b22780a0ba89b9810e1aea684e253e43c4e3b8d56ec1dcdd7ea96dde119c29df59c936cde16062159eae4225c691e19c70b432b6e6f - languageName: node - linkType: hard - "minipass-collect@npm:^2.0.1": version: 2.0.1 resolution: "minipass-collect@npm:2.0.1" @@ -3908,13 +3414,6 @@ __metadata: languageName: node linkType: hard -"mkdirp-classic@npm:^0.5.2": - version: 0.5.3 - resolution: "mkdirp-classic@npm:0.5.3" - checksum: 10/3f4e088208270bbcc148d53b73e9a5bd9eef05ad2cbf3b3d0ff8795278d50dd1d11a8ef1875ff5aea3fa888931f95bfcb2ad5b7c1061cfefd6284d199e6776ac - languageName: node - linkType: hard - "mkdirp@npm:^1.0.4": version: 1.0.4 resolution: "mkdirp@npm:1.0.4" @@ -3940,15 +3439,6 @@ __metadata: languageName: node linkType: hard -"nan@npm:^2.19.0, nan@npm:^2.20.0": - version: 2.22.2 - resolution: "nan@npm:2.22.2" - dependencies: - node-gyp: "npm:latest" - checksum: 10/bee49de633650213970596ffbdf036bfe2109ff283a40f7742c3aa6d1fc15b9836f62bfee82192b879f56ab5f9fa9a1e5c58a908a50e5c87d91fb2118ef70827 - languageName: node - linkType: hard - "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -4031,14 +3521,7 @@ __metadata: languageName: node linkType: hard -"on-exit-leak-free@npm:^2.1.0": - version: 2.1.2 - resolution: "on-exit-leak-free@npm:2.1.2" - checksum: 10/f7b4b7200026a08f6e4a17ba6d72e6c5cbb41789ed9cf7deaf9d9e322872c7dc5a7898549a894651ee0ee9ae635d34a678115bf8acdfba8ebd2ba2af688b563c - languageName: node - linkType: hard - -"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": +"once@npm:^1.3.0": version: 1.4.0 resolution: "once@npm:1.4.0" dependencies: @@ -4211,68 +3694,6 @@ __metadata: languageName: node linkType: hard -"pino-abstract-transport@npm:^1.0.0, pino-abstract-transport@npm:^1.2.0": - version: 1.2.0 - resolution: "pino-abstract-transport@npm:1.2.0" - dependencies: - readable-stream: "npm:^4.0.0" - split2: "npm:^4.0.0" - checksum: 10/6ec1d19a7ff3347fd21576f744c31c3e38ca4463ae638818408f43698c936f96be6a0bc750af5f7c1ae81873183bfcb062b7a0d12dc159a1813ea900c388c693 - languageName: node - linkType: hard - -"pino-pretty@npm:^10.2.3": - version: 10.3.1 - resolution: "pino-pretty@npm:10.3.1" - dependencies: - colorette: "npm:^2.0.7" - dateformat: "npm:^4.6.3" - fast-copy: "npm:^3.0.0" - fast-safe-stringify: "npm:^2.1.1" - help-me: "npm:^5.0.0" - joycon: "npm:^3.1.1" - minimist: "npm:^1.2.6" - on-exit-leak-free: "npm:^2.1.0" - pino-abstract-transport: "npm:^1.0.0" - pump: "npm:^3.0.0" - readable-stream: "npm:^4.0.0" - secure-json-parse: "npm:^2.4.0" - sonic-boom: "npm:^3.0.0" - strip-json-comments: "npm:^3.1.1" - bin: - pino-pretty: bin.js - checksum: 10/4284f125f7e8a5a10e856c8fd591ba34c30c0a0071a0b265a9eda43c3e447ba11d40b06cc67108675586358a5d1213a6ac3a92f6abd2896abfbab9a5b4c17072 - languageName: node - linkType: hard - -"pino-std-serializers@npm:^6.0.0": - version: 6.2.2 - resolution: "pino-std-serializers@npm:6.2.2" - checksum: 10/a00cdff4e1fbc206da9bed047e6dc400b065f43e8b4cef1635b0192feab0e8f932cdeb0faaa38a5d93d2e777ba4cda939c2ed4c1a70f6839ff25f9aef97c27ff - languageName: node - linkType: hard - -"pino@npm:^8.16.0": - version: 8.21.0 - resolution: "pino@npm:8.21.0" - dependencies: - atomic-sleep: "npm:^1.0.0" - fast-redact: "npm:^3.1.1" - on-exit-leak-free: "npm:^2.1.0" - pino-abstract-transport: "npm:^1.2.0" - pino-std-serializers: "npm:^6.0.0" - process-warning: "npm:^3.0.0" - quick-format-unescaped: "npm:^4.0.3" - real-require: "npm:^0.2.0" - safe-stable-stringify: "npm:^2.3.1" - sonic-boom: "npm:^3.7.0" - thread-stream: "npm:^2.6.0" - bin: - pino: bin.js - checksum: 10/5a054eab533ab91b20f63497b86070f0a6b40e4688cde9de66d23e03d6046c4e95d69c3f526dea9f30bcbc5874c7fbf0f91660cded4753946fd02261ca8ac340 - languageName: node - linkType: hard - "pirates@npm:^4.0.4": version: 4.0.6 resolution: "pirates@npm:4.0.6" @@ -4314,27 +3735,6 @@ __metadata: languageName: node linkType: hard -"process-nextick-args@npm:~2.0.0": - version: 2.0.1 - resolution: "process-nextick-args@npm:2.0.1" - checksum: 10/1d38588e520dab7cea67cbbe2efdd86a10cc7a074c09657635e34f035277b59fbb57d09d8638346bf7090f8e8ebc070c96fa5fd183b777fff4f5edff5e9466cf - languageName: node - linkType: hard - -"process-warning@npm:^3.0.0": - version: 3.0.0 - resolution: "process-warning@npm:3.0.0" - checksum: 10/2d82fa641e50a5789eaf0f2b33453760996e373d4591aac576a22d696186ab7e240a0592db86c264d4f28a46c2abbe9b94689752017db7dadc90f169f12b0924 - languageName: node - linkType: hard - -"process@npm:^0.11.10": - version: 0.11.10 - resolution: "process@npm:0.11.10" - checksum: 10/dbaa7e8d1d5cf375c36963ff43116772a989ef2bb47c9bdee20f38fd8fc061119cf38140631cf90c781aca4d3f0f0d2c834711952b728953f04fd7d238f59f5b - languageName: node - linkType: hard - "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -4355,36 +3755,6 @@ __metadata: languageName: node linkType: hard -"proper-lockfile@npm:^4.1.2": - version: 4.1.2 - resolution: "proper-lockfile@npm:4.1.2" - dependencies: - graceful-fs: "npm:^4.2.4" - retry: "npm:^0.12.0" - signal-exit: "npm:^3.0.2" - checksum: 10/000a4875f543f591872b36ca94531af8a6463ddb0174f41c0b004d19e231d7445268b422ff1ea595e43d238655c702250cd3d27f408e7b9d97b56f1533ba26bf - languageName: node - linkType: hard - -"properties-reader@npm:^2.3.0": - version: 2.3.0 - resolution: "properties-reader@npm:2.3.0" - dependencies: - mkdirp: "npm:^1.0.4" - checksum: 10/0b41eb4136dc278ae0d97968ccce8de2d48d321655b319192e31f2424f1c6e052182204671e65aa8967216360cb3e7cbd9129830062e058fe9d6a1d74964c29a - languageName: node - linkType: hard - -"pump@npm:^3.0.0": - version: 3.0.2 - resolution: "pump@npm:3.0.2" - dependencies: - end-of-stream: "npm:^1.1.0" - once: "npm:^1.3.1" - checksum: 10/e0c4216874b96bd25ddf31a0b61a5613e26cc7afa32379217cf39d3915b0509def3565f5f6968fafdad2894c8bbdbd67d340e84f3634b2a29b950cffb6442d9f - languageName: node - linkType: hard - "punycode@npm:^2.1.0": version: 2.3.1 resolution: "punycode@npm:2.3.1" @@ -4406,13 +3776,6 @@ __metadata: languageName: node linkType: hard -"quick-format-unescaped@npm:^4.0.3": - version: 4.0.4 - resolution: "quick-format-unescaped@npm:4.0.4" - checksum: 10/591eca457509a99368b623db05248c1193aa3cedafc9a077d7acab09495db1231017ba3ad1b5386e5633271edd0a03b312d8640a59ee585b8516a42e15438aa7 - languageName: node - linkType: hard - "react-is@npm:^18.0.0": version: 18.3.1 resolution: "react-is@npm:18.3.1" @@ -4420,61 +3783,6 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.0.5": - version: 2.3.8 - resolution: "readable-stream@npm:2.3.8" - dependencies: - core-util-is: "npm:~1.0.0" - inherits: "npm:~2.0.3" - isarray: "npm:~1.0.0" - process-nextick-args: "npm:~2.0.0" - safe-buffer: "npm:~5.1.1" - string_decoder: "npm:~1.1.1" - util-deprecate: "npm:~1.0.1" - checksum: 10/8500dd3a90e391d6c5d889256d50ec6026c059fadee98ae9aa9b86757d60ac46fff24fafb7a39fa41d54cb39d8be56cc77be202ebd4cd8ffcf4cb226cbaa40d4 - languageName: node - linkType: hard - -"readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0": - version: 3.6.2 - resolution: "readable-stream@npm:3.6.2" - dependencies: - inherits: "npm:^2.0.3" - string_decoder: "npm:^1.1.1" - util-deprecate: "npm:^1.0.1" - checksum: 10/d9e3e53193adcdb79d8f10f2a1f6989bd4389f5936c6f8b870e77570853561c362bee69feca2bbb7b32368ce96a85504aa4cedf7cf80f36e6a9de30d64244048 - languageName: node - linkType: hard - -"readable-stream@npm:^4.0.0": - version: 4.7.0 - resolution: "readable-stream@npm:4.7.0" - dependencies: - abort-controller: "npm:^3.0.0" - buffer: "npm:^6.0.3" - events: "npm:^3.3.0" - process: "npm:^0.11.10" - string_decoder: "npm:^1.3.0" - checksum: 10/bdf096c8ff59452ce5d08f13da9597f9fcfe400b4facfaa88e74ec057e5ad1fdfa140ffe28e5ed806cf4d2055f0b812806e962bca91dce31bc4cef08e53be3a4 - languageName: node - linkType: hard - -"readdir-glob@npm:^1.1.2": - version: 1.1.3 - resolution: "readdir-glob@npm:1.1.3" - dependencies: - minimatch: "npm:^5.1.0" - checksum: 10/ca3a20aa1e715d671302d4ec785a32bf08e59d6d0dd25d5fc03e9e5a39f8c612cdf809ab3e638a79973db7ad6868492edf38504701e313328e767693671447d6 - languageName: node - linkType: hard - -"real-require@npm:^0.2.0": - version: 0.2.0 - resolution: "real-require@npm:0.2.0" - checksum: 10/ddf44ee76301c774e9c9f2826da8a3c5c9f8fc87310f4a364e803ef003aa1a43c378b4323051ced212097fff1af459070f4499338b36a7469df1d4f7e8c0ba4c - languageName: node - linkType: hard - "require-directory@npm:^2.1.1": version: 2.1.1 resolution: "require-directory@npm:2.1.1" @@ -4585,17 +3893,11 @@ __metadata: "@types/jest": "npm:^29.5.6" "@types/node": "npm:^18.18.6" fast-check: "npm:^3.15.0" - fp-ts: "npm:^2.16.1" - io-ts: "npm:^2.2.20" jest: "npm:^29.7.0" jest-fast-check: "npm:^2.0.0" jest-gh-md-reporter: "npm:^0.0.2" jest-html-reporters: "npm:^3.1.4" jest-junit: "npm:^16.0.0" - pino: "npm:^8.16.0" - pino-pretty: "npm:^10.2.3" - rxjs: "npm:^7.8.1" - testcontainers: "npm:^10.3.2" ts-jest: "npm:^29.1.1" ts-node: "npm:^10.9.1" turbo: "npm:^1.10.16" @@ -4612,50 +3914,13 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:^7.8.1": - version: 7.8.2 - resolution: "rxjs@npm:7.8.2" - dependencies: - tslib: "npm:^2.1.0" - checksum: 10/03dff09191356b2b87d94fbc1e97c4e9eb3c09d4452399dddd451b09c2f1ba8d56925a40af114282d7bc0c6fe7514a2236ca09f903cf70e4bbf156650dddb49d - languageName: node - linkType: hard - -"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": - version: 5.1.2 - resolution: "safe-buffer@npm:5.1.2" - checksum: 10/7eb5b48f2ed9a594a4795677d5a150faa7eb54483b2318b568dc0c4fc94092a6cce5be02c7288a0500a156282f5276d5688bce7259299568d1053b2150ef374a - languageName: node - linkType: hard - -"safe-buffer@npm:~5.2.0": - version: 5.2.1 - resolution: "safe-buffer@npm:5.2.1" - checksum: 10/32872cd0ff68a3ddade7a7617b8f4c2ae8764d8b7d884c651b74457967a9e0e886267d3ecc781220629c44a865167b61c375d2da6c720c840ecd73f45d5d9451 - languageName: node - linkType: hard - -"safe-stable-stringify@npm:^2.3.1": - version: 2.5.0 - resolution: "safe-stable-stringify@npm:2.5.0" - checksum: 10/2697fa186c17c38c3ca5309637b4ac6de2f1c3d282da27cd5e1e3c88eca0fb1f9aea568a6aabdf284111592c8782b94ee07176f17126031be72ab1313ed46c5c - languageName: node - linkType: hard - -"safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:~2.1.0": +"safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" checksum: 10/7eaf7a0cf37cc27b42fb3ef6a9b1df6e93a1c6d98c6c6702b02fe262d5fcbd89db63320793b99b21cb5348097d0a53de81bd5f4e8b86e20cc9412e3f1cfb4e83 languageName: node linkType: hard -"secure-json-parse@npm:^2.4.0": - version: 2.7.0 - resolution: "secure-json-parse@npm:2.7.0" - checksum: 10/974386587060b6fc5b1ac06481b2f9dbbb0d63c860cc73dc7533f27835fdb67b0ef08762dbfef25625c15bc0a0c366899e00076cb0d556af06b71e22f1dede4c - languageName: node - linkType: hard - "semver@npm:^6.3.0, semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1" @@ -4690,7 +3955,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: 10/a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 @@ -4746,15 +4011,6 @@ __metadata: languageName: node linkType: hard -"sonic-boom@npm:^3.0.0, sonic-boom@npm:^3.7.0": - version: 3.8.1 - resolution: "sonic-boom@npm:3.8.1" - dependencies: - atomic-sleep: "npm:^1.0.0" - checksum: 10/e03c9611e43fa81132cd2ce0fe4eb7fbcf19db267e9dec20dc6c586f82465c9c906e91a02f72150c740463ad9335536ea2131850307aaa6686d1fb5d4cc4be3e - languageName: node - linkType: hard - "source-map-support@npm:0.5.13": version: 0.5.13 resolution: "source-map-support@npm:0.5.13" @@ -4772,20 +4028,6 @@ __metadata: languageName: node linkType: hard -"split-ca@npm:^1.0.1": - version: 1.0.1 - resolution: "split-ca@npm:1.0.1" - checksum: 10/1e7409938a95ee843fe2593156a5735e6ee63772748ee448ea8477a5a3e3abde193c3325b3696e56a5aff07c7dcf6b1f6a2f2a036895b4f3afe96abb366d893f - languageName: node - linkType: hard - -"split2@npm:^4.0.0": - version: 4.2.0 - resolution: "split2@npm:4.2.0" - checksum: 10/09bbefc11bcf03f044584c9764cd31a252d8e52cea29130950b26161287c11f519807c5e54bd9e5804c713b79c02cefe6a98f4688630993386be353e03f534ab - languageName: node - linkType: hard - "sprintf-js@npm:^1.1.3": version: 1.1.3 resolution: "sprintf-js@npm:1.1.3" @@ -4800,33 +4042,6 @@ __metadata: languageName: node linkType: hard -"ssh-remote-port-forward@npm:^1.0.4": - version: 1.0.4 - resolution: "ssh-remote-port-forward@npm:1.0.4" - dependencies: - "@types/ssh2": "npm:^0.5.48" - ssh2: "npm:^1.4.0" - checksum: 10/c6c04c5ddfde7cb06e9a8655a152bd28fe6771c6fe62ff0bc08be229491546c410f30b153c968b8d6817a57d38678a270c228f30143ec0fe1be546efc4f6b65a - languageName: node - linkType: hard - -"ssh2@npm:^1.11.0, ssh2@npm:^1.4.0": - version: 1.16.0 - resolution: "ssh2@npm:1.16.0" - dependencies: - asn1: "npm:^0.2.6" - bcrypt-pbkdf: "npm:^1.0.2" - cpu-features: "npm:~0.0.10" - nan: "npm:^2.20.0" - dependenciesMeta: - cpu-features: - optional: true - nan: - optional: true - checksum: 10/0951c22d9c5a0e3b89a8e5ae890ebcbce9f1f94dbed37d1490e4e48e26bc8b074fa81f202ee57b708e31b5f33033f4c870b92047f4f02b6bc26c32225b01d84c - languageName: node - linkType: hard - "ssri@npm:^12.0.0": version: 12.0.0 resolution: "ssri@npm:12.0.0" @@ -4845,20 +4060,6 @@ __metadata: languageName: node linkType: hard -"streamx@npm:^2.15.0, streamx@npm:^2.21.0": - version: 2.22.0 - resolution: "streamx@npm:2.22.0" - dependencies: - bare-events: "npm:^2.2.0" - fast-fifo: "npm:^1.3.2" - text-decoder: "npm:^1.1.0" - dependenciesMeta: - bare-events: - optional: true - checksum: 10/9c329bb316e2085e207e471ecd0da18b4ed5b1cfe5cf10e9e7fad3f8f50c6ca1a6a844bdfd9bc7521560b97f229890de82ca162a0e66115300b91a489b1cbefd - languageName: node - linkType: hard - "string-length@npm:^4.0.1": version: 4.0.2 resolution: "string-length@npm:4.0.2" @@ -4891,24 +4092,6 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": - version: 1.3.0 - resolution: "string_decoder@npm:1.3.0" - dependencies: - safe-buffer: "npm:~5.2.0" - checksum: 10/54d23f4a6acae0e93f999a585e673be9e561b65cd4cca37714af1e893ab8cd8dfa52a9e4f58f48f87b4a44918d3a9254326cb80ed194bf2e4c226e2b21767e56 - languageName: node - linkType: hard - -"string_decoder@npm:~1.1.1": - version: 1.1.1 - resolution: "string_decoder@npm:1.1.1" - dependencies: - safe-buffer: "npm:~5.1.0" - checksum: 10/7c41c17ed4dea105231f6df208002ebddd732e8e9e2d619d133cecd8e0087ddfd9587d2feb3c8caf3213cbd841ada6d057f5142cae68a4e62d3540778d9819b4 - languageName: node - linkType: hard - "strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" @@ -4973,59 +4156,6 @@ __metadata: languageName: node linkType: hard -"tar-fs@npm:^3.0.6": - version: 3.0.8 - resolution: "tar-fs@npm:3.0.8" - dependencies: - bare-fs: "npm:^4.0.1" - bare-path: "npm:^3.0.0" - pump: "npm:^3.0.0" - tar-stream: "npm:^3.1.5" - dependenciesMeta: - bare-fs: - optional: true - bare-path: - optional: true - checksum: 10/fdcd1c66dc5e2cad5544ffe7eab9a470b419290b22300c344688df51bf06127963da07a1e3ae23cae80851cd9f60149e80b38e56485dd7a14aea701241ac2f81 - languageName: node - linkType: hard - -"tar-fs@npm:~2.0.1": - version: 2.0.1 - resolution: "tar-fs@npm:2.0.1" - dependencies: - chownr: "npm:^1.1.1" - mkdirp-classic: "npm:^0.5.2" - pump: "npm:^3.0.0" - tar-stream: "npm:^2.0.0" - checksum: 10/85ceac6fce0e9175b5b67c0eca8864b7d29a940cae8b7657c60b66e8a252319d701c3df12814162a6839e6120f9e1975757293bdeaf294ad5b15721d236c4d32 - languageName: node - linkType: hard - -"tar-stream@npm:^2.0.0": - version: 2.2.0 - resolution: "tar-stream@npm:2.2.0" - dependencies: - bl: "npm:^4.0.3" - end-of-stream: "npm:^1.4.1" - fs-constants: "npm:^1.0.0" - inherits: "npm:^2.0.3" - readable-stream: "npm:^3.1.1" - checksum: 10/1a52a51d240c118cbcd30f7368ea5e5baef1eac3e6b793fb1a41e6cd7319296c79c0264ccc5859f5294aa80f8f00b9239d519e627b9aade80038de6f966fec6a - languageName: node - linkType: hard - -"tar-stream@npm:^3.0.0, tar-stream@npm:^3.1.5": - version: 3.1.7 - resolution: "tar-stream@npm:3.1.7" - dependencies: - b4a: "npm:^1.6.4" - fast-fifo: "npm:^1.2.0" - streamx: "npm:^2.15.0" - checksum: 10/b21a82705a72792544697c410451a4846af1f744176feb0ff11a7c3dd0896961552e3def5e1c9a6bbee4f0ae298b8252a1f4c9381e9f991553b9e4847976f05c - languageName: node - linkType: hard - "tar@npm:^7.4.3": version: 7.4.3 resolution: "tar@npm:7.4.3" @@ -5051,38 +4181,6 @@ __metadata: languageName: node linkType: hard -"testcontainers@npm:^10.3.2": - version: 10.18.0 - resolution: "testcontainers@npm:10.18.0" - dependencies: - "@balena/dockerignore": "npm:^1.0.2" - "@types/dockerode": "npm:^3.3.29" - archiver: "npm:^7.0.1" - async-lock: "npm:^1.4.1" - byline: "npm:^5.0.0" - debug: "npm:^4.3.5" - docker-compose: "npm:^0.24.8" - dockerode: "npm:^3.3.5" - get-port: "npm:^5.1.1" - proper-lockfile: "npm:^4.1.2" - properties-reader: "npm:^2.3.0" - ssh-remote-port-forward: "npm:^1.0.4" - tar-fs: "npm:^3.0.6" - tmp: "npm:^0.2.3" - undici: "npm:^5.28.5" - checksum: 10/c15ab1071bcfb4c5713b299184c8431001c05c10a1fd6439e480a50b0721badd641939b1b09a6950d4de2f1ad68450afb824af0feaaf2e96afb41a282e804b88 - languageName: node - linkType: hard - -"text-decoder@npm:^1.1.0": - version: 1.2.3 - resolution: "text-decoder@npm:1.2.3" - dependencies: - b4a: "npm:^1.6.4" - checksum: 10/bcdec33c0f070aeac38e46e4cafdcd567a58473ed308bdf75260bfbd8f7dc76acbc0b13226afaec4a169d0cb44cec2ab89c57b6395ccf02e941eaebbe19e124a - languageName: node - linkType: hard - "text-table@npm:^0.2.0": version: 0.2.0 resolution: "text-table@npm:0.2.0" @@ -5090,22 +4188,6 @@ __metadata: languageName: node linkType: hard -"thread-stream@npm:^2.6.0": - version: 2.7.0 - resolution: "thread-stream@npm:2.7.0" - dependencies: - real-require: "npm:^0.2.0" - checksum: 10/03e743a2ccb2af5fa695d2e4369113336ee9b9f09c4453d50a222cbb4ae3af321bff658e0e5bf8bfbce9d7f5a7bf6262d12a2a365e160f4e76380ec624d32e7b - languageName: node - linkType: hard - -"tmp@npm:^0.2.3": - version: 0.2.3 - resolution: "tmp@npm:0.2.3" - checksum: 10/7b13696787f159c9754793a83aa79a24f1522d47b87462ddb57c18ee93ff26c74cbb2b8d9138f571d2e0e765c728fb2739863a672b280528512c6d83d511c6fa - languageName: node - linkType: hard - "tmpl@npm:1.0.5": version: 1.0.5 resolution: "tmpl@npm:1.0.5" @@ -5197,13 +4279,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.1.0": - version: 2.8.1 - resolution: "tslib@npm:2.8.1" - checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 - languageName: node - linkType: hard - "turbo-darwin-64@npm:1.13.4": version: 1.13.4 resolution: "turbo-darwin-64@npm:1.13.4" @@ -5275,13 +4350,6 @@ __metadata: languageName: node linkType: hard -"tweetnacl@npm:^0.14.3": - version: 0.14.5 - resolution: "tweetnacl@npm:0.14.5" - checksum: 10/04ee27901cde46c1c0a64b9584e04c96c5fe45b38c0d74930710751ea991408b405747d01dfae72f80fc158137018aea94f9c38c651cb9c318f0861a310c3679 - languageName: node - linkType: hard - "type-check@npm:^0.4.0, type-check@npm:~0.4.0": version: 0.4.0 resolution: "type-check@npm:0.4.0" @@ -5346,15 +4414,6 @@ __metadata: languageName: node linkType: hard -"undici@npm:^5.28.5": - version: 5.28.5 - resolution: "undici@npm:5.28.5" - dependencies: - "@fastify/busboy": "npm:^2.0.0" - checksum: 10/459cd84ab75fe90d696fa2634a8b5b23f9e1080b27236c6809bd74e51862be85df6d95b4a8fed3ee42554495008cb3c05f1bc9d4a1807478f433cca567003d70 - languageName: node - linkType: hard - "unique-filename@npm:^4.0.0": version: 4.0.0 resolution: "unique-filename@npm:4.0.0" @@ -5403,13 +4462,6 @@ __metadata: languageName: node linkType: hard -"util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1": - version: 1.0.2 - resolution: "util-deprecate@npm:1.0.2" - checksum: 10/474acf1146cb2701fe3b074892217553dfcf9a031280919ba1b8d651a068c9b15d863b7303cb15bd00a862b498e6cf4ad7b4a08fb134edd5a6f7641681cb54a2 - languageName: node - linkType: hard - "uuid@npm:^8.3.2": version: 8.3.2 resolution: "uuid@npm:8.3.2" @@ -5549,15 +4601,6 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.2.2": - version: 2.7.0 - resolution: "yaml@npm:2.7.0" - bin: - yaml: bin.mjs - checksum: 10/c8c314c62fbd49244a6a51b06482f6d495b37ab10fa685fcafa1bbaae7841b7233ee7d12cab087bcca5a0b28adc92868b6e437322276430c28d00f1c1732eeec - languageName: node - linkType: hard - "yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" @@ -5593,14 +4636,3 @@ __metadata: checksum: 10/f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 languageName: node linkType: hard - -"zip-stream@npm:^6.0.1": - version: 6.0.1 - resolution: "zip-stream@npm:6.0.1" - dependencies: - archiver-utils: "npm:^5.0.0" - compress-commons: "npm:^6.0.2" - readable-stream: "npm:^4.0.0" - checksum: 10/aa5abd6a89590eadeba040afbc375f53337f12637e5e98330012a12d9886cde7a3ccc28bd91aafab50576035bbb1de39a9a316eecf2411c8b9009c9f94f0db27 - languageName: node - linkType: hard From ebb272817102818b84b4a785b9c3716165ccabbc Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 17 Apr 2025 17:09:58 -0500 Subject: [PATCH 009/202] update turbo and scripts --- compact/turbo.json | 4 +- contracts/erc20/package.json | 8 +- contracts/initializable/package.json | 7 +- contracts/utils/package.json | 7 +- package.json | 10 ++- turbo.json | 117 ++++++++++----------------- yarn.lock | 70 +++++++++------- 7 files changed, 107 insertions(+), 116 deletions(-) diff --git a/compact/turbo.json b/compact/turbo.json index 6d8bfaa0..9c9378a1 100644 --- a/compact/turbo.json +++ b/compact/turbo.json @@ -1,11 +1,11 @@ { "$schema": "https://turbo.build/schema.json", "extends": ["//"], - "pipeline": { + "tasks": { "build": { "outputs": ["compactc/**"], "env": ["COMPACT_HOME"], - "inputs": ["src/**"], + "inputs": ["src/**.cjs"], "cache": true } } diff --git a/contracts/erc20/package.json b/contracts/erc20/package.json index 67cbfe99..ec3c05ba 100644 --- a/contracts/erc20/package.json +++ b/contracts/erc20/package.json @@ -15,10 +15,12 @@ "scripts": { "compact": "find src -name '*.compact' -exec sh -c 'run-compactc \"{}\" \"src/artifacts/$(basename \"{}\" .compact)\"' \\;", "test": "NODE_OPTIONS=--experimental-vm-modules jest", - "prepack": "yarn build", "build": "rm -rf dist && tsc --project tsconfig.build.json && cp -Rf ./src/artifacts ./dist/artifacts && cp ./src/ERC20.compact ./dist", - "lint": "eslint src", - "typecheck": "tsc -p tsconfig.json --noEmit" + "types": "tsc -p tsconfig.json --noEmit", + "fmt": "biome format", + "fmt:fix": "biome format --write", + "lint": "biome lint", + "lint:fix": "biome check --write" }, "dependencies": { "@openzeppelin-midnight-contracts/utils-contract": "workspace:^" diff --git a/contracts/initializable/package.json b/contracts/initializable/package.json index e6868ab2..0ae68562 100644 --- a/contracts/initializable/package.json +++ b/contracts/initializable/package.json @@ -17,7 +17,10 @@ "test": "NODE_OPTIONS=--experimental-vm-modules jest", "prepack": "yarn build", "build": "rm -rf dist && tsc --project tsconfig.build.json && cp -Rf ./src/artifacts ./dist/artifacts && cp ./src/Initializable.compact ./dist", - "lint": "eslint src", - "typecheck": "tsc -p tsconfig.json --noEmit" + "types": "tsc -p tsconfig.json --noEmit", + "fmt": "biome format", + "fmt:fix": "biome format --write", + "lint": "biome lint", + "lint:fix": "biome check --write" } } diff --git a/contracts/utils/package.json b/contracts/utils/package.json index 3fdcfcf5..c08d085f 100644 --- a/contracts/utils/package.json +++ b/contracts/utils/package.json @@ -17,7 +17,10 @@ "test": "NODE_OPTIONS=--experimental-vm-modules jest", "prepack": "yarn build", "build": "rm -rf dist && tsc --project tsconfig.build.json && cp -Rf ./src/artifacts ./dist/artifacts && cp ./src/Utils.compact ./dist", - "lint": "eslint src", - "typecheck": "tsc -p tsconfig.json --noEmit" + "types": "tsc -p tsconfig.json --noEmit", + "fmt": "biome format", + "fmt:fix": "biome format --write", + "lint": "biome lint", + "lint:fix": "biome check --write" } } diff --git a/package.json b/package.json index 5d3ea810..67183a32 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,19 @@ "packageManager": "yarn@4.1.0", "workspaces": [ "compact", + "contracts/erc20/", "contracts/initializable/", "contracts/utils/" ], "scripts": { "compact": "turbo run compact", "build": "turbo run build", - "lint": "turbo run lint" + "test": "turbo run test", + "fmt": "turbo run fmt", + "fmt:fix": "turbo run fmt:fix", + "lint": "turbo run lint", + "lint:fix": "turbo run lint:fix", + "types": "turbo run types" }, "dependencies": { "@midnight-ntwrk/compact-runtime": "^0.7.0" @@ -27,7 +33,7 @@ "jest-junit": "^16.0.0", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", - "turbo": "^1.10.16", + "turbo": "^2.4.4", "typescript": "^5.2.2" } } diff --git a/turbo.json b/turbo.json index 770d717d..e590773b 100644 --- a/turbo.json +++ b/turbo.json @@ -1,97 +1,66 @@ { "$schema": "https://turbo.build/schema.json", - "globalDependencies": [".prettierrc.json"], - "pipeline": { - "typecheck": { - "dependsOn": ["^build", "compact"], + "tasks": { + "compact": { + "env": [ + "COMPACT_HOME" + ], "inputs": [ - "src/**/*.ts", - "src/**/*.tsx", - "src/**/*.mts", - "tsconfig.json" + "src/**/*.compact" ], - "outputMode": "new-only", - "outputs": [] + "outputLogs": "new-only", + "outputs": [ + "src/artifacts/**", + "src/gen/**", + "gen/**" + ] }, - "compact": { - "dependsOn": ["^build"], - "env": ["COMPACT_HOME"], - "inputs": ["src/**/*.compact"], - "outputMode": "new-only", - "outputs": ["src/artifacts/**", "src/gen/**", "gen/**"] + "test": { + "outputs": [], + "cache": false }, "build": { - "dependsOn": ["^build", "compact", "typecheck"], - "outputMode": "new-only", - "inputs": [ - "src/**/*.ts", - "src/**/*.mts", - "src/**/*.tsx", - "!src/**/*.test.ts", - "!tests/**/*.ts", - "tsconfig.json", - "tsconfig.build.json", - ".env" + "dependsOn": [ + "^build", + "compact" + ], + "env": [ + "COMPACT_HOME" ], - "outputs": ["dist/**"] - }, - "build-storybook": { - "dependsOn": ["^build", "typecheck"], - "outputMode": "new-only", "inputs": [ "src/**/*.ts", - "src/**/*.mts", - "src/**/*.tsx", "!src/**/*.test.ts", "!tests/**/*.ts", "tsconfig.json", "tsconfig.build.json", - ".env", - "vite.config.ts", - ".storybook/**" - ], - "outputs": ["storybook-static/**"] - }, - "lint": { - "outputMode": "new-only", - "dependsOn": ["compact", "^build", "typecheck"], - "inputs": ["src/**/*.ts", "src/**/*.mts", "src/**/*.tsx", ".eslintrc.cjs"] - }, - "test": { - "outputMode": "new-only", - "dependsOn": ["^build", "compact", "typecheck"], - "inputs": [ - "src/**/*.ts", - "src/**/*.mts", - "src/**/*.tsx", - "jest.config.ts", - "tsconfig.json", - "tsconfig.test.json", - "test-compose.yml" + ".env" ], - "outputs": ["reports/**"] + "outputs": [ + "dist/**" + ] }, - "check": { - "outputMode": "new-only", - "dependsOn": ["build", "lint", "test", "build-storybook"] + "types": { + "outputs": [], + "cache": false, + "dependsOn": [ + "compact" + ] }, - "test-e2e": { - "outputMode": "new-only", - "dependsOn": ["build", "compact"] + "fmt": { + "outputs": [], + "cache": false }, - "start": { - "cache": false, - "persistent": true, - "dependsOn": ["build"] + "fmt:fix": { + "outputs": [], + "cache": false }, - "dev": { - "cache": false, - "persistent": true + "lint": { + "outputs": [], + "cache": false }, - "storybook": { - "cache": false, - "persistent": true, - "dependsOn": ["build"] + "lint:fix": { + "outputs": [], + "cache": false } } } diff --git a/yarn.lock b/yarn.lock index 2be4f8ea..a747c230 100644 --- a/yarn.lock +++ b/yarn.lock @@ -969,13 +969,21 @@ __metadata: languageName: node linkType: hard +"@openzeppelin-midnight-contracts/erc20-contract@workspace:contracts/erc20": + version: 0.0.0-use.local + resolution: "@openzeppelin-midnight-contracts/erc20-contract@workspace:contracts/erc20" + dependencies: + "@openzeppelin-midnight-contracts/utils-contract": "workspace:^" + languageName: unknown + linkType: soft + "@openzeppelin-midnight-contracts/initializable-contract@workspace:contracts/initializable": version: 0.0.0-use.local resolution: "@openzeppelin-midnight-contracts/initializable-contract@workspace:contracts/initializable" languageName: unknown linkType: soft -"@openzeppelin-midnight-contracts/utils-contract@workspace:contracts/utils": +"@openzeppelin-midnight-contracts/utils-contract@workspace:^, @openzeppelin-midnight-contracts/utils-contract@workspace:contracts/utils": version: 0.0.0-use.local resolution: "@openzeppelin-midnight-contracts/utils-contract@workspace:contracts/utils" languageName: unknown @@ -3900,7 +3908,7 @@ __metadata: jest-junit: "npm:^16.0.0" ts-jest: "npm:^29.1.1" ts-node: "npm:^10.9.1" - turbo: "npm:^1.10.16" + turbo: "npm:^2.4.4" typescript: "npm:^5.2.2" languageName: unknown linkType: soft @@ -4279,58 +4287,58 @@ __metadata: languageName: node linkType: hard -"turbo-darwin-64@npm:1.13.4": - version: 1.13.4 - resolution: "turbo-darwin-64@npm:1.13.4" +"turbo-darwin-64@npm:2.5.0": + version: 2.5.0 + resolution: "turbo-darwin-64@npm:2.5.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"turbo-darwin-arm64@npm:1.13.4": - version: 1.13.4 - resolution: "turbo-darwin-arm64@npm:1.13.4" +"turbo-darwin-arm64@npm:2.5.0": + version: 2.5.0 + resolution: "turbo-darwin-arm64@npm:2.5.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"turbo-linux-64@npm:1.13.4": - version: 1.13.4 - resolution: "turbo-linux-64@npm:1.13.4" +"turbo-linux-64@npm:2.5.0": + version: 2.5.0 + resolution: "turbo-linux-64@npm:2.5.0" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"turbo-linux-arm64@npm:1.13.4": - version: 1.13.4 - resolution: "turbo-linux-arm64@npm:1.13.4" +"turbo-linux-arm64@npm:2.5.0": + version: 2.5.0 + resolution: "turbo-linux-arm64@npm:2.5.0" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"turbo-windows-64@npm:1.13.4": - version: 1.13.4 - resolution: "turbo-windows-64@npm:1.13.4" +"turbo-windows-64@npm:2.5.0": + version: 2.5.0 + resolution: "turbo-windows-64@npm:2.5.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"turbo-windows-arm64@npm:1.13.4": - version: 1.13.4 - resolution: "turbo-windows-arm64@npm:1.13.4" +"turbo-windows-arm64@npm:2.5.0": + version: 2.5.0 + resolution: "turbo-windows-arm64@npm:2.5.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"turbo@npm:^1.10.16": - version: 1.13.4 - resolution: "turbo@npm:1.13.4" - dependencies: - turbo-darwin-64: "npm:1.13.4" - turbo-darwin-arm64: "npm:1.13.4" - turbo-linux-64: "npm:1.13.4" - turbo-linux-arm64: "npm:1.13.4" - turbo-windows-64: "npm:1.13.4" - turbo-windows-arm64: "npm:1.13.4" +"turbo@npm:^2.4.4": + version: 2.5.0 + resolution: "turbo@npm:2.5.0" + dependencies: + turbo-darwin-64: "npm:2.5.0" + turbo-darwin-arm64: "npm:2.5.0" + turbo-linux-64: "npm:2.5.0" + turbo-linux-arm64: "npm:2.5.0" + turbo-windows-64: "npm:2.5.0" + turbo-windows-arm64: "npm:2.5.0" dependenciesMeta: turbo-darwin-64: optional: true @@ -4346,7 +4354,7 @@ __metadata: optional: true bin: turbo: bin/turbo - checksum: 10/b8187def43760428e117313fc4a559af5c02b473d816b3efef406165c2c45cf3a256fbda4b15f0c1a48ccd188bd43cf93686f2aeab176a15a01553cd165071cc + checksum: 10/09a9a441bee424925444a9d3c071efe6139187257296a2b04a6474b719ce385a118c42677a67db1b77ca34f041a72a3fc152a60efcd96d407fd8d3a708c259a4 languageName: node linkType: hard From cd757c0f841cb861eb8bb3ad2f4ebe43130242ca Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 17 Apr 2025 17:15:40 -0500 Subject: [PATCH 010/202] add clean script --- contracts/erc20/package.json | 3 ++- contracts/initializable/package.json | 3 ++- contracts/utils/package.json | 4 ++-- package.json | 3 ++- turbo.json | 4 ++++ 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/contracts/erc20/package.json b/contracts/erc20/package.json index ec3c05ba..2a8ca839 100644 --- a/contracts/erc20/package.json +++ b/contracts/erc20/package.json @@ -20,7 +20,8 @@ "fmt": "biome format", "fmt:fix": "biome format --write", "lint": "biome lint", - "lint:fix": "biome check --write" + "lint:fix": "biome check --write", + "clean": "git clean -fXd" }, "dependencies": { "@openzeppelin-midnight-contracts/utils-contract": "workspace:^" diff --git a/contracts/initializable/package.json b/contracts/initializable/package.json index 0ae68562..9864007a 100644 --- a/contracts/initializable/package.json +++ b/contracts/initializable/package.json @@ -21,6 +21,7 @@ "fmt": "biome format", "fmt:fix": "biome format --write", "lint": "biome lint", - "lint:fix": "biome check --write" + "lint:fix": "biome check --write", + "clean": "git clean -fXd" } } diff --git a/contracts/utils/package.json b/contracts/utils/package.json index c08d085f..e197d1d5 100644 --- a/contracts/utils/package.json +++ b/contracts/utils/package.json @@ -15,12 +15,12 @@ "scripts": { "compact": "find src -name '*.compact' -exec sh -c 'run-compactc \"{}\" \"src/artifacts/$(basename \"{}\" .compact)\"' \\;", "test": "NODE_OPTIONS=--experimental-vm-modules jest", - "prepack": "yarn build", "build": "rm -rf dist && tsc --project tsconfig.build.json && cp -Rf ./src/artifacts ./dist/artifacts && cp ./src/Utils.compact ./dist", "types": "tsc -p tsconfig.json --noEmit", "fmt": "biome format", "fmt:fix": "biome format --write", "lint": "biome lint", - "lint:fix": "biome check --write" + "lint:fix": "biome check --write", + "clean": "git clean -fXd" } } diff --git a/package.json b/package.json index 67183a32..1005b4b3 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "fmt:fix": "turbo run fmt:fix", "lint": "turbo run lint", "lint:fix": "turbo run lint:fix", - "types": "turbo run types" + "types": "turbo run types", + "clean": "git clean -fXd -e \\\\!node_modules -e \\\\!node_modules/**/*" }, "dependencies": { "@midnight-ntwrk/compact-runtime": "^0.7.0" diff --git a/turbo.json b/turbo.json index e590773b..0940f33c 100644 --- a/turbo.json +++ b/turbo.json @@ -61,6 +61,10 @@ "lint:fix": { "outputs": [], "cache": false + }, + "clean": { + "outputs": [], + "cache": false } } } From a5fb60b666b956ab975bd2cad8853f0725929198 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 17 Apr 2025 17:22:23 -0500 Subject: [PATCH 011/202] add clean script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1005b4b3..df03994a 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "lint": "turbo run lint", "lint:fix": "turbo run lint:fix", "types": "turbo run types", - "clean": "git clean -fXd -e \\\\!node_modules -e \\\\!node_modules/**/*" + "clean": "turbo run clean" }, "dependencies": { "@midnight-ntwrk/compact-runtime": "^0.7.0" From b4fb0dfe32d1e5c36a8b3945b9ba5a42b5d35fb5 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 17 Apr 2025 20:47:24 -0500 Subject: [PATCH 012/202] clean up test config --- contracts/erc20/jest.config.ts | 14 +------------- contracts/initializable/jest.config.ts | 14 +------------- contracts/utils/jest.config.ts | 14 +------------- 3 files changed, 3 insertions(+), 39 deletions(-) diff --git a/contracts/erc20/jest.config.ts b/contracts/erc20/jest.config.ts index 5d1dbd14..bde5bde1 100644 --- a/contracts/erc20/jest.config.ts +++ b/contracts/erc20/jest.config.ts @@ -9,20 +9,8 @@ const config: Config.InitialOptions = { passWithNoTests: false, testMatch: ['**/*.test.ts'], extensionsToTreatAsEsm: ['.ts'], - collectCoverage: true, + collectCoverage: false, resolver: '/js-resolver.cjs', - coverageThreshold: { - global: { - branches: 50, - functions: 50, - lines: 50, - }, - }, - reporters: [ - 'default', - ['jest-junit', { outputDirectory: 'reports', outputName: 'report.xml' }], - ['jest-html-reporters', { publicPath: 'reports', filename: 'report.html' }], - ], }; export default config; diff --git a/contracts/initializable/jest.config.ts b/contracts/initializable/jest.config.ts index 5d1dbd14..bde5bde1 100644 --- a/contracts/initializable/jest.config.ts +++ b/contracts/initializable/jest.config.ts @@ -9,20 +9,8 @@ const config: Config.InitialOptions = { passWithNoTests: false, testMatch: ['**/*.test.ts'], extensionsToTreatAsEsm: ['.ts'], - collectCoverage: true, + collectCoverage: false, resolver: '/js-resolver.cjs', - coverageThreshold: { - global: { - branches: 50, - functions: 50, - lines: 50, - }, - }, - reporters: [ - 'default', - ['jest-junit', { outputDirectory: 'reports', outputName: 'report.xml' }], - ['jest-html-reporters', { publicPath: 'reports', filename: 'report.html' }], - ], }; export default config; diff --git a/contracts/utils/jest.config.ts b/contracts/utils/jest.config.ts index f15dd79d..bde5bde1 100644 --- a/contracts/utils/jest.config.ts +++ b/contracts/utils/jest.config.ts @@ -9,20 +9,8 @@ const config: Config.InitialOptions = { passWithNoTests: false, testMatch: ['**/*.test.ts'], extensionsToTreatAsEsm: ['.ts'], - collectCoverage: true, + collectCoverage: false, resolver: '/js-resolver.cjs', - coverageThreshold: { - global: { - //branches: 60, - //functions: 75, - //lines: 70, - }, - }, - reporters: [ - 'default', - ['jest-junit', { outputDirectory: 'reports', outputName: 'report.xml' }], - ['jest-html-reporters', { publicPath: 'reports', filename: 'report.html' }], - ], }; export default config; From 3462103b1ad2bfeac1dc676f6c8d011fd4c0ed64 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 17 Apr 2025 20:49:55 -0500 Subject: [PATCH 013/202] remove jest reporters --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index df03994a..85492b3a 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,6 @@ "fast-check": "^3.15.0", "jest": "^29.7.0", "jest-fast-check": "^2.0.0", - "jest-gh-md-reporter": "^0.0.2", - "jest-html-reporters": "^3.1.4", - "jest-junit": "^16.0.0", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", "turbo": "^2.4.4", From de6ab59d7e2972284513353d5dfe2619d8296030 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 17 Apr 2025 22:26:37 -0500 Subject: [PATCH 014/202] bump turbo to 2.5 --- package.json | 2 +- yarn.lock | 134 ++------------------------------------------------- 2 files changed, 5 insertions(+), 131 deletions(-) diff --git a/package.json b/package.json index 85492b3a..99e81577 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "jest-fast-check": "^2.0.0", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", - "turbo": "^2.4.4", + "turbo": "^2.5.0", "typescript": "^5.2.2" } } diff --git a/yarn.lock b/yarn.lock index a747c230..bdc16704 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1695,13 +1695,6 @@ __metadata: languageName: node linkType: hard -"define-lazy-prop@npm:^2.0.0": - version: 2.0.0 - resolution: "define-lazy-prop@npm:2.0.0" - checksum: 10/0115fdb065e0490918ba271d7339c42453d209d4cb619dfe635870d906731eff3e1ade8028bb461ea27ce8264ec5e22c6980612d332895977e89c1bbc80fcee2 - languageName: node - linkType: hard - "detect-newline@npm:^3.0.0": version: 3.1.0 resolution: "detect-newline@npm:3.1.0" @@ -1739,7 +1732,7 @@ __metadata: languageName: node linkType: hard -"ejs@npm:^3.1.10, ejs@npm:^3.1.7": +"ejs@npm:^3.1.10": version: 3.1.10 resolution: "ejs@npm:3.1.10" dependencies: @@ -2116,17 +2109,6 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^10.0.0": - version: 10.1.0 - resolution: "fs-extra@npm:10.1.0" - dependencies: - graceful-fs: "npm:^4.2.0" - jsonfile: "npm:^6.0.1" - universalify: "npm:^2.0.0" - checksum: 10/05ce2c3b59049bcb7b52001acd000e44b3c4af4ec1f8839f383ef41ec0048e3cfa7fd8a637b1bddfefad319145db89be91f4b7c1db2908205d38bf91e7d1d3b7 - languageName: node - linkType: hard - "fs-minipass@npm:^3.0.0": version: 3.0.3 resolution: "fs-minipass@npm:3.0.3" @@ -2252,7 +2234,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 @@ -2411,15 +2393,6 @@ __metadata: languageName: node linkType: hard -"is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": - version: 2.2.1 - resolution: "is-docker@npm:2.2.1" - bin: - is-docker: cli.js - checksum: 10/3fef7ddbf0be25958e8991ad941901bf5922ab2753c46980b60b05c1bf9c9c2402d35e6dc32e4380b980ef5e1970a5d9d5e5aa2e02d77727c3b6b5e918474c56 - languageName: node - linkType: hard - "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -2471,15 +2444,6 @@ __metadata: languageName: node linkType: hard -"is-wsl@npm:^2.2.0": - version: 2.2.0 - resolution: "is-wsl@npm:2.2.0" - dependencies: - is-docker: "npm:^2.0.0" - checksum: 10/20849846ae414997d290b75e16868e5261e86ff5047f104027026fd61d8b5a9b0b3ade16239f35e1a067b3c7cc02f70183cb661010ed16f4b6c7c93dad1b19d8 - languageName: node - linkType: hard - "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -2754,15 +2718,6 @@ __metadata: languageName: node linkType: hard -"jest-gh-md-reporter@npm:^0.0.2": - version: 0.0.2 - resolution: "jest-gh-md-reporter@npm:0.0.2" - dependencies: - ejs: "npm:^3.1.7" - checksum: 10/e61e63807a124184aeacef13816c313e8f2211aecd31974cc14f7d0dc65819286fc63969fd8e215504e2be7a3a5946fd023a231aca1d4981af14238c7dc41a7b - languageName: node - linkType: hard - "jest-haste-map@npm:^29.7.0": version: 29.7.0 resolution: "jest-haste-map@npm:29.7.0" @@ -2786,28 +2741,6 @@ __metadata: languageName: node linkType: hard -"jest-html-reporters@npm:^3.1.4": - version: 3.1.7 - resolution: "jest-html-reporters@npm:3.1.7" - dependencies: - fs-extra: "npm:^10.0.0" - open: "npm:^8.0.3" - checksum: 10/4aa780dd8cae9065e11fe0e5abb6e339eb9001f1745761a90f2e984b2dd7cf575be5cb5fef862e314b108d1602234de8ad5cf81aaf6142cf0b4eb3adc7797509 - languageName: node - linkType: hard - -"jest-junit@npm:^16.0.0": - version: 16.0.0 - resolution: "jest-junit@npm:16.0.0" - dependencies: - mkdirp: "npm:^1.0.4" - strip-ansi: "npm:^6.0.1" - uuid: "npm:^8.3.2" - xml: "npm:^1.0.1" - checksum: 10/2c33ee8bfd0c83b9aa1f8ba5905084890d5f519d294ccc2829d778ac860d5adffffec75d930f44f1d498aa8370c783e0aa6a632d947fb7e81205f0e7b926669d - languageName: node - linkType: hard - "jest-leak-detector@npm:^29.7.0": version: 29.7.0 resolution: "jest-leak-detector@npm:29.7.0" @@ -3149,19 +3082,6 @@ __metadata: languageName: node linkType: hard -"jsonfile@npm:^6.0.1": - version: 6.1.0 - resolution: "jsonfile@npm:6.1.0" - dependencies: - graceful-fs: "npm:^4.1.6" - universalify: "npm:^2.0.0" - dependenciesMeta: - graceful-fs: - optional: true - checksum: 10/03014769e7dc77d4cf05fa0b534907270b60890085dd5e4d60a382ff09328580651da0b8b4cdf44d91e4c8ae64d91791d965f05707beff000ed494a38b6fec85 - languageName: node - linkType: hard - "keyv@npm:^4.5.3": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -3422,15 +3342,6 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^1.0.4": - version: 1.0.4 - resolution: "mkdirp@npm:1.0.4" - bin: - mkdirp: bin/cmd.js - checksum: 10/d71b8dcd4b5af2fe13ecf3bd24070263489404fe216488c5ba7e38ece1f54daf219e72a833a3a2dc404331e870e9f44963a33399589490956bff003a3404d3b2 - languageName: node - linkType: hard - "mkdirp@npm:^3.0.1": version: 3.0.1 resolution: "mkdirp@npm:3.0.1" @@ -3547,17 +3458,6 @@ __metadata: languageName: node linkType: hard -"open@npm:^8.0.3": - version: 8.4.2 - resolution: "open@npm:8.4.2" - dependencies: - define-lazy-prop: "npm:^2.0.0" - is-docker: "npm:^2.1.1" - is-wsl: "npm:^2.2.0" - checksum: 10/acd81a1d19879c818acb3af2d2e8e9d81d17b5367561e623248133deb7dd3aefaed527531df2677d3e6aaf0199f84df57b6b2262babff8bf46ea0029aac536c9 - languageName: node - linkType: hard - "optionator@npm:^0.9.3": version: 0.9.4 resolution: "optionator@npm:0.9.4" @@ -3903,12 +3803,9 @@ __metadata: fast-check: "npm:^3.15.0" jest: "npm:^29.7.0" jest-fast-check: "npm:^2.0.0" - jest-gh-md-reporter: "npm:^0.0.2" - jest-html-reporters: "npm:^3.1.4" - jest-junit: "npm:^16.0.0" ts-jest: "npm:^29.1.1" ts-node: "npm:^10.9.1" - turbo: "npm:^2.4.4" + turbo: "npm:^2.5.0" typescript: "npm:^5.2.2" languageName: unknown linkType: soft @@ -4329,7 +4226,7 @@ __metadata: languageName: node linkType: hard -"turbo@npm:^2.4.4": +"turbo@npm:^2.5.0": version: 2.5.0 resolution: "turbo@npm:2.5.0" dependencies: @@ -4440,13 +4337,6 @@ __metadata: languageName: node linkType: hard -"universalify@npm:^2.0.0": - version: 2.0.1 - resolution: "universalify@npm:2.0.1" - checksum: 10/ecd8469fe0db28e7de9e5289d32bd1b6ba8f7183db34f3bfc4ca53c49891c2d6aa05f3fb3936a81285a905cc509fb641a0c3fc131ec786167eff41236ae32e60 - languageName: node - linkType: hard - "update-browserslist-db@npm:^1.1.1": version: 1.1.3 resolution: "update-browserslist-db@npm:1.1.3" @@ -4470,15 +4360,6 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^8.3.2": - version: 8.3.2 - resolution: "uuid@npm:8.3.2" - bin: - uuid: dist/bin/uuid - checksum: 10/9a5f7aa1d6f56dd1e8d5f2478f855f25c645e64e26e347a98e98d95781d5ed20062d6cca2eecb58ba7c84bc3910be95c0451ef4161906abaab44f9cb68ffbdd1 - languageName: node - linkType: hard - "v8-compile-cache-lib@npm:^3.0.1": version: 3.0.1 resolution: "v8-compile-cache-lib@npm:3.0.1" @@ -4574,13 +4455,6 @@ __metadata: languageName: node linkType: hard -"xml@npm:^1.0.1": - version: 1.0.1 - resolution: "xml@npm:1.0.1" - checksum: 10/6c4c31a1308e45732e5ac6b50edbca0e8f7abe5cb5de10215d8e3c688819fe7c7706e056f6fb59b1a23fdf1000c2d7a8bba0a89e94aa1796cd2376d9a5ba401e - languageName: node - linkType: hard - "y18n@npm:^5.0.5": version: 5.0.8 resolution: "y18n@npm:5.0.8" From 7c211d7b9e2cbb168d8edcfddb44f0f69deaea98 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 24 Apr 2025 18:33:01 -0500 Subject: [PATCH 015/202] bump turbo --- package.json | 2 +- yarn.lock | 58 ++++++++++++++++++++++++++-------------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 99e81577..5bb71715 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "jest-fast-check": "^2.0.0", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", - "turbo": "^2.5.0", + "turbo": "^2.5.1", "typescript": "^5.2.2" } } diff --git a/yarn.lock b/yarn.lock index bdc16704..9d0ba076 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3805,7 +3805,7 @@ __metadata: jest-fast-check: "npm:^2.0.0" ts-jest: "npm:^29.1.1" ts-node: "npm:^10.9.1" - turbo: "npm:^2.5.0" + turbo: "npm:^2.5.1" typescript: "npm:^5.2.2" languageName: unknown linkType: soft @@ -4184,58 +4184,58 @@ __metadata: languageName: node linkType: hard -"turbo-darwin-64@npm:2.5.0": - version: 2.5.0 - resolution: "turbo-darwin-64@npm:2.5.0" +"turbo-darwin-64@npm:2.5.1": + version: 2.5.1 + resolution: "turbo-darwin-64@npm:2.5.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"turbo-darwin-arm64@npm:2.5.0": - version: 2.5.0 - resolution: "turbo-darwin-arm64@npm:2.5.0" +"turbo-darwin-arm64@npm:2.5.1": + version: 2.5.1 + resolution: "turbo-darwin-arm64@npm:2.5.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"turbo-linux-64@npm:2.5.0": - version: 2.5.0 - resolution: "turbo-linux-64@npm:2.5.0" +"turbo-linux-64@npm:2.5.1": + version: 2.5.1 + resolution: "turbo-linux-64@npm:2.5.1" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"turbo-linux-arm64@npm:2.5.0": - version: 2.5.0 - resolution: "turbo-linux-arm64@npm:2.5.0" +"turbo-linux-arm64@npm:2.5.1": + version: 2.5.1 + resolution: "turbo-linux-arm64@npm:2.5.1" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"turbo-windows-64@npm:2.5.0": - version: 2.5.0 - resolution: "turbo-windows-64@npm:2.5.0" +"turbo-windows-64@npm:2.5.1": + version: 2.5.1 + resolution: "turbo-windows-64@npm:2.5.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"turbo-windows-arm64@npm:2.5.0": - version: 2.5.0 - resolution: "turbo-windows-arm64@npm:2.5.0" +"turbo-windows-arm64@npm:2.5.1": + version: 2.5.1 + resolution: "turbo-windows-arm64@npm:2.5.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"turbo@npm:^2.5.0": - version: 2.5.0 - resolution: "turbo@npm:2.5.0" +"turbo@npm:^2.5.1": + version: 2.5.1 + resolution: "turbo@npm:2.5.1" dependencies: - turbo-darwin-64: "npm:2.5.0" - turbo-darwin-arm64: "npm:2.5.0" - turbo-linux-64: "npm:2.5.0" - turbo-linux-arm64: "npm:2.5.0" - turbo-windows-64: "npm:2.5.0" - turbo-windows-arm64: "npm:2.5.0" + turbo-darwin-64: "npm:2.5.1" + turbo-darwin-arm64: "npm:2.5.1" + turbo-linux-64: "npm:2.5.1" + turbo-linux-arm64: "npm:2.5.1" + turbo-windows-64: "npm:2.5.1" + turbo-windows-arm64: "npm:2.5.1" dependenciesMeta: turbo-darwin-64: optional: true @@ -4251,7 +4251,7 @@ __metadata: optional: true bin: turbo: bin/turbo - checksum: 10/09a9a441bee424925444a9d3c071efe6139187257296a2b04a6474b719ce385a118c42677a67db1b77ca34f041a72a3fc152a60efcd96d407fd8d3a708c259a4 + checksum: 10/54d7c16d7cc9b60098db62d37940bef4663a14344bada1cb521e3260b6c30ec4bebc88a3dbb4108e4a3436fdc32b43091323f9d841ad3198719513765a73ddd9 languageName: node linkType: hard From 5cfcf80bd497054194f14a9e8672f7ac4e8f72c5 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 25 Apr 2025 01:45:09 -0500 Subject: [PATCH 016/202] fix package names --- contracts/erc20/package.json | 4 ++-- contracts/erc20/src/ERC20.compact | 2 +- contracts/initializable/package.json | 2 +- contracts/utils/package.json | 2 +- yarn.lock | 14 +++++++------- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/contracts/erc20/package.json b/contracts/erc20/package.json index 2a8ca839..c64d5517 100644 --- a/contracts/erc20/package.json +++ b/contracts/erc20/package.json @@ -1,5 +1,5 @@ { - "name": "@openzeppelin-midnight-contracts/erc20-contract", + "name": "@openzeppelin-midnight/erc20", "type": "module", "main": "dist/index.js", "module": "dist/index.js", @@ -24,6 +24,6 @@ "clean": "git clean -fXd" }, "dependencies": { - "@openzeppelin-midnight-contracts/utils-contract": "workspace:^" + "@openzeppelin-midnight/utils": "workspace:^" } } diff --git a/contracts/erc20/src/ERC20.compact b/contracts/erc20/src/ERC20.compact index 96bdab69..31ca0059 100644 --- a/contracts/erc20/src/ERC20.compact +++ b/contracts/erc20/src/ERC20.compact @@ -22,7 +22,7 @@ pragma language_version >= 0.14.0; */ module ERC20 { import CompactStandardLibrary; - import "../../node_modules/@openzeppelin-midnight-contracts/utils-contract/src/Utils" prefix Utils_; + import "../../node_modules/@openzeppelin-midnight/utils/src/Utils" prefix Utils_; /// Public state export sealed ledger _name: Maybe>; diff --git a/contracts/initializable/package.json b/contracts/initializable/package.json index 9864007a..ca84c452 100644 --- a/contracts/initializable/package.json +++ b/contracts/initializable/package.json @@ -1,5 +1,5 @@ { - "name": "@openzeppelin-midnight-contracts/initializable-contract", + "name": "@openzeppelin-midnight/initializable", "type": "module", "main": "dist/index.js", "module": "dist/index.js", diff --git a/contracts/utils/package.json b/contracts/utils/package.json index e197d1d5..e47de479 100644 --- a/contracts/utils/package.json +++ b/contracts/utils/package.json @@ -1,5 +1,5 @@ { - "name": "@openzeppelin-midnight-contracts/utils-contract", + "name": "@openzeppelin-midnight/utils", "type": "module", "main": "dist/index.js", "module": "dist/index.js", diff --git a/yarn.lock b/yarn.lock index 9d0ba076..cd1784ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -969,23 +969,23 @@ __metadata: languageName: node linkType: hard -"@openzeppelin-midnight-contracts/erc20-contract@workspace:contracts/erc20": +"@openzeppelin-midnight/erc20@workspace:contracts/erc20": version: 0.0.0-use.local - resolution: "@openzeppelin-midnight-contracts/erc20-contract@workspace:contracts/erc20" + resolution: "@openzeppelin-midnight/erc20@workspace:contracts/erc20" dependencies: - "@openzeppelin-midnight-contracts/utils-contract": "workspace:^" + "@openzeppelin-midnight/utils": "workspace:^" languageName: unknown linkType: soft -"@openzeppelin-midnight-contracts/initializable-contract@workspace:contracts/initializable": +"@openzeppelin-midnight/initializable@workspace:contracts/initializable": version: 0.0.0-use.local - resolution: "@openzeppelin-midnight-contracts/initializable-contract@workspace:contracts/initializable" + resolution: "@openzeppelin-midnight/initializable@workspace:contracts/initializable" languageName: unknown linkType: soft -"@openzeppelin-midnight-contracts/utils-contract@workspace:^, @openzeppelin-midnight-contracts/utils-contract@workspace:contracts/utils": +"@openzeppelin-midnight/utils@workspace:^, @openzeppelin-midnight/utils@workspace:contracts/utils": version: 0.0.0-use.local - resolution: "@openzeppelin-midnight-contracts/utils-contract@workspace:contracts/utils" + resolution: "@openzeppelin-midnight/utils@workspace:contracts/utils" languageName: unknown linkType: soft From f23d40bb633538ec095abf0270ed171e413a51f1 Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 27 Apr 2025 01:46:24 -0500 Subject: [PATCH 017/202] set up compact compiler and builder --- compact/.eslintrc.cjs | 24 ----- compact/package.json | 41 +++++++-- compact/src/Builder.ts | 155 ++++++++++++++++++++++++++++++++ compact/src/Compiler.ts | 170 +++++++++++++++++++++++++++++++++++ compact/src/run-compactc.cjs | 32 ------- compact/src/runBuilder.ts | 43 +++++++++ compact/src/runCompiler.ts | 40 +++++++++ compact/tsconfig.json | 21 +++++ 8 files changed, 461 insertions(+), 65 deletions(-) delete mode 100644 compact/.eslintrc.cjs create mode 100755 compact/src/Builder.ts create mode 100755 compact/src/Compiler.ts delete mode 100755 compact/src/run-compactc.cjs create mode 100644 compact/src/runBuilder.ts create mode 100644 compact/src/runCompiler.ts create mode 100644 compact/tsconfig.json diff --git a/compact/.eslintrc.cjs b/compact/.eslintrc.cjs deleted file mode 100644 index 32fa19b8..00000000 --- a/compact/.eslintrc.cjs +++ /dev/null @@ -1,24 +0,0 @@ -module.exports = { - env: { - browser: true, - es2021: true, - node: true, - jest: true, - }, - extends: [ - 'plugin:prettier/recommended', - 'plugin:@typescript-eslint/recommended-requiring-type-checking', - ], - overrides: [], - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - project: ['tsconfig.json'], - }, - rules: { - '@typescript-eslint/no-misused-promises': 'off', // https://github.com/typescript-eslint/typescript-eslint/issues/5807 - '@typescript-eslint/no-floating-promises': 'warn', - '@typescript-eslint/promise-function-async': 'off', - '@typescript-eslint/no-redeclare': 'off', - }, -}; diff --git a/compact/package.json b/compact/package.json index a2ab6849..15b7595a 100644 --- a/compact/package.json +++ b/compact/package.json @@ -1,17 +1,40 @@ { - "name": "@midnight-ntwrk/compact", + "packageManager": "yarn@4.1.0", + "name": "@openzeppelin-midnight/compact", + "version": "0.0.1", + "keywords": [ + "compact", + "compiler" + ], + "author": "OpenZeppelin Community ", + "license": "MIT", "description": "Compact fetcher", - "author": "IOG", - "license": "Apache-2.0", - "private": true, - "version": "0.21.0", "type": "module", + "main": "index.js", "bin": { - "run-compactc": "src/run-compactc.cjs" + "compact-builder": "dist/runBuilder.js", + "compact-compiler": "dist/runCompiler.js" + }, + "scripts": { + "types": "tsc -p tsconfig.json --noEmit", + "fmt": "biome format", + "fmt:fix": "biome format --write", + "lint": "biome lint", + "lint:fix": "biome check --write", + "clean": "git clean -fXd" }, "devDependencies": { - "eslint": "^8.52.0", - "ts-node": "^10.9.2", - "typescript": "^5.2.2" + "@types/jest": "^29.5.6", + "@types/node": "^22.13.10", + "fast-check": "^3.15.0", + "jest": "^29.7.0", + "jest-fast-check": "^2.0.0", + "ts-jest": "^29.1.1", + "typescript": "^5.8.2" + }, + "dependencies": { + "chalk": "^5.4.1", + "log-symbols": "^7.0.0", + "ora": "^8.2.0" } } diff --git a/compact/src/Builder.ts b/compact/src/Builder.ts new file mode 100755 index 00000000..638157fa --- /dev/null +++ b/compact/src/Builder.ts @@ -0,0 +1,155 @@ +#!/usr/bin/env node + +import { exec } from 'node:child_process'; +import { promisify } from 'node:util'; +import chalk from 'chalk'; +import ora, { type Ora } from 'ora'; +import { CompactCompiler } from './Compiler.js'; + +// Promisified exec for async execution +const execAsync = promisify(exec); + +/** + * A class to handle the build process for a project. + * Runs CompactCompiler as a prerequisite, then executes build steps (TypeScript compilation, + * artifact copying, etc.) + * with progress feedback and colored output for success and error states. + * + * @example + * ```typescript + * const builder = new ProjectBuilder('--skip-zk'); // Optional flags for compactc + * builder.build().catch(err => console.error(err)); + * ``` + * + * @example Successful Build Output + * ``` + * ℹ [COMPILE] Found 2 .compact file(s) to compile + * ✔ [COMPILE] [1/2] Compiled AccessControl.compact + * Compactc version: 0.22.0 + * ✔ [COMPILE] [2/2] Compiled MockAccessControl.compact + * Compactc version: 0.22.0 + * ✔ [BUILD] [1/3] Compiling TypeScript + * ✔ [BUILD] [2/3] Copying artifacts + * ✔ [BUILD] [3/3] Copying and cleaning .compact files + * ``` + * + * @example Failed Compilation Output + * ``` + * ℹ [COMPILE] Found 2 .compact file(s) to compile + * ✖ [COMPILE] [1/2] Failed AccessControl.compact + * Compactc version: 0.22.0 + * Error: Expected ';' at line 5 in AccessControl.compact + * ``` + * + * @example Failed Build Step Output + * ``` + * ℹ [COMPILE] Found 2 .compact file(s) to compile + * ✔ [COMPILE] [1/2] Compiled AccessControl.compact + * ✔ [COMPILE] [2/2] Compiled MockAccessControl.compact + * ✖ [BUILD] [1/3] Failed Compiling TypeScript + * error TS1005: ';' expected at line 10 in file.ts + * [BUILD] ❌ Build failed: Command failed: tsc --project tsconfig.build.json + * ``` + */ +export class CompactBuilder { + private readonly compilerFlags: string; + private readonly steps: Array<{ cmd: string; msg: string; shell?: string }> = + [ + { + cmd: 'tsc --project tsconfig.build.json', + msg: 'Compiling TypeScript', + }, + { + cmd: 'mkdir -p dist/artifacts && cp -Rf src/artifacts/* dist/artifacts/ 2>/dev/null || true', + msg: 'Copying artifacts', + shell: '/bin/bash', + }, + { + cmd: 'cp src/*.compact dist/ 2>/dev/null || true && rm dist/Mock*.compact 2>/dev/null || true', + msg: 'Copying and cleaning .compact files', + shell: '/bin/bash', + }, + ]; + + /** + * Constructs a new ProjectBuilder instance. + * @param compilerFlags - Optional space-separated string of `compactc` flags (e.g., "--skip-zk") + */ + constructor(compilerFlags = '') { + this.compilerFlags = compilerFlags; + } + + /** + * Executes the full build process: compiles .compact files first, then runs build steps. + * Displays progress with spinners and outputs results in color. + * + * @returns A promise that resolves when all steps complete successfully + * @throws Error if compilation or any build step fails + */ + public async build(): Promise { + // Run compact compilation as a prerequisite + const compiler = new CompactCompiler(this.compilerFlags); + await compiler.compile(); + + // Proceed with build steps + for (const [index, step] of this.steps.entries()) { + await this.executeStep(step, index, this.steps.length); + } + } + + /** + * Executes a single build step. + * Runs the command, shows a spinner, and prints output with indentation. + * + * @param step - The build step containing command and message + * @param index - Current step index (0-based) for progress display + * @param total - Total number of steps for progress display + * @returns A promise that resolves when the step completes successfully + * @throws Error if the step fails + */ + private async executeStep( + step: { cmd: string; msg: string; shell?: string }, + index: number, + total: number, + ): Promise { + const stepLabel: string = `[${index + 1}/${total}]`; + const spinner: Ora = ora(`[BUILD] ${stepLabel} ${step.msg}`).start(); + + try { + const { stdout, stderr }: { stdout: string; stderr: string } = + await execAsync(step.cmd, { + shell: step.shell, // Only pass shell where needed + }); + spinner.succeed(`[BUILD] ${stepLabel} ${step.msg}`); + this.printOutput(stdout, chalk.cyan); + this.printOutput(stderr, chalk.yellow); // Show stderr (warnings) in yellow if present + } catch (error: any) { + spinner.fail(`[BUILD] ${stepLabel} ${step.msg}`); + this.printOutput(error.stdout, chalk.cyan); + this.printOutput(error.stderr, chalk.red); + console.error(chalk.red('[BUILD] ❌ Build failed:', error.message)); + process.exit(1); + } + } + + /** + * Prints command output with indentation and specified color. + * Filters out empty lines and indents each line for readability. + * + * @param output - The command output string to print (stdout or stderr) + * @param colorFn - Chalk color function to style the output (e.g., `chalk.cyan` for success, `chalk.red` for errors) + */ + private printOutput( + output: string | undefined, + colorFn: (text: string) => string, + ): void { + if (output) { + const lines: string[] = output + .split('\n') + .filter((line: string): boolean => line.trim() !== '') + .map((line: string): string => ` ${line}`); + // biome-ignore lint/suspicious/noConsoleLog: needed for debugging + console.log(colorFn(lines.join('\n'))); + } + } +} diff --git a/compact/src/Compiler.ts b/compact/src/Compiler.ts new file mode 100755 index 00000000..1ae6659c --- /dev/null +++ b/compact/src/Compiler.ts @@ -0,0 +1,170 @@ +#!/usr/bin/env node + +import { exec as execCallback } from 'node:child_process'; +import { existsSync, readdirSync } from 'node:fs'; +import { basename, dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { promisify } from 'node:util'; +import chalk from 'chalk'; +import ora, { type Ora } from 'ora'; + +const DIRNAME: string = dirname(fileURLToPath(import.meta.url)); +const SRC_DIR: string = 'src'; +const ARTIFACTS_DIR: string = 'src/artifacts'; +const COMPACT_HOME: string = + process.env.COMPACT_HOME ?? resolve(DIRNAME, '../compactc'); +const COMPACTC_PATH: string = join(COMPACT_HOME, 'compactc'); + +/** + * A class to handle compilation of `.compact` files using the `compactc` compiler. + * Provides progress feedback and colored output for success and error states. + * + * @example + * ```typescript + * const compiler = new CompactCompiler('--skip-zk'); + * compiler.compile().catch(err => console.error(err)); + * ``` + * + * @example Successful Compilation Output + * ``` + * ℹ [COMPILE] Found 2 .compact file(s) to compile + * ✔ [COMPILE] [1/2] Compiled AccessControl.compact + * Compactc version: 0.22.0 + * ✔ [COMPILE] [2/2] Compiled MockAccessControl.compact + * Compactc version: 0.22.0 + * Compiling circuit "src/artifacts/MockAccessControl/zkir/grantRole.zkir"... (skipped proving keys) + * ``` + * + * @example Failed Compilation Output + * ``` + * ℹ [COMPILE] Found 2 .compact file(s) to compile + * ✖ [COMPILE] [1/2] Failed AccessControl.compact + * Compactc version: 0.22.0 + * Error: Expected ';' at line 5 in AccessControl.compact + * ``` + */ +export class CompactCompiler { + /** Stores the compiler flags passed via command-line arguments */ + private readonly flags: string; + + /** + * Constructs a new CompactCompiler instance, validating the `compactc` binary path. + * + * @param flags - Space-separated string of `compactc` flags (e.g., "--skip-zk --no-communications-commitment") + * @throws {Error} If the `compactc` binary is not found at the resolved path + */ + constructor(flags: string) { + this.flags = flags.trim(); + const spinner = ora(); + + spinner.info(chalk.blue(`[COMPILE] COMPACT_HOME: ${COMPACT_HOME}`)); + spinner.info(chalk.blue(`[COMPILE] COMPACTC_PATH: ${COMPACTC_PATH}`)); + + if (!existsSync(COMPACTC_PATH)) { + spinner.fail( + chalk.red( + `[COMPILE] Error: compactc not found at ${COMPACTC_PATH}. Set COMPACT_HOME to the compactc binary path.`, + ), + ); + throw new Error(`compactc not found at ${COMPACTC_PATH}`); + } + } + + /** + * Compiles all `.compact` files in the source directory and its subdirectories (e.g., `src/test/mock/`). + * Scans the `src` directory recursively for `.compact` files, compiles each one using `compactc`, + * and displays progress with a spinner and colored output. + * + * @returns A promise that resolves when all files are compiled successfully + * @throws {Error} If compilation fails for any file + */ + public async compile(): Promise { + const compactFiles: string[] = readdirSync(SRC_DIR, { + recursive: true, + withFileTypes: true, + }) + .filter((dirent): boolean => { + const filePath = join(dirent.path, dirent.name); + return dirent.isFile() && filePath.endsWith('.compact'); + }) + .map((dirent): string => + join(dirent.path, dirent.name).replace(`${SRC_DIR}/`, ''), + ); + + const spinner = ora(); + if (compactFiles.length === 0) { + spinner.warn(chalk.yellow('[COMPILE] No .compact files found.')); + return; + } + + spinner.info( + chalk.blue( + `[COMPILE] Found ${compactFiles.length} .compact file(s) to compile`, + ), + ); + + for (const [index, file] of compactFiles.entries()) { + await this.compileFile(file, index, compactFiles.length); + } + } + + /** + * Compiles a single `.compact` file. + * Executes the `compactc` compiler with the provided flags, input file, and output directory. + * + * @param file - Relative path of the `.compact` file to compile (e.g., "test/mock/MockFile.compact") + * @param index - Current file index (0-based) for progress display + * @param total - Total number of files to compile for progress display + * @returns A promise that resolves when the file is compiled successfully + * @throws {Error} If compilation fails + */ + private async compileFile( + file: string, + index: number, + total: number, + ): Promise { + const execAsync = promisify(execCallback); + const inputPath: string = join(SRC_DIR, file); + const outputDir: string = join(ARTIFACTS_DIR, basename(file, '.compact')); + const step: string = `[${index + 1}/${total}]`; + const spinner: Ora = ora( + chalk.blue(`[COMPILE] ${step} Compiling ${file}`), + ).start(); + + try { + const command: string = + `${COMPACTC_PATH} ${this.flags} "${inputPath}" "${outputDir}"`.trim(); + spinner.text = chalk.blue(`[COMPILE] ${step} Running: ${command}`); + const { stdout, stderr }: { stdout: string; stderr: string } = + await execAsync(command); + spinner.succeed(chalk.green(`[COMPILE] ${step} Compiled ${file}`)); + this.printOutput(stdout, chalk.cyan); + this.printOutput(stderr, chalk.yellow); + } catch (error: any) { + spinner.fail(chalk.red(`[COMPILE] ${step} Failed ${file}`)); + this.printOutput(error.stdout, chalk.cyan); + this.printOutput(error.stderr, chalk.red); + throw error; + } + } + + /** + * Prints compiler output with indentation and specified color. + * + * @param output - The compiler output string to print (stdout or stderr) + * @param colorFn - Chalk color function to style the output (e.g., `chalk.cyan` for success, `chalk.red` for errors) + */ + private printOutput( + output: string | undefined, + colorFn: (text: string) => string, + ): void { + if (output) { + const lines: string[] = output + .split('\n') + .filter((line: string): boolean => line.trim() !== '') + .map((line: string): string => ` ${line}`); + // biome-ignore lint/suspicious/noConsoleLog: needed for debugging + console.log(colorFn(lines.join('\n'))); + } + } +} diff --git a/compact/src/run-compactc.cjs b/compact/src/run-compactc.cjs deleted file mode 100755 index dca2891f..00000000 --- a/compact/src/run-compactc.cjs +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env node - -const childProcess = require('node:child_process'); -const path = require('node:path'); - -const [_node, _script, ...args] = process.argv; -const COMPACT_HOME_ENV = process.env.COMPACT_HOME; - -let compactPath; -if (COMPACT_HOME_ENV != null) { - compactPath = COMPACT_HOME_ENV; - console.log( - `COMPACT_HOME env variable is set; using Compact from ${compactPath}`, - ); -} else { - compactPath = path.resolve(__dirname, '..', 'compactc'); - console.log( - `COMPACT_HOME env variable is not set; using fetched compact from ${compactPath}`, - ); -} - -// yarn runs everything with node... -const child = childProcess.spawn(path.resolve(compactPath, 'compactc'), args, { - stdio: 'inherit', -}); -child.on('exit', (code, signal) => { - if (code === 0) { - process.exit(0); - } else { - process.exit(code ?? signal); - } -}); diff --git a/compact/src/runBuilder.ts b/compact/src/runBuilder.ts new file mode 100644 index 00000000..bf55d90d --- /dev/null +++ b/compact/src/runBuilder.ts @@ -0,0 +1,43 @@ +#!/usr/bin/env node + +import chalk from 'chalk'; +import ora from 'ora'; +import { CompactBuilder } from './Builder.js'; + +/** + * Executes the Compact builder CLI. + * Builds projects using the `CompactBuilder` class with provided flags, including compilation and additional steps. + * + * @example + * ```bash + * npx compact-builder --skip-zk + * ``` + * Expected output: + * ``` + * ℹ [BUILD] Compact builder started + * ℹ [COMPILE] COMPACT_HOME: /path/to/compactc + * ℹ [COMPILE] COMPACTC_PATH: /path/to/compactc/compactc + * ℹ [COMPILE] Found 1 .compact file(s) to compile + * ✔ [COMPILE] [1/1] Compiled Foo.compact + * Compactc version: 0.22.0 + * ✔ [BUILD] [1/3] Compiling TypeScript + * ✔ [BUILD] [2/3] Copying artifacts + * ✔ [BUILD] [3/3] Copying and cleaning .compact files + * ``` + */ +async function runBuilder(): Promise { + const spinner = ora(chalk.blue('[BUILD] Compact Builder started')).info(); + + try { + const compilerFlags = process.argv.slice(2).join(' '); + const builder = new CompactBuilder(compilerFlags); + await builder.build(); + } catch (err) { + spinner.fail( + chalk.red('[BUILD] Unexpected error:', (err as Error).message), + ); + process.exit(1); + } +} + +runBuilder(); diff --git a/compact/src/runCompiler.ts b/compact/src/runCompiler.ts new file mode 100644 index 00000000..27de79d0 --- /dev/null +++ b/compact/src/runCompiler.ts @@ -0,0 +1,40 @@ +#!/usr/bin/env node + +import chalk from 'chalk'; +import ora from 'ora'; +import { CompactCompiler } from './Compiler.js'; + +/** + * Executes the Compact compiler CLI. + * Compiles `.compact` files using the `CompactCompiler` class with provided flags. + * + * @example + * ```bash + * npx compact-compiler --skip-zk + * ``` + * Expected output: + * ``` + * ℹ [COMPILE] Compact compiler started + * ℹ [COMPILE] COMPACT_HOME: /path/to/compactc + * ℹ [COMPILE] COMPACTC_PATH: /path/to/compactc/compactc + * ℹ [COMPILE] Found 1 .compact file(s) to compile + * ✔ [COMPILE] [1/1] Compiled Foo.compact + * Compactc version: 0.22.0 + * ``` + */ +async function runCompiler(): Promise { + const spinner = ora(chalk.blue('[COMPILE] Compact Compiler started')).info(); + + try { + const compilerFlags = process.argv.slice(2).join(' '); + const compiler = new CompactCompiler(compilerFlags); + await compiler.compile(); + } catch (err) { + spinner.fail( + chalk.red('[COMPILE] Unexpected error:', (err as Error).message), + ); + process.exit(1); + } +} + +runCompiler(); diff --git a/compact/tsconfig.json b/compact/tsconfig.json new file mode 100644 index 00000000..c81efc66 --- /dev/null +++ b/compact/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "moduleResolution": "node", + "declaration": true, + "sourceMap": true + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] +} From 69e86c141ec0f09c9143deb20f8b153b129f45ed Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 27 Apr 2025 01:48:43 -0500 Subject: [PATCH 018/202] update scripts --- contracts/erc20/package.json | 6 +++--- contracts/utils/package.json | 7 +++++-- package.json | 3 ++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/contracts/erc20/package.json b/contracts/erc20/package.json index c64d5517..922efaa5 100644 --- a/contracts/erc20/package.json +++ b/contracts/erc20/package.json @@ -13,9 +13,9 @@ } }, "scripts": { - "compact": "find src -name '*.compact' -exec sh -c 'run-compactc \"{}\" \"src/artifacts/$(basename \"{}\" .compact)\"' \\;", + "compact": "npx compact-compiler", + "build": "npx compact-builder && tsc", "test": "NODE_OPTIONS=--experimental-vm-modules jest", - "build": "rm -rf dist && tsc --project tsconfig.build.json && cp -Rf ./src/artifacts ./dist/artifacts && cp ./src/ERC20.compact ./dist", "types": "tsc -p tsconfig.json --noEmit", "fmt": "biome format", "fmt:fix": "biome format --write", @@ -24,6 +24,6 @@ "clean": "git clean -fXd" }, "dependencies": { - "@openzeppelin-midnight/utils": "workspace:^" + "@openzeppelin-midnight/compact": "workspace:^" } } diff --git a/contracts/utils/package.json b/contracts/utils/package.json index 8f5ecb26..8389ab1d 100644 --- a/contracts/utils/package.json +++ b/contracts/utils/package.json @@ -13,14 +13,17 @@ } }, "scripts": { - "compact": "find src -name '*.compact' -exec sh -c 'run-compactc \"{}\" \"src/artifacts/$(basename \"{}\" .compact)\"' \\;", + "compact": "npx compact-compiler", + "build": "npx compact-builder && tsc", "test": "NODE_OPTIONS=--experimental-vm-modules jest", - "build": "rm -rf dist && tsc --project tsconfig.build.json && cp -Rf ./src/artifacts ./dist/artifacts && cp ./src/{Initializable,Pausable,Utils}.compact ./dist", "types": "tsc -p tsconfig.json --noEmit", "fmt": "biome format", "fmt:fix": "biome format --write", "lint": "biome lint", "lint:fix": "biome check --write", "clean": "git clean -fXd" + }, + "dependencies": { + "@openzeppelin-midnight/compact": "workspace:^" } } diff --git a/package.json b/package.json index 93bbacc3..0a6f059f 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { "packageManager": "yarn@4.1.0", "workspaces": [ - "compact", + "compact/", "contracts/erc20/", "contracts/utils/" ], "scripts": { + "prepare": "npx tsc -p ./compact && yarn rebuild", "compact": "turbo run compact", "build": "turbo run build", "test": "turbo run test", From 065f48be753b7719ed93def188eef8bb23f94b92 Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 27 Apr 2025 01:49:01 -0500 Subject: [PATCH 019/202] update yarn.lock --- yarn.lock | 1055 +++++++++++++++++------------------------------------ 1 file changed, 329 insertions(+), 726 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4b43ba7e..866086ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26,7 +26,7 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.26.5": +"@babel/compat-data@npm:^7.26.8": version: 7.26.8 resolution: "@babel/compat-data@npm:7.26.8" checksum: 10/bdddf577f670e0e12996ef37e134856c8061032edb71a13418c3d4dae8135da28910b7cd6dec6e668ab3a41e42089ef7ee9c54ef52fe0860b54cb420b0d14948 @@ -34,51 +34,51 @@ __metadata: linkType: hard "@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.23.9": - version: 7.26.9 - resolution: "@babel/core@npm:7.26.9" + version: 7.26.10 + resolution: "@babel/core@npm:7.26.10" dependencies: "@ampproject/remapping": "npm:^2.2.0" "@babel/code-frame": "npm:^7.26.2" - "@babel/generator": "npm:^7.26.9" + "@babel/generator": "npm:^7.26.10" "@babel/helper-compilation-targets": "npm:^7.26.5" "@babel/helper-module-transforms": "npm:^7.26.0" - "@babel/helpers": "npm:^7.26.9" - "@babel/parser": "npm:^7.26.9" + "@babel/helpers": "npm:^7.26.10" + "@babel/parser": "npm:^7.26.10" "@babel/template": "npm:^7.26.9" - "@babel/traverse": "npm:^7.26.9" - "@babel/types": "npm:^7.26.9" + "@babel/traverse": "npm:^7.26.10" + "@babel/types": "npm:^7.26.10" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10/ceed199dbe25f286a0a59a2ea7879aed37c1f3bb289375d061eda4752cab2ba365e7f9e969c7fd3b9b95c930493db6eeb5a6d6f017dd135fb5a4503449aad753 + checksum: 10/68f6707eebd6bb8beed7ceccf5153e35b86c323e40d11d796d75c626ac8f1cc4e1f795584c5ab5f886bc64150c22d5088123d68c069c63f29984c4fc054d1dab languageName: node linkType: hard -"@babel/generator@npm:^7.26.9, @babel/generator@npm:^7.7.2": - version: 7.26.9 - resolution: "@babel/generator@npm:7.26.9" +"@babel/generator@npm:^7.26.10, @babel/generator@npm:^7.27.0, @babel/generator@npm:^7.7.2": + version: 7.27.0 + resolution: "@babel/generator@npm:7.27.0" dependencies: - "@babel/parser": "npm:^7.26.9" - "@babel/types": "npm:^7.26.9" + "@babel/parser": "npm:^7.27.0" + "@babel/types": "npm:^7.27.0" "@jridgewell/gen-mapping": "npm:^0.3.5" "@jridgewell/trace-mapping": "npm:^0.3.25" jsesc: "npm:^3.0.2" - checksum: 10/95075dd6158a49efcc71d7f2c5d20194fcf245348de7723ca35e37cd5800587f1d4de2be6c4ba87b5f5fbb967c052543c109eaab14b43f6a73eb05ccd9a5bb44 + checksum: 10/5447c402b1d841132534a0a9715e89f4f28b6f2886a23e70aaa442150dba4a1e29e4e2351814f439ee1775294dccdef9ab0a4192b6e6a5ad44e24233b3611da2 languageName: node linkType: hard "@babel/helper-compilation-targets@npm:^7.26.5": - version: 7.26.5 - resolution: "@babel/helper-compilation-targets@npm:7.26.5" + version: 7.27.0 + resolution: "@babel/helper-compilation-targets@npm:7.27.0" dependencies: - "@babel/compat-data": "npm:^7.26.5" + "@babel/compat-data": "npm:^7.26.8" "@babel/helper-validator-option": "npm:^7.25.9" browserslist: "npm:^4.24.0" lru-cache: "npm:^5.1.1" semver: "npm:^6.3.1" - checksum: 10/f3b5f0bfcd7b6adf03be1a494b269782531c6e415afab2b958c077d570371cf1bfe001c442508092c50ed3711475f244c05b8f04457d8dea9c34df2b741522bf + checksum: 10/32224b512e813fc808539b4ca7fca8c224849487c365abcef8cb8b0eea635c65375b81429f82d076e9ec1f3f3b3db1d0d56aac4d482a413f58d5ad608f912155 languageName: node linkType: hard @@ -133,24 +133,24 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.26.9": - version: 7.26.9 - resolution: "@babel/helpers@npm:7.26.9" +"@babel/helpers@npm:^7.26.10": + version: 7.27.0 + resolution: "@babel/helpers@npm:7.27.0" dependencies: - "@babel/template": "npm:^7.26.9" - "@babel/types": "npm:^7.26.9" - checksum: 10/267dfa7d04dff7720610497f466aa7b60652b7ec8dde5914527879350c9d655271e892117c5b2f0f083d92d2a8e5e2cf9832d4f98cd7fb72d78f796002af19a1 + "@babel/template": "npm:^7.27.0" + "@babel/types": "npm:^7.27.0" + checksum: 10/0dd40ba1e5ba4b72d1763bb381384585a56f21a61a19dc1b9a03381fe8e840207fdaa4da645d14dc028ad768087d41aad46347cc6573bd69d82f597f5a12dc6f languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.26.9": - version: 7.26.9 - resolution: "@babel/parser@npm:7.26.9" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.26.10, @babel/parser@npm:^7.27.0": + version: 7.27.0 + resolution: "@babel/parser@npm:7.27.0" dependencies: - "@babel/types": "npm:^7.26.9" + "@babel/types": "npm:^7.27.0" bin: parser: ./bin/babel-parser.js - checksum: 10/cb84fe3ba556d6a4360f3373cf7eb0901c46608c8d77330cc1ca021d60f5d6ebb4056a8e7f9dd0ef231923ef1fe69c87b11ce9e160d2252e089a20232a2b942b + checksum: 10/0fee9f05c6db753882ca9d10958301493443da9f6986d7020ebd7a696b35886240016899bc0b47d871aea2abcafd64632343719742e87432c8145e0ec2af2a03 languageName: node linkType: hard @@ -341,39 +341,39 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.26.9, @babel/template@npm:^7.3.3": - version: 7.26.9 - resolution: "@babel/template@npm:7.26.9" +"@babel/template@npm:^7.26.9, @babel/template@npm:^7.27.0, @babel/template@npm:^7.3.3": + version: 7.27.0 + resolution: "@babel/template@npm:7.27.0" dependencies: "@babel/code-frame": "npm:^7.26.2" - "@babel/parser": "npm:^7.26.9" - "@babel/types": "npm:^7.26.9" - checksum: 10/240288cebac95b1cc1cb045ad143365643da0470e905e11731e63280e43480785bd259924f4aea83898ef68e9fa7c176f5f2d1e8b0a059b27966e8ca0b41a1b6 + "@babel/parser": "npm:^7.27.0" + "@babel/types": "npm:^7.27.0" + checksum: 10/7159ca1daea287ad34676d45a7146675444d42c7664aca3e617abc9b1d9548c8f377f35a36bb34cf956e1d3610dcb7acfcfe890aebf81880d35f91a7bd273ee5 languageName: node linkType: hard -"@babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.9": - version: 7.26.9 - resolution: "@babel/traverse@npm:7.26.9" +"@babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.10": + version: 7.27.0 + resolution: "@babel/traverse@npm:7.27.0" dependencies: "@babel/code-frame": "npm:^7.26.2" - "@babel/generator": "npm:^7.26.9" - "@babel/parser": "npm:^7.26.9" - "@babel/template": "npm:^7.26.9" - "@babel/types": "npm:^7.26.9" + "@babel/generator": "npm:^7.27.0" + "@babel/parser": "npm:^7.27.0" + "@babel/template": "npm:^7.27.0" + "@babel/types": "npm:^7.27.0" debug: "npm:^4.3.1" globals: "npm:^11.1.0" - checksum: 10/c16a79522eafa0a7e40eb556bf1e8a3d50dbb0ff943a80f2c06cee2ec7ff87baa0c5d040a5cff574d9bcb3bed05e7d8c6f13b238a931c97267674b02c6cf45b4 + checksum: 10/b0675bc16bd87187e8b090557b0650135de56a621692ad8614b20f32621350ae0fc2e1129b73b780d64a9ed4beab46849a17f90d5267b6ae6ce09ec8412a12c7 languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.9, @babel/types@npm:^7.3.3": - version: 7.26.9 - resolution: "@babel/types@npm:7.26.9" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.0, @babel/types@npm:^7.3.3": + version: 7.27.0 + resolution: "@babel/types@npm:7.27.0" dependencies: "@babel/helper-string-parser": "npm:^7.25.9" "@babel/helper-validator-identifier": "npm:^7.25.9" - checksum: 10/11b62ea7ed64ef7e39cc9b33852c1084064c3b970ae0eaa5db659241cfb776577d1e68cbff4de438bada885d3a827b52cc0f3746112d8e1bc672bb99a8eb5b56 + checksum: 10/2c322bce107c8a534dc4a23be60d570e6a4cc7ca2e44d4f0eee08c0b626104eb7e60ab8de03463bc5da1773a2f69f1e6edec1648d648d65461d6520a7f3b0770 languageName: node linkType: hard @@ -484,73 +484,6 @@ __metadata: languageName: node linkType: hard -"@eslint-community/eslint-utils@npm:^4.2.0": - version: 4.4.1 - resolution: "@eslint-community/eslint-utils@npm:4.4.1" - dependencies: - eslint-visitor-keys: "npm:^3.4.3" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10/ae92a11412674329b4bd38422518601ec9ceae28e251104d1cad83715da9d38e321f68c817c39b64e66d0af7d98df6f9a10ad2dc638911254b47fb8932df00ef - languageName: node - linkType: hard - -"@eslint-community/regexpp@npm:^4.6.1": - version: 4.12.1 - resolution: "@eslint-community/regexpp@npm:4.12.1" - checksum: 10/c08f1dd7dd18fbb60bdd0d85820656d1374dd898af9be7f82cb00451313402a22d5e30569c150315b4385907cdbca78c22389b2a72ab78883b3173be317620cc - languageName: node - linkType: hard - -"@eslint/eslintrc@npm:^2.1.4": - version: 2.1.4 - resolution: "@eslint/eslintrc@npm:2.1.4" - dependencies: - ajv: "npm:^6.12.4" - debug: "npm:^4.3.2" - espree: "npm:^9.6.0" - globals: "npm:^13.19.0" - ignore: "npm:^5.2.0" - import-fresh: "npm:^3.2.1" - js-yaml: "npm:^4.1.0" - minimatch: "npm:^3.1.2" - strip-json-comments: "npm:^3.1.1" - checksum: 10/7a3b14f4b40fc1a22624c3f84d9f467a3d9ea1ca6e9a372116cb92507e485260359465b58e25bcb6c9981b155416b98c9973ad9b796053fd7b3f776a6946bce8 - languageName: node - linkType: hard - -"@eslint/js@npm:8.57.1": - version: 8.57.1 - resolution: "@eslint/js@npm:8.57.1" - checksum: 10/7562b21be10c2adbfa4aa5bb2eccec2cb9ac649a3569560742202c8d1cb6c931ce634937a2f0f551e078403a1c1285d6c2c0aa345dafc986149665cd69fe8b59 - languageName: node - linkType: hard - -"@humanwhocodes/config-array@npm:^0.13.0": - version: 0.13.0 - resolution: "@humanwhocodes/config-array@npm:0.13.0" - dependencies: - "@humanwhocodes/object-schema": "npm:^2.0.3" - debug: "npm:^4.3.1" - minimatch: "npm:^3.0.5" - checksum: 10/524df31e61a85392a2433bf5d03164e03da26c03d009f27852e7dcfdafbc4a23f17f021dacf88e0a7a9fe04ca032017945d19b57a16e2676d9114c22a53a9d11 - languageName: node - linkType: hard - -"@humanwhocodes/module-importer@npm:^1.0.1": - version: 1.0.1 - resolution: "@humanwhocodes/module-importer@npm:1.0.1" - checksum: 10/e993950e346331e5a32eefb27948ecdee2a2c4ab3f072b8f566cd213ef485dd50a3ca497050608db91006f5479e43f91a439aef68d2a313bd3ded06909c7c5b3 - languageName: node - linkType: hard - -"@humanwhocodes/object-schema@npm:^2.0.3": - version: 2.0.3 - resolution: "@humanwhocodes/object-schema@npm:2.0.3" - checksum: 10/05bb99ed06c16408a45a833f03a732f59bf6184795d4efadd33238ff8699190a8c871ad1121241bb6501589a9598dc83bf25b99dcbcf41e155cdf36e35e937a3 - languageName: node - linkType: hard - "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -887,18 +820,6 @@ __metadata: languageName: node linkType: hard -"@midnight-ntwrk/compact@workspace:compact": - version: 0.0.0-use.local - resolution: "@midnight-ntwrk/compact@workspace:compact" - dependencies: - eslint: "npm:^8.52.0" - ts-node: "npm:^10.9.2" - typescript: "npm:^5.2.2" - bin: - run-compactc: src/run-compactc.cjs - languageName: unknown - linkType: soft - "@midnight-ntwrk/ledger@npm:^3.0.6": version: 3.0.6 resolution: "@midnight-ntwrk/ledger@npm:3.0.6" @@ -920,33 +841,6 @@ __metadata: languageName: node linkType: hard -"@nodelib/fs.scandir@npm:2.1.5": - version: 2.1.5 - resolution: "@nodelib/fs.scandir@npm:2.1.5" - dependencies: - "@nodelib/fs.stat": "npm:2.0.5" - run-parallel: "npm:^1.1.9" - checksum: 10/6ab2a9b8a1d67b067922c36f259e3b3dfd6b97b219c540877a4944549a4d49ea5ceba5663905ab5289682f1f3c15ff441d02f0447f620a42e1cb5e1937174d4b - languageName: node - linkType: hard - -"@nodelib/fs.stat@npm:2.0.5": - version: 2.0.5 - resolution: "@nodelib/fs.stat@npm:2.0.5" - checksum: 10/012480b5ca9d97bff9261571dbbec7bbc6033f69cc92908bc1ecfad0792361a5a1994bc48674b9ef76419d056a03efadfce5a6cf6dbc0a36559571a7a483f6f0 - languageName: node - linkType: hard - -"@nodelib/fs.walk@npm:^1.2.8": - version: 1.2.8 - resolution: "@nodelib/fs.walk@npm:1.2.8" - dependencies: - "@nodelib/fs.scandir": "npm:2.1.5" - fastq: "npm:^1.6.0" - checksum: 10/40033e33e96e97d77fba5a238e4bba4487b8284678906a9f616b5579ddaf868a18874c0054a75402c9fbaaa033a25ceae093af58c9c30278e35c23c9479e79b0 - languageName: node - linkType: hard - "@npmcli/agent@npm:^3.0.0": version: 3.0.0 resolution: "@npmcli/agent@npm:3.0.0" @@ -969,17 +863,39 @@ __metadata: languageName: node linkType: hard +"@openzeppelin-midnight/compact@workspace:^, @openzeppelin-midnight/compact@workspace:compact": + version: 0.0.0-use.local + resolution: "@openzeppelin-midnight/compact@workspace:compact" + dependencies: + "@types/jest": "npm:^29.5.6" + "@types/node": "npm:^22.13.10" + chalk: "npm:^5.4.1" + fast-check: "npm:^3.15.0" + jest: "npm:^29.7.0" + jest-fast-check: "npm:^2.0.0" + log-symbols: "npm:^7.0.0" + ora: "npm:^8.2.0" + ts-jest: "npm:^29.1.1" + typescript: "npm:^5.8.2" + bin: + compact-builder: dist/runBuilder.js + compact-compiler: dist/runCompiler.js + languageName: unknown + linkType: soft + "@openzeppelin-midnight/erc20@workspace:contracts/erc20": version: 0.0.0-use.local resolution: "@openzeppelin-midnight/erc20@workspace:contracts/erc20" dependencies: - "@openzeppelin-midnight/utils": "workspace:^" + "@openzeppelin-midnight/compact": "workspace:^" languageName: unknown linkType: soft -"@openzeppelin-midnight/utils@workspace:^, @openzeppelin-midnight/utils@workspace:contracts/utils": +"@openzeppelin-midnight/utils@workspace:contracts/utils": version: 0.0.0-use.local resolution: "@openzeppelin-midnight/utils@workspace:contracts/utils" + dependencies: + "@openzeppelin-midnight/compact": "workspace:^" languageName: unknown linkType: soft @@ -1057,11 +973,11 @@ __metadata: linkType: hard "@types/babel__generator@npm:*": - version: 7.6.8 - resolution: "@types/babel__generator@npm:7.6.8" + version: 7.27.0 + resolution: "@types/babel__generator@npm:7.27.0" dependencies: "@babel/types": "npm:^7.0.0" - checksum: 10/b53c215e9074c69d212402990b0ca8fa57595d09e10d94bda3130aa22b55d796e50449199867879e4ea0ee968f3a2099e009cfb21a726a53324483abbf25cd30 + checksum: 10/f572e67a9a39397664350a4437d8a7fbd34acc83ff4887a8cf08349e39f8aeb5ad2f70fb78a0a0a23a280affe3a5f4c25f50966abdce292bcf31237af1c27b1a languageName: node linkType: hard @@ -1076,11 +992,11 @@ __metadata: linkType: hard "@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6": - version: 7.20.6 - resolution: "@types/babel__traverse@npm:7.20.6" + version: 7.20.7 + resolution: "@types/babel__traverse@npm:7.20.7" dependencies: "@babel/types": "npm:^7.20.7" - checksum: 10/63d13a3789aa1e783b87a8b03d9fb2c2c90078de7782422feff1631b8c2a25db626e63a63ac5a1465d47359201c73069dacb4b52149d17c568187625da3064ae + checksum: 10/d005b58e1c26bdafc1ce564f60db0ee938393c7fc586b1197bdb71a02f7f33f72bc10ae4165776b6cafc77c4b6f2e1a164dd20bc36518c471b1131b153b4baa6 languageName: node linkType: hard @@ -1128,21 +1044,21 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*": - version: 22.13.8 - resolution: "@types/node@npm:22.13.8" +"@types/node@npm:*, @types/node@npm:^22.13.10": + version: 22.15.2 + resolution: "@types/node@npm:22.15.2" dependencies: - undici-types: "npm:~6.20.0" - checksum: 10/b69de3caab80336747bf41b5063478d23b196b9594c6f2eb819791380cc571676087dceb0fde9531ef7a1293f3eae12a9aaf79d8de349378c29a17c5e657bc78 + undici-types: "npm:~6.21.0" + checksum: 10/e22071571205413518aa3710644ed9603d8f4a417fc59f0e180240e1c05aaf7fb8feecdf553a2da305247b3533d03b58eab6e333115f01f581b9139a6b1dcd47 languageName: node linkType: hard "@types/node@npm:^18.18.6": - version: 18.19.78 - resolution: "@types/node@npm:18.19.78" + version: 18.19.87 + resolution: "@types/node@npm:18.19.87" dependencies: undici-types: "npm:~5.26.4" - checksum: 10/c9b285b965c054d4ffd511fd31c18b3d1512dfdf2af0f0340b19a225716aedc2a26d87dadc2dd27b33e2fa6cbcdacff2bb8bf70f91978b2ef3b18dfa327afdfb + checksum: 10/1e71b6d16dedeaa1fd5ff55baf1f353ca1f9e673b2e482d7fe82fa685addea5159a36602a344784c989b5e07ca1be633d0c493adf5951dee5a29cee69d613e7f languageName: node linkType: hard @@ -1176,26 +1092,10 @@ __metadata: languageName: node linkType: hard -"@ungap/structured-clone@npm:^1.2.0": - version: 1.3.0 - resolution: "@ungap/structured-clone@npm:1.3.0" - checksum: 10/80d6910946f2b1552a2406650051c91bbd1f24a6bf854354203d84fe2714b3e8ce4618f49cc3410494173a1c1e8e9777372fe68dce74bd45faf0a7a1a6ccf448 - languageName: node - linkType: hard - "abbrev@npm:^3.0.0": - version: 3.0.0 - resolution: "abbrev@npm:3.0.0" - checksum: 10/2ceee14efdeda42ef7355178c1069499f183546ff7112b3efe79c1edef09d20ad9c17939752215fb8f7fcf48d10e6a7c0aa00136dc9cf4d293d963718bb1d200 - languageName: node - linkType: hard - -"acorn-jsx@npm:^5.3.2": - version: 5.3.2 - resolution: "acorn-jsx@npm:5.3.2" - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 10/d4371eaef7995530b5b5ca4183ff6f062ca17901a6d3f673c9ac011b01ede37e7a1f7f61f8f5cfe709e88054757bb8f3277dc4061087cdf4f2a1f90ccbcdb977 + version: 3.0.1 + resolution: "abbrev@npm:3.0.1" + checksum: 10/ebd2c149dda6f543b66ce3779ea612151bb3aa9d0824f169773ee9876f1ca5a4e0adbcccc7eed048c04da7998e1825e2aa76fcca92d9e67dea50ac2b0a58dc2e languageName: node linkType: hard @@ -1208,12 +1108,12 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.11.0, acorn@npm:^8.4.1, acorn@npm:^8.9.0": - version: 8.14.0 - resolution: "acorn@npm:8.14.0" +"acorn@npm:^8.11.0, acorn@npm:^8.4.1": + version: 8.14.1 + resolution: "acorn@npm:8.14.1" bin: acorn: bin/acorn - checksum: 10/6df29c35556782ca9e632db461a7f97947772c6c1d5438a81f0c873a3da3a792487e83e404d1c6c25f70513e91aa18745f6eafb1fcc3a43ecd1920b21dd173d2 + checksum: 10/d1379bbee224e8d44c3c3946e6ba6973e999fbdd4e22e41c3455d7f9b6f72f7ce18d3dc218002e1e48eea789539cf1cb6d1430c81838c6744799c712fb557d92 languageName: node linkType: hard @@ -1224,18 +1124,6 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.12.4": - version: 6.12.6 - resolution: "ajv@npm:6.12.6" - dependencies: - fast-deep-equal: "npm:^3.1.1" - fast-json-stable-stringify: "npm:^2.0.0" - json-schema-traverse: "npm:^0.4.1" - uri-js: "npm:^4.2.2" - checksum: 10/48d6ad21138d12eb4d16d878d630079a2bda25a04e745c07846a4ad768319533031e28872a9b3c5790fa1ec41aabdf2abed30a56e5a03ebc2cf92184b8ee306c - languageName: node - linkType: hard - "ansi-escapes@npm:^4.2.1": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" @@ -1308,13 +1196,6 @@ __metadata: languageName: node linkType: hard -"argparse@npm:^2.0.1": - version: 2.0.1 - resolution: "argparse@npm:2.0.1" - checksum: 10/18640244e641a417ec75a9bd38b0b2b6b95af5199aa241b131d4b2fb206f334d7ecc600bd194861610a5579084978bfcbb02baa399dbe442d56d0ae5e60dbaef - languageName: node - linkType: hard - "async@npm:^3.2.3": version: 3.2.6 resolution: "async@npm:3.2.6" @@ -1517,9 +1398,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.30001688": - version: 1.0.30001701 - resolution: "caniuse-lite@npm:1.0.30001701" - checksum: 10/d121607a96f9165128203a317d6aee6a4c7808d52a1f3b46ef5fb918abe9e9d4463e57b0bd5ffe2f4316292bd5b8d85a832b4456b7ca6f024f377b498911bfec + version: 1.0.30001715 + resolution: "caniuse-lite@npm:1.0.30001715" + checksum: 10/5608cdaf609eb5fe3a86ab6c1c2f3943dbdab813041725f4747f5432b05e6e19fc606faa8a9b75c329b37b772c91c47e8db483e76a6b715b59c289ce53dcba68 languageName: node linkType: hard @@ -1533,6 +1414,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^5.3.0, chalk@npm:^5.4.1": + version: 5.4.1 + resolution: "chalk@npm:5.4.1" + checksum: 10/29df3ffcdf25656fed6e95962e2ef86d14dfe03cd50e7074b06bad9ffbbf6089adbb40f75c00744d843685c8d008adaf3aed31476780312553caf07fa86e5bc7 + languageName: node + linkType: hard + "char-regex@npm:^1.0.2": version: 1.0.2 resolution: "char-regex@npm:1.0.2" @@ -1561,6 +1449,22 @@ __metadata: languageName: node linkType: hard +"cli-cursor@npm:^5.0.0": + version: 5.0.0 + resolution: "cli-cursor@npm:5.0.0" + dependencies: + restore-cursor: "npm:^5.0.0" + checksum: 10/1eb9a3f878b31addfe8d82c6d915ec2330cec8447ab1f117f4aa34f0137fbb3137ec3466e1c9a65bcb7557f6e486d343f2da57f253a2f668d691372dfa15c090 + languageName: node + linkType: hard + +"cli-spinners@npm:^2.9.2": + version: 2.9.2 + resolution: "cli-spinners@npm:2.9.2" + checksum: 10/a0a863f442df35ed7294424f5491fa1756bd8d2e4ff0c8736531d886cec0ece4d85e8663b77a5afaf1d296e3cbbebff92e2e99f52bbea89b667cbe789b994794 + languageName: node + linkType: hard + "cliui@npm:^8.0.1": version: 8.0.1 resolution: "cliui@npm:8.0.1" @@ -1640,7 +1544,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": +"cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -1651,7 +1555,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.4": version: 4.4.0 resolution: "debug@npm:4.4.0" dependencies: @@ -1675,13 +1579,6 @@ __metadata: languageName: node linkType: hard -"deep-is@npm:^0.1.3": - version: 0.1.4 - resolution: "deep-is@npm:0.1.4" - checksum: 10/ec12d074aef5ae5e81fa470b9317c313142c9e8e2afe3f8efa124db309720db96d1d222b82b84c834e5f87e7a614b44a4684b6683583118b87c833b3be40d4d8 - languageName: node - linkType: hard - "deepmerge@npm:^4.2.2": version: 4.3.1 resolution: "deepmerge@npm:4.3.1" @@ -1710,15 +1607,6 @@ __metadata: languageName: node linkType: hard -"doctrine@npm:^3.0.0": - version: 3.0.0 - resolution: "doctrine@npm:3.0.0" - dependencies: - esutils: "npm:^2.0.2" - checksum: 10/b4b28f1df5c563f7d876e7461254a4597b8cabe915abe94d7c5d1633fed263fcf9a85e8d3836591fc2d040108e822b0d32758e5ec1fe31c590dc7e08086e3e48 - languageName: node - linkType: hard - "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0" @@ -1738,9 +1626,9 @@ __metadata: linkType: hard "electron-to-chromium@npm:^1.5.73": - version: 1.5.109 - resolution: "electron-to-chromium@npm:1.5.109" - checksum: 10/4f6bd5963a2a55cbff97b2374cb0dbd6141f85e5cf8cb07267d91b0e56f3a4c8df72a7be905ddb1770b9277deef207567e97f94b9385c7cba3775620af17a932 + version: 1.5.143 + resolution: "electron-to-chromium@npm:1.5.143" + checksum: 10/91a7980f96da5ad33b77e95c7e628f468e6bb53eac41437612882810a7552514132728f7c34ee5c967c599557af491fcc4f75e9138f82d22c1b1cdfc63fb8d6b languageName: node linkType: hard @@ -1751,6 +1639,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^10.3.0": + version: 10.4.0 + resolution: "emoji-regex@npm:10.4.0" + checksum: 10/76bb92c5bcf0b6980d37e535156231e4a9d0aa6ab3b9f5eabf7690231d5aa5d5b8e516f36e6804cbdd0f1c23dfef2a60c40ab7bb8aedd890584281a565b97c50 + languageName: node + linkType: hard + "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -1811,89 +1706,6 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:^4.0.0": - version: 4.0.0 - resolution: "escape-string-regexp@npm:4.0.0" - checksum: 10/98b48897d93060f2322108bf29db0feba7dd774be96cd069458d1453347b25ce8682ecc39859d4bca2203cc0ab19c237bcc71755eff49a0f8d90beadeeba5cc5 - languageName: node - linkType: hard - -"eslint-scope@npm:^7.2.2": - version: 7.2.2 - resolution: "eslint-scope@npm:7.2.2" - dependencies: - esrecurse: "npm:^4.3.0" - estraverse: "npm:^5.2.0" - checksum: 10/5c660fb905d5883ad018a6fea2b49f3cb5b1cbf2cd4bd08e98646e9864f9bc2c74c0839bed2d292e90a4a328833accc197c8f0baed89cbe8d605d6f918465491 - languageName: node - linkType: hard - -"eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3": - version: 3.4.3 - resolution: "eslint-visitor-keys@npm:3.4.3" - checksum: 10/3f357c554a9ea794b094a09bd4187e5eacd1bc0d0653c3adeb87962c548e6a1ab8f982b86963ae1337f5d976004146536dcee5d0e2806665b193fbfbf1a9231b - languageName: node - linkType: hard - -"eslint@npm:^8.52.0": - version: 8.57.1 - resolution: "eslint@npm:8.57.1" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.2.0" - "@eslint-community/regexpp": "npm:^4.6.1" - "@eslint/eslintrc": "npm:^2.1.4" - "@eslint/js": "npm:8.57.1" - "@humanwhocodes/config-array": "npm:^0.13.0" - "@humanwhocodes/module-importer": "npm:^1.0.1" - "@nodelib/fs.walk": "npm:^1.2.8" - "@ungap/structured-clone": "npm:^1.2.0" - ajv: "npm:^6.12.4" - chalk: "npm:^4.0.0" - cross-spawn: "npm:^7.0.2" - debug: "npm:^4.3.2" - doctrine: "npm:^3.0.0" - escape-string-regexp: "npm:^4.0.0" - eslint-scope: "npm:^7.2.2" - eslint-visitor-keys: "npm:^3.4.3" - espree: "npm:^9.6.1" - esquery: "npm:^1.4.2" - esutils: "npm:^2.0.2" - fast-deep-equal: "npm:^3.1.3" - file-entry-cache: "npm:^6.0.1" - find-up: "npm:^5.0.0" - glob-parent: "npm:^6.0.2" - globals: "npm:^13.19.0" - graphemer: "npm:^1.4.0" - ignore: "npm:^5.2.0" - imurmurhash: "npm:^0.1.4" - is-glob: "npm:^4.0.0" - is-path-inside: "npm:^3.0.3" - js-yaml: "npm:^4.1.0" - json-stable-stringify-without-jsonify: "npm:^1.0.1" - levn: "npm:^0.4.1" - lodash.merge: "npm:^4.6.2" - minimatch: "npm:^3.1.2" - natural-compare: "npm:^1.4.0" - optionator: "npm:^0.9.3" - strip-ansi: "npm:^6.0.1" - text-table: "npm:^0.2.0" - bin: - eslint: bin/eslint.js - checksum: 10/5504fa24879afdd9f9929b2fbfc2ee9b9441a3d464efd9790fbda5f05738858530182029f13323add68d19fec749d3ab4a70320ded091ca4432b1e9cc4ed104c - languageName: node - linkType: hard - -"espree@npm:^9.6.0, espree@npm:^9.6.1": - version: 9.6.1 - resolution: "espree@npm:9.6.1" - dependencies: - acorn: "npm:^8.9.0" - acorn-jsx: "npm:^5.3.2" - eslint-visitor-keys: "npm:^3.4.1" - checksum: 10/255ab260f0d711a54096bdeda93adff0eadf02a6f9b92f02b323e83a2b7fc258797919437ad331efec3930475feb0142c5ecaaf3cdab4befebd336d47d3f3134 - languageName: node - linkType: hard - "esprima@npm:^4.0.0": version: 4.0.1 resolution: "esprima@npm:4.0.1" @@ -1904,38 +1716,6 @@ __metadata: languageName: node linkType: hard -"esquery@npm:^1.4.2": - version: 1.6.0 - resolution: "esquery@npm:1.6.0" - dependencies: - estraverse: "npm:^5.1.0" - checksum: 10/c587fb8ec9ed83f2b1bc97cf2f6854cc30bf784a79d62ba08c6e358bf22280d69aee12827521cf38e69ae9761d23fb7fde593ce315610f85655c139d99b05e5a - languageName: node - linkType: hard - -"esrecurse@npm:^4.3.0": - version: 4.3.0 - resolution: "esrecurse@npm:4.3.0" - dependencies: - estraverse: "npm:^5.2.0" - checksum: 10/44ffcd89e714ea6b30143e7f119b104fc4d75e77ee913f34d59076b40ef2d21967f84e019f84e1fd0465b42cdbf725db449f232b5e47f29df29ed76194db8e16 - languageName: node - linkType: hard - -"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0": - version: 5.3.0 - resolution: "estraverse@npm:5.3.0" - checksum: 10/37cbe6e9a68014d34dbdc039f90d0baf72436809d02edffcc06ba3c2a12eb298048f877511353b130153e532aac8d68ba78430c0dd2f44806ebc7c014b01585e - languageName: node - linkType: hard - -"esutils@npm:^2.0.2": - version: 2.0.3 - resolution: "esutils@npm:2.0.3" - checksum: 10/b23acd24791db11d8f65be5ea58fd9a6ce2df5120ae2da65c16cfc5331ff59d5ac4ef50af66cd4bde238881503ec839928a0135b99a036a9cdfa22d17fd56cdb - languageName: node - linkType: hard - "execa@npm:^5.0.0": version: 5.1.1 resolution: "execa@npm:5.1.1" @@ -1989,36 +1769,13 @@ __metadata: languageName: node linkType: hard -"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": - version: 3.1.3 - resolution: "fast-deep-equal@npm:3.1.3" - checksum: 10/e21a9d8d84f53493b6aa15efc9cfd53dd5b714a1f23f67fb5dc8f574af80df889b3bce25dc081887c6d25457cce704e636395333abad896ccdec03abaf1f3f9d - languageName: node - linkType: hard - -"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": +"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.1.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" checksum: 10/2c20055c1fa43c922428f16ca8bb29f2807de63e5c851f665f7ac9790176c01c3b40335257736b299764a8d383388dabc73c8083b8e1bc3d99f0a941444ec60e languageName: node linkType: hard -"fast-levenshtein@npm:^2.0.6": - version: 2.0.6 - resolution: "fast-levenshtein@npm:2.0.6" - checksum: 10/eb7e220ecf2bab5159d157350b81d01f75726a4382f5a9266f42b9150c4523b9795f7f5d9fbbbeaeac09a441b2369f05ee02db48ea938584205530fe5693cfe1 - languageName: node - linkType: hard - -"fastq@npm:^1.6.0": - version: 1.19.1 - resolution: "fastq@npm:1.19.1" - dependencies: - reusify: "npm:^1.0.4" - checksum: 10/75679dc226316341c4f2a6b618571f51eac96779906faecd8921b984e844d6ae42fabb2df69b1071327d398d5716693ea9c9c8941f64ac9e89ec2032ce59d730 - languageName: node - linkType: hard - "fb-watchman@npm:^2.0.0": version: 2.0.2 resolution: "fb-watchman@npm:2.0.2" @@ -2028,12 +1785,15 @@ __metadata: languageName: node linkType: hard -"file-entry-cache@npm:^6.0.1": - version: 6.0.1 - resolution: "file-entry-cache@npm:6.0.1" - dependencies: - flat-cache: "npm:^3.0.4" - checksum: 10/099bb9d4ab332cb93c48b14807a6918a1da87c45dce91d4b61fd40e6505d56d0697da060cb901c729c90487067d93c9243f5da3dc9c41f0358483bfdebca736b +"fdir@npm:^6.4.4": + version: 6.4.4 + resolution: "fdir@npm:6.4.4" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10/d0000d6b790059b35f4ed19acc8847a66452e0bc68b28766c929ffd523e5ec2083811fc8a545e4a1d4945ce70e887b3a610c145c681073b506143ae3076342ed languageName: node linkType: hard @@ -2065,34 +1825,6 @@ __metadata: languageName: node linkType: hard -"find-up@npm:^5.0.0": - version: 5.0.0 - resolution: "find-up@npm:5.0.0" - dependencies: - locate-path: "npm:^6.0.0" - path-exists: "npm:^4.0.0" - checksum: 10/07955e357348f34660bde7920783204ff5a26ac2cafcaa28bace494027158a97b9f56faaf2d89a6106211a8174db650dd9f503f9c0d526b1202d5554a00b9095 - languageName: node - linkType: hard - -"flat-cache@npm:^3.0.4": - version: 3.2.0 - resolution: "flat-cache@npm:3.2.0" - dependencies: - flatted: "npm:^3.2.9" - keyv: "npm:^4.5.3" - rimraf: "npm:^3.0.2" - checksum: 10/02381c6ece5e9fa5b826c9bbea481d7fd77645d96e4b0b1395238124d581d10e56f17f723d897b6d133970f7a57f0fab9148cbbb67237a0a0ffe794ba60c0c70 - languageName: node - linkType: hard - -"flatted@npm:^3.2.9": - version: 3.3.3 - resolution: "flatted@npm:3.3.3" - checksum: 10/8c96c02fbeadcf4e8ffd0fa24983241e27698b0781295622591fc13585e2f226609d95e422bcf2ef044146ffacb6b68b1f20871454eddf75ab3caa6ee5f4a1fe - languageName: node - linkType: hard - "foreground-child@npm:^3.1.0": version: 3.3.1 resolution: "foreground-child@npm:3.3.1" @@ -2159,6 +1891,13 @@ __metadata: languageName: node linkType: hard +"get-east-asian-width@npm:^1.0.0": + version: 1.3.0 + resolution: "get-east-asian-width@npm:1.3.0" + checksum: 10/8e8e779eb28701db7fdb1c8cab879e39e6ae23f52dadd89c8aed05869671cee611a65d4f8557b83e981428623247d8bc5d0c7a4ef3ea7a41d826e73600112ad8 + languageName: node + linkType: hard + "get-package-type@npm:^0.1.0": version: 0.1.0 resolution: "get-package-type@npm:0.1.0" @@ -2173,16 +1912,7 @@ __metadata: languageName: node linkType: hard -"glob-parent@npm:^6.0.2": - version: 6.0.2 - resolution: "glob-parent@npm:6.0.2" - dependencies: - is-glob: "npm:^4.0.3" - checksum: 10/c13ee97978bef4f55106b71e66428eb1512e71a7466ba49025fc2aec59a5bfb0954d5abd58fc5ee6c9b076eef4e1f6d3375c2e964b88466ca390da4419a786a8 - languageName: node - linkType: hard - -"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7": +"glob@npm:^10.2.2": version: 10.4.5 resolution: "glob@npm:10.4.5" dependencies: @@ -2219,15 +1949,6 @@ __metadata: languageName: node linkType: hard -"globals@npm:^13.19.0": - version: 13.24.0 - resolution: "globals@npm:13.24.0" - dependencies: - type-fest: "npm:^0.20.2" - checksum: 10/62c5b1997d06674fc7191d3e01e324d3eda4d65ac9cc4e78329fa3b5c4fd42a0e1c8722822497a6964eee075255ce21ccf1eec2d83f92ef3f06653af4d0ee28e - languageName: node - linkType: hard - "graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" @@ -2235,13 +1956,6 @@ __metadata: languageName: node linkType: hard -"graphemer@npm:^1.4.0": - version: 1.4.0 - resolution: "graphemer@npm:1.4.0" - checksum: 10/6dd60dba97007b21e3a829fab3f771803cc1292977fe610e240ea72afd67e5690ac9eeaafc4a99710e78962e5936ab5a460787c2a1180f1cb0ccfac37d29f897 - languageName: node - linkType: hard - "has-flag@npm:^4.0.0": version: 4.0.0 resolution: "has-flag@npm:4.0.0" @@ -2308,23 +2022,6 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^5.2.0": - version: 5.3.2 - resolution: "ignore@npm:5.3.2" - checksum: 10/cceb6a457000f8f6a50e1196429750d782afce5680dd878aa4221bd79972d68b3a55b4b1458fc682be978f4d3c6a249046aa0880637367216444ab7b014cfc98 - languageName: node - linkType: hard - -"import-fresh@npm:^3.2.1": - version: 3.3.1 - resolution: "import-fresh@npm:3.3.1" - dependencies: - parent-module: "npm:^1.0.0" - resolve-from: "npm:^4.0.0" - checksum: 10/a06b19461b4879cc654d46f8a6244eb55eb053437afd4cbb6613cad6be203811849ed3e4ea038783092879487299fda24af932b86bdfff67c9055ba3612b8c87 - languageName: node - linkType: hard - "import-local@npm:^3.0.2": version: 3.2.0 resolution: "import-local@npm:3.2.0" @@ -2387,13 +2084,6 @@ __metadata: languageName: node linkType: hard -"is-extglob@npm:^2.1.1": - version: 2.1.1 - resolution: "is-extglob@npm:2.1.1" - checksum: 10/df033653d06d0eb567461e58a7a8c9f940bd8c22274b94bf7671ab36df5719791aae15eef6d83bbb5e23283967f2f984b8914559d4449efda578c775c4be6f85 - languageName: node - linkType: hard - "is-fullwidth-code-point@npm:^3.0.0": version: 3.0.0 resolution: "is-fullwidth-code-point@npm:3.0.0" @@ -2408,12 +2098,10 @@ __metadata: languageName: node linkType: hard -"is-glob@npm:^4.0.0, is-glob@npm:^4.0.3": - version: 4.0.3 - resolution: "is-glob@npm:4.0.3" - dependencies: - is-extglob: "npm:^2.1.1" - checksum: 10/3ed74f2b0cdf4f401f38edb0442ddfde3092d79d7d35c9919c86641efdbcbb32e45aa3c0f70ce5eecc946896cd5a0f26e4188b9f2b881876f7cb6c505b82da11 +"is-interactive@npm:^2.0.0": + version: 2.0.0 + resolution: "is-interactive@npm:2.0.0" + checksum: 10/e8d52ad490bed7ae665032c7675ec07732bbfe25808b0efbc4d5a76b1a1f01c165f332775c63e25e9a03d319ebb6b24f571a9e902669fc1e40b0a60b5be6e26c languageName: node linkType: hard @@ -2424,13 +2112,6 @@ __metadata: languageName: node linkType: hard -"is-path-inside@npm:^3.0.3": - version: 3.0.3 - resolution: "is-path-inside@npm:3.0.3" - checksum: 10/abd50f06186a052b349c15e55b182326f1936c89a78bf6c8f2b707412517c097ce04bc49a0ca221787bc44e1049f51f09a2ffb63d22899051988d3a618ba13e9 - languageName: node - linkType: hard - "is-stream@npm:^2.0.0": version: 2.0.1 resolution: "is-stream@npm:2.0.1" @@ -2438,6 +2119,20 @@ __metadata: languageName: node linkType: hard +"is-unicode-supported@npm:^1.3.0": + version: 1.3.0 + resolution: "is-unicode-supported@npm:1.3.0" + checksum: 10/20a1fc161afafaf49243551a5ac33b6c4cf0bbcce369fcd8f2951fbdd000c30698ce320de3ee6830497310a8f41880f8066d440aa3eb0a853e2aa4836dd89abc + languageName: node + linkType: hard + +"is-unicode-supported@npm:^2.0.0": + version: 2.1.0 + resolution: "is-unicode-supported@npm:2.1.0" + checksum: 10/f254e3da6b0ab1a57a94f7273a7798dd35d1d45b227759f600d0fa9d5649f9c07fa8d3c8a6360b0e376adf916d151ec24fc9a50c5295c58bae7ca54a76a063f9 + languageName: node + linkType: hard + "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -3012,17 +2707,6 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:^4.1.0": - version: 4.1.0 - resolution: "js-yaml@npm:4.1.0" - dependencies: - argparse: "npm:^2.0.1" - bin: - js-yaml: bin/js-yaml.js - checksum: 10/c138a34a3fd0d08ebaf71273ad4465569a483b8a639e0b118ff65698d257c2791d3199e3f303631f2cb98213fa7b5f5d6a4621fd0fff819421b990d30d967140 - languageName: node - linkType: hard - "jsbn@npm:1.1.0": version: 1.1.0 resolution: "jsbn@npm:1.1.0" @@ -3039,13 +2723,6 @@ __metadata: languageName: node linkType: hard -"json-buffer@npm:3.0.1": - version: 3.0.1 - resolution: "json-buffer@npm:3.0.1" - checksum: 10/82876154521b7b68ba71c4f969b91572d1beabadd87bd3a6b236f85fbc7dc4695089191ed60bb59f9340993c51b33d479f45b6ba9f3548beb519705281c32c3c - languageName: node - linkType: hard - "json-parse-even-better-errors@npm:^2.3.0": version: 2.3.1 resolution: "json-parse-even-better-errors@npm:2.3.1" @@ -3053,20 +2730,6 @@ __metadata: languageName: node linkType: hard -"json-schema-traverse@npm:^0.4.1": - version: 0.4.1 - resolution: "json-schema-traverse@npm:0.4.1" - checksum: 10/7486074d3ba247769fda17d5181b345c9fb7d12e0da98b22d1d71a5db9698d8b4bd900a3ec1a4ffdd60846fc2556274a5c894d0c48795f14cb03aeae7b55260b - languageName: node - linkType: hard - -"json-stable-stringify-without-jsonify@npm:^1.0.1": - version: 1.0.1 - resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" - checksum: 10/12786c2e2f22c27439e6db0532ba321f1d0617c27ad8cb1c352a0e9249a50182fd1ba8b52a18899291604b0c32eafa8afd09e51203f19109a0537f68db2b652d - languageName: node - linkType: hard - "json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" @@ -3076,15 +2739,6 @@ __metadata: languageName: node linkType: hard -"keyv@npm:^4.5.3": - version: 4.5.4 - resolution: "keyv@npm:4.5.4" - dependencies: - json-buffer: "npm:3.0.1" - checksum: 10/167eb6ef64cc84b6fa0780ee50c9de456b422a1e18802209234f7c2cf7eae648c7741f32e50d7e24ccb22b24c13154070b01563d642755b156c357431a191e75 - languageName: node - linkType: hard - "kleur@npm:^3.0.3": version: 3.0.3 resolution: "kleur@npm:3.0.3" @@ -3099,16 +2753,6 @@ __metadata: languageName: node linkType: hard -"levn@npm:^0.4.1": - version: 0.4.1 - resolution: "levn@npm:0.4.1" - dependencies: - prelude-ls: "npm:^1.2.1" - type-check: "npm:~0.4.0" - checksum: 10/2e4720ff79f21ae08d42374b0a5c2f664c5be8b6c8f565bb4e1315c96ed3a8acaa9de788ffed82d7f2378cf36958573de07ef92336cb5255ed74d08b8318c9ee - languageName: node - linkType: hard - "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -3125,15 +2769,6 @@ __metadata: languageName: node linkType: hard -"locate-path@npm:^6.0.0": - version: 6.0.0 - resolution: "locate-path@npm:6.0.0" - dependencies: - p-locate: "npm:^5.0.0" - checksum: 10/72eb661788a0368c099a184c59d2fee760b3831c9c1c33955e8a19ae4a21b4116e53fa736dc086cdeb9fce9f7cc508f2f92d2d3aae516f133e16a2bb59a39f5a - languageName: node - linkType: hard - "lodash.memoize@npm:^4.1.2": version: 4.1.2 resolution: "lodash.memoize@npm:4.1.2" @@ -3141,10 +2776,23 @@ __metadata: languageName: node linkType: hard -"lodash.merge@npm:^4.6.2": - version: 4.6.2 - resolution: "lodash.merge@npm:4.6.2" - checksum: 10/d0ea2dd0097e6201be083865d50c3fb54fbfbdb247d9cc5950e086c991f448b7ab0cdab0d57eacccb43473d3f2acd21e134db39f22dac2d6c9ba6bf26978e3d6 +"log-symbols@npm:^6.0.0": + version: 6.0.0 + resolution: "log-symbols@npm:6.0.0" + dependencies: + chalk: "npm:^5.3.0" + is-unicode-supported: "npm:^1.3.0" + checksum: 10/510cdda36700cbcd87a2a691ea08d310a6c6b449084018f7f2ec4f732ca5e51b301ff1327aadd96f53c08318e616276c65f7fe22f2a16704fb0715d788bc3c33 + languageName: node + linkType: hard + +"log-symbols@npm:^7.0.0": + version: 7.0.0 + resolution: "log-symbols@npm:7.0.0" + dependencies: + is-unicode-supported: "npm:^2.0.0" + yoctocolors: "npm:^2.1.1" + checksum: 10/a6cb6e90bfe9f0774a09ff783e2035cd7e375a42757d7e401b391916a67f6da382f4966b57dda89430faaebe2ed13803ea867e104f8d67caf66082943a7153f0 languageName: node linkType: hard @@ -3232,7 +2880,14 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": +"mimic-function@npm:^5.0.0": + version: 5.0.1 + resolution: "mimic-function@npm:5.0.1" + checksum: 10/eb5893c99e902ccebbc267c6c6b83092966af84682957f79313311edb95e8bb5f39fb048d77132b700474d1c86d90ccc211e99bae0935447a4834eb4c882982c + languageName: node + linkType: hard + +"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -3327,12 +2982,11 @@ __metadata: linkType: hard "minizlib@npm:^3.0.1": - version: 3.0.1 - resolution: "minizlib@npm:3.0.1" + version: 3.0.2 + resolution: "minizlib@npm:3.0.2" dependencies: - minipass: "npm:^7.0.4" - rimraf: "npm:^5.0.5" - checksum: 10/622cb85f51e5c206a080a62d20db0d7b4066f308cb6ce82a9644da112367c3416ae7062017e631eb7ac8588191cfa4a9a279b8651c399265202b298e98c4acef + minipass: "npm:^7.1.2" + checksum: 10/c075bed1594f68dcc8c35122333520112daefd4d070e5d0a228bd4cf5580e9eed3981b96c0ae1d62488e204e80fd27b2b9d0068ca9a5ef3993e9565faf63ca41 languageName: node linkType: hard @@ -3367,22 +3021,22 @@ __metadata: linkType: hard "node-gyp@npm:latest": - version: 11.1.0 - resolution: "node-gyp@npm:11.1.0" + version: 11.2.0 + resolution: "node-gyp@npm:11.2.0" dependencies: env-paths: "npm:^2.2.0" exponential-backoff: "npm:^3.1.1" - glob: "npm:^10.3.10" graceful-fs: "npm:^4.2.6" make-fetch-happen: "npm:^14.0.3" nopt: "npm:^8.0.0" proc-log: "npm:^5.0.0" semver: "npm:^7.3.5" tar: "npm:^7.4.3" + tinyglobby: "npm:^0.2.12" which: "npm:^5.0.0" bin: node-gyp: bin/node-gyp.js - checksum: 10/3314ebfeb99dbcdf9e8c810df1ee52294045399873d4ab1e6740608c4fbe63adaf6580c0610b23c6eda125e298536553f5bb6fb0df714016a5c721ed31095e42 + checksum: 10/806fd8e3adc9157e17bf0d4a2c899cf6b98a0bbe9f453f630094ce791866271f6cddcaf2133e6513715d934fcba2014d287c7053d5d7934937b3a34d5a3d84ad languageName: node linkType: hard @@ -3452,17 +3106,29 @@ __metadata: languageName: node linkType: hard -"optionator@npm:^0.9.3": - version: 0.9.4 - resolution: "optionator@npm:0.9.4" +"onetime@npm:^7.0.0": + version: 7.0.0 + resolution: "onetime@npm:7.0.0" dependencies: - deep-is: "npm:^0.1.3" - fast-levenshtein: "npm:^2.0.6" - levn: "npm:^0.4.1" - prelude-ls: "npm:^1.2.1" - type-check: "npm:^0.4.0" - word-wrap: "npm:^1.2.5" - checksum: 10/a8398559c60aef88d7f353a4f98dcdff6090a4e70f874c827302bf1213d9106a1c4d5fcb68dacb1feb3c30a04c4102f41047aa55d4c576b863d6fc876e001af6 + mimic-function: "npm:^5.0.0" + checksum: 10/eb08d2da9339819e2f9d52cab9caf2557d80e9af8c7d1ae86e1a0fef027d00a88e9f5bd67494d350df360f7c559fbb44e800b32f310fb989c860214eacbb561c + languageName: node + linkType: hard + +"ora@npm:^8.2.0": + version: 8.2.0 + resolution: "ora@npm:8.2.0" + dependencies: + chalk: "npm:^5.3.0" + cli-cursor: "npm:^5.0.0" + cli-spinners: "npm:^2.9.2" + is-interactive: "npm:^2.0.0" + is-unicode-supported: "npm:^2.0.0" + log-symbols: "npm:^6.0.0" + stdin-discarder: "npm:^0.2.2" + string-width: "npm:^7.2.0" + strip-ansi: "npm:^7.1.0" + checksum: 10/cea932fdcb29549cd7b5af81f427760986429cadc752b1dd4bf31bc6821f5ba137e1ef9a18cde7bdfbe5b4e3d3201e76b048765c51a27b15d18c57ac0e0a909a languageName: node linkType: hard @@ -3475,7 +3141,7 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": +"p-limit@npm:^3.1.0": version: 3.1.0 resolution: "p-limit@npm:3.1.0" dependencies: @@ -3493,15 +3159,6 @@ __metadata: languageName: node linkType: hard -"p-locate@npm:^5.0.0": - version: 5.0.0 - resolution: "p-locate@npm:5.0.0" - dependencies: - p-limit: "npm:^3.0.2" - checksum: 10/1623088f36cf1cbca58e9b61c4e62bf0c60a07af5ae1ca99a720837356b5b6c5ba3eb1b2127e47a06865fee59dd0453cad7cc844cda9d5a62ac1a5a51b7c86d3 - languageName: node - linkType: hard - "p-map@npm:^7.0.2": version: 7.0.3 resolution: "p-map@npm:7.0.3" @@ -3523,15 +3180,6 @@ __metadata: languageName: node linkType: hard -"parent-module@npm:^1.0.0": - version: 1.0.1 - resolution: "parent-module@npm:1.0.1" - dependencies: - callsites: "npm:^3.0.0" - checksum: 10/6ba8b255145cae9470cf5551eb74be2d22281587af787a2626683a6c20fbb464978784661478dd2a3f1dad74d1e802d403e1b03c1a31fab310259eec8ac560ff - languageName: node - linkType: hard - "parse-json@npm:^5.2.0": version: 5.2.0 resolution: "parse-json@npm:5.2.0" @@ -3596,10 +3244,17 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^4.0.2": + version: 4.0.2 + resolution: "picomatch@npm:4.0.2" + checksum: 10/ce617b8da36797d09c0baacb96ca8a44460452c89362d7cb8f70ca46b4158ba8bc3606912de7c818eb4a939f7f9015cef3c766ec8a0c6bfc725fdc078e39c717 + languageName: node + linkType: hard + "pirates@npm:^4.0.4": - version: 4.0.6 - resolution: "pirates@npm:4.0.6" - checksum: 10/d02dda76f4fec1cbdf395c36c11cf26f76a644f9f9a1bfa84d3167d0d3154d5289aacc72677aa20d599bb4a6937a471de1b65c995e2aea2d8687cbcd7e43ea5f + version: 4.0.7 + resolution: "pirates@npm:4.0.7" + checksum: 10/2427f371366081ae42feb58214f04805d6b41d6b84d74480ebcc9e0ddbd7105a139f7c653daeaf83ad8a1a77214cf07f64178e76de048128fec501eab3305a96 languageName: node linkType: hard @@ -3612,13 +3267,6 @@ __metadata: languageName: node linkType: hard -"prelude-ls@npm:^1.2.1": - version: 1.2.1 - resolution: "prelude-ls@npm:1.2.1" - checksum: 10/0b9d2c76801ca652a7f64892dd37b7e3fab149a37d2424920099bf894acccc62abb4424af2155ab36dea8744843060a2d8ddc983518d0b1e22265a22324b72ed - languageName: node - linkType: hard - "pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0": version: 29.7.0 resolution: "pretty-format@npm:29.7.0" @@ -3657,13 +3305,6 @@ __metadata: languageName: node linkType: hard -"punycode@npm:^2.1.0": - version: 2.3.1 - resolution: "punycode@npm:2.3.1" - checksum: 10/febdc4362bead22f9e2608ff0171713230b57aff9dddc1c273aa2a651fbd366f94b7d6a71d78342a7c0819906750351ca7f2edd26ea41b626d87d6a13d1bd059 - languageName: node - linkType: hard - "pure-rand@npm:^6.0.0, pure-rand@npm:^6.1.0": version: 6.1.0 resolution: "pure-rand@npm:6.1.0" @@ -3671,13 +3312,6 @@ __metadata: languageName: node linkType: hard -"queue-microtask@npm:^1.2.2": - version: 1.2.3 - resolution: "queue-microtask@npm:1.2.3" - checksum: 10/72900df0616e473e824202113c3df6abae59150dfb73ed13273503127235320e9c8ca4aaaaccfd58cf417c6ca92a6e68ee9a5c3182886ae949a768639b388a7b - languageName: node - linkType: hard - "react-is@npm:^18.0.0": version: 18.3.1 resolution: "react-is@npm:18.3.1" @@ -3701,13 +3335,6 @@ __metadata: languageName: node linkType: hard -"resolve-from@npm:^4.0.0": - version: 4.0.0 - resolution: "resolve-from@npm:4.0.0" - checksum: 10/91eb76ce83621eea7bbdd9b55121a5c1c4a39e54a9ce04a9ad4517f102f8b5131c2cf07622c738a6683991bf54f2ce178f5a42803ecbd527ddc5105f362cc9e3 - languageName: node - linkType: hard - "resolve-from@npm:^5.0.0": version: 5.0.0 resolution: "resolve-from@npm:5.0.0" @@ -3748,39 +3375,20 @@ __metadata: languageName: node linkType: hard -"retry@npm:^0.12.0": - version: 0.12.0 - resolution: "retry@npm:0.12.0" - checksum: 10/1f914879f97e7ee931ad05fe3afa629bd55270fc6cf1c1e589b6a99fab96d15daad0fa1a52a00c729ec0078045fe3e399bd4fd0c93bcc906957bdc17f89cb8e6 - languageName: node - linkType: hard - -"reusify@npm:^1.0.4": - version: 1.1.0 - resolution: "reusify@npm:1.1.0" - checksum: 10/af47851b547e8a8dc89af144fceee17b80d5beaf5e6f57ed086432d79943434ff67ca526e92275be6f54b6189f6920a24eace75c2657eed32d02c400312b21ec - languageName: node - linkType: hard - -"rimraf@npm:^3.0.2": - version: 3.0.2 - resolution: "rimraf@npm:3.0.2" +"restore-cursor@npm:^5.0.0": + version: 5.1.0 + resolution: "restore-cursor@npm:5.1.0" dependencies: - glob: "npm:^7.1.3" - bin: - rimraf: bin.js - checksum: 10/063ffaccaaaca2cfd0ef3beafb12d6a03dd7ff1260d752d62a6077b5dfff6ae81bea571f655bb6b589d366930ec1bdd285d40d560c0dae9b12f125e54eb743d5 + onetime: "npm:^7.0.0" + signal-exit: "npm:^4.1.0" + checksum: 10/838dd54e458d89cfbc1a923b343c1b0f170a04100b4ce1733e97531842d7b440463967e521216e8ab6c6f8e89df877acc7b7f4c18ec76e99fb9bf5a60d358d2c languageName: node linkType: hard -"rimraf@npm:^5.0.5": - version: 5.0.10 - resolution: "rimraf@npm:5.0.10" - dependencies: - glob: "npm:^10.3.7" - bin: - rimraf: dist/esm/bin.mjs - checksum: 10/f3b8ce81eecbde4628b07bdf9e2fa8b684e0caea4999acb1e3b0402c695cd41f28cd075609a808e61ce2672f528ca079f675ab1d8e8d5f86d56643a03e0b8d2e +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10/1f914879f97e7ee931ad05fe3afa629bd55270fc6cf1c1e589b6a99fab96d15daad0fa1a52a00c729ec0078045fe3e399bd4fd0c93bcc906957bdc17f89cb8e6 languageName: node linkType: hard @@ -3804,15 +3412,6 @@ __metadata: languageName: unknown linkType: soft -"run-parallel@npm:^1.1.9": - version: 1.2.0 - resolution: "run-parallel@npm:1.2.0" - dependencies: - queue-microtask: "npm:^1.2.2" - checksum: 10/cb4f97ad25a75ebc11a8ef4e33bb962f8af8516bb2001082ceabd8902e15b98f4b84b4f8a9b222e5d57fc3bd1379c483886ed4619367a7680dad65316993021d - languageName: node - linkType: hard - "safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -3861,7 +3460,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^4.0.1": +"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": version: 4.1.0 resolution: "signal-exit@npm:4.1.0" checksum: 10/c9fa63bbbd7431066174a48ba2dd9986dfd930c3a8b59de9c29d7b6854ec1c12a80d15310869ea5166d413b99f041bfa3dd80a7947bcd44ea8e6eb3ffeabfa1f @@ -3959,6 +3558,13 @@ __metadata: languageName: node linkType: hard +"stdin-discarder@npm:^0.2.2": + version: 0.2.2 + resolution: "stdin-discarder@npm:0.2.2" + checksum: 10/642ffd05bd5b100819d6b24a613d83c6e3857c6de74eb02fc51506fa61dc1b0034665163831873868157c4538d71e31762bcf319be86cea04c3aba5336470478 + languageName: node + linkType: hard + "string-length@npm:^4.0.1": version: 4.0.2 resolution: "string-length@npm:4.0.2" @@ -3991,6 +3597,17 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^7.2.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: "npm:^10.3.0" + get-east-asian-width: "npm:^1.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10/42f9e82f61314904a81393f6ef75b832c39f39761797250de68c041d8ba4df2ef80db49ab6cd3a292923a6f0f409b8c9980d120f7d32c820b4a8a84a2598a295 + languageName: node + linkType: hard + "strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" @@ -4000,7 +3617,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^7.0.1": +"strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0": version: 7.1.0 resolution: "strip-ansi@npm:7.1.0" dependencies: @@ -4080,10 +3697,13 @@ __metadata: languageName: node linkType: hard -"text-table@npm:^0.2.0": - version: 0.2.0 - resolution: "text-table@npm:0.2.0" - checksum: 10/4383b5baaeffa9bb4cda2ac33a4aa2e6d1f8aaf811848bf73513a9b88fd76372dc461f6fd6d2e9cb5100f48b473be32c6f95bd983509b7d92bb4d92c10747452 +"tinyglobby@npm:^0.2.12": + version: 0.2.13 + resolution: "tinyglobby@npm:0.2.13" + dependencies: + fdir: "npm:^6.4.4" + picomatch: "npm:^4.0.2" + checksum: 10/b04557ee58ad2be5f2d2cbb4b441476436c92bb45ba2e1fc464d686b793392b305ed0bcb8b877429e9b5036bdd46770c161a08384c0720b6682b7cd6ac80e403 languageName: node linkType: hard @@ -4104,8 +3724,8 @@ __metadata: linkType: hard "ts-jest@npm:^29.1.1": - version: 29.2.6 - resolution: "ts-jest@npm:29.2.6" + version: 29.3.2 + resolution: "ts-jest@npm:29.3.2" dependencies: bs-logger: "npm:^0.2.6" ejs: "npm:^3.1.10" @@ -4115,6 +3735,7 @@ __metadata: lodash.memoize: "npm:^4.1.2" make-error: "npm:^1.3.6" semver: "npm:^7.7.1" + type-fest: "npm:^4.39.1" yargs-parser: "npm:^21.1.1" peerDependencies: "@babel/core": ">=7.0.0-beta.0 <8" @@ -4136,11 +3757,11 @@ __metadata: optional: true bin: ts-jest: cli.js - checksum: 10/9cb6804266be7c9384cecace346f65d2ab5a685d252c5275b53b5958f6545951328a5c4d48c49c5f92d1e04187ca31e348e5a3540d20cb365c33d1eb89371e22 + checksum: 10/62fb226a4df408174a3f28919c89440b2f5df4dec404bb49696591e61d75536b1c3be8ae726d187958a467654d82294d81d2dd70d9ec370542a30907183aaf61 languageName: node linkType: hard -"ts-node@npm:^10.9.1, ts-node@npm:^10.9.2": +"ts-node@npm:^10.9.1": version: 10.9.2 resolution: "ts-node@npm:10.9.2" dependencies: @@ -4178,58 +3799,58 @@ __metadata: languageName: node linkType: hard -"turbo-darwin-64@npm:2.5.1": - version: 2.5.1 - resolution: "turbo-darwin-64@npm:2.5.1" +"turbo-darwin-64@npm:2.5.2": + version: 2.5.2 + resolution: "turbo-darwin-64@npm:2.5.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"turbo-darwin-arm64@npm:2.5.1": - version: 2.5.1 - resolution: "turbo-darwin-arm64@npm:2.5.1" +"turbo-darwin-arm64@npm:2.5.2": + version: 2.5.2 + resolution: "turbo-darwin-arm64@npm:2.5.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"turbo-linux-64@npm:2.5.1": - version: 2.5.1 - resolution: "turbo-linux-64@npm:2.5.1" +"turbo-linux-64@npm:2.5.2": + version: 2.5.2 + resolution: "turbo-linux-64@npm:2.5.2" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"turbo-linux-arm64@npm:2.5.1": - version: 2.5.1 - resolution: "turbo-linux-arm64@npm:2.5.1" +"turbo-linux-arm64@npm:2.5.2": + version: 2.5.2 + resolution: "turbo-linux-arm64@npm:2.5.2" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"turbo-windows-64@npm:2.5.1": - version: 2.5.1 - resolution: "turbo-windows-64@npm:2.5.1" +"turbo-windows-64@npm:2.5.2": + version: 2.5.2 + resolution: "turbo-windows-64@npm:2.5.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"turbo-windows-arm64@npm:2.5.1": - version: 2.5.1 - resolution: "turbo-windows-arm64@npm:2.5.1" +"turbo-windows-arm64@npm:2.5.2": + version: 2.5.2 + resolution: "turbo-windows-arm64@npm:2.5.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard "turbo@npm:^2.5.1": - version: 2.5.1 - resolution: "turbo@npm:2.5.1" - dependencies: - turbo-darwin-64: "npm:2.5.1" - turbo-darwin-arm64: "npm:2.5.1" - turbo-linux-64: "npm:2.5.1" - turbo-linux-arm64: "npm:2.5.1" - turbo-windows-64: "npm:2.5.1" - turbo-windows-arm64: "npm:2.5.1" + version: 2.5.2 + resolution: "turbo@npm:2.5.2" + dependencies: + turbo-darwin-64: "npm:2.5.2" + turbo-darwin-arm64: "npm:2.5.2" + turbo-linux-64: "npm:2.5.2" + turbo-linux-arm64: "npm:2.5.2" + turbo-windows-64: "npm:2.5.2" + turbo-windows-arm64: "npm:2.5.2" dependenciesMeta: turbo-darwin-64: optional: true @@ -4245,16 +3866,7 @@ __metadata: optional: true bin: turbo: bin/turbo - checksum: 10/54d7c16d7cc9b60098db62d37940bef4663a14344bada1cb521e3260b6c30ec4bebc88a3dbb4108e4a3436fdc32b43091323f9d841ad3198719513765a73ddd9 - languageName: node - linkType: hard - -"type-check@npm:^0.4.0, type-check@npm:~0.4.0": - version: 0.4.0 - resolution: "type-check@npm:0.4.0" - dependencies: - prelude-ls: "npm:^1.2.1" - checksum: 10/14687776479d048e3c1dbfe58a2409e00367810d6960c0f619b33793271ff2a27f81b52461f14a162f1f89a9b1d8da1b237fc7c99b0e1fdcec28ec63a86b1fec + checksum: 10/dee9047dbeeddd5584744e604620267749e278e71f9658fd58ca72f6f71d38c47132ea958aee7b7049e51c85bcfce35ca6efb1e8d180b03d7504d7427f05b026 languageName: node linkType: hard @@ -4265,13 +3877,6 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^0.20.2": - version: 0.20.2 - resolution: "type-fest@npm:0.20.2" - checksum: 10/8907e16284b2d6cfa4f4817e93520121941baba36b39219ea36acfe64c86b9dbc10c9941af450bd60832c8f43464974d51c0957f9858bc66b952b66b6914cbb9 - languageName: node - linkType: hard - "type-fest@npm:^0.21.3": version: 0.21.3 resolution: "type-fest@npm:0.21.3" @@ -4279,23 +3884,30 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.2.2": - version: 5.8.2 - resolution: "typescript@npm:5.8.2" +"type-fest@npm:^4.39.1": + version: 4.40.1 + resolution: "type-fest@npm:4.40.1" + checksum: 10/907767cd7889c8f17d94f4a811ec27c33339a9134f6842a1a56b4d6ee87cb1d6b01332f366a3f03adc10923fd6d511d73b73076f7ab5256bf5c0b43a03ab6e8b + languageName: node + linkType: hard + +"typescript@npm:^5.2.2, typescript@npm:^5.8.2": + version: 5.8.3 + resolution: "typescript@npm:5.8.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/dbc2168a55d56771f4d581997be52bab5cbc09734fec976cfbaabd787e61fb4c6cf9125fd48c6f98054ce549c77ecedefc7f64252a830dd8e9c3381f61fbeb78 + checksum: 10/65c40944c51b513b0172c6710ee62e951b70af6f75d5a5da745cb7fab132c09ae27ffdf7838996e3ed603bb015dadd099006658046941bd0ba30340cc563ae92 languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.2.2#optional!builtin": - version: 5.8.2 - resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin::version=5.8.2&hash=d69c25" +"typescript@patch:typescript@npm%3A^5.2.2#optional!builtin, typescript@patch:typescript@npm%3A^5.8.2#optional!builtin": + version: 5.8.3 + resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=d69c25" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/6ae9b2c4d3254ec2eaee6f26ed997e19c02177a212422993209f81e87092b2bb0a4738085549c5b0164982a5609364c047c72aeb281f6c8d802cd0d1c6f0d353 + checksum: 10/98470634034ec37fd9ea61cc82dcf9a27950d0117a4646146b767d085a2ec14b137aae9642a83d1c62732d7fdcdac19bb6288b0bb468a72f7a06ae4e1d2c72c9 languageName: node linkType: hard @@ -4306,10 +3918,10 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~6.20.0": - version: 6.20.0 - resolution: "undici-types@npm:6.20.0" - checksum: 10/583ac7bbf4ff69931d3985f4762cde2690bb607844c16a5e2fbb92ed312fe4fa1b365e953032d469fa28ba8b224e88a595f0b10a449332f83fa77c695e567dbe +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 10/ec8f41aa4359d50f9b59fa61fe3efce3477cc681908c8f84354d8567bb3701fafdddf36ef6bff307024d3feb42c837cf6f670314ba37fc8145e219560e473d14 languageName: node linkType: hard @@ -4345,15 +3957,6 @@ __metadata: languageName: node linkType: hard -"uri-js@npm:^4.2.2": - version: 4.4.1 - resolution: "uri-js@npm:4.4.1" - dependencies: - punycode: "npm:^2.1.0" - checksum: 10/b271ca7e3d46b7160222e3afa3e531505161c9a4e097febae9664e4b59912f4cbe94861361a4175edac3a03fee99d91e44b6a58c17a634bc5a664b19fc76fbcb - languageName: node - linkType: hard - "v8-compile-cache-lib@npm:^3.0.1": version: 3.0.1 resolution: "v8-compile-cache-lib@npm:3.0.1" @@ -4403,13 +4006,6 @@ __metadata: languageName: node linkType: hard -"word-wrap@npm:^1.2.5": - version: 1.2.5 - resolution: "word-wrap@npm:1.2.5" - checksum: 10/1ec6f6089f205f83037be10d0c4b34c9183b0b63fca0834a5b3cee55dd321429d73d40bb44c8fc8471b5203d6e8f8275717f49a8ff4b2b0ab41d7e1b563e0854 - languageName: node - linkType: hard - "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" @@ -4512,3 +4108,10 @@ __metadata: checksum: 10/f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 languageName: node linkType: hard + +"yoctocolors@npm:^2.1.1": + version: 2.1.1 + resolution: "yoctocolors@npm:2.1.1" + checksum: 10/563fbec88bce9716d1044bc98c96c329e1d7a7c503e6f1af68f1ff914adc3ba55ce953c871395e2efecad329f85f1632f51a99c362032940321ff80c42a6f74d + languageName: node + linkType: hard From dfef7ec079df130fe8d1cd9e565b700550cb8c91 Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 27 Apr 2025 01:49:14 -0500 Subject: [PATCH 020/202] update readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 90f64ec7..6ee7941c 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,12 @@ Clone the repository: git clone git@github.com:OpenZeppelin/midnight-contracts.git ``` -`cd` into it and then install dependencies and build: +`cd` into it and then install dependencies, prepare compiler, and compile: ```bash -cd midnight-contracts -yarn -npx turbo build +yarn && \ +yarn run prepare && \ +npx turbo compact ``` ### Run tests From d4678fd196012de2999e218fa08862c5db75d470 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 28 Apr 2025 00:40:23 -0500 Subject: [PATCH 021/202] fix fmt --- compact/package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compact/package.json b/compact/package.json index 15b7595a..84ede8f5 100644 --- a/compact/package.json +++ b/compact/package.json @@ -2,10 +2,7 @@ "packageManager": "yarn@4.1.0", "name": "@openzeppelin-midnight/compact", "version": "0.0.1", - "keywords": [ - "compact", - "compiler" - ], + "keywords": ["compact", "compiler"], "author": "OpenZeppelin Community ", "license": "MIT", "description": "Compact fetcher", From 6135d9c60a44cdd5a5329ca6279686dd87f0440f Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 29 Apr 2025 19:00:15 -0500 Subject: [PATCH 022/202] fix fmt --- compact/package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compact/package.json b/compact/package.json index 15b7595a..84ede8f5 100644 --- a/compact/package.json +++ b/compact/package.json @@ -2,10 +2,7 @@ "packageManager": "yarn@4.1.0", "name": "@openzeppelin-midnight/compact", "version": "0.0.1", - "keywords": [ - "compact", - "compiler" - ], + "keywords": ["compact", "compiler"], "author": "OpenZeppelin Community ", "license": "MIT", "description": "Compact fetcher", From 8bbfce30613f342c829a5fba060f9b94074b4b79 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 29 Apr 2025 19:10:45 -0500 Subject: [PATCH 023/202] remove lingering file --- compact/src/run-compactc.cjs | 32 -------------------------------- 1 file changed, 32 deletions(-) delete mode 100755 compact/src/run-compactc.cjs diff --git a/compact/src/run-compactc.cjs b/compact/src/run-compactc.cjs deleted file mode 100755 index dca2891f..00000000 --- a/compact/src/run-compactc.cjs +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env node - -const childProcess = require('node:child_process'); -const path = require('node:path'); - -const [_node, _script, ...args] = process.argv; -const COMPACT_HOME_ENV = process.env.COMPACT_HOME; - -let compactPath; -if (COMPACT_HOME_ENV != null) { - compactPath = COMPACT_HOME_ENV; - console.log( - `COMPACT_HOME env variable is set; using Compact from ${compactPath}`, - ); -} else { - compactPath = path.resolve(__dirname, '..', 'compactc'); - console.log( - `COMPACT_HOME env variable is not set; using fetched compact from ${compactPath}`, - ); -} - -// yarn runs everything with node... -const child = childProcess.spawn(path.resolve(compactPath, 'compactc'), args, { - stdio: 'inherit', -}); -child.on('exit', (code, signal) => { - if (code === 0) { - process.exit(0); - } else { - process.exit(code ?? signal); - } -}); From 7634d77b1004c1e965cccb22b0eaee4236a5f4b3 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 29 Apr 2025 19:23:10 -0500 Subject: [PATCH 024/202] update biome, fix fmt --- biome.json | 33 ++++++++++++++++++++++--- contracts/erc20/src/test/erc20.test.ts | 3 +-- contracts/erc20/src/test/utils/index.ts | 2 -- contracts/utils/src/test/utils/index.ts | 10 -------- 4 files changed, 31 insertions(+), 17 deletions(-) delete mode 100644 contracts/erc20/src/test/utils/index.ts delete mode 100644 contracts/utils/src/test/utils/index.ts diff --git a/biome.json b/biome.json index 656beeee..65873551 100644 --- a/biome.json +++ b/biome.json @@ -24,15 +24,42 @@ "organizeImports": { "enabled": true }, - "linter": { +"linter": { "enabled": true, "rules": { - "recommended": true + "recommended": true, + "a11y": { + "useButtonType": "off" + }, + "correctness": { + "noUnusedVariables": "error", + "useExhaustiveDependencies": "error", + "noUnusedImports": "error" + }, + "performance": { + "noBarrelFile": "error", + "noReExportAll": "error", + "noDelete": "off" + }, + "style": { + "noNonNullAssertion": "off", + "useShorthandArrayType": "error" + }, + "suspicious": { + "noArrayIndexKey": "off", + "noConfusingVoidType": "off", + "noConsoleLog": "error", + "noExplicitAny": "off" + } } }, "javascript": { "formatter": { - "quoteStyle": "single" + "quoteStyle": "single", + "trailingCommas": "all", + "semicolons": "always", + "indentStyle": "space", + "indentWidth": 2 } } } diff --git a/contracts/erc20/src/test/erc20.test.ts b/contracts/erc20/src/test/erc20.test.ts index 8e9a4596..414b3ea2 100644 --- a/contracts/erc20/src/test/erc20.test.ts +++ b/contracts/erc20/src/test/erc20.test.ts @@ -1,7 +1,7 @@ import type { CoinPublicKey } from '@midnight-ntwrk/compact-runtime'; import { ERC20Simulator } from './simulators/ERC20Simulator'; import type { MaybeString } from './types/string'; -import * as utils from './utils'; +import * as utils from './utils/address'; const NO_STRING: MaybeString = { is_some: false, @@ -35,7 +35,6 @@ const Z_OWNER = utils.createEitherTestUser('OWNER'); const Z_RECIPIENT = utils.createEitherTestUser('RECIPIENT'); const Z_SPENDER = utils.createEitherTestUser('SPENDER'); const Z_OTHER = utils.createEitherTestUser('OTHER'); -const SOME_CONTRACT = utils.createEitherTestContractAddress('SOME_CONTRACT'); let token: ERC20Simulator; let caller: CoinPublicKey; diff --git a/contracts/erc20/src/test/utils/index.ts b/contracts/erc20/src/test/utils/index.ts deleted file mode 100644 index 731fe1ec..00000000 --- a/contracts/erc20/src/test/utils/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { useCircuitContext as circuitContext } from './test'; -export * from './address'; diff --git a/contracts/utils/src/test/utils/index.ts b/contracts/utils/src/test/utils/index.ts deleted file mode 100644 index b8e9585d..00000000 --- a/contracts/utils/src/test/utils/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export { useCircuitContext as circuitContext } from './test'; -export { - pad, - encodeToPK, - encodeToAddress, - createEitherTestUser, - createEitherTestContractAddress, - ZERO_KEY, - ZERO_ADDRESS, -} from './address'; From 18ab0f323911d008f31b16e41ff8b77fbcf391a0 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 29 Apr 2025 22:59:52 -0500 Subject: [PATCH 025/202] add requirements in dev section --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 6ee7941c..0fd030ef 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,12 @@ ## Development +> ### Requirements +> +> - [node](https://nodejs.org/) +> - [yarn](https://yarnpkg.com/getting-started/install) +> - [compact](https://docs.midnight.network/develop/tutorial/building/#midnight-compact-compiler) + Clone the repository: ```bash From bedff4fa76ac3295816f96d9fd07cba3595ef9a3 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 30 Apr 2025 02:39:04 -0500 Subject: [PATCH 026/202] add devdeps to contracts packages --- compact/package.json | 5 ++++- contracts/erc20/package.json | 7 +++++++ contracts/utils/package.json | 7 +++++++ yarn.lock | 10 ++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/compact/package.json b/compact/package.json index 84ede8f5..15b7595a 100644 --- a/compact/package.json +++ b/compact/package.json @@ -2,7 +2,10 @@ "packageManager": "yarn@4.1.0", "name": "@openzeppelin-midnight/compact", "version": "0.0.1", - "keywords": ["compact", "compiler"], + "keywords": [ + "compact", + "compiler" + ], "author": "OpenZeppelin Community ", "license": "MIT", "description": "Compact fetcher", diff --git a/contracts/erc20/package.json b/contracts/erc20/package.json index 922efaa5..bb471653 100644 --- a/contracts/erc20/package.json +++ b/contracts/erc20/package.json @@ -25,5 +25,12 @@ }, "dependencies": { "@openzeppelin-midnight/compact": "workspace:^" + }, + "devDependencies": { + "@biomejs/biome": "1.9.4", + "@types/jest": "^29.5.6", + "@types/node": "^18.18.6", + "jest": "^29.7.0", + "typescript": "^5.2.2" } } diff --git a/contracts/utils/package.json b/contracts/utils/package.json index 8389ab1d..5205b373 100644 --- a/contracts/utils/package.json +++ b/contracts/utils/package.json @@ -25,5 +25,12 @@ }, "dependencies": { "@openzeppelin-midnight/compact": "workspace:^" + }, + "devDependencies": { + "@biomejs/biome": "1.9.4", + "@types/jest": "^29.5.6", + "@types/node": "^18.18.6", + "jest": "^29.7.0", + "typescript": "^5.2.2" } } diff --git a/yarn.lock b/yarn.lock index 866086ec..0bb1b54d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -887,7 +887,12 @@ __metadata: version: 0.0.0-use.local resolution: "@openzeppelin-midnight/erc20@workspace:contracts/erc20" dependencies: + "@biomejs/biome": "npm:1.9.4" "@openzeppelin-midnight/compact": "workspace:^" + "@types/jest": "npm:^29.5.6" + "@types/node": "npm:^18.18.6" + jest: "npm:^29.7.0" + typescript: "npm:^5.2.2" languageName: unknown linkType: soft @@ -895,7 +900,12 @@ __metadata: version: 0.0.0-use.local resolution: "@openzeppelin-midnight/utils@workspace:contracts/utils" dependencies: + "@biomejs/biome": "npm:1.9.4" "@openzeppelin-midnight/compact": "workspace:^" + "@types/jest": "npm:^29.5.6" + "@types/node": "npm:^18.18.6" + jest: "npm:^29.7.0" + typescript: "npm:^5.2.2" languageName: unknown linkType: soft From dc7ae8f1607e4287f6185b6bf86459d063b7924b Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 30 Apr 2025 22:42:31 -0500 Subject: [PATCH 027/202] simplify workspace --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 0a6f059f..eef2ac9b 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,7 @@ "packageManager": "yarn@4.1.0", "workspaces": [ "compact/", - "contracts/erc20/", - "contracts/utils/" + "contracts/*/" ], "scripts": { "prepare": "npx tsc -p ./compact && yarn rebuild", From ff967e5d970bc476ad97b11d6cad0b0d3bc29ce7 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 1 May 2025 13:53:16 -0500 Subject: [PATCH 028/202] remove unnecessary button rule --- biome.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/biome.json b/biome.json index 65873551..74a65acc 100644 --- a/biome.json +++ b/biome.json @@ -28,9 +28,6 @@ "enabled": true, "rules": { "recommended": true, - "a11y": { - "useButtonType": "off" - }, "correctness": { "noUnusedVariables": "error", "useExhaustiveDependencies": "error", From 8ce9ac0e1698a170044d023ba2eefc064e647704 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 1 May 2025 15:16:37 -0500 Subject: [PATCH 029/202] fix fmt --- compact/package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compact/package.json b/compact/package.json index 15b7595a..84ede8f5 100644 --- a/compact/package.json +++ b/compact/package.json @@ -2,10 +2,7 @@ "packageManager": "yarn@4.1.0", "name": "@openzeppelin-midnight/compact", "version": "0.0.1", - "keywords": [ - "compact", - "compiler" - ], + "keywords": ["compact", "compiler"], "author": "OpenZeppelin Community ", "license": "MIT", "description": "Compact fetcher", From f869fac5a89d95cbd25eace7c1bcb7be8643d63a Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 1 May 2025 15:17:02 -0500 Subject: [PATCH 030/202] remove useExhaustiveDeps rule --- biome.json | 1 - 1 file changed, 1 deletion(-) diff --git a/biome.json b/biome.json index 74a65acc..c41cd318 100644 --- a/biome.json +++ b/biome.json @@ -30,7 +30,6 @@ "recommended": true, "correctness": { "noUnusedVariables": "error", - "useExhaustiveDependencies": "error", "noUnusedImports": "error" }, "performance": { From d0995ce891f38c68a250dd6f5d58e5e938a57eee Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 2 May 2025 20:08:10 -0500 Subject: [PATCH 031/202] add contract template --- contracts/myContract/jest.config.ts | 16 +++ contracts/myContract/js-resolver.cjs | 20 ++++ contracts/myContract/package.json | 36 ++++++ contracts/myContract/src/MyContract.compact | 41 +++++++ .../myContract/src/test/MyContract.test.ts | 19 ++++ .../src/test/mocks/MockMyContract.compact | 21 ++++ .../test/simulators/MyContractSimulator.ts | 104 ++++++++++++++++++ contracts/myContract/src/test/types/string.ts | 4 + contracts/myContract/src/test/types/test.ts | 26 +++++ .../myContract/src/test/utils/address.ts | 81 ++++++++++++++ contracts/myContract/src/test/utils/test.ts | 68 ++++++++++++ .../src/witnesses/MyContractWitnesses.ts | 3 + contracts/myContract/tsconfig.build.json | 5 + contracts/myContract/tsconfig.json | 21 ++++ yarn.lock | 13 +++ 15 files changed, 478 insertions(+) create mode 100644 contracts/myContract/jest.config.ts create mode 100644 contracts/myContract/js-resolver.cjs create mode 100644 contracts/myContract/package.json create mode 100644 contracts/myContract/src/MyContract.compact create mode 100644 contracts/myContract/src/test/MyContract.test.ts create mode 100644 contracts/myContract/src/test/mocks/MockMyContract.compact create mode 100644 contracts/myContract/src/test/simulators/MyContractSimulator.ts create mode 100644 contracts/myContract/src/test/types/string.ts create mode 100644 contracts/myContract/src/test/types/test.ts create mode 100644 contracts/myContract/src/test/utils/address.ts create mode 100644 contracts/myContract/src/test/utils/test.ts create mode 100644 contracts/myContract/src/witnesses/MyContractWitnesses.ts create mode 100644 contracts/myContract/tsconfig.build.json create mode 100644 contracts/myContract/tsconfig.json diff --git a/contracts/myContract/jest.config.ts b/contracts/myContract/jest.config.ts new file mode 100644 index 00000000..bde5bde1 --- /dev/null +++ b/contracts/myContract/jest.config.ts @@ -0,0 +1,16 @@ +import type { Config } from '@jest/types'; + +const config: Config.InitialOptions = { + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', + verbose: true, + roots: [''], + modulePaths: [''], + passWithNoTests: false, + testMatch: ['**/*.test.ts'], + extensionsToTreatAsEsm: ['.ts'], + collectCoverage: false, + resolver: '/js-resolver.cjs', +}; + +export default config; diff --git a/contracts/myContract/js-resolver.cjs b/contracts/myContract/js-resolver.cjs new file mode 100644 index 00000000..19b6f50c --- /dev/null +++ b/contracts/myContract/js-resolver.cjs @@ -0,0 +1,20 @@ +const jsResolver = (path, options) => { + const jsExtRegex = /\.js$/i; + const resolver = options.defaultResolver; + if ( + jsExtRegex.test(path) && + !options.basedir.includes('node_modules') && + !path.includes('node_modules') + ) { + const newPath = path.replace(jsExtRegex, '.ts'); + try { + return resolver(newPath, options); + } catch { + // use default resolver + } + } + + return resolver(path, options); +}; + +module.exports = jsResolver; diff --git a/contracts/myContract/package.json b/contracts/myContract/package.json new file mode 100644 index 00000000..7e991454 --- /dev/null +++ b/contracts/myContract/package.json @@ -0,0 +1,36 @@ +{ + "name": "@openzeppelin-midnight/myContract", + "type": "module", + "main": "dist/index.js", + "module": "dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "require": "./dist/index.js", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "scripts": { + "compact": "npx compact-compiler", + "build": "npx compact-builder && tsc", + "test": "NODE_OPTIONS=--experimental-vm-modules jest", + "types": "tsc -p tsconfig.json --noEmit", + "fmt": "biome format", + "fmt:fix": "biome format --write", + "lint": "biome lint", + "lint:fix": "biome check --write", + "clean": "git clean -fXd" + }, + "dependencies": { + "@openzeppelin-midnight/compact": "workspace:^" + }, + "devDependencies": { + "@biomejs/biome": "1.9.4", + "@types/jest": "^29.5.6", + "@types/node": "^18.18.6", + "jest": "^29.7.0", + "typescript": "^5.2.2" + } +} diff --git a/contracts/myContract/src/MyContract.compact b/contracts/myContract/src/MyContract.compact new file mode 100644 index 00000000..25ddf50f --- /dev/null +++ b/contracts/myContract/src/MyContract.compact @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT + +pragma language_version >= 0.14.0; + +/** + * @module MyContract + * @description Get rekt, losers + */ +module MyContract { + import CompactStandardLibrary; + + /// Public state + export ledger name: Maybe>; + + /** + * @description Initializes MyContract's name. + */ + export circuit initializer( + _name: Maybe> + ): [] { + return setName(_name); + } + + /** + * @description Returns the contract name. + * + * @return {Maybe>} - The token name. + */ + export circuit getName(): Maybe> { + return name; + } + + /** + * @description Sets the contract name. + * + * @return {[]} - None. + */ + export circuit setName(newName: Maybe>): [] { + name = newName; + } +} diff --git a/contracts/myContract/src/test/MyContract.test.ts b/contracts/myContract/src/test/MyContract.test.ts new file mode 100644 index 00000000..575d867d --- /dev/null +++ b/contracts/myContract/src/test/MyContract.test.ts @@ -0,0 +1,19 @@ +import { MyContractSimulator } from './simulators/MyContractSimulator'; +import type { MaybeString } from './types/string'; + +const NAME: MaybeString = { + is_some: true, + value: 'NAME', +}; + +let contract: MyContractSimulator; + +describe('MyContract', () => { + describe('name', () => { + it('should return name', () => { + contract = new MyContractSimulator(NAME); + + expect(contract.getName()).toEqual(NAME); + }); + }); +}); diff --git a/contracts/myContract/src/test/mocks/MockMyContract.compact b/contracts/myContract/src/test/mocks/MockMyContract.compact new file mode 100644 index 00000000..8caabf35 --- /dev/null +++ b/contracts/myContract/src/test/mocks/MockMyContract.compact @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +pragma language_version >= 0.14.0; + +import CompactStandardLibrary; + +import "../../MyContract" prefix MyContract_; + +export { ZswapCoinPublicKey, ContractAddress, Either, Maybe }; + +constructor(name: Maybe>) { + MyContract_initializer(name); +} + +export circuit getName(): Maybe> { + return MyContract_getName(); +} + +export circuit setName(newName: Maybe>): [] { + return MyContract_setName(newName); +} diff --git a/contracts/myContract/src/test/simulators/MyContractSimulator.ts b/contracts/myContract/src/test/simulators/MyContractSimulator.ts new file mode 100644 index 00000000..329906cd --- /dev/null +++ b/contracts/myContract/src/test/simulators/MyContractSimulator.ts @@ -0,0 +1,104 @@ +import { + type CircuitContext, + type ContractState, + QueryContext, + constructorContext, +} from '@midnight-ntwrk/compact-runtime'; +import { sampleContractAddress } from '@midnight-ntwrk/zswap'; +import { + type Ledger, + Contract as MockMyContract, + ledger, +} from '../../artifacts/MockMyContract/contract/index.cjs'; // Combined imports +import { + type MyContractPrivateState, + MyContractWitnesses, +} from '../../witnesses/MyContractWitnesses'; +import type { MaybeString } from '../types/string'; +import type { IContractSimulator } from '../types/test'; + +/** + * @description A simulator implementation of a contract for testing purposes. + * @template P - The private state type, fixed to MyContractPrivateState. + * @template L - The ledger type, fixed to Contract.Ledger. + */ +export class MyContractSimulator + implements IContractSimulator +{ + /** @description The underlying contract instance managing contract logic. */ + readonly contract: MockMyContract; + + /** @description The deployed address of the contract. */ + readonly contractAddress: string; + + /** @description The current circuit context, updated by contract operations. */ + circuitContext: CircuitContext; + + /** + * @description Initializes the mock contract. + */ + constructor(name: MaybeString) { + this.contract = new MockMyContract( + MyContractWitnesses, + ); + const { + currentPrivateState, + currentContractState, + currentZswapLocalState, + } = this.contract.initialState( + constructorContext({}, '0'.repeat(64)), + name, + ); + this.circuitContext = { + currentPrivateState, + currentZswapLocalState, + originalState: currentContractState, + transactionContext: new QueryContext( + currentContractState.data, + sampleContractAddress(), + ), + }; + this.contractAddress = this.circuitContext.transactionContext.address; + } + + /** + * @description Retrieves the current public ledger state of the contract. + * @returns The ledger state as defined by the contract. + */ + public getCurrentPublicState(): Ledger { + return ledger(this.circuitContext.transactionContext.state); + } + + /** + * @description Retrieves the current private state of the contract. + * @returns The private state of type MyContractPrivateState. + */ + public getCurrentPrivateState(): MyContractPrivateState { + return this.circuitContext.currentPrivateState; + } + + /** + * @description Retrieves the current contract state. + * @returns The contract state object. + */ + public getCurrentContractState(): ContractState { + return this.circuitContext.originalState; + } + + /** + * @description Returns the contract name. + * @returns The contract name. + */ + public getName(): MaybeString { + return this.contract.impureCircuits.getName(this.circuitContext).result; + } + + /** + * @description Sets the contract name. + * @returns None. + */ + public setName(newName: MaybeString) { + return this.contract.impureCircuits.setName(this.circuitContext, newName) + .result; + } +} diff --git a/contracts/myContract/src/test/types/string.ts b/contracts/myContract/src/test/types/string.ts new file mode 100644 index 00000000..430a139e --- /dev/null +++ b/contracts/myContract/src/test/types/string.ts @@ -0,0 +1,4 @@ +export type MaybeString = { + is_some: boolean; + value: string; +}; diff --git a/contracts/myContract/src/test/types/test.ts b/contracts/myContract/src/test/types/test.ts new file mode 100644 index 00000000..7a909543 --- /dev/null +++ b/contracts/myContract/src/test/types/test.ts @@ -0,0 +1,26 @@ +import type { + CircuitContext, + ContractState, +} from '@midnight-ntwrk/compact-runtime'; + +/** + * Generic interface for mock contract implementations. + * @template P - The type of the contract's private state. + * @template L - The type of the contract's ledger (public state). + */ +export interface IContractSimulator { + /** The contract's deployed address. */ + readonly contractAddress: string; + + /** The current circuit context. */ + circuitContext: CircuitContext

; + + /** Retrieves the current ledger state. */ + getCurrentPublicState(): L; + + /** Retrieves the current private state. */ + getCurrentPrivateState(): P; + + /** Retrieves the current contract state. */ + getCurrentContractState(): ContractState; +} diff --git a/contracts/myContract/src/test/utils/address.ts b/contracts/myContract/src/test/utils/address.ts new file mode 100644 index 00000000..3580e196 --- /dev/null +++ b/contracts/myContract/src/test/utils/address.ts @@ -0,0 +1,81 @@ +import { + convert_bigint_to_Uint8Array, + encodeCoinPublicKey, +} from '@midnight-ntwrk/compact-runtime'; +import { encodeContractAddress } from '@midnight-ntwrk/ledger'; +import type * as Compact from '../../artifacts/MockERC20/contract/index.cjs'; + +const PREFIX_ADDRESS = '0200'; + +export const pad = (s: string, n: number): Uint8Array => { + const encoder = new TextEncoder(); + const utf8Bytes = encoder.encode(s); + if (n < utf8Bytes.length) { + throw new Error(`The padded length n must be at least ${utf8Bytes.length}`); + } + const paddedArray = new Uint8Array(n); + paddedArray.set(utf8Bytes); + return paddedArray; +}; + +/** + * @description Generates ZswapCoinPublicKey from `str` for testing purposes. + * @param str String to hexify and encode. + * @returns Encoded `ZswapCoinPublicKey`. + */ +export const encodeToPK = (str: string): Compact.ZswapCoinPublicKey => { + const toHex = Buffer.from(str, 'ascii').toString('hex'); + return { bytes: encodeCoinPublicKey(String(toHex).padStart(64, '0')) }; +}; + +/** + * @description Generates ContractAddress from `str` for testing purposes. + * Prepends 32-byte hex with PREFIX_ADDRESS before encoding. + * @param str String to hexify and encode. + * @returns Encoded `ZswapCoinPublicKey`. + */ +export const encodeToAddress = (str: string): Compact.ContractAddress => { + const toHex = Buffer.from(str, 'ascii').toString('hex'); + const fullAddress = PREFIX_ADDRESS + String(toHex).padStart(64, '0'); + return { bytes: encodeContractAddress(fullAddress) }; +}; + +/** + * @description Generates an Either object for ZswapCoinPublicKey for testing. + * For use when an Either argument is expected. + * @param str String to hexify and encode. + * @returns Defined Either object for ZswapCoinPublicKey. + */ +export const createEitherTestUser = (str: string) => { + return { + is_left: true, + left: encodeToPK(str), + right: encodeToAddress(''), + }; +}; + +/** + * @description Generates an Either object for ContractAddress for testing. + * For use when an Either argument is expected. + * @param str String to hexify and encode. + * @returns Defined Either object for ContractAddress. + */ +export const createEitherTestContractAddress = (str: string) => { + return { + is_left: false, + left: encodeToPK(''), + right: encodeToAddress(str), + }; +}; + +export const ZERO_KEY = { + is_left: true, + left: { bytes: convert_bigint_to_Uint8Array(32, BigInt(0)) }, + right: encodeToAddress(''), +}; + +export const ZERO_ADDRESS = { + is_left: false, + left: encodeToPK(''), + right: { bytes: convert_bigint_to_Uint8Array(32, BigInt(0)) }, +}; diff --git a/contracts/myContract/src/test/utils/test.ts b/contracts/myContract/src/test/utils/test.ts new file mode 100644 index 00000000..d467e572 --- /dev/null +++ b/contracts/myContract/src/test/utils/test.ts @@ -0,0 +1,68 @@ +import { + type CircuitContext, + type CoinPublicKey, + type ContractAddress, + type ContractState, + QueryContext, + emptyZswapLocalState, +} from '@midnight-ntwrk/compact-runtime'; +import type { IContractSimulator } from '../types/test'; + +/** + * Constructs a `CircuitContext` from the given state and sender information. + * + * This is typically used at runtime to provide the necessary context + * for executing circuits, including contract state, private state, + * sender identity, and transaction data. + * + * @template P - The type of the contract's private state. + * @param privateState - The current private state of the contract. + * @param contractState - The full contract state, including public and private data. + * @param sender - The public key of the sender (used in the circuit). + * @param contractAddress - The address of the deployed contract. + * @returns A fully populated `CircuitContext` for circuit execution. + * @todo TODO: Move this utility to a generic package for broader reuse across contracts. + */ +export function useCircuitContext

( + privateState: P, + contractState: ContractState, + sender: CoinPublicKey, + contractAddress: ContractAddress, +): CircuitContext

{ + return { + originalState: contractState, + currentPrivateState: privateState, + transactionContext: new QueryContext(contractState.data, contractAddress), + currentZswapLocalState: emptyZswapLocalState(sender), + }; +} + +/** + * Prepares a new `CircuitContext` using the given sender and contract. + * + * Useful for mocking or updating the circuit context with a custom sender. + * + * @template P - The type of the contract's private state. + * @template L - The type of the contract's ledger (public state). + * @template C - The specific type of the contract implementing `MockContract`. + * @param contract - The contract instance implementing `MockContract`. + * @param sender - The public key to set as the sender in the new circuit context. + * @returns A new `CircuitContext` with the sender and updated context values. + * @todo TODO: Move this utility to a generic package for broader reuse across contracts. + */ +export function useCircuitContextSender< + P, + L, + C extends IContractSimulator, +>(contract: C, sender: CoinPublicKey): CircuitContext

{ + const currentPrivateState = contract.getCurrentPrivateState(); + const originalState = contract.getCurrentContractState(); + const contractAddress = contract.contractAddress; + + return { + originalState, + currentPrivateState, + transactionContext: new QueryContext(originalState.data, contractAddress), + currentZswapLocalState: emptyZswapLocalState(sender), + }; +} diff --git a/contracts/myContract/src/witnesses/MyContractWitnesses.ts b/contracts/myContract/src/witnesses/MyContractWitnesses.ts new file mode 100644 index 00000000..9547c404 --- /dev/null +++ b/contracts/myContract/src/witnesses/MyContractWitnesses.ts @@ -0,0 +1,3 @@ +// This is how we type an empty object. +export type MyContractPrivateState = Record; +export const MyContractWitnesses = {}; diff --git a/contracts/myContract/tsconfig.build.json b/contracts/myContract/tsconfig.build.json new file mode 100644 index 00000000..f1132509 --- /dev/null +++ b/contracts/myContract/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["src/test/**/*.ts"], + "compilerOptions": {} +} diff --git a/contracts/myContract/tsconfig.json b/contracts/myContract/tsconfig.json new file mode 100644 index 00000000..3e90b0a9 --- /dev/null +++ b/contracts/myContract/tsconfig.json @@ -0,0 +1,21 @@ +{ + "include": ["src/**/*.ts"], + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "declaration": true, + "lib": ["ESNext"], + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "node", + "allowJs": true, + "forceConsistentCasingInFileNames": true, + "noImplicitAny": true, + "strict": true, + "isolatedModules": true, + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "skipLibCheck": true + } +} diff --git a/yarn.lock b/yarn.lock index 0bb1b54d..25a6a3ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -896,6 +896,19 @@ __metadata: languageName: unknown linkType: soft +"@openzeppelin-midnight/myContract@workspace:contracts/myContract": + version: 0.0.0-use.local + resolution: "@openzeppelin-midnight/myContract@workspace:contracts/myContract" + dependencies: + "@biomejs/biome": "npm:1.9.4" + "@openzeppelin-midnight/compact": "workspace:^" + "@types/jest": "npm:^29.5.6" + "@types/node": "npm:^18.18.6" + jest: "npm:^29.7.0" + typescript: "npm:^5.2.2" + languageName: unknown + linkType: soft + "@openzeppelin-midnight/utils@workspace:contracts/utils": version: 0.0.0-use.local resolution: "@openzeppelin-midnight/utils@workspace:contracts/utils" From efef77a6609e5b579e7c3d1d976fa932a0bccfea Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 6 May 2025 19:10:24 -0500 Subject: [PATCH 032/202] start ownable shielded --- compact/package.json | 5 +- contracts/myContract/src/MyContract.compact | 41 ----- .../myContract/src/test/MyContract.test.ts | 19 -- .../src/test/mocks/MockMyContract.compact | 21 --- .../test/simulators/MyContractSimulator.ts | 104 ----------- .../src/witnesses/MyContractWitnesses.ts | 3 - .../{myContract => ownable}/jest.config.ts | 0 .../{myContract => ownable}/js-resolver.cjs | 0 .../{myContract => ownable}/package.json | 2 +- contracts/ownable/src/Ownable.compact | 77 +++++++++ contracts/ownable/src/test/Ownable.test.ts | 33 ++++ .../src/test/mocks/MockOwnable.compact | 34 ++++ .../src/test/simulators/OwnableSimulator.ts | 162 ++++++++++++++++++ .../src/test/types/string.ts | 0 .../src/test/types/test.ts | 0 .../src/test/utils/address.ts | 0 .../src/test/utils/test.ts | 0 .../ownable/src/witnesses/OwnableWitnesses.ts | 45 +++++ contracts/ownable/src/witnesses/interface.ts | 17 ++ .../tsconfig.build.json | 0 .../{myContract => ownable}/tsconfig.json | 0 yarn.lock | 4 +- 22 files changed, 375 insertions(+), 192 deletions(-) delete mode 100644 contracts/myContract/src/MyContract.compact delete mode 100644 contracts/myContract/src/test/MyContract.test.ts delete mode 100644 contracts/myContract/src/test/mocks/MockMyContract.compact delete mode 100644 contracts/myContract/src/test/simulators/MyContractSimulator.ts delete mode 100644 contracts/myContract/src/witnesses/MyContractWitnesses.ts rename contracts/{myContract => ownable}/jest.config.ts (100%) rename contracts/{myContract => ownable}/js-resolver.cjs (100%) rename contracts/{myContract => ownable}/package.json (95%) create mode 100644 contracts/ownable/src/Ownable.compact create mode 100644 contracts/ownable/src/test/Ownable.test.ts create mode 100644 contracts/ownable/src/test/mocks/MockOwnable.compact create mode 100644 contracts/ownable/src/test/simulators/OwnableSimulator.ts rename contracts/{myContract => ownable}/src/test/types/string.ts (100%) rename contracts/{myContract => ownable}/src/test/types/test.ts (100%) rename contracts/{myContract => ownable}/src/test/utils/address.ts (100%) rename contracts/{myContract => ownable}/src/test/utils/test.ts (100%) create mode 100644 contracts/ownable/src/witnesses/OwnableWitnesses.ts create mode 100644 contracts/ownable/src/witnesses/interface.ts rename contracts/{myContract => ownable}/tsconfig.build.json (100%) rename contracts/{myContract => ownable}/tsconfig.json (100%) diff --git a/compact/package.json b/compact/package.json index 84ede8f5..15b7595a 100644 --- a/compact/package.json +++ b/compact/package.json @@ -2,7 +2,10 @@ "packageManager": "yarn@4.1.0", "name": "@openzeppelin-midnight/compact", "version": "0.0.1", - "keywords": ["compact", "compiler"], + "keywords": [ + "compact", + "compiler" + ], "author": "OpenZeppelin Community ", "license": "MIT", "description": "Compact fetcher", diff --git a/contracts/myContract/src/MyContract.compact b/contracts/myContract/src/MyContract.compact deleted file mode 100644 index 25ddf50f..00000000 --- a/contracts/myContract/src/MyContract.compact +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma language_version >= 0.14.0; - -/** - * @module MyContract - * @description Get rekt, losers - */ -module MyContract { - import CompactStandardLibrary; - - /// Public state - export ledger name: Maybe>; - - /** - * @description Initializes MyContract's name. - */ - export circuit initializer( - _name: Maybe> - ): [] { - return setName(_name); - } - - /** - * @description Returns the contract name. - * - * @return {Maybe>} - The token name. - */ - export circuit getName(): Maybe> { - return name; - } - - /** - * @description Sets the contract name. - * - * @return {[]} - None. - */ - export circuit setName(newName: Maybe>): [] { - name = newName; - } -} diff --git a/contracts/myContract/src/test/MyContract.test.ts b/contracts/myContract/src/test/MyContract.test.ts deleted file mode 100644 index 575d867d..00000000 --- a/contracts/myContract/src/test/MyContract.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { MyContractSimulator } from './simulators/MyContractSimulator'; -import type { MaybeString } from './types/string'; - -const NAME: MaybeString = { - is_some: true, - value: 'NAME', -}; - -let contract: MyContractSimulator; - -describe('MyContract', () => { - describe('name', () => { - it('should return name', () => { - contract = new MyContractSimulator(NAME); - - expect(contract.getName()).toEqual(NAME); - }); - }); -}); diff --git a/contracts/myContract/src/test/mocks/MockMyContract.compact b/contracts/myContract/src/test/mocks/MockMyContract.compact deleted file mode 100644 index 8caabf35..00000000 --- a/contracts/myContract/src/test/mocks/MockMyContract.compact +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma language_version >= 0.14.0; - -import CompactStandardLibrary; - -import "../../MyContract" prefix MyContract_; - -export { ZswapCoinPublicKey, ContractAddress, Either, Maybe }; - -constructor(name: Maybe>) { - MyContract_initializer(name); -} - -export circuit getName(): Maybe> { - return MyContract_getName(); -} - -export circuit setName(newName: Maybe>): [] { - return MyContract_setName(newName); -} diff --git a/contracts/myContract/src/test/simulators/MyContractSimulator.ts b/contracts/myContract/src/test/simulators/MyContractSimulator.ts deleted file mode 100644 index 329906cd..00000000 --- a/contracts/myContract/src/test/simulators/MyContractSimulator.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { - type CircuitContext, - type ContractState, - QueryContext, - constructorContext, -} from '@midnight-ntwrk/compact-runtime'; -import { sampleContractAddress } from '@midnight-ntwrk/zswap'; -import { - type Ledger, - Contract as MockMyContract, - ledger, -} from '../../artifacts/MockMyContract/contract/index.cjs'; // Combined imports -import { - type MyContractPrivateState, - MyContractWitnesses, -} from '../../witnesses/MyContractWitnesses'; -import type { MaybeString } from '../types/string'; -import type { IContractSimulator } from '../types/test'; - -/** - * @description A simulator implementation of a contract for testing purposes. - * @template P - The private state type, fixed to MyContractPrivateState. - * @template L - The ledger type, fixed to Contract.Ledger. - */ -export class MyContractSimulator - implements IContractSimulator -{ - /** @description The underlying contract instance managing contract logic. */ - readonly contract: MockMyContract; - - /** @description The deployed address of the contract. */ - readonly contractAddress: string; - - /** @description The current circuit context, updated by contract operations. */ - circuitContext: CircuitContext; - - /** - * @description Initializes the mock contract. - */ - constructor(name: MaybeString) { - this.contract = new MockMyContract( - MyContractWitnesses, - ); - const { - currentPrivateState, - currentContractState, - currentZswapLocalState, - } = this.contract.initialState( - constructorContext({}, '0'.repeat(64)), - name, - ); - this.circuitContext = { - currentPrivateState, - currentZswapLocalState, - originalState: currentContractState, - transactionContext: new QueryContext( - currentContractState.data, - sampleContractAddress(), - ), - }; - this.contractAddress = this.circuitContext.transactionContext.address; - } - - /** - * @description Retrieves the current public ledger state of the contract. - * @returns The ledger state as defined by the contract. - */ - public getCurrentPublicState(): Ledger { - return ledger(this.circuitContext.transactionContext.state); - } - - /** - * @description Retrieves the current private state of the contract. - * @returns The private state of type MyContractPrivateState. - */ - public getCurrentPrivateState(): MyContractPrivateState { - return this.circuitContext.currentPrivateState; - } - - /** - * @description Retrieves the current contract state. - * @returns The contract state object. - */ - public getCurrentContractState(): ContractState { - return this.circuitContext.originalState; - } - - /** - * @description Returns the contract name. - * @returns The contract name. - */ - public getName(): MaybeString { - return this.contract.impureCircuits.getName(this.circuitContext).result; - } - - /** - * @description Sets the contract name. - * @returns None. - */ - public setName(newName: MaybeString) { - return this.contract.impureCircuits.setName(this.circuitContext, newName) - .result; - } -} diff --git a/contracts/myContract/src/witnesses/MyContractWitnesses.ts b/contracts/myContract/src/witnesses/MyContractWitnesses.ts deleted file mode 100644 index 9547c404..00000000 --- a/contracts/myContract/src/witnesses/MyContractWitnesses.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This is how we type an empty object. -export type MyContractPrivateState = Record; -export const MyContractWitnesses = {}; diff --git a/contracts/myContract/jest.config.ts b/contracts/ownable/jest.config.ts similarity index 100% rename from contracts/myContract/jest.config.ts rename to contracts/ownable/jest.config.ts diff --git a/contracts/myContract/js-resolver.cjs b/contracts/ownable/js-resolver.cjs similarity index 100% rename from contracts/myContract/js-resolver.cjs rename to contracts/ownable/js-resolver.cjs diff --git a/contracts/myContract/package.json b/contracts/ownable/package.json similarity index 95% rename from contracts/myContract/package.json rename to contracts/ownable/package.json index 7e991454..d1016a6a 100644 --- a/contracts/myContract/package.json +++ b/contracts/ownable/package.json @@ -1,5 +1,5 @@ { - "name": "@openzeppelin-midnight/myContract", + "name": "@openzeppelin-midnight/ownable", "type": "module", "main": "dist/index.js", "module": "dist/index.js", diff --git a/contracts/ownable/src/Ownable.compact b/contracts/ownable/src/Ownable.compact new file mode 100644 index 00000000..8f6952f6 --- /dev/null +++ b/contracts/ownable/src/Ownable.compact @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +pragma language_version >= 0.14.0; + +/** + * @module Shielded Ownable module + * @description Get rekt, losers + */ +module Ownable { + import CompactStandardLibrary; + + /// Public state + export ledger _owner: Bytes<32>; + export ledger _pendingOwner: Bytes<32>; + export ledger _instance: Counter; + + /// Witnesses + witness localSecretKey(): Bytes<32>; + //witness setPendingOwner(currentOwner: Bytes<32>): Bytes<32>; + + /** + * @description Add me... + */ + export circuit initializer(): [] { + _instance.increment(1); + const initialOwner = disclose(publicKey(localSecretKey(), _instance as Field as Bytes<32>)); + _owner = initialOwner; + } + + /** + * @description Add me... + */ + export circuit owner(): Bytes<32> { + return _owner; + } + + /** + * @description Add me... + */ + //export circuit proposeNewOwner(): Bytes<32> { + // + //} + + /** + * @description Add me... + */ + export circuit renounceOwnership(): [] { + assertOnlyOwner(); + const zeroAddress = burn_address().left.bytes; + _transferOwnership(zeroAddress); + } + + /** + * @description Add me... + */ + export circuit assertOnlyOwner(): [] { + assert _owner == publicKey(localSecretKey(), _instance as Field as Bytes<32>) "Ownable: not owner"; + } + + /** + * @description Add me... + */ + export circuit publicKey(sk: Bytes<32>, instance: Bytes<32>): Bytes<32> { + // Using `self` ensures `_owner` will be unique even if the same `sk` is used in + // other contracts that use the `ownable:pk:` domain with the same `instance` + const self = kernel.self().bytes; + return persistent_hash>>([self, pad(32, "ownable:pk:"), instance, sk]); + } + + /** + * @description Add me... + */ + export circuit _transferOwnership(newOwner: Bytes<32>): [] { + _instance.increment(1); + _owner = newOwner; + } +} diff --git a/contracts/ownable/src/test/Ownable.test.ts b/contracts/ownable/src/test/Ownable.test.ts new file mode 100644 index 00000000..ce175d87 --- /dev/null +++ b/contracts/ownable/src/test/Ownable.test.ts @@ -0,0 +1,33 @@ +import { CoinPublicKey, encodeCoinPublicKey } from '@midnight-ntwrk/compact-runtime'; +import { OwnableSimulator } from './simulators/OwnableSimulator'; +import * as utils from './utils/address'; + +const OWNER = String(Buffer.from('OWNER', 'ascii').toString('hex')).padStart( + 64, + '0', +); +const SPENDER = String( + Buffer.from('SPENDER', 'ascii').toString('hex'), +).padStart(64, '0'); +const UNAUTHORIZED = String( + Buffer.from('UNAUTHORIZED', 'ascii').toString('hex'), +).padStart(64, '0'); +const ZERO = String().padStart(64, '0'); +const Z_OWNER = utils.createEitherTestUser('OWNER'); +const Z_RECIPIENT = utils.createEitherTestUser('RECIPIENT'); +const Z_SPENDER = utils.createEitherTestUser('SPENDER'); +const Z_OTHER = utils.createEitherTestUser('OTHER'); +const EMPTY_BYTES = utils.ZERO_KEY.left.bytes; + +let ownable: OwnableSimulator; +let caller: CoinPublicKey; + +describe('Ownable', () => { + describe('initializer', () => { + it('should initialize and set the caller as owner', () => { + caller = OWNER; + ownable = new OwnableSimulator(OWNER); + //expect(ownable.owner()).not.toEqual(utils.ZERO_ADDRESS); + }); + }); +}); diff --git a/contracts/ownable/src/test/mocks/MockOwnable.compact b/contracts/ownable/src/test/mocks/MockOwnable.compact new file mode 100644 index 00000000..d550c7dc --- /dev/null +++ b/contracts/ownable/src/test/mocks/MockOwnable.compact @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +pragma language_version >= 0.14.0; + +import CompactStandardLibrary; + +import "../../Ownable" prefix Ownable_; + +export { ZswapCoinPublicKey, ContractAddress, Either, Maybe }; +export { Ownable__owner, Ownable__pendingOwner, Ownable__instance }; + +constructor() { + Ownable_initializer(); +} + +export circuit owner(): Bytes<32> { + return Ownable_owner(); + } + +export circuit renounceOwnership(): [] { + return Ownable_renounceOwnership(); +} + +export circuit assertOnlyOwner(): [] { + return Ownable_assertOnlyOwner(); +} + +export circuit publicKey(sk: Bytes<32>, instance: Bytes<32>): Bytes<32> { + return Ownable_publicKey(sk, instance); +} + +export circuit _transferOwnership(newOwner: Bytes<32>): [] { + return Ownable__transferOwnership(newOwner); +} diff --git a/contracts/ownable/src/test/simulators/OwnableSimulator.ts b/contracts/ownable/src/test/simulators/OwnableSimulator.ts new file mode 100644 index 00000000..036e24e9 --- /dev/null +++ b/contracts/ownable/src/test/simulators/OwnableSimulator.ts @@ -0,0 +1,162 @@ +import { + type CircuitContext, + CoinPublicKey, + type ContractState, + QueryContext, + constructorContext, + emptyZswapLocalState, +} from '@midnight-ntwrk/compact-runtime'; +import { sampleContractAddress } from '@midnight-ntwrk/zswap'; +import { + type Ledger, + Contract as MockOwnable, + ledger, +} from '../../artifacts/MockOwnable/contract/index.cjs'; // Combined imports +import { + OwnablePrivateState, + OwnableWitnesses, +} from '../../witnesses/OwnableWitnesses'; +import type { MaybeString } from '../types/string'; +import type { IContractSimulator } from '../types/test'; + +/** + * @description A simulator implementation of a contract for testing purposes. + * @template P - The private state type, fixed to OwnablePrivateState. + * @template L - The ledger type, fixed to Contract.Ledger. + */ +export class OwnableSimulator + implements IContractSimulator +{ + /** @description The underlying contract instance managing contract logic. */ + readonly contract: MockOwnable; + + /** @description The deployed address of the contract. */ + readonly contractAddress: string; + + /** @description The deployer address of the contract. */ + readonly deployer: CoinPublicKey; + + /** @description The current circuit context, updated by contract operations. */ + circuitContext: CircuitContext; + + /** + * @description Initializes the mock contract. + */ + constructor(deployer: CoinPublicKey) { + this.contract = new MockOwnable( + OwnableWitnesses(), + ); + this.deployer = deployer; + const { + currentPrivateState, + currentContractState, + currentZswapLocalState, + } = this.contract.initialState( + constructorContext(OwnablePrivateState.generate(), deployer), + ); + this.circuitContext = { + currentPrivateState, + currentZswapLocalState, + originalState: currentContractState, + transactionContext: new QueryContext( + currentContractState.data, + sampleContractAddress(), + ), + }; + this.contractAddress = this.circuitContext.transactionContext.address; + } + + /** + * @description Retrieves the current public ledger state of the contract. + * @returns The ledger state as defined by the contract. + */ + public getCurrentPublicState(): Ledger { + return ledger(this.circuitContext.transactionContext.state); + } + + /** + * @description Retrieves the current private state of the contract. + * @returns The private state of type OwnablePrivateState. + */ + public getCurrentPrivateState(): OwnablePrivateState { + return this.circuitContext.currentPrivateState; + } + + /** + * @description Retrieves the current contract state. + * @returns The contract state object. + */ + public getCurrentContractState(): ContractState { + return this.circuitContext.originalState; + } + + /** + * @description Returns the contract name. + * @returns The contract name. + */ + //public getName(): MaybeString { + // return this.contract.impureCircuits.getName(this.circuitContext).result; + //} + + /** + * @description Sets the contract name. + * @returns None. + */ + //public setName(newName: MaybeString) { + // return this.contract.impureCircuits.setName(this.circuitContext, newName) + // .result; + //} + + public owner(): Uint8Array { + return this.contract.impureCircuits.owner(this.circuitContext).result; + } + + public renounceOwnership(sender: CoinPublicKey): CircuitContext { + const res = this.contract.impureCircuits.renounceOwnership( + { + ...this.circuitContext, + currentZswapLocalState: sender + ? emptyZswapLocalState(sender) + : this.circuitContext.currentZswapLocalState, + }, + ); + + this.circuitContext = res.context; + return this.circuitContext; + } + + public assertOnlyOwner(sender: CoinPublicKey): CircuitContext { + const res = this.contract.impureCircuits.renounceOwnership( + { + ...this.circuitContext, + currentZswapLocalState: sender + ? emptyZswapLocalState(sender) + : this.circuitContext.currentZswapLocalState, + }, + ); + + this.circuitContext = res.context; + return this.circuitContext; + } + + public publicKey(sk: Uint8Array, instance: Uint8Array, sender: CoinPublicKey): CircuitContext { + const res = this.contract.impureCircuits.publicKey( + { + ...this.circuitContext, + currentZswapLocalState: sender + ? emptyZswapLocalState(sender) + : this.circuitContext.currentZswapLocalState, + }, + sk, + instance + ); + + this.circuitContext = res.context; + return this.circuitContext; + } + + public _transferOwnership(newOwner: Uint8Array): CircuitContext { + this.circuitContext = this.contract.impureCircuits._transferOwnership(this.circuitContext, newOwner).context; + return this.circuitContext; + } +} diff --git a/contracts/myContract/src/test/types/string.ts b/contracts/ownable/src/test/types/string.ts similarity index 100% rename from contracts/myContract/src/test/types/string.ts rename to contracts/ownable/src/test/types/string.ts diff --git a/contracts/myContract/src/test/types/test.ts b/contracts/ownable/src/test/types/test.ts similarity index 100% rename from contracts/myContract/src/test/types/test.ts rename to contracts/ownable/src/test/types/test.ts diff --git a/contracts/myContract/src/test/utils/address.ts b/contracts/ownable/src/test/utils/address.ts similarity index 100% rename from contracts/myContract/src/test/utils/address.ts rename to contracts/ownable/src/test/utils/address.ts diff --git a/contracts/myContract/src/test/utils/test.ts b/contracts/ownable/src/test/utils/test.ts similarity index 100% rename from contracts/myContract/src/test/utils/test.ts rename to contracts/ownable/src/test/utils/test.ts diff --git a/contracts/ownable/src/witnesses/OwnableWitnesses.ts b/contracts/ownable/src/witnesses/OwnableWitnesses.ts new file mode 100644 index 00000000..9f737582 --- /dev/null +++ b/contracts/ownable/src/witnesses/OwnableWitnesses.ts @@ -0,0 +1,45 @@ +import { getRandomValues } from 'node:crypto'; +import { IOwnableWitnesses } from './interface'; +import type { WitnessContext } from '@midnight-ntwrk/compact-runtime'; +import { + type Ledger, + } from '../artifacts/MockOwnable/contract/index.cjs'; // Combined imports + +/** + * @description Represents the private state of an access control contract, storing a secret key and role assignments. + */ +export type OwnablePrivateState = { + /** @description A 32-byte secret key used for cryptographic operations, such as nullifier generation. */ + secretKey: Buffer; + }; + +/** + * @description Utility object for managing the private state of an ownable contract. + */ +export const OwnablePrivateState = { + /** + * @description Generates a new private state with a random secret key and empty roles. + * @returns A fresh OwnablePrivateState instance. + */ + generate: (): OwnablePrivateState => { + return { secretKey: getRandomValues(Buffer.alloc(32)) }; + }, +} + +/** + * @description Factory function creating witness implementations for access control operations. + * @returns An object implementing the Witnesses interface for AccessContractPrivateState. + */ +export const OwnableWitnesses = + (): IOwnableWitnesses => ({ + /** + * @description Retrieves the secret key from the private state. + * @param context - The witness context containing the private state. + * @returns A tuple of the unchanged private state and the secret key as a Uint8Array. + */ + localSecretKey( + context: WitnessContext, + ): [OwnablePrivateState, Uint8Array] { + return [context.privateState, context.privateState.secretKey]; + }, +}); \ No newline at end of file diff --git a/contracts/ownable/src/witnesses/interface.ts b/contracts/ownable/src/witnesses/interface.ts new file mode 100644 index 00000000..d264ee9d --- /dev/null +++ b/contracts/ownable/src/witnesses/interface.ts @@ -0,0 +1,17 @@ +import type { WitnessContext } from '@midnight-ntwrk/compact-runtime'; +import { + type Ledger, + } from '../artifacts/MockOwnable/contract/index.cjs'; // Combined imports + +/** + * @description Interface defining the witness methods for ownable operations. + * @template P - The private state type. + */ +export interface IOwnableWitnesses

{ + /** + * Retrieves the secret key from the private state. + * @param context - The witness context containing the private state. + * @returns A tuple of the private state and the secret key as a Uint8Array. + */ + localSecretKey(context: WitnessContext): [P, Uint8Array]; +} diff --git a/contracts/myContract/tsconfig.build.json b/contracts/ownable/tsconfig.build.json similarity index 100% rename from contracts/myContract/tsconfig.build.json rename to contracts/ownable/tsconfig.build.json diff --git a/contracts/myContract/tsconfig.json b/contracts/ownable/tsconfig.json similarity index 100% rename from contracts/myContract/tsconfig.json rename to contracts/ownable/tsconfig.json diff --git a/yarn.lock b/yarn.lock index 25a6a3ae..5214c131 100644 --- a/yarn.lock +++ b/yarn.lock @@ -896,9 +896,9 @@ __metadata: languageName: unknown linkType: soft -"@openzeppelin-midnight/myContract@workspace:contracts/myContract": +"@openzeppelin-midnight/ownable@workspace:contracts/ownable": version: 0.0.0-use.local - resolution: "@openzeppelin-midnight/myContract@workspace:contracts/myContract" + resolution: "@openzeppelin-midnight/ownable@workspace:contracts/ownable" dependencies: "@biomejs/biome": "npm:1.9.4" "@openzeppelin-midnight/compact": "workspace:^" From e13bdc83ef6091dfa8ef9ff0d3c7f769d7a47874 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 7 May 2025 18:15:47 -0500 Subject: [PATCH 033/202] fix pk circuit --- contracts/ownable/src/Ownable.compact | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/contracts/ownable/src/Ownable.compact b/contracts/ownable/src/Ownable.compact index 8f6952f6..e501c055 100644 --- a/contracts/ownable/src/Ownable.compact +++ b/contracts/ownable/src/Ownable.compact @@ -61,10 +61,7 @@ module Ownable { * @description Add me... */ export circuit publicKey(sk: Bytes<32>, instance: Bytes<32>): Bytes<32> { - // Using `self` ensures `_owner` will be unique even if the same `sk` is used in - // other contracts that use the `ownable:pk:` domain with the same `instance` - const self = kernel.self().bytes; - return persistent_hash>>([self, pad(32, "ownable:pk:"), instance, sk]); + return persistent_hash>>([pad(32, "ownable:pk:"), instance, sk]); } /** From f41efd52d4a7e8c5b6824aaa26757cbc308407f1 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 7 May 2025 18:16:10 -0500 Subject: [PATCH 034/202] add custom witness context --- .../ownable/src/witnesses/OwnableWitnesses.ts | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/contracts/ownable/src/witnesses/OwnableWitnesses.ts b/contracts/ownable/src/witnesses/OwnableWitnesses.ts index 9f737582..26350db3 100644 --- a/contracts/ownable/src/witnesses/OwnableWitnesses.ts +++ b/contracts/ownable/src/witnesses/OwnableWitnesses.ts @@ -6,10 +6,10 @@ import { } from '../artifacts/MockOwnable/contract/index.cjs'; // Combined imports /** - * @description Represents the private state of an access control contract, storing a secret key and role assignments. + * @description Represents the private state of an ownable contract, storing a secret key. */ export type OwnablePrivateState = { - /** @description A 32-byte secret key used for cryptographic operations, such as nullifier generation. */ + /** @description A 32-byte secret key used for cryptographic operations. */ secretKey: Buffer; }; @@ -18,7 +18,7 @@ export type OwnablePrivateState = { */ export const OwnablePrivateState = { /** - * @description Generates a new private state with a random secret key and empty roles. + * @description Generates a new private state with a random secret key. * @returns A fresh OwnablePrivateState instance. */ generate: (): OwnablePrivateState => { @@ -27,8 +27,8 @@ export const OwnablePrivateState = { } /** - * @description Factory function creating witness implementations for access control operations. - * @returns An object implementing the Witnesses interface for AccessContractPrivateState. + * @description Factory function creating witness implementations for ownable operations. + * @returns An object implementing the Witnesses interface for OwnablePrivateState. */ export const OwnableWitnesses = (): IOwnableWitnesses => ({ @@ -42,4 +42,18 @@ export const OwnableWitnesses = ): [OwnablePrivateState, Uint8Array] { return [context.privateState, context.privateState.secretKey]; }, -}); \ No newline at end of file +}); + +export const SetWitnessContext = + (sk: Uint8Array): IOwnableWitnesses => ({ + /** + * @description Retrieves the secret key from the private state. + * @param context - The witness context containing the private state. + * @returns A tuple of the unchanged private state and the passed `sk` as a Uint8Array. + */ + localSecretKey( + context: WitnessContext, + ): [OwnablePrivateState, Uint8Array] { + return [context.privateState, sk]; + }, +}); From 67b90351512e6b3c9d2858873e34174357a64d48 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 7 May 2025 18:16:42 -0500 Subject: [PATCH 035/202] add setWitnessContext method --- .../src/test/simulators/OwnableSimulator.ts | 50 +++++-------------- 1 file changed, 12 insertions(+), 38 deletions(-) diff --git a/contracts/ownable/src/test/simulators/OwnableSimulator.ts b/contracts/ownable/src/test/simulators/OwnableSimulator.ts index 036e24e9..41cb3c27 100644 --- a/contracts/ownable/src/test/simulators/OwnableSimulator.ts +++ b/contracts/ownable/src/test/simulators/OwnableSimulator.ts @@ -15,9 +15,10 @@ import { import { OwnablePrivateState, OwnableWitnesses, + SetWitnessContext, } from '../../witnesses/OwnableWitnesses'; -import type { MaybeString } from '../types/string'; import type { IContractSimulator } from '../types/test'; +import { useCircuitContextSender } from '../utils/test'; /** * @description A simulator implementation of a contract for testing purposes. @@ -91,56 +92,29 @@ export class OwnableSimulator } /** - * @description Returns the contract name. - * @returns The contract name. - */ - //public getName(): MaybeString { - // return this.contract.impureCircuits.getName(this.circuitContext).result; - //} - - /** - * @description Sets the contract name. + * @description Changes the witness context by setting `sk` + * as the `secretKey`. * @returns None. */ - //public setName(newName: MaybeString) { - // return this.contract.impureCircuits.setName(this.circuitContext, newName) - // .result; - //} + public setWitnessContext(sk: Uint8Array) { + this.contract.witnesses = SetWitnessContext(sk); + } public owner(): Uint8Array { return this.contract.impureCircuits.owner(this.circuitContext).result; } - public renounceOwnership(sender: CoinPublicKey): CircuitContext { - const res = this.contract.impureCircuits.renounceOwnership( - { - ...this.circuitContext, - currentZswapLocalState: sender - ? emptyZswapLocalState(sender) - : this.circuitContext.currentZswapLocalState, - }, - ); - - this.circuitContext = res.context; + public renounceOwnership(): CircuitContext { + this.circuitContext = this.contract.impureCircuits.renounceOwnership(this.circuitContext).context; return this.circuitContext; } - public assertOnlyOwner(sender: CoinPublicKey): CircuitContext { - const res = this.contract.impureCircuits.renounceOwnership( - { - ...this.circuitContext, - currentZswapLocalState: sender - ? emptyZswapLocalState(sender) - : this.circuitContext.currentZswapLocalState, - }, - ); - - this.circuitContext = res.context; - return this.circuitContext; + public assertOnlyOwner(): CircuitContext { + return this.contract.impureCircuits.assertOnlyOwner(this.circuitContext).context; } public publicKey(sk: Uint8Array, instance: Uint8Array, sender: CoinPublicKey): CircuitContext { - const res = this.contract.impureCircuits.publicKey( + const res = this.contract.circuits.publicKey( { ...this.circuitContext, currentZswapLocalState: sender From 565449bda8377cdab38d7bd9bd519122458aa09a Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 7 May 2025 18:17:00 -0500 Subject: [PATCH 036/202] add ownable tests --- contracts/ownable/src/test/Ownable.test.ts | 96 +++++++++++++++++++++- 1 file changed, 94 insertions(+), 2 deletions(-) diff --git a/contracts/ownable/src/test/Ownable.test.ts b/contracts/ownable/src/test/Ownable.test.ts index ce175d87..f6e18c2c 100644 --- a/contracts/ownable/src/test/Ownable.test.ts +++ b/contracts/ownable/src/test/Ownable.test.ts @@ -1,4 +1,4 @@ -import { CoinPublicKey, encodeCoinPublicKey } from '@midnight-ntwrk/compact-runtime'; +import { CoinPublicKey, convert_bigint_to_Uint8Array, convert_Uint8Array_to_bigint } from '@midnight-ntwrk/compact-runtime'; import { OwnableSimulator } from './simulators/OwnableSimulator'; import * as utils from './utils/address'; @@ -18,16 +18,108 @@ const Z_RECIPIENT = utils.createEitherTestUser('RECIPIENT'); const Z_SPENDER = utils.createEitherTestUser('SPENDER'); const Z_OTHER = utils.createEitherTestUser('OTHER'); const EMPTY_BYTES = utils.ZERO_KEY.left.bytes; +const BAD_SECRET_KEY = convert_bigint_to_Uint8Array(32, 123456789n); let ownable: OwnableSimulator; let caller: CoinPublicKey; +let ownerSK: Uint8Array; describe('Ownable', () => { describe('initializer', () => { it('should initialize and set the caller as owner', () => { caller = OWNER; ownable = new OwnableSimulator(OWNER); - //expect(ownable.owner()).not.toEqual(utils.ZERO_ADDRESS); + expect(ownable.owner()).not.toEqual(utils.ZERO_ADDRESS); + expect(ownable.getCurrentPublicState().ownable_Instance).toEqual(1n); + }); + }); + + beforeEach(() => { + ownable = new OwnableSimulator(OWNER); + ownerSK = ownable.getCurrentPrivateState().secretKey; + }); + + describe('assertOnlyOwner', () => { + it('should allow owner to call', () => { + ownable.assertOnlyOwner(); + }); + + it('should fail with unauthorized caller', () => { + // Change secret key in witness context + ownable.setWitnessContext(BAD_SECRET_KEY); + + expect(() => { + ownable.assertOnlyOwner() + }).toThrow('Ownable: not owner'); + }); + + it('should handle owner → not-owner → owner calls', () => { + // Owner + ownable.assertOnlyOwner(); + + // Not owner + ownable.setWitnessContext(BAD_SECRET_KEY); + expect(() => { + ownable.assertOnlyOwner() + }).toThrow('Ownable: not owner'); + + // Owner + ownable.setWitnessContext(ownerSK); + ownable.assertOnlyOwner(); + }); + }); + + describe('renounceOwnership', () => { + it('should renounce ownership', () => { + ownable.renounceOwnership(); + expect(ownable.owner()).toEqual(EMPTY_BYTES); + + // Check that original owner can no longer call protected circuits + expect(() => { + ownable.assertOnlyOwner() + }).toThrow('Ownable: not owner'); + }); + + it('should not renounce ownership from non-owner', () => { + ownable.setWitnessContext(BAD_SECRET_KEY); + expect(() => { + ownable.renounceOwnership(); + }).toThrow('Ownable: not owner'); + }); + + it('should not renounce ownership from non-owner', () => { + ownable.setWitnessContext(BAD_SECRET_KEY); + expect(() => { + ownable.renounceOwnership(); + }).toThrow('Ownable: not owner'); + }); + + it('should not renounce ownership more than once', () => { + ownable.renounceOwnership(); + + expect(() => { + ownable.renounceOwnership(); + }).toThrow('Ownable: not owner'); }); }); }); + + +//const sk = ownable.getCurrentPrivateState().secretKey; +//console.log("skkkkkk", sk); +// +//ownable.setWitnessContext(BAD_SECRET_KEY); +////ownable.owner2(); +// +//expect(ownable.owner()).not.toEqual(utils.ZERO_ADDRESS); +//console.log("ownerrrr after", ownable.owner()); +// +//expect(() => { +// ownable.assertOnlyOwner() +//}).toThrow('Ownable: not owner'); +// +//ownable.setWitnessContext(sk); +//expect(() => { +// ownable.assertOnlyOwner() +//}).toThrow('Ownable: not owner'); +// \ No newline at end of file From 98ffd94dd6b21f878ddcbb93da6f9a39b22873ed Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 12 May 2025 00:36:39 -0500 Subject: [PATCH 037/202] add ownablePK --- contracts/ownable/src/OwnablePK.compact | 102 +++++++ contracts/ownable/src/test/OwnablePK.test.ts | 286 ++++++++++++++++++ .../src/test/mocks/MockOwnablePK.compact | 58 ++++ .../src/test/simulators/OwnablePKSimulator.ts | 183 +++++++++++ 4 files changed, 629 insertions(+) create mode 100644 contracts/ownable/src/OwnablePK.compact create mode 100644 contracts/ownable/src/test/OwnablePK.test.ts create mode 100644 contracts/ownable/src/test/mocks/MockOwnablePK.compact create mode 100644 contracts/ownable/src/test/simulators/OwnablePKSimulator.ts diff --git a/contracts/ownable/src/OwnablePK.compact b/contracts/ownable/src/OwnablePK.compact new file mode 100644 index 00000000..a9eea15d --- /dev/null +++ b/contracts/ownable/src/OwnablePK.compact @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT + +pragma language_version >= 0.14.0; + +/** + * @module Shielded Ownable Public Key module + * @description Get rekt, losers + */ +module OwnablePK { + import CompactStandardLibrary; + + /// Public state + export ledger _owner: Bytes<32>; + export ledger _pendingOwner: Bytes<32>; + export ledger _instance: Counter; + + /** + * @description Add me... + */ + export circuit initializer(initOwner: ZswapCoinPublicKey): [] { + assert initOwner != burn_address().left "OwnablePK: new owner cannot be zero"; + const nextInstance = _instance + 1 as Field as Bytes<32>; + const shieldedOwner = shieldOwner(initOwner, nextInstance); + _transferOwnership(shieldedOwner); + } + + /** + * @description Add me... + */ + export circuit owner(): Bytes<32> { + return _owner; + } + + /** + * @description Add me... + */ + export circuit pendingOwner(): Bytes<32> { + return _pendingOwner; + } + + /** + * @description Add me... + */ + export circuit transferOwnership(newOwner: ZswapCoinPublicKey): [] { + assertOnlyOwner(); + _proposeOwner(newOwner); + } + + /** + * @description Add me... + */ + export circuit acceptOwnership(): [] { + const caller = own_public_key(); + const nextInstance = _instance + 1 as Field as Bytes<32>; + const shieldedOwner = shieldOwner(caller, nextInstance); + assert shieldedOwner == _pendingOwner "OwnablePK: caller is not pending owner"; + + // Reset pending owner and assign new owner + _transferOwnership(shieldedOwner); + } + + /** + * @description Add me... + */ + export circuit renounceOwnership(): [] { + assertOnlyOwner(); + _transferOwnership(default>); + } + + /** + * @description Add me... + */ + export circuit assertOnlyOwner(): [] { + const caller = own_public_key(); + assert _owner == shieldOwner(caller, _instance as Field as Bytes<32>) "OwnablePK: not owner"; + } + + /** + * @description Add me... + */ + export circuit shieldOwner(ownerPK: ZswapCoinPublicKey, instance: Bytes<32>): Bytes<32> { + return persistent_hash>>([pad(32, "OwnablePK:shield:"), instance, ownerPK.bytes]); + } + + /** + * @description Add me... + */ + export circuit _transferOwnership(newOwner: Bytes<32>): [] { + _pendingOwner = default>; + _instance.increment(1); + _owner = newOwner; + } + + /** + * @description Add me... + */ + export circuit _proposeOwner(newOwner: ZswapCoinPublicKey): [] { + assert newOwner != burn_address().left "OwnablePK: new owner cannot be zero"; + const nextInstance = _instance + 1 as Field as Bytes<32>; + _pendingOwner = shieldOwner(newOwner, nextInstance); + } +} diff --git a/contracts/ownable/src/test/OwnablePK.test.ts b/contracts/ownable/src/test/OwnablePK.test.ts new file mode 100644 index 00000000..6c1583bc --- /dev/null +++ b/contracts/ownable/src/test/OwnablePK.test.ts @@ -0,0 +1,286 @@ +import { CoinPublicKey, convert_bigint_to_Uint8Array, convert_Uint8Array_to_bigint } from '@midnight-ntwrk/compact-runtime'; +import { OwnablePKSimulator } from './simulators/OwnablePKSimulator'; +import * as utils from './utils/address'; + +const OWNER = String(Buffer.from('OWNER', 'ascii').toString('hex')).padStart( + 64, + '0', +); +const NEW_OWNER = String( + Buffer.from('NEW_OWNER', 'ascii').toString('hex'), +).padStart(64, '0'); +const UNAUTHORIZED = String( + Buffer.from('UNAUTHORIZED', 'ascii').toString('hex'), +).padStart(64, '0'); +const Z_ZERO = utils.encodeToPK(''); +const Z_OWNER = utils.encodeToPK('OWNER'); +const Z_NEW_OWNER = utils.encodeToPK('NEW_OWNER'); +const Z_NEW_NEW_OWNER = utils.encodeToPK('Z_NEW_NEW_OWNER'); +const EMPTY_BYTES = utils.ZERO_KEY.left.bytes; + +let ownable: OwnablePKSimulator; +let caller: CoinPublicKey; +let ownerSK: Uint8Array; + +describe('OwnablePK', () => { + describe('initializer', () => { + it('should initialize and set the shielded owner', () => { + ownable = new OwnablePKSimulator(Z_OWNER, OWNER); + + // Check instance + const instance = ownable.getCurrentPublicState().ownablePK_Instance; + expect(instance).toEqual(1n); + + // Check shielded owner + const expOwner = ownable.shieldOwner(Z_OWNER, convert_bigint_to_Uint8Array(32, instance)); + expect(ownable.owner()).toEqual(expOwner); + + // Check pending owner + const pendingOwner = ownable.getCurrentPublicState().ownablePK_PendingOwner; + expect(pendingOwner).toEqual(EMPTY_BYTES) + }); + + it('should fail when initializing owner as zero', () => { + expect(() => { + ownable = new OwnablePKSimulator(utils.ZERO_KEY.left, OWNER); + }).toThrow('OwnablePK: new owner cannot be zero'); + }); + }); + + describe('with owner set', () => { + beforeEach(() => { + ownable = new OwnablePKSimulator(Z_OWNER, OWNER); + }); + + describe('owner', () => { + it('should return correct owner', () => { + expect(ownable.owner()).toEqual(ownable.getCurrentPublicState().ownablePK_Owner); + }); + + it('should return no owner', () => { + // Set owner to zero + ownable._transferOwnership(EMPTY_BYTES); + expect(ownable.owner()).toEqual(EMPTY_BYTES); + }); + }); + + describe('pendingOwner', () => { + it('should return pending owner', () => { + const nextInstance = ownable.getCurrentPublicState().ownablePK_Instance + 1n; + const expPending = ownable.shieldOwner(Z_NEW_OWNER, convert_bigint_to_Uint8Array(32, nextInstance)); + ownable._proposeOwner(Z_NEW_OWNER); + expect(ownable.pendingOwner()).toEqual(expPending); + }); + + it('should return no pending owner', () => { + expect(ownable.pendingOwner()).toEqual(EMPTY_BYTES); + }); + }); + + describe('transferOwnership', () => { + it('should start two-step transfer', () => { + caller = OWNER; + + ownable.transferOwnership(Z_NEW_OWNER, caller); + + // Check pending owner + const nextInstance = ownable.getCurrentPublicState().ownablePK_Instance + 1n; + const expPending = ownable.shieldOwner(Z_NEW_OWNER, convert_bigint_to_Uint8Array(32, nextInstance)); + expect(ownable.pendingOwner()).toEqual(expPending); + + // Check current owner + const thisInstance = ownable.getCurrentPublicState().ownablePK_Instance; + const expOwner = ownable.shieldOwner(Z_OWNER, convert_bigint_to_Uint8Array(32, thisInstance)); + expect(ownable.owner()).toEqual(expOwner); + }); + + it('should not transfer zero as owner', () => { + caller = OWNER; + + expect(() => { + ownable.transferOwnership(Z_ZERO, caller); + }).toThrow('OwnablePK: new owner cannot be zero'); + }); + + it('should not transfer owner from unauthorized caller', () => { + caller = UNAUTHORIZED; + + expect(() => { + ownable.transferOwnership(Z_NEW_OWNER, caller); + }).toThrow('OwnablePK: not owner'); + }); + + it('should overwrite pending owner with new owner', () => { + caller = OWNER; + + ownable.transferOwnership(Z_NEW_OWNER, caller); + ownable.transferOwnership(Z_NEW_NEW_OWNER, caller); + + // Check new pending owner + const nextInstance = ownable.getCurrentPublicState().ownablePK_Instance + 1n; + const expPending = ownable.shieldOwner(Z_NEW_NEW_OWNER, convert_bigint_to_Uint8Array(32, nextInstance)); + expect(ownable.pendingOwner()).toEqual(expPending); + }); + }); + + describe('acceptOwnership', () => { + describe('when owner is pending', () => { + beforeEach(() => { + ownable._proposeOwner(Z_NEW_OWNER); + }); + + it('should accept ownership from pending owner', () => { + caller = NEW_OWNER; + const beforeInstance = ownable.getCurrentPublicState().ownablePK_Instance; + + ownable.acceptOwnership(caller); + + // Check instance is bumped + const afterInstance = ownable.getCurrentPublicState().ownablePK_Instance; + expect(afterInstance).toEqual(beforeInstance + 1n); + + // Check new owner + const expOwner = ownable.shieldOwner(Z_NEW_OWNER, convert_bigint_to_Uint8Array(32, afterInstance)); + expect(ownable.owner()).toEqual(expOwner); + + // Check pending owner is reset + expect(ownable.pendingOwner()).toEqual(EMPTY_BYTES); + }); + + it('should not accept ownership from unauthorized', () => { + caller = UNAUTHORIZED; + + expect(() => { + ownable.acceptOwnership(caller); + }).toThrow('OwnablePK: caller is not pending owner'); + }); + + it('should not accept ownership from current owner', () => { + caller = OWNER; + + expect(() => { + ownable.acceptOwnership(caller); + }).toThrow('OwnablePK: caller is not pending owner'); + }); + + it('should not accept ownership from previous owner', () => { + caller = NEW_OWNER; + // Sets new owner + ownable.acceptOwnership(caller); + + // New owner proposes another new owner + ownable.transferOwnership(Z_NEW_NEW_OWNER, caller); + + // Initial owner tries to accept + caller = OWNER; + expect(() => { + ownable.acceptOwnership(caller); + }).toThrow('OwnablePK: caller is not pending owner') + }); + }); + }); + + describe('renounceOwnership', () => { + it('should renounce ownership', () => { + caller = OWNER; + const beforeInstance = ownable.getCurrentPublicState().ownablePK_Instance; + ownable.renounceOwnership(caller); + + expect(ownable.owner()).toEqual(EMPTY_BYTES); + expect(ownable.pendingOwner()).toEqual(EMPTY_BYTES); + expect(ownable.getCurrentPublicState().ownablePK_Instance).toEqual(beforeInstance + 1n); + }); + + it('should not renounce from unauthorized', () => { + caller = UNAUTHORIZED; + expect(() => { + ownable.renounceOwnership(caller); + }).toThrow('OwnablePK: not owner'); + }); + }); + + describe('assertOnlyOwner', () => { + it('should allow owner to call', () => { + caller = OWNER; + + ownable.assertOnlyOwner(caller); + }); + + it('should not allow unauthorized to call', () => { + caller = UNAUTHORIZED; + expect(() => { + ownable.assertOnlyOwner(caller); + }).toThrow('OwnablePK: not owner'); + }); + + it('should update who can and cannot call', () => { + caller = OWNER; + ownable.assertOnlyOwner(caller); + + caller = NEW_OWNER; + expect(() => { + ownable.assertOnlyOwner(caller); + }).toThrow('OwnablePK: not owner'); + + // Transfer to new owner + const nextInstance = ownable.getCurrentPublicState().ownablePK_Instance + 1n; + const newOwner = ownable.shieldOwner(Z_NEW_OWNER, convert_bigint_to_Uint8Array(32, nextInstance)); + ownable._transferOwnership(newOwner); + + caller = NEW_OWNER; + ownable.assertOnlyOwner(caller); + + caller = OWNER; + expect(() => { + ownable.assertOnlyOwner(caller); + }).toThrow('OwnablePK: not owner'); + }); + }); + + describe('shieldOwner', () => { + it.skip('should hash owner correctly', () => { + const instance = convert_bigint_to_Uint8Array(32, 123n); + const expHash = ownable.shieldOwner(Z_OWNER, instance); + // TODO add matching algo in js + }); + }); + + describe('_transferOwnership', () => { + it('should transfer ownership', () => { + const beforeInstance = ownable.getCurrentPublicState().ownablePK_Instance; + ownable._proposeOwner(Z_NEW_NEW_OWNER); + + ownable._transferOwnership(Z_NEW_OWNER.bytes); + + // _transferownership does not shield the input so it should be a == a + expect(ownable.owner()).toEqual(Z_NEW_OWNER.bytes); + // Check instance is bumped + expect(ownable.getCurrentPublicState().ownablePK_Instance).toEqual(beforeInstance + 1n); + // Check pending owner is reset + expect(ownable.pendingOwner()).toEqual(EMPTY_BYTES); + }); + + it('should transfer ownership to zero', () => { + // _transfer does not shield the input so it should be a == a + ownable._transferOwnership(EMPTY_BYTES); + expect(ownable.owner()).toEqual(EMPTY_BYTES); + }); + }); + + describe('proposeOwner', () => { + it('should propose owner', () => { + ownable._proposeOwner(Z_NEW_OWNER); + + const nextInstance = ownable.getCurrentPublicState().ownablePK_Instance + 1n; + const expOwner = ownable.shieldOwner(Z_NEW_OWNER, convert_bigint_to_Uint8Array(32, nextInstance)); + expect(ownable.pendingOwner()).toEqual(expOwner); + }); + + it('should not propose zero as owner', () => { + expect(() => { + ownable._proposeOwner(utils.ZERO_KEY.left); + }).toThrow('OwnablePK: new owner cannot be zero'); + }); + }); + }); +}); diff --git a/contracts/ownable/src/test/mocks/MockOwnablePK.compact b/contracts/ownable/src/test/mocks/MockOwnablePK.compact new file mode 100644 index 00000000..5b39fc36 --- /dev/null +++ b/contracts/ownable/src/test/mocks/MockOwnablePK.compact @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT + +pragma language_version >= 0.14.0; + +import CompactStandardLibrary; +import "../../OwnablePK" prefix OwnablePK_; + +export { ZswapCoinPublicKey, ContractAddress, Either, Maybe }; +export { OwnablePK__owner, OwnablePK__pendingOwner, OwnablePK__instance }; + +constructor(initOwner: ZswapCoinPublicKey) { + OwnablePK_initializer(initOwner); +} + +export circuit owner(): Bytes<32> { + return OwnablePK_owner(); +} + +export circuit pendingOwner(): Bytes<32> { + return OwnablePK_pendingOwner(); +} + +export circuit transferOwnership(newOwner: ZswapCoinPublicKey): [] { + return OwnablePK_transferOwnership(newOwner); +} + +export circuit acceptOwnership(): [] { + return OwnablePK_acceptOwnership(); +} + +export circuit renounceOwnership(): [] { + return OwnablePK_renounceOwnership(); +} + +export circuit assertOnlyOwner(): [] { + return OwnablePK_assertOnlyOwner(); +} + +export circuit shieldOwner(ownerPK: ZswapCoinPublicKey, instance: Bytes<32>): Bytes<32> { + return OwnablePK_shieldOwner(ownerPK, instance); +} + +export circuit _transferOwnership(newOwner: Bytes<32>): [] { + return OwnablePK__transferOwnership(newOwner); +} + +export circuit _proposeOwner(newOwner: ZswapCoinPublicKey): [] { + return OwnablePK__proposeOwner(newOwner); +} + +export circuit thing(): Bytes<32> { + const f = kernel.self(); + return f.bytes; +} + +export circuit thing2(): ContractAddress { + return kernel.self(); +} diff --git a/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts new file mode 100644 index 00000000..64780a02 --- /dev/null +++ b/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts @@ -0,0 +1,183 @@ +import { + type CircuitContext, + CoinPublicKey, + type ContractState, + QueryContext, + constructorContext, + emptyZswapLocalState, +} from '@midnight-ntwrk/compact-runtime'; +import { sampleContractAddress } from '@midnight-ntwrk/zswap'; +import { + type Ledger, + Contract as MockOwnable, + ledger, +} from '../../artifacts/MockOwnablePK/contract/index.cjs'; // Combined imports +import { + OwnablePrivateState, + OwnableWitnesses, + SetWitnessContext, +} from '../../witnesses/OwnableWitnesses'; +import type { IContractSimulator } from '../types/test'; +import { useCircuitContextSender } from '../utils/test'; +import { ZswapCoinPublicKey } from '../../artifacts/MockOwnable/contract/index.cjs'; + +/** + * @description A simulator implementation of a contract for testing purposes. + * @template P - The private state type, fixed to OwnablePrivateState. + * @template L - The ledger type, fixed to Contract.Ledger. + */ +export class OwnablePKSimulator + implements IContractSimulator +{ + /** @description The underlying contract instance managing contract logic. */ + readonly contract: MockOwnable; + + /** @description The deployed address of the contract. */ + readonly contractAddress: string; + + /** @description The deployer address of the contract. */ + readonly deployer: CoinPublicKey; + + /** @description The current circuit context, updated by contract operations. */ + circuitContext: CircuitContext; + + /** + * @description Initializes the mock contract. + */ + constructor(initOwner: ZswapCoinPublicKey, deployer: CoinPublicKey) { + this.contract = new MockOwnable( + OwnableWitnesses(), + ); + this.deployer = deployer; + const { + currentPrivateState, + currentContractState, + currentZswapLocalState, + } = this.contract.initialState( + constructorContext(OwnablePrivateState.generate(), deployer), + initOwner + ); + this.circuitContext = { + currentPrivateState, + currentZswapLocalState, + originalState: currentContractState, + transactionContext: new QueryContext( + currentContractState.data, + sampleContractAddress(), + ), + }; + this.contractAddress = this.circuitContext.transactionContext.address; + } + + /** + * @description Retrieves the current public ledger state of the contract. + * @returns The ledger state as defined by the contract. + */ + public getCurrentPublicState(): Ledger { + return ledger(this.circuitContext.transactionContext.state); + } + + /** + * @description Retrieves the current private state of the contract. + * @returns The private state of type OwnablePrivateState. + */ + public getCurrentPrivateState(): OwnablePrivateState { + return this.circuitContext.currentPrivateState; + } + + /** + * @description Retrieves the current contract state. + * @returns The contract state object. + */ + public getCurrentContractState(): ContractState { + return this.circuitContext.originalState; + } + + public owner(): Uint8Array { + return this.contract.impureCircuits.owner(this.circuitContext).result; + } + + public pendingOwner(): Uint8Array { + return this.contract.impureCircuits.pendingOwner(this.circuitContext).result; + } + + public transferOwnership(newOwner: ZswapCoinPublicKey, sender: CoinPublicKey): CircuitContext { + const res = this.contract.impureCircuits.transferOwnership( + { + ...this.circuitContext, + currentZswapLocalState: sender + ? emptyZswapLocalState(sender) + : this.circuitContext.currentZswapLocalState, + }, + newOwner, + ); + + this.circuitContext = res.context; + return this.circuitContext; + } + + public acceptOwnership(sender: CoinPublicKey): CircuitContext { + const res = this.contract.impureCircuits.acceptOwnership( + { + ...this.circuitContext, + currentZswapLocalState: sender + ? emptyZswapLocalState(sender) + : this.circuitContext.currentZswapLocalState, + }, + ); + + this.circuitContext = res.context; + return this.circuitContext; + } + + public renounceOwnership(sender: CoinPublicKey): CircuitContext { + const res = this.contract.impureCircuits.renounceOwnership( + { + ...this.circuitContext, + currentZswapLocalState: sender + ? emptyZswapLocalState(sender) + : this.circuitContext.currentZswapLocalState, + }, + ); + + this.circuitContext = res.context; + return this.circuitContext; + } + + public assertOnlyOwner(sender: CoinPublicKey): CircuitContext { + const res = this.contract.impureCircuits.assertOnlyOwner( + { + ...this.circuitContext, + currentZswapLocalState: sender + ? emptyZswapLocalState(sender) + : this.circuitContext.currentZswapLocalState, + }, + ); + + this.circuitContext = res.context; + return this.circuitContext; + } + + public shieldOwner(ownerPK: ZswapCoinPublicKey, instance: Uint8Array): Uint8Array { + return this.contract.circuits.shieldOwner(this.circuitContext, ownerPK, instance).result + } + + public _transferOwnership(newOwner: Uint8Array): CircuitContext { + this.circuitContext = this.contract.impureCircuits._transferOwnership(this.circuitContext, newOwner).context; + return this.circuitContext; + } + + public _proposeOwner(newOwner: ZswapCoinPublicKey): CircuitContext { + this.circuitContext = this.contract.impureCircuits._proposeOwner(this.circuitContext, newOwner).context; + return this.circuitContext; + } + + public thing(): Uint8Array { + return this.contract.impureCircuits.thing(this.circuitContext).result; + } + + public thing2() { + return this.contract.impureCircuits.thing2(this.circuitContext).result; + } + +} From 7a73e927deaf670ce46341c71059eeee6d614821 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 12 May 2025 00:37:13 -0500 Subject: [PATCH 038/202] clean up mock and sim --- contracts/ownable/src/test/mocks/MockOwnablePK.compact | 9 --------- .../ownable/src/test/simulators/OwnablePKSimulator.ts | 9 --------- 2 files changed, 18 deletions(-) diff --git a/contracts/ownable/src/test/mocks/MockOwnablePK.compact b/contracts/ownable/src/test/mocks/MockOwnablePK.compact index 5b39fc36..1dcc9de1 100644 --- a/contracts/ownable/src/test/mocks/MockOwnablePK.compact +++ b/contracts/ownable/src/test/mocks/MockOwnablePK.compact @@ -47,12 +47,3 @@ export circuit _transferOwnership(newOwner: Bytes<32>): [] { export circuit _proposeOwner(newOwner: ZswapCoinPublicKey): [] { return OwnablePK__proposeOwner(newOwner); } - -export circuit thing(): Bytes<32> { - const f = kernel.self(); - return f.bytes; -} - -export circuit thing2(): ContractAddress { - return kernel.self(); -} diff --git a/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts index 64780a02..04934c46 100644 --- a/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts @@ -171,13 +171,4 @@ export class OwnablePKSimulator this.circuitContext = this.contract.impureCircuits._proposeOwner(this.circuitContext, newOwner).context; return this.circuitContext; } - - public thing(): Uint8Array { - return this.contract.impureCircuits.thing(this.circuitContext).result; - } - - public thing2() { - return this.contract.impureCircuits.thing2(this.circuitContext).result; - } - } From 60de48e85fefdd20b5448118cba13f8b632d6947 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 12 May 2025 01:06:30 -0500 Subject: [PATCH 039/202] fix import --- contracts/ownable/src/test/utils/address.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/ownable/src/test/utils/address.ts b/contracts/ownable/src/test/utils/address.ts index 3580e196..d061e4b2 100644 --- a/contracts/ownable/src/test/utils/address.ts +++ b/contracts/ownable/src/test/utils/address.ts @@ -3,7 +3,7 @@ import { encodeCoinPublicKey, } from '@midnight-ntwrk/compact-runtime'; import { encodeContractAddress } from '@midnight-ntwrk/ledger'; -import type * as Compact from '../../artifacts/MockERC20/contract/index.cjs'; +import type * as Compact from '../../artifacts/MockOwnablePK/contract/index.cjs'; const PREFIX_ADDRESS = '0200'; From bf94cde37f04a70a035b51e4d4eca950f84f30b0 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 12 May 2025 01:06:57 -0500 Subject: [PATCH 040/202] fix fmt --- compact/package.json | 5 +- contracts/ownable/src/test/Ownable.test.ts | 15 +- contracts/ownable/src/test/OwnablePK.test.ts | 92 +++++++++--- .../src/test/simulators/OwnablePKSimulator.ts | 137 ++++++++++-------- .../src/test/simulators/OwnableSimulator.ts | 28 ++-- .../ownable/src/witnesses/OwnableWitnesses.ts | 74 +++++----- contracts/ownable/src/witnesses/interface.ts | 4 +- 7 files changed, 212 insertions(+), 143 deletions(-) diff --git a/compact/package.json b/compact/package.json index 15b7595a..84ede8f5 100644 --- a/compact/package.json +++ b/compact/package.json @@ -2,10 +2,7 @@ "packageManager": "yarn@4.1.0", "name": "@openzeppelin-midnight/compact", "version": "0.0.1", - "keywords": [ - "compact", - "compiler" - ], + "keywords": ["compact", "compiler"], "author": "OpenZeppelin Community ", "license": "MIT", "description": "Compact fetcher", diff --git a/contracts/ownable/src/test/Ownable.test.ts b/contracts/ownable/src/test/Ownable.test.ts index f6e18c2c..09c27268 100644 --- a/contracts/ownable/src/test/Ownable.test.ts +++ b/contracts/ownable/src/test/Ownable.test.ts @@ -1,4 +1,8 @@ -import { CoinPublicKey, convert_bigint_to_Uint8Array, convert_Uint8Array_to_bigint } from '@midnight-ntwrk/compact-runtime'; +import { + CoinPublicKey, + convert_bigint_to_Uint8Array, + convert_Uint8Array_to_bigint, +} from '@midnight-ntwrk/compact-runtime'; import { OwnableSimulator } from './simulators/OwnableSimulator'; import * as utils from './utils/address'; @@ -49,7 +53,7 @@ describe('Ownable', () => { ownable.setWitnessContext(BAD_SECRET_KEY); expect(() => { - ownable.assertOnlyOwner() + ownable.assertOnlyOwner(); }).toThrow('Ownable: not owner'); }); @@ -60,7 +64,7 @@ describe('Ownable', () => { // Not owner ownable.setWitnessContext(BAD_SECRET_KEY); expect(() => { - ownable.assertOnlyOwner() + ownable.assertOnlyOwner(); }).toThrow('Ownable: not owner'); // Owner @@ -76,7 +80,7 @@ describe('Ownable', () => { // Check that original owner can no longer call protected circuits expect(() => { - ownable.assertOnlyOwner() + ownable.assertOnlyOwner(); }).toThrow('Ownable: not owner'); }); @@ -104,7 +108,6 @@ describe('Ownable', () => { }); }); - //const sk = ownable.getCurrentPrivateState().secretKey; //console.log("skkkkkk", sk); // @@ -122,4 +125,4 @@ describe('Ownable', () => { //expect(() => { // ownable.assertOnlyOwner() //}).toThrow('Ownable: not owner'); -// \ No newline at end of file +// diff --git a/contracts/ownable/src/test/OwnablePK.test.ts b/contracts/ownable/src/test/OwnablePK.test.ts index 6c1583bc..640b79a3 100644 --- a/contracts/ownable/src/test/OwnablePK.test.ts +++ b/contracts/ownable/src/test/OwnablePK.test.ts @@ -1,4 +1,8 @@ -import { CoinPublicKey, convert_bigint_to_Uint8Array, convert_Uint8Array_to_bigint } from '@midnight-ntwrk/compact-runtime'; +import { + CoinPublicKey, + convert_bigint_to_Uint8Array, + convert_Uint8Array_to_bigint, +} from '@midnight-ntwrk/compact-runtime'; import { OwnablePKSimulator } from './simulators/OwnablePKSimulator'; import * as utils from './utils/address'; @@ -32,12 +36,16 @@ describe('OwnablePK', () => { expect(instance).toEqual(1n); // Check shielded owner - const expOwner = ownable.shieldOwner(Z_OWNER, convert_bigint_to_Uint8Array(32, instance)); + const expOwner = ownable.shieldOwner( + Z_OWNER, + convert_bigint_to_Uint8Array(32, instance), + ); expect(ownable.owner()).toEqual(expOwner); // Check pending owner - const pendingOwner = ownable.getCurrentPublicState().ownablePK_PendingOwner; - expect(pendingOwner).toEqual(EMPTY_BYTES) + const pendingOwner = + ownable.getCurrentPublicState().ownablePK_PendingOwner; + expect(pendingOwner).toEqual(EMPTY_BYTES); }); it('should fail when initializing owner as zero', () => { @@ -54,7 +62,9 @@ describe('OwnablePK', () => { describe('owner', () => { it('should return correct owner', () => { - expect(ownable.owner()).toEqual(ownable.getCurrentPublicState().ownablePK_Owner); + expect(ownable.owner()).toEqual( + ownable.getCurrentPublicState().ownablePK_Owner, + ); }); it('should return no owner', () => { @@ -66,8 +76,12 @@ describe('OwnablePK', () => { describe('pendingOwner', () => { it('should return pending owner', () => { - const nextInstance = ownable.getCurrentPublicState().ownablePK_Instance + 1n; - const expPending = ownable.shieldOwner(Z_NEW_OWNER, convert_bigint_to_Uint8Array(32, nextInstance)); + const nextInstance = + ownable.getCurrentPublicState().ownablePK_Instance + 1n; + const expPending = ownable.shieldOwner( + Z_NEW_OWNER, + convert_bigint_to_Uint8Array(32, nextInstance), + ); ownable._proposeOwner(Z_NEW_OWNER); expect(ownable.pendingOwner()).toEqual(expPending); }); @@ -84,13 +98,20 @@ describe('OwnablePK', () => { ownable.transferOwnership(Z_NEW_OWNER, caller); // Check pending owner - const nextInstance = ownable.getCurrentPublicState().ownablePK_Instance + 1n; - const expPending = ownable.shieldOwner(Z_NEW_OWNER, convert_bigint_to_Uint8Array(32, nextInstance)); + const nextInstance = + ownable.getCurrentPublicState().ownablePK_Instance + 1n; + const expPending = ownable.shieldOwner( + Z_NEW_OWNER, + convert_bigint_to_Uint8Array(32, nextInstance), + ); expect(ownable.pendingOwner()).toEqual(expPending); // Check current owner const thisInstance = ownable.getCurrentPublicState().ownablePK_Instance; - const expOwner = ownable.shieldOwner(Z_OWNER, convert_bigint_to_Uint8Array(32, thisInstance)); + const expOwner = ownable.shieldOwner( + Z_OWNER, + convert_bigint_to_Uint8Array(32, thisInstance), + ); expect(ownable.owner()).toEqual(expOwner); }); @@ -117,8 +138,12 @@ describe('OwnablePK', () => { ownable.transferOwnership(Z_NEW_NEW_OWNER, caller); // Check new pending owner - const nextInstance = ownable.getCurrentPublicState().ownablePK_Instance + 1n; - const expPending = ownable.shieldOwner(Z_NEW_NEW_OWNER, convert_bigint_to_Uint8Array(32, nextInstance)); + const nextInstance = + ownable.getCurrentPublicState().ownablePK_Instance + 1n; + const expPending = ownable.shieldOwner( + Z_NEW_NEW_OWNER, + convert_bigint_to_Uint8Array(32, nextInstance), + ); expect(ownable.pendingOwner()).toEqual(expPending); }); }); @@ -131,16 +156,21 @@ describe('OwnablePK', () => { it('should accept ownership from pending owner', () => { caller = NEW_OWNER; - const beforeInstance = ownable.getCurrentPublicState().ownablePK_Instance; + const beforeInstance = + ownable.getCurrentPublicState().ownablePK_Instance; ownable.acceptOwnership(caller); // Check instance is bumped - const afterInstance = ownable.getCurrentPublicState().ownablePK_Instance; + const afterInstance = + ownable.getCurrentPublicState().ownablePK_Instance; expect(afterInstance).toEqual(beforeInstance + 1n); // Check new owner - const expOwner = ownable.shieldOwner(Z_NEW_OWNER, convert_bigint_to_Uint8Array(32, afterInstance)); + const expOwner = ownable.shieldOwner( + Z_NEW_OWNER, + convert_bigint_to_Uint8Array(32, afterInstance), + ); expect(ownable.owner()).toEqual(expOwner); // Check pending owner is reset @@ -175,7 +205,7 @@ describe('OwnablePK', () => { caller = OWNER; expect(() => { ownable.acceptOwnership(caller); - }).toThrow('OwnablePK: caller is not pending owner') + }).toThrow('OwnablePK: caller is not pending owner'); }); }); }); @@ -183,12 +213,15 @@ describe('OwnablePK', () => { describe('renounceOwnership', () => { it('should renounce ownership', () => { caller = OWNER; - const beforeInstance = ownable.getCurrentPublicState().ownablePK_Instance; + const beforeInstance = + ownable.getCurrentPublicState().ownablePK_Instance; ownable.renounceOwnership(caller); expect(ownable.owner()).toEqual(EMPTY_BYTES); expect(ownable.pendingOwner()).toEqual(EMPTY_BYTES); - expect(ownable.getCurrentPublicState().ownablePK_Instance).toEqual(beforeInstance + 1n); + expect(ownable.getCurrentPublicState().ownablePK_Instance).toEqual( + beforeInstance + 1n, + ); }); it('should not renounce from unauthorized', () => { @@ -223,8 +256,12 @@ describe('OwnablePK', () => { }).toThrow('OwnablePK: not owner'); // Transfer to new owner - const nextInstance = ownable.getCurrentPublicState().ownablePK_Instance + 1n; - const newOwner = ownable.shieldOwner(Z_NEW_OWNER, convert_bigint_to_Uint8Array(32, nextInstance)); + const nextInstance = + ownable.getCurrentPublicState().ownablePK_Instance + 1n; + const newOwner = ownable.shieldOwner( + Z_NEW_OWNER, + convert_bigint_to_Uint8Array(32, nextInstance), + ); ownable._transferOwnership(newOwner); caller = NEW_OWNER; @@ -247,7 +284,8 @@ describe('OwnablePK', () => { describe('_transferOwnership', () => { it('should transfer ownership', () => { - const beforeInstance = ownable.getCurrentPublicState().ownablePK_Instance; + const beforeInstance = + ownable.getCurrentPublicState().ownablePK_Instance; ownable._proposeOwner(Z_NEW_NEW_OWNER); ownable._transferOwnership(Z_NEW_OWNER.bytes); @@ -255,7 +293,9 @@ describe('OwnablePK', () => { // _transferownership does not shield the input so it should be a == a expect(ownable.owner()).toEqual(Z_NEW_OWNER.bytes); // Check instance is bumped - expect(ownable.getCurrentPublicState().ownablePK_Instance).toEqual(beforeInstance + 1n); + expect(ownable.getCurrentPublicState().ownablePK_Instance).toEqual( + beforeInstance + 1n, + ); // Check pending owner is reset expect(ownable.pendingOwner()).toEqual(EMPTY_BYTES); }); @@ -271,8 +311,12 @@ describe('OwnablePK', () => { it('should propose owner', () => { ownable._proposeOwner(Z_NEW_OWNER); - const nextInstance = ownable.getCurrentPublicState().ownablePK_Instance + 1n; - const expOwner = ownable.shieldOwner(Z_NEW_OWNER, convert_bigint_to_Uint8Array(32, nextInstance)); + const nextInstance = + ownable.getCurrentPublicState().ownablePK_Instance + 1n; + const expOwner = ownable.shieldOwner( + Z_NEW_OWNER, + convert_bigint_to_Uint8Array(32, nextInstance), + ); expect(ownable.pendingOwner()).toEqual(expOwner); }); diff --git a/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts index 04934c46..9ce66143 100644 --- a/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts @@ -45,9 +45,7 @@ export class OwnablePKSimulator * @description Initializes the mock contract. */ constructor(initOwner: ZswapCoinPublicKey, deployer: CoinPublicKey) { - this.contract = new MockOwnable( - OwnableWitnesses(), - ); + this.contract = new MockOwnable(OwnableWitnesses()); this.deployer = deployer; const { currentPrivateState, @@ -55,7 +53,7 @@ export class OwnablePKSimulator currentZswapLocalState, } = this.contract.initialState( constructorContext(OwnablePrivateState.generate(), deployer), - initOwner + initOwner, ); this.circuitContext = { currentPrivateState, @@ -98,77 +96,98 @@ export class OwnablePKSimulator } public pendingOwner(): Uint8Array { - return this.contract.impureCircuits.pendingOwner(this.circuitContext).result; + return this.contract.impureCircuits.pendingOwner(this.circuitContext) + .result; } - public transferOwnership(newOwner: ZswapCoinPublicKey, sender: CoinPublicKey): CircuitContext { + public transferOwnership( + newOwner: ZswapCoinPublicKey, + sender: CoinPublicKey, + ): CircuitContext { const res = this.contract.impureCircuits.transferOwnership( - { - ...this.circuitContext, - currentZswapLocalState: sender - ? emptyZswapLocalState(sender) - : this.circuitContext.currentZswapLocalState, - }, - newOwner, - ); - - this.circuitContext = res.context; - return this.circuitContext; + { + ...this.circuitContext, + currentZswapLocalState: sender + ? emptyZswapLocalState(sender) + : this.circuitContext.currentZswapLocalState, + }, + newOwner, + ); + + this.circuitContext = res.context; + return this.circuitContext; } - public acceptOwnership(sender: CoinPublicKey): CircuitContext { - const res = this.contract.impureCircuits.acceptOwnership( - { - ...this.circuitContext, - currentZswapLocalState: sender - ? emptyZswapLocalState(sender) - : this.circuitContext.currentZswapLocalState, - }, - ); - - this.circuitContext = res.context; - return this.circuitContext; + public acceptOwnership( + sender: CoinPublicKey, + ): CircuitContext { + const res = this.contract.impureCircuits.acceptOwnership({ + ...this.circuitContext, + currentZswapLocalState: sender + ? emptyZswapLocalState(sender) + : this.circuitContext.currentZswapLocalState, + }); + + this.circuitContext = res.context; + return this.circuitContext; } - public renounceOwnership(sender: CoinPublicKey): CircuitContext { - const res = this.contract.impureCircuits.renounceOwnership( - { - ...this.circuitContext, - currentZswapLocalState: sender - ? emptyZswapLocalState(sender) - : this.circuitContext.currentZswapLocalState, - }, - ); - - this.circuitContext = res.context; - return this.circuitContext; + public renounceOwnership( + sender: CoinPublicKey, + ): CircuitContext { + const res = this.contract.impureCircuits.renounceOwnership({ + ...this.circuitContext, + currentZswapLocalState: sender + ? emptyZswapLocalState(sender) + : this.circuitContext.currentZswapLocalState, + }); + + this.circuitContext = res.context; + return this.circuitContext; } - public assertOnlyOwner(sender: CoinPublicKey): CircuitContext { - const res = this.contract.impureCircuits.assertOnlyOwner( - { - ...this.circuitContext, - currentZswapLocalState: sender - ? emptyZswapLocalState(sender) - : this.circuitContext.currentZswapLocalState, - }, - ); - - this.circuitContext = res.context; - return this.circuitContext; + public assertOnlyOwner( + sender: CoinPublicKey, + ): CircuitContext { + const res = this.contract.impureCircuits.assertOnlyOwner({ + ...this.circuitContext, + currentZswapLocalState: sender + ? emptyZswapLocalState(sender) + : this.circuitContext.currentZswapLocalState, + }); + + this.circuitContext = res.context; + return this.circuitContext; } - public shieldOwner(ownerPK: ZswapCoinPublicKey, instance: Uint8Array): Uint8Array { - return this.contract.circuits.shieldOwner(this.circuitContext, ownerPK, instance).result + public shieldOwner( + ownerPK: ZswapCoinPublicKey, + instance: Uint8Array, + ): Uint8Array { + return this.contract.circuits.shieldOwner( + this.circuitContext, + ownerPK, + instance, + ).result; } - public _transferOwnership(newOwner: Uint8Array): CircuitContext { - this.circuitContext = this.contract.impureCircuits._transferOwnership(this.circuitContext, newOwner).context; + public _transferOwnership( + newOwner: Uint8Array, + ): CircuitContext { + this.circuitContext = this.contract.impureCircuits._transferOwnership( + this.circuitContext, + newOwner, + ).context; return this.circuitContext; } - public _proposeOwner(newOwner: ZswapCoinPublicKey): CircuitContext { - this.circuitContext = this.contract.impureCircuits._proposeOwner(this.circuitContext, newOwner).context; + public _proposeOwner( + newOwner: ZswapCoinPublicKey, + ): CircuitContext { + this.circuitContext = this.contract.impureCircuits._proposeOwner( + this.circuitContext, + newOwner, + ).context; return this.circuitContext; } } diff --git a/contracts/ownable/src/test/simulators/OwnableSimulator.ts b/contracts/ownable/src/test/simulators/OwnableSimulator.ts index 41cb3c27..5a013ff9 100644 --- a/contracts/ownable/src/test/simulators/OwnableSimulator.ts +++ b/contracts/ownable/src/test/simulators/OwnableSimulator.ts @@ -44,9 +44,7 @@ export class OwnableSimulator * @description Initializes the mock contract. */ constructor(deployer: CoinPublicKey) { - this.contract = new MockOwnable( - OwnableWitnesses(), - ); + this.contract = new MockOwnable(OwnableWitnesses()); this.deployer = deployer; const { currentPrivateState, @@ -105,15 +103,22 @@ export class OwnableSimulator } public renounceOwnership(): CircuitContext { - this.circuitContext = this.contract.impureCircuits.renounceOwnership(this.circuitContext).context; + this.circuitContext = this.contract.impureCircuits.renounceOwnership( + this.circuitContext, + ).context; return this.circuitContext; } public assertOnlyOwner(): CircuitContext { - return this.contract.impureCircuits.assertOnlyOwner(this.circuitContext).context; + return this.contract.impureCircuits.assertOnlyOwner(this.circuitContext) + .context; } - public publicKey(sk: Uint8Array, instance: Uint8Array, sender: CoinPublicKey): CircuitContext { + public publicKey( + sk: Uint8Array, + instance: Uint8Array, + sender: CoinPublicKey, + ): CircuitContext { const res = this.contract.circuits.publicKey( { ...this.circuitContext, @@ -122,15 +127,20 @@ export class OwnableSimulator : this.circuitContext.currentZswapLocalState, }, sk, - instance + instance, ); this.circuitContext = res.context; return this.circuitContext; } - public _transferOwnership(newOwner: Uint8Array): CircuitContext { - this.circuitContext = this.contract.impureCircuits._transferOwnership(this.circuitContext, newOwner).context; + public _transferOwnership( + newOwner: Uint8Array, + ): CircuitContext { + this.circuitContext = this.contract.impureCircuits._transferOwnership( + this.circuitContext, + newOwner, + ).context; return this.circuitContext; } } diff --git a/contracts/ownable/src/witnesses/OwnableWitnesses.ts b/contracts/ownable/src/witnesses/OwnableWitnesses.ts index 26350db3..80135c1e 100644 --- a/contracts/ownable/src/witnesses/OwnableWitnesses.ts +++ b/contracts/ownable/src/witnesses/OwnableWitnesses.ts @@ -1,59 +1,57 @@ import { getRandomValues } from 'node:crypto'; import { IOwnableWitnesses } from './interface'; import type { WitnessContext } from '@midnight-ntwrk/compact-runtime'; -import { - type Ledger, - } from '../artifacts/MockOwnable/contract/index.cjs'; // Combined imports +import { type Ledger } from '../artifacts/MockOwnable/contract/index.cjs'; // Combined imports /** * @description Represents the private state of an ownable contract, storing a secret key. */ export type OwnablePrivateState = { - /** @description A 32-byte secret key used for cryptographic operations. */ - secretKey: Buffer; - }; + /** @description A 32-byte secret key used for cryptographic operations. */ + secretKey: Buffer; +}; /** * @description Utility object for managing the private state of an ownable contract. */ export const OwnablePrivateState = { - /** - * @description Generates a new private state with a random secret key. - * @returns A fresh OwnablePrivateState instance. - */ - generate: (): OwnablePrivateState => { - return { secretKey: getRandomValues(Buffer.alloc(32)) }; - }, -} + /** + * @description Generates a new private state with a random secret key. + * @returns A fresh OwnablePrivateState instance. + */ + generate: (): OwnablePrivateState => { + return { secretKey: getRandomValues(Buffer.alloc(32)) }; + }, +}; /** * @description Factory function creating witness implementations for ownable operations. * @returns An object implementing the Witnesses interface for OwnablePrivateState. */ -export const OwnableWitnesses = - (): IOwnableWitnesses => ({ - /** - * @description Retrieves the secret key from the private state. - * @param context - The witness context containing the private state. - * @returns A tuple of the unchanged private state and the secret key as a Uint8Array. - */ - localSecretKey( - context: WitnessContext, - ): [OwnablePrivateState, Uint8Array] { - return [context.privateState, context.privateState.secretKey]; - }, +export const OwnableWitnesses = (): IOwnableWitnesses => ({ + /** + * @description Retrieves the secret key from the private state. + * @param context - The witness context containing the private state. + * @returns A tuple of the unchanged private state and the secret key as a Uint8Array. + */ + localSecretKey( + context: WitnessContext, + ): [OwnablePrivateState, Uint8Array] { + return [context.privateState, context.privateState.secretKey]; + }, }); -export const SetWitnessContext = - (sk: Uint8Array): IOwnableWitnesses => ({ - /** - * @description Retrieves the secret key from the private state. - * @param context - The witness context containing the private state. - * @returns A tuple of the unchanged private state and the passed `sk` as a Uint8Array. - */ - localSecretKey( - context: WitnessContext, - ): [OwnablePrivateState, Uint8Array] { - return [context.privateState, sk]; - }, +export const SetWitnessContext = ( + sk: Uint8Array, +): IOwnableWitnesses => ({ + /** + * @description Retrieves the secret key from the private state. + * @param context - The witness context containing the private state. + * @returns A tuple of the unchanged private state and the passed `sk` as a Uint8Array. + */ + localSecretKey( + context: WitnessContext, + ): [OwnablePrivateState, Uint8Array] { + return [context.privateState, sk]; + }, }); diff --git a/contracts/ownable/src/witnesses/interface.ts b/contracts/ownable/src/witnesses/interface.ts index d264ee9d..18f7635a 100644 --- a/contracts/ownable/src/witnesses/interface.ts +++ b/contracts/ownable/src/witnesses/interface.ts @@ -1,7 +1,5 @@ import type { WitnessContext } from '@midnight-ntwrk/compact-runtime'; -import { - type Ledger, - } from '../artifacts/MockOwnable/contract/index.cjs'; // Combined imports +import { type Ledger } from '../artifacts/MockOwnable/contract/index.cjs'; // Combined imports /** * @description Interface defining the witness methods for ownable operations. From 5c509387f43c4a6600b68c285de6cddb844fef1c Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 12 May 2025 01:10:48 -0500 Subject: [PATCH 041/202] fix lint --- contracts/ownable/src/test/Ownable.test.ts | 19 +------------------ contracts/ownable/src/test/OwnablePK.test.ts | 6 ++---- .../src/test/simulators/OwnablePKSimulator.ts | 6 ++---- .../src/test/simulators/OwnableSimulator.ts | 3 +-- .../ownable/src/witnesses/OwnableWitnesses.ts | 4 ++-- contracts/ownable/src/witnesses/interface.ts | 2 +- 6 files changed, 9 insertions(+), 31 deletions(-) diff --git a/contracts/ownable/src/test/Ownable.test.ts b/contracts/ownable/src/test/Ownable.test.ts index 09c27268..6ee3e8c2 100644 --- a/contracts/ownable/src/test/Ownable.test.ts +++ b/contracts/ownable/src/test/Ownable.test.ts @@ -1,8 +1,4 @@ -import { - CoinPublicKey, - convert_bigint_to_Uint8Array, - convert_Uint8Array_to_bigint, -} from '@midnight-ntwrk/compact-runtime'; +import { convert_bigint_to_Uint8Array } from '@midnight-ntwrk/compact-runtime'; import { OwnableSimulator } from './simulators/OwnableSimulator'; import * as utils from './utils/address'; @@ -10,28 +6,15 @@ const OWNER = String(Buffer.from('OWNER', 'ascii').toString('hex')).padStart( 64, '0', ); -const SPENDER = String( - Buffer.from('SPENDER', 'ascii').toString('hex'), -).padStart(64, '0'); -const UNAUTHORIZED = String( - Buffer.from('UNAUTHORIZED', 'ascii').toString('hex'), -).padStart(64, '0'); -const ZERO = String().padStart(64, '0'); -const Z_OWNER = utils.createEitherTestUser('OWNER'); -const Z_RECIPIENT = utils.createEitherTestUser('RECIPIENT'); -const Z_SPENDER = utils.createEitherTestUser('SPENDER'); -const Z_OTHER = utils.createEitherTestUser('OTHER'); const EMPTY_BYTES = utils.ZERO_KEY.left.bytes; const BAD_SECRET_KEY = convert_bigint_to_Uint8Array(32, 123456789n); let ownable: OwnableSimulator; -let caller: CoinPublicKey; let ownerSK: Uint8Array; describe('Ownable', () => { describe('initializer', () => { it('should initialize and set the caller as owner', () => { - caller = OWNER; ownable = new OwnableSimulator(OWNER); expect(ownable.owner()).not.toEqual(utils.ZERO_ADDRESS); expect(ownable.getCurrentPublicState().ownable_Instance).toEqual(1n); diff --git a/contracts/ownable/src/test/OwnablePK.test.ts b/contracts/ownable/src/test/OwnablePK.test.ts index 640b79a3..f96a0acf 100644 --- a/contracts/ownable/src/test/OwnablePK.test.ts +++ b/contracts/ownable/src/test/OwnablePK.test.ts @@ -1,7 +1,6 @@ import { - CoinPublicKey, + type CoinPublicKey, convert_bigint_to_Uint8Array, - convert_Uint8Array_to_bigint, } from '@midnight-ntwrk/compact-runtime'; import { OwnablePKSimulator } from './simulators/OwnablePKSimulator'; import * as utils from './utils/address'; @@ -24,7 +23,6 @@ const EMPTY_BYTES = utils.ZERO_KEY.left.bytes; let ownable: OwnablePKSimulator; let caller: CoinPublicKey; -let ownerSK: Uint8Array; describe('OwnablePK', () => { describe('initializer', () => { @@ -277,7 +275,7 @@ describe('OwnablePK', () => { describe('shieldOwner', () => { it.skip('should hash owner correctly', () => { const instance = convert_bigint_to_Uint8Array(32, 123n); - const expHash = ownable.shieldOwner(Z_OWNER, instance); + const _expHash = ownable.shieldOwner(Z_OWNER, instance); // TODO add matching algo in js }); }); diff --git a/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts index 9ce66143..7a6ba6f0 100644 --- a/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts @@ -1,12 +1,13 @@ import { type CircuitContext, - CoinPublicKey, + type CoinPublicKey, type ContractState, QueryContext, constructorContext, emptyZswapLocalState, } from '@midnight-ntwrk/compact-runtime'; import { sampleContractAddress } from '@midnight-ntwrk/zswap'; +import type { ZswapCoinPublicKey } from '../../artifacts/MockOwnable/contract/index.cjs'; import { type Ledger, Contract as MockOwnable, @@ -15,11 +16,8 @@ import { import { OwnablePrivateState, OwnableWitnesses, - SetWitnessContext, } from '../../witnesses/OwnableWitnesses'; import type { IContractSimulator } from '../types/test'; -import { useCircuitContextSender } from '../utils/test'; -import { ZswapCoinPublicKey } from '../../artifacts/MockOwnable/contract/index.cjs'; /** * @description A simulator implementation of a contract for testing purposes. diff --git a/contracts/ownable/src/test/simulators/OwnableSimulator.ts b/contracts/ownable/src/test/simulators/OwnableSimulator.ts index 5a013ff9..29312b32 100644 --- a/contracts/ownable/src/test/simulators/OwnableSimulator.ts +++ b/contracts/ownable/src/test/simulators/OwnableSimulator.ts @@ -1,6 +1,6 @@ import { type CircuitContext, - CoinPublicKey, + type CoinPublicKey, type ContractState, QueryContext, constructorContext, @@ -18,7 +18,6 @@ import { SetWitnessContext, } from '../../witnesses/OwnableWitnesses'; import type { IContractSimulator } from '../types/test'; -import { useCircuitContextSender } from '../utils/test'; /** * @description A simulator implementation of a contract for testing purposes. diff --git a/contracts/ownable/src/witnesses/OwnableWitnesses.ts b/contracts/ownable/src/witnesses/OwnableWitnesses.ts index 80135c1e..e00ccd01 100644 --- a/contracts/ownable/src/witnesses/OwnableWitnesses.ts +++ b/contracts/ownable/src/witnesses/OwnableWitnesses.ts @@ -1,7 +1,7 @@ import { getRandomValues } from 'node:crypto'; -import { IOwnableWitnesses } from './interface'; import type { WitnessContext } from '@midnight-ntwrk/compact-runtime'; -import { type Ledger } from '../artifacts/MockOwnable/contract/index.cjs'; // Combined imports +import type { Ledger } from '../artifacts/MockOwnable/contract/index.cjs'; // Combined imports +import type { IOwnableWitnesses } from './interface'; /** * @description Represents the private state of an ownable contract, storing a secret key. diff --git a/contracts/ownable/src/witnesses/interface.ts b/contracts/ownable/src/witnesses/interface.ts index 18f7635a..af8bddf2 100644 --- a/contracts/ownable/src/witnesses/interface.ts +++ b/contracts/ownable/src/witnesses/interface.ts @@ -1,5 +1,5 @@ import type { WitnessContext } from '@midnight-ntwrk/compact-runtime'; -import { type Ledger } from '../artifacts/MockOwnable/contract/index.cjs'; // Combined imports +import type { Ledger } from '../artifacts/MockOwnable/contract/index.cjs'; // Combined imports /** * @description Interface defining the witness methods for ownable operations. From 10c92955325d65d430dd1e0f993b119fa308f9cc Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 12 May 2025 01:34:22 -0500 Subject: [PATCH 042/202] remove og ownable --- contracts/ownable/src/Ownable.compact | 74 --------- contracts/ownable/src/test/Ownable.test.ts | 111 -------------- .../src/test/mocks/MockOwnable.compact | 34 ---- .../src/test/simulators/OwnableSimulator.ts | 145 ------------------ .../ownable/src/witnesses/OwnableWitnesses.ts | 57 ------- 5 files changed, 421 deletions(-) delete mode 100644 contracts/ownable/src/Ownable.compact delete mode 100644 contracts/ownable/src/test/Ownable.test.ts delete mode 100644 contracts/ownable/src/test/mocks/MockOwnable.compact delete mode 100644 contracts/ownable/src/test/simulators/OwnableSimulator.ts delete mode 100644 contracts/ownable/src/witnesses/OwnableWitnesses.ts diff --git a/contracts/ownable/src/Ownable.compact b/contracts/ownable/src/Ownable.compact deleted file mode 100644 index e501c055..00000000 --- a/contracts/ownable/src/Ownable.compact +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma language_version >= 0.14.0; - -/** - * @module Shielded Ownable module - * @description Get rekt, losers - */ -module Ownable { - import CompactStandardLibrary; - - /// Public state - export ledger _owner: Bytes<32>; - export ledger _pendingOwner: Bytes<32>; - export ledger _instance: Counter; - - /// Witnesses - witness localSecretKey(): Bytes<32>; - //witness setPendingOwner(currentOwner: Bytes<32>): Bytes<32>; - - /** - * @description Add me... - */ - export circuit initializer(): [] { - _instance.increment(1); - const initialOwner = disclose(publicKey(localSecretKey(), _instance as Field as Bytes<32>)); - _owner = initialOwner; - } - - /** - * @description Add me... - */ - export circuit owner(): Bytes<32> { - return _owner; - } - - /** - * @description Add me... - */ - //export circuit proposeNewOwner(): Bytes<32> { - // - //} - - /** - * @description Add me... - */ - export circuit renounceOwnership(): [] { - assertOnlyOwner(); - const zeroAddress = burn_address().left.bytes; - _transferOwnership(zeroAddress); - } - - /** - * @description Add me... - */ - export circuit assertOnlyOwner(): [] { - assert _owner == publicKey(localSecretKey(), _instance as Field as Bytes<32>) "Ownable: not owner"; - } - - /** - * @description Add me... - */ - export circuit publicKey(sk: Bytes<32>, instance: Bytes<32>): Bytes<32> { - return persistent_hash>>([pad(32, "ownable:pk:"), instance, sk]); - } - - /** - * @description Add me... - */ - export circuit _transferOwnership(newOwner: Bytes<32>): [] { - _instance.increment(1); - _owner = newOwner; - } -} diff --git a/contracts/ownable/src/test/Ownable.test.ts b/contracts/ownable/src/test/Ownable.test.ts deleted file mode 100644 index 6ee3e8c2..00000000 --- a/contracts/ownable/src/test/Ownable.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { convert_bigint_to_Uint8Array } from '@midnight-ntwrk/compact-runtime'; -import { OwnableSimulator } from './simulators/OwnableSimulator'; -import * as utils from './utils/address'; - -const OWNER = String(Buffer.from('OWNER', 'ascii').toString('hex')).padStart( - 64, - '0', -); -const EMPTY_BYTES = utils.ZERO_KEY.left.bytes; -const BAD_SECRET_KEY = convert_bigint_to_Uint8Array(32, 123456789n); - -let ownable: OwnableSimulator; -let ownerSK: Uint8Array; - -describe('Ownable', () => { - describe('initializer', () => { - it('should initialize and set the caller as owner', () => { - ownable = new OwnableSimulator(OWNER); - expect(ownable.owner()).not.toEqual(utils.ZERO_ADDRESS); - expect(ownable.getCurrentPublicState().ownable_Instance).toEqual(1n); - }); - }); - - beforeEach(() => { - ownable = new OwnableSimulator(OWNER); - ownerSK = ownable.getCurrentPrivateState().secretKey; - }); - - describe('assertOnlyOwner', () => { - it('should allow owner to call', () => { - ownable.assertOnlyOwner(); - }); - - it('should fail with unauthorized caller', () => { - // Change secret key in witness context - ownable.setWitnessContext(BAD_SECRET_KEY); - - expect(() => { - ownable.assertOnlyOwner(); - }).toThrow('Ownable: not owner'); - }); - - it('should handle owner → not-owner → owner calls', () => { - // Owner - ownable.assertOnlyOwner(); - - // Not owner - ownable.setWitnessContext(BAD_SECRET_KEY); - expect(() => { - ownable.assertOnlyOwner(); - }).toThrow('Ownable: not owner'); - - // Owner - ownable.setWitnessContext(ownerSK); - ownable.assertOnlyOwner(); - }); - }); - - describe('renounceOwnership', () => { - it('should renounce ownership', () => { - ownable.renounceOwnership(); - expect(ownable.owner()).toEqual(EMPTY_BYTES); - - // Check that original owner can no longer call protected circuits - expect(() => { - ownable.assertOnlyOwner(); - }).toThrow('Ownable: not owner'); - }); - - it('should not renounce ownership from non-owner', () => { - ownable.setWitnessContext(BAD_SECRET_KEY); - expect(() => { - ownable.renounceOwnership(); - }).toThrow('Ownable: not owner'); - }); - - it('should not renounce ownership from non-owner', () => { - ownable.setWitnessContext(BAD_SECRET_KEY); - expect(() => { - ownable.renounceOwnership(); - }).toThrow('Ownable: not owner'); - }); - - it('should not renounce ownership more than once', () => { - ownable.renounceOwnership(); - - expect(() => { - ownable.renounceOwnership(); - }).toThrow('Ownable: not owner'); - }); - }); -}); - -//const sk = ownable.getCurrentPrivateState().secretKey; -//console.log("skkkkkk", sk); -// -//ownable.setWitnessContext(BAD_SECRET_KEY); -////ownable.owner2(); -// -//expect(ownable.owner()).not.toEqual(utils.ZERO_ADDRESS); -//console.log("ownerrrr after", ownable.owner()); -// -//expect(() => { -// ownable.assertOnlyOwner() -//}).toThrow('Ownable: not owner'); -// -//ownable.setWitnessContext(sk); -//expect(() => { -// ownable.assertOnlyOwner() -//}).toThrow('Ownable: not owner'); -// diff --git a/contracts/ownable/src/test/mocks/MockOwnable.compact b/contracts/ownable/src/test/mocks/MockOwnable.compact deleted file mode 100644 index d550c7dc..00000000 --- a/contracts/ownable/src/test/mocks/MockOwnable.compact +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma language_version >= 0.14.0; - -import CompactStandardLibrary; - -import "../../Ownable" prefix Ownable_; - -export { ZswapCoinPublicKey, ContractAddress, Either, Maybe }; -export { Ownable__owner, Ownable__pendingOwner, Ownable__instance }; - -constructor() { - Ownable_initializer(); -} - -export circuit owner(): Bytes<32> { - return Ownable_owner(); - } - -export circuit renounceOwnership(): [] { - return Ownable_renounceOwnership(); -} - -export circuit assertOnlyOwner(): [] { - return Ownable_assertOnlyOwner(); -} - -export circuit publicKey(sk: Bytes<32>, instance: Bytes<32>): Bytes<32> { - return Ownable_publicKey(sk, instance); -} - -export circuit _transferOwnership(newOwner: Bytes<32>): [] { - return Ownable__transferOwnership(newOwner); -} diff --git a/contracts/ownable/src/test/simulators/OwnableSimulator.ts b/contracts/ownable/src/test/simulators/OwnableSimulator.ts deleted file mode 100644 index 29312b32..00000000 --- a/contracts/ownable/src/test/simulators/OwnableSimulator.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { - type CircuitContext, - type CoinPublicKey, - type ContractState, - QueryContext, - constructorContext, - emptyZswapLocalState, -} from '@midnight-ntwrk/compact-runtime'; -import { sampleContractAddress } from '@midnight-ntwrk/zswap'; -import { - type Ledger, - Contract as MockOwnable, - ledger, -} from '../../artifacts/MockOwnable/contract/index.cjs'; // Combined imports -import { - OwnablePrivateState, - OwnableWitnesses, - SetWitnessContext, -} from '../../witnesses/OwnableWitnesses'; -import type { IContractSimulator } from '../types/test'; - -/** - * @description A simulator implementation of a contract for testing purposes. - * @template P - The private state type, fixed to OwnablePrivateState. - * @template L - The ledger type, fixed to Contract.Ledger. - */ -export class OwnableSimulator - implements IContractSimulator -{ - /** @description The underlying contract instance managing contract logic. */ - readonly contract: MockOwnable; - - /** @description The deployed address of the contract. */ - readonly contractAddress: string; - - /** @description The deployer address of the contract. */ - readonly deployer: CoinPublicKey; - - /** @description The current circuit context, updated by contract operations. */ - circuitContext: CircuitContext; - - /** - * @description Initializes the mock contract. - */ - constructor(deployer: CoinPublicKey) { - this.contract = new MockOwnable(OwnableWitnesses()); - this.deployer = deployer; - const { - currentPrivateState, - currentContractState, - currentZswapLocalState, - } = this.contract.initialState( - constructorContext(OwnablePrivateState.generate(), deployer), - ); - this.circuitContext = { - currentPrivateState, - currentZswapLocalState, - originalState: currentContractState, - transactionContext: new QueryContext( - currentContractState.data, - sampleContractAddress(), - ), - }; - this.contractAddress = this.circuitContext.transactionContext.address; - } - - /** - * @description Retrieves the current public ledger state of the contract. - * @returns The ledger state as defined by the contract. - */ - public getCurrentPublicState(): Ledger { - return ledger(this.circuitContext.transactionContext.state); - } - - /** - * @description Retrieves the current private state of the contract. - * @returns The private state of type OwnablePrivateState. - */ - public getCurrentPrivateState(): OwnablePrivateState { - return this.circuitContext.currentPrivateState; - } - - /** - * @description Retrieves the current contract state. - * @returns The contract state object. - */ - public getCurrentContractState(): ContractState { - return this.circuitContext.originalState; - } - - /** - * @description Changes the witness context by setting `sk` - * as the `secretKey`. - * @returns None. - */ - public setWitnessContext(sk: Uint8Array) { - this.contract.witnesses = SetWitnessContext(sk); - } - - public owner(): Uint8Array { - return this.contract.impureCircuits.owner(this.circuitContext).result; - } - - public renounceOwnership(): CircuitContext { - this.circuitContext = this.contract.impureCircuits.renounceOwnership( - this.circuitContext, - ).context; - return this.circuitContext; - } - - public assertOnlyOwner(): CircuitContext { - return this.contract.impureCircuits.assertOnlyOwner(this.circuitContext) - .context; - } - - public publicKey( - sk: Uint8Array, - instance: Uint8Array, - sender: CoinPublicKey, - ): CircuitContext { - const res = this.contract.circuits.publicKey( - { - ...this.circuitContext, - currentZswapLocalState: sender - ? emptyZswapLocalState(sender) - : this.circuitContext.currentZswapLocalState, - }, - sk, - instance, - ); - - this.circuitContext = res.context; - return this.circuitContext; - } - - public _transferOwnership( - newOwner: Uint8Array, - ): CircuitContext { - this.circuitContext = this.contract.impureCircuits._transferOwnership( - this.circuitContext, - newOwner, - ).context; - return this.circuitContext; - } -} diff --git a/contracts/ownable/src/witnesses/OwnableWitnesses.ts b/contracts/ownable/src/witnesses/OwnableWitnesses.ts deleted file mode 100644 index e00ccd01..00000000 --- a/contracts/ownable/src/witnesses/OwnableWitnesses.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { getRandomValues } from 'node:crypto'; -import type { WitnessContext } from '@midnight-ntwrk/compact-runtime'; -import type { Ledger } from '../artifacts/MockOwnable/contract/index.cjs'; // Combined imports -import type { IOwnableWitnesses } from './interface'; - -/** - * @description Represents the private state of an ownable contract, storing a secret key. - */ -export type OwnablePrivateState = { - /** @description A 32-byte secret key used for cryptographic operations. */ - secretKey: Buffer; -}; - -/** - * @description Utility object for managing the private state of an ownable contract. - */ -export const OwnablePrivateState = { - /** - * @description Generates a new private state with a random secret key. - * @returns A fresh OwnablePrivateState instance. - */ - generate: (): OwnablePrivateState => { - return { secretKey: getRandomValues(Buffer.alloc(32)) }; - }, -}; - -/** - * @description Factory function creating witness implementations for ownable operations. - * @returns An object implementing the Witnesses interface for OwnablePrivateState. - */ -export const OwnableWitnesses = (): IOwnableWitnesses => ({ - /** - * @description Retrieves the secret key from the private state. - * @param context - The witness context containing the private state. - * @returns A tuple of the unchanged private state and the secret key as a Uint8Array. - */ - localSecretKey( - context: WitnessContext, - ): [OwnablePrivateState, Uint8Array] { - return [context.privateState, context.privateState.secretKey]; - }, -}); - -export const SetWitnessContext = ( - sk: Uint8Array, -): IOwnableWitnesses => ({ - /** - * @description Retrieves the secret key from the private state. - * @param context - The witness context containing the private state. - * @returns A tuple of the unchanged private state and the passed `sk` as a Uint8Array. - */ - localSecretKey( - context: WitnessContext, - ): [OwnablePrivateState, Uint8Array] { - return [context.privateState, sk]; - }, -}); From cba873b97d2c0c295d56778287468f32a92dabd4 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 12 May 2025 01:34:34 -0500 Subject: [PATCH 043/202] fix name, fmt, lint --- .../src/test/simulators/OwnablePKSimulator.ts | 37 +++++++++---------- .../src/witnesses/OwnablePKWitnesses.ts | 3 ++ 2 files changed, 20 insertions(+), 20 deletions(-) create mode 100644 contracts/ownable/src/witnesses/OwnablePKWitnesses.ts diff --git a/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts index 7a6ba6f0..9a3deecc 100644 --- a/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts @@ -14,21 +14,21 @@ import { ledger, } from '../../artifacts/MockOwnablePK/contract/index.cjs'; // Combined imports import { - OwnablePrivateState, - OwnableWitnesses, -} from '../../witnesses/OwnableWitnesses'; + type OwnablePKPrivateState, + OwnablePKWitnesses, +} from '../../witnesses/OwnablePKWitnesses'; import type { IContractSimulator } from '../types/test'; /** * @description A simulator implementation of a contract for testing purposes. - * @template P - The private state type, fixed to OwnablePrivateState. + * @template P - The private state type, fixed to OwnablePKPrivateState. * @template L - The ledger type, fixed to Contract.Ledger. */ export class OwnablePKSimulator - implements IContractSimulator + implements IContractSimulator { /** @description The underlying contract instance managing contract logic. */ - readonly contract: MockOwnable; + readonly contract: MockOwnable; /** @description The deployed address of the contract. */ readonly contractAddress: string; @@ -37,22 +37,19 @@ export class OwnablePKSimulator readonly deployer: CoinPublicKey; /** @description The current circuit context, updated by contract operations. */ - circuitContext: CircuitContext; + circuitContext: CircuitContext; /** * @description Initializes the mock contract. */ constructor(initOwner: ZswapCoinPublicKey, deployer: CoinPublicKey) { - this.contract = new MockOwnable(OwnableWitnesses()); + this.contract = new MockOwnable(OwnablePKWitnesses); this.deployer = deployer; const { currentPrivateState, currentContractState, currentZswapLocalState, - } = this.contract.initialState( - constructorContext(OwnablePrivateState.generate(), deployer), - initOwner, - ); + } = this.contract.initialState(constructorContext({}, deployer), initOwner); this.circuitContext = { currentPrivateState, currentZswapLocalState, @@ -75,9 +72,9 @@ export class OwnablePKSimulator /** * @description Retrieves the current private state of the contract. - * @returns The private state of type OwnablePrivateState. + * @returns The private state of type OwnablePKPrivateState. */ - public getCurrentPrivateState(): OwnablePrivateState { + public getCurrentPrivateState(): OwnablePKPrivateState { return this.circuitContext.currentPrivateState; } @@ -101,7 +98,7 @@ export class OwnablePKSimulator public transferOwnership( newOwner: ZswapCoinPublicKey, sender: CoinPublicKey, - ): CircuitContext { + ): CircuitContext { const res = this.contract.impureCircuits.transferOwnership( { ...this.circuitContext, @@ -118,7 +115,7 @@ export class OwnablePKSimulator public acceptOwnership( sender: CoinPublicKey, - ): CircuitContext { + ): CircuitContext { const res = this.contract.impureCircuits.acceptOwnership({ ...this.circuitContext, currentZswapLocalState: sender @@ -132,7 +129,7 @@ export class OwnablePKSimulator public renounceOwnership( sender: CoinPublicKey, - ): CircuitContext { + ): CircuitContext { const res = this.contract.impureCircuits.renounceOwnership({ ...this.circuitContext, currentZswapLocalState: sender @@ -146,7 +143,7 @@ export class OwnablePKSimulator public assertOnlyOwner( sender: CoinPublicKey, - ): CircuitContext { + ): CircuitContext { const res = this.contract.impureCircuits.assertOnlyOwner({ ...this.circuitContext, currentZswapLocalState: sender @@ -171,7 +168,7 @@ export class OwnablePKSimulator public _transferOwnership( newOwner: Uint8Array, - ): CircuitContext { + ): CircuitContext { this.circuitContext = this.contract.impureCircuits._transferOwnership( this.circuitContext, newOwner, @@ -181,7 +178,7 @@ export class OwnablePKSimulator public _proposeOwner( newOwner: ZswapCoinPublicKey, - ): CircuitContext { + ): CircuitContext { this.circuitContext = this.contract.impureCircuits._proposeOwner( this.circuitContext, newOwner, diff --git a/contracts/ownable/src/witnesses/OwnablePKWitnesses.ts b/contracts/ownable/src/witnesses/OwnablePKWitnesses.ts new file mode 100644 index 00000000..4976e327 --- /dev/null +++ b/contracts/ownable/src/witnesses/OwnablePKWitnesses.ts @@ -0,0 +1,3 @@ +// This is how we type an empty object. +export type OwnablePKPrivateState = Record; +export const OwnablePKWitnesses = {}; From 82bb3f701d56b7e531e06748a57772b611973d24 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 14 May 2025 18:50:38 -0500 Subject: [PATCH 044/202] add documentation for ownablePK --- contracts/ownable/src/OwnablePK.compact | 88 +++++++++++++++++++++---- 1 file changed, 77 insertions(+), 11 deletions(-) diff --git a/contracts/ownable/src/OwnablePK.compact b/contracts/ownable/src/OwnablePK.compact index a9eea15d..1752529e 100644 --- a/contracts/ownable/src/OwnablePK.compact +++ b/contracts/ownable/src/OwnablePK.compact @@ -4,7 +4,27 @@ pragma language_version >= 0.14.0; /** * @module Shielded Ownable Public Key module - * @description Get rekt, losers + * @description The OwnablePK module provides a basic access control mechanism, + * where there is an account (an owner) that can be granted exclusive access + * to specific circuits. + * + * The initial owner can be set by using the `initializer` circuit during + * construction. The owner's public key will be obfuscated in the ledger + * (thus shielded) by the `shieldOwner` circuit. + * + * This module enforces a two-step ownership transfer mechanism. The mechanism + * flow starts with the current owner calling `transferOwnership` and passing + * the new owner's ZswapCoinPublicKey. The proposed owner's key is obfuscated + * similarly via the `shieldOwner` circuit. After the owner proposes the new + * owner, the new owner must accept ownership by calling `acceptOwnership`. + * This circuit validates that the caller is the proposed owner. Thereafter, + * the new owner may call `assertOnlyOwner` circuits. + * + * The reason this module enforces the two-step mechanism is for safety. + * If the owner transferred ownership to the wrong pubkey without the mechanism, + * it's likely that the ownership privileges will be lost for the contract forever. + * With the two-step mechanism, the current owner can overwrite the pending + * owner by calling `transferOwnership` with a different pubkey. */ module OwnablePK { import CompactStandardLibrary; @@ -15,7 +35,10 @@ module OwnablePK { export ledger _instance: Counter; /** - * @description Add me... + * @description Initializes the contract by setting `initOwner` as the + * (shielded) contract owner. + * + * @returns {[]} - None. */ export circuit initializer(initOwner: ZswapCoinPublicKey): [] { assert initOwner != burn_address().left "OwnablePK: new owner cannot be zero"; @@ -25,21 +48,31 @@ module OwnablePK { } /** - * @description Add me... + * @description Returns the shielded owner. + * + * @returns {Bytes<32>} - The shielded owner. */ export circuit owner(): Bytes<32> { return _owner; } /** - * @description Add me... + * @description Returns the shielded pending owner. + * + * @returns {Bytes<32>} - The shielded proposed owner. */ export circuit pendingOwner(): Bytes<32> { return _pendingOwner; } /** - * @description Add me... + * @description Initiates the two-step ownership transfer to `newOwner`. + * + * Requirements: + * + * - The caller must be the current contract owner. + * + * @returns {[]} - None. */ export circuit transferOwnership(newOwner: ZswapCoinPublicKey): [] { assertOnlyOwner(); @@ -47,7 +80,14 @@ module OwnablePK { } /** - * @description Add me... + * @description Finishes the two-step ownership transfer process by accepting + * the ownership. Can only be called by the pending owner. + * + * Requirements: + * + * - The caller is the pending owner. + * + * @returns {[]} - None. */ export circuit acceptOwnership(): [] { const caller = own_public_key(); @@ -60,7 +100,15 @@ module OwnablePK { } /** - * @description Add me... + * @description Leaves the contract without an owner. It will not be + * possible to call `assertOnlyOnwer` circuits anymore. Can only be + * called by the current owner. + * + * Requirements: + * + * - The caller is the contract owner. + * + * @returns {[]} - None. */ export circuit renounceOwnership(): [] { assertOnlyOwner(); @@ -68,7 +116,10 @@ module OwnablePK { } /** - * @description Add me... + * @description Throws if called by any account other than the owner. + * Use this to restrict access to sensitive circuits. + * + * @returns {[]} - None. */ export circuit assertOnlyOwner(): [] { const caller = own_public_key(); @@ -76,14 +127,22 @@ module OwnablePK { } /** - * @description Add me... + * @description Obfuscates the `ownerPK` be hashing it with a domain separator and + * the passed `instance`. + * + * @returns {Bytes<32>} - The shielded hash of the owner and instance. */ export circuit shieldOwner(ownerPK: ZswapCoinPublicKey, instance: Bytes<32>): Bytes<32> { return persistent_hash>>([pad(32, "OwnablePK:shield:"), instance, ownerPK.bytes]); } /** - * @description Add me... + * @description Internal circuit that transfers ownership of the contract to `newOwner`. + * This circuit does not have access control and thus should not be exposed. + * + * Be careful with this circuit. `newOwner` will be stored in the ledger as it's + * passed meaning that `newOwner` must be shielded via `shieldOwner` beforehand. + * Maybe include `shieldOwner()` in logic so it's difficult to misuse? */ export circuit _transferOwnership(newOwner: Bytes<32>): [] { _pendingOwner = default>; @@ -92,7 +151,14 @@ module OwnablePK { } /** - * @description Add me... + * @description Internal circuit that sets the pending owner. + * This circuit shields `newOwner` internally. + * + * Requirements: + * + * - `newOwner` can not be the zero address. + * + * @returns {[]} - None. */ export circuit _proposeOwner(newOwner: ZswapCoinPublicKey): [] { assert newOwner != burn_address().left "OwnablePK: new owner cannot be zero"; From 3ce5eec204634d76a8f7b6af8b6ef3f81c6668e6 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 14 May 2025 22:09:03 -0500 Subject: [PATCH 045/202] add comments to simulator --- .../src/test/simulators/OwnablePKSimulator.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts index 349be6e3..5d5dfc29 100644 --- a/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts @@ -86,15 +86,26 @@ export class OwnablePKSimulator return this.circuitContext.originalState; } + /** + * @description Returns the shielded owner. + * @returns The shielded owner. + */ public owner(): Uint8Array { return this.contract.impureCircuits.owner(this.circuitContext).result; } + /** + * @description Returns the shielded pending owner. + * @returns The shielded proposed owner. + */ public pendingOwner(): Uint8Array { return this.contract.impureCircuits.pendingOwner(this.circuitContext) .result; } + /** + * @description Initiates the two-step ownership transfer to `newOwner`. + */ public transferOwnership( newOwner: ZswapCoinPublicKey, sender: CoinPublicKey, @@ -113,6 +124,10 @@ export class OwnablePKSimulator return this.circuitContext; } + /** + * @description Finishes the two-step ownership transfer process by accepting + * the ownership. Can only be called by the pending owner. + */ public acceptOwnership( sender: CoinPublicKey, ): CircuitContext { @@ -127,6 +142,11 @@ export class OwnablePKSimulator return this.circuitContext; } + /** + * @description Leaves the contract without an owner. It will not be + * possible to call `assertOnlyOnwer` circuits anymore. Can only be + * called by the current owner. + */ public renounceOwnership( sender: CoinPublicKey, ): CircuitContext { @@ -141,6 +161,10 @@ export class OwnablePKSimulator return this.circuitContext; } + /** + * @description Throws if called by any account other than the owner. + * Use this to restrict access to sensitive circuits. + */ public assertOnlyOwner( sender: CoinPublicKey, ): CircuitContext { @@ -155,6 +179,11 @@ export class OwnablePKSimulator return this.circuitContext; } + /** + * @description Obfuscates the `ownerPK` be hashing it with a domain separator and + * the passed `instance`. + * @returns The shielded hash of the owner and instance. + */ public shieldOwner( ownerPK: ZswapCoinPublicKey, instance: Uint8Array, @@ -166,6 +195,10 @@ export class OwnablePKSimulator ).result; } + + /** + * @description Internal circuit that transfers ownership of the contract to `newOwner`. + */ public _transferOwnership( newOwner: Uint8Array, ): CircuitContext { @@ -176,6 +209,9 @@ export class OwnablePKSimulator return this.circuitContext; } + /** + * @description Internal circuit that sets the pending owner. + */ public _proposeOwner( newOwner: ZswapCoinPublicKey, ): CircuitContext { From d4f36f5446c47886568e725cdf36b1d12ce9809b Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 15 May 2025 01:24:40 -0500 Subject: [PATCH 046/202] allow ownership proposals to zero (to cancel) --- contracts/ownable/src/OwnablePK.compact | 22 ++++++++++++-------- contracts/ownable/src/test/OwnablePK.test.ts | 18 +++++++++------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/contracts/ownable/src/OwnablePK.compact b/contracts/ownable/src/OwnablePK.compact index 1752529e..1071f3e0 100644 --- a/contracts/ownable/src/OwnablePK.compact +++ b/contracts/ownable/src/OwnablePK.compact @@ -24,7 +24,8 @@ pragma language_version >= 0.14.0; * If the owner transferred ownership to the wrong pubkey without the mechanism, * it's likely that the ownership privileges will be lost for the contract forever. * With the two-step mechanism, the current owner can overwrite the pending - * owner by calling `transferOwnership` with a different pubkey. + * owner by calling `transferOwnership` with a different pubkey or passing + * zero to cancel the transfer. */ module OwnablePK { import CompactStandardLibrary; @@ -67,6 +68,8 @@ module OwnablePK { /** * @description Initiates the two-step ownership transfer to `newOwner`. + * To cancel an ownership transfer, the current owner can call this circuit + * and pass zero as `newOwner`. * * Requirements: * @@ -152,17 +155,18 @@ module OwnablePK { /** * @description Internal circuit that sets the pending owner. - * This circuit shields `newOwner` internally. - * - * Requirements: - * - * - `newOwner` can not be the zero address. + * Passing `newOwner` as zero cancels the two-step ownership + * transfer. Otherwise, this circuit shields `newOwner` and + * sets it in the ledger. * * @returns {[]} - None. */ export circuit _proposeOwner(newOwner: ZswapCoinPublicKey): [] { - assert newOwner != burn_address().left "OwnablePK: new owner cannot be zero"; - const nextInstance = _instance + 1 as Field as Bytes<32>; - _pendingOwner = shieldOwner(newOwner, nextInstance); + if (newOwner == burn_address().left) { + _pendingOwner = pad(32, ""); + } else { + const nextInstance = _instance + 1 as Field as Bytes<32>; + _pendingOwner = shieldOwner(newOwner, nextInstance); + } } } diff --git a/contracts/ownable/src/test/OwnablePK.test.ts b/contracts/ownable/src/test/OwnablePK.test.ts index f96a0acf..dbfad780 100644 --- a/contracts/ownable/src/test/OwnablePK.test.ts +++ b/contracts/ownable/src/test/OwnablePK.test.ts @@ -113,12 +113,14 @@ describe('OwnablePK', () => { expect(ownable.owner()).toEqual(expOwner); }); - it('should not transfer zero as owner', () => { + it('should cancel two-step transfer', () => { caller = OWNER; - expect(() => { - ownable.transferOwnership(Z_ZERO, caller); - }).toThrow('OwnablePK: new owner cannot be zero'); + // Start transfer process + ownable.transferOwnership(Z_NEW_OWNER, caller); + // Cancel transfer by transferring to zero + ownable.transferOwnership(Z_ZERO, caller); + expect(ownable.pendingOwner()).toEqual(Z_ZERO.bytes); }); it('should not transfer owner from unauthorized caller', () => { @@ -318,10 +320,10 @@ describe('OwnablePK', () => { expect(ownable.pendingOwner()).toEqual(expOwner); }); - it('should not propose zero as owner', () => { - expect(() => { - ownable._proposeOwner(utils.ZERO_KEY.left); - }).toThrow('OwnablePK: new owner cannot be zero'); + it('should propose owner and cancel', () => { + ownable._proposeOwner(Z_NEW_OWNER); + ownable._proposeOwner(utils.ZERO_KEY.left); + expect(ownable.pendingOwner()).toEqual(Z_ZERO.bytes); }); }); }); From 5741be712ebfff22ad9001b6e54a8f85768585f0 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 15 May 2025 01:32:50 -0500 Subject: [PATCH 047/202] fix fmt --- contracts/ownable/src/OwnablePK.compact | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/ownable/src/OwnablePK.compact b/contracts/ownable/src/OwnablePK.compact index 1071f3e0..7697c756 100644 --- a/contracts/ownable/src/OwnablePK.compact +++ b/contracts/ownable/src/OwnablePK.compact @@ -30,7 +30,7 @@ pragma language_version >= 0.14.0; module OwnablePK { import CompactStandardLibrary; - /// Public state + /** Public state */ export ledger _owner: Bytes<32>; export ledger _pendingOwner: Bytes<32>; export ledger _instance: Counter; From 7120d98786d4ec5f153e5bde095660c127b3819d Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 30 Jun 2025 15:35:40 -0300 Subject: [PATCH 048/202] migrate ownable to vitest, fix package, bump compact version --- contracts/ownable/jest.config.ts | 16 --------------- contracts/ownable/js-resolver.cjs | 20 ------------------- contracts/ownable/package.json | 20 ++++++++----------- contracts/ownable/src/OwnablePK.compact | 2 +- contracts/ownable/src/test/OwnablePK.test.ts | 1 + .../src/test/mocks/MockOwnablePK.compact | 2 +- contracts/ownable/vitest.config.ts | 10 ++++++++++ yarn.lock | 7 +++---- 8 files changed, 24 insertions(+), 54 deletions(-) delete mode 100644 contracts/ownable/jest.config.ts delete mode 100644 contracts/ownable/js-resolver.cjs create mode 100644 contracts/ownable/vitest.config.ts diff --git a/contracts/ownable/jest.config.ts b/contracts/ownable/jest.config.ts deleted file mode 100644 index bde5bde1..00000000 --- a/contracts/ownable/jest.config.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Config } from '@jest/types'; - -const config: Config.InitialOptions = { - preset: 'ts-jest/presets/default-esm', - testEnvironment: 'node', - verbose: true, - roots: [''], - modulePaths: [''], - passWithNoTests: false, - testMatch: ['**/*.test.ts'], - extensionsToTreatAsEsm: ['.ts'], - collectCoverage: false, - resolver: '/js-resolver.cjs', -}; - -export default config; diff --git a/contracts/ownable/js-resolver.cjs b/contracts/ownable/js-resolver.cjs deleted file mode 100644 index 19b6f50c..00000000 --- a/contracts/ownable/js-resolver.cjs +++ /dev/null @@ -1,20 +0,0 @@ -const jsResolver = (path, options) => { - const jsExtRegex = /\.js$/i; - const resolver = options.defaultResolver; - if ( - jsExtRegex.test(path) && - !options.basedir.includes('node_modules') && - !path.includes('node_modules') - ) { - const newPath = path.replace(jsExtRegex, '.ts'); - try { - return resolver(newPath, options); - } catch { - // use default resolver - } - } - - return resolver(path, options); -}; - -module.exports = jsResolver; diff --git a/contracts/ownable/package.json b/contracts/ownable/package.json index d1016a6a..73f59507 100644 --- a/contracts/ownable/package.json +++ b/contracts/ownable/package.json @@ -1,5 +1,6 @@ { "name": "@openzeppelin-midnight/ownable", + "private": true, "type": "module", "main": "dist/index.js", "module": "dist/index.js", @@ -13,24 +14,19 @@ } }, "scripts": { - "compact": "npx compact-compiler", - "build": "npx compact-builder && tsc", - "test": "NODE_OPTIONS=--experimental-vm-modules jest", + "compact": "compact-compiler", + "build": "compact-builder && tsc", + "test": "vitest run", "types": "tsc -p tsconfig.json --noEmit", - "fmt": "biome format", - "fmt:fix": "biome format --write", - "lint": "biome lint", - "lint:fix": "biome check --write", "clean": "git clean -fXd" }, "dependencies": { "@openzeppelin-midnight/compact": "workspace:^" }, "devDependencies": { - "@biomejs/biome": "1.9.4", - "@types/jest": "^29.5.6", - "@types/node": "^18.18.6", - "jest": "^29.7.0", - "typescript": "^5.2.2" + "@types/node": "22.14.0", + "ts-node": "^10.9.2", + "typescript": "^5.2.2", + "vitest": "^3.1.3" } } diff --git a/contracts/ownable/src/OwnablePK.compact b/contracts/ownable/src/OwnablePK.compact index 7697c756..3c4ab389 100644 --- a/contracts/ownable/src/OwnablePK.compact +++ b/contracts/ownable/src/OwnablePK.compact @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma language_version >= 0.14.0; +pragma language_version >= 0.15.0; /** * @module Shielded Ownable Public Key module diff --git a/contracts/ownable/src/test/OwnablePK.test.ts b/contracts/ownable/src/test/OwnablePK.test.ts index dbfad780..8e0f2fa1 100644 --- a/contracts/ownable/src/test/OwnablePK.test.ts +++ b/contracts/ownable/src/test/OwnablePK.test.ts @@ -4,6 +4,7 @@ import { } from '@midnight-ntwrk/compact-runtime'; import { OwnablePKSimulator } from './simulators/OwnablePKSimulator'; import * as utils from './utils/address'; +import { beforeEach, describe, expect, it } from 'vitest'; const OWNER = String(Buffer.from('OWNER', 'ascii').toString('hex')).padStart( 64, diff --git a/contracts/ownable/src/test/mocks/MockOwnablePK.compact b/contracts/ownable/src/test/mocks/MockOwnablePK.compact index 1dcc9de1..85ebcc76 100644 --- a/contracts/ownable/src/test/mocks/MockOwnablePK.compact +++ b/contracts/ownable/src/test/mocks/MockOwnablePK.compact @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma language_version >= 0.14.0; +pragma language_version >= 0.15.0; import CompactStandardLibrary; import "../../OwnablePK" prefix OwnablePK_; diff --git a/contracts/ownable/vitest.config.ts b/contracts/ownable/vitest.config.ts new file mode 100644 index 00000000..785b792e --- /dev/null +++ b/contracts/ownable/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['src/test/**/*.test.ts'], + reporters: 'verbose', + }, +}); diff --git a/yarn.lock b/yarn.lock index ec5144d1..605724e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -432,12 +432,11 @@ __metadata: version: 0.0.0-use.local resolution: "@openzeppelin-midnight/ownable@workspace:contracts/ownable" dependencies: - "@biomejs/biome": "npm:1.9.4" "@openzeppelin-midnight/compact": "workspace:^" - "@types/jest": "npm:^29.5.6" - "@types/node": "npm:^18.18.6" - jest: "npm:^29.7.0" + "@types/node": "npm:22.14.0" + ts-node: "npm:^10.9.2" typescript: "npm:^5.2.2" + vitest: "npm:^3.1.3" languageName: unknown linkType: soft From 5a0135af9757f2ea5fa1d10d7b7149a2242de989 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 30 Jun 2025 15:36:09 -0300 Subject: [PATCH 049/202] fix fmt --- contracts/ownable/src/test/OwnablePK.test.ts | 2 +- contracts/ownable/src/test/simulators/OwnablePKSimulator.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/ownable/src/test/OwnablePK.test.ts b/contracts/ownable/src/test/OwnablePK.test.ts index 8e0f2fa1..1d8cfcb4 100644 --- a/contracts/ownable/src/test/OwnablePK.test.ts +++ b/contracts/ownable/src/test/OwnablePK.test.ts @@ -2,9 +2,9 @@ import { type CoinPublicKey, convert_bigint_to_Uint8Array, } from '@midnight-ntwrk/compact-runtime'; +import { beforeEach, describe, expect, it } from 'vitest'; import { OwnablePKSimulator } from './simulators/OwnablePKSimulator'; import * as utils from './utils/address'; -import { beforeEach, describe, expect, it } from 'vitest'; const OWNER = String(Buffer.from('OWNER', 'ascii').toString('hex')).padStart( 64, diff --git a/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts index 5d5dfc29..dff1a37b 100644 --- a/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts @@ -195,7 +195,6 @@ export class OwnablePKSimulator ).result; } - /** * @description Internal circuit that transfers ownership of the contract to `newOwner`. */ From d3c7a6206524c899d6e4ca68c4ba2d34efcaefe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Wed, 23 Jul 2025 19:09:40 -0400 Subject: [PATCH 050/202] Init Shielded AcessControl --- contracts/shieldedAccessControl/package.json | 32 ++ .../src/ShieldedAccessControl.compact | 313 ++++++++++++++++++ .../mocks/MockShieldedAccessControl.compact | 53 +++ .../shieldedAccessControl/tsconfig.build.json | 5 + contracts/shieldedAccessControl/tsconfig.json | 25 ++ .../shieldedAccessControl/vitest.config.ts | 10 + .../ROOT/pages/api/shieldedAccessControl.adoc | 0 .../ROOT/pages/shieldedAccessControl.adoc | 0 8 files changed, 438 insertions(+) create mode 100644 contracts/shieldedAccessControl/package.json create mode 100644 contracts/shieldedAccessControl/src/ShieldedAccessControl.compact create mode 100644 contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact create mode 100644 contracts/shieldedAccessControl/tsconfig.build.json create mode 100644 contracts/shieldedAccessControl/tsconfig.json create mode 100644 contracts/shieldedAccessControl/vitest.config.ts create mode 100644 docs/modules/ROOT/pages/api/shieldedAccessControl.adoc create mode 100644 docs/modules/ROOT/pages/shieldedAccessControl.adoc diff --git a/contracts/shieldedAccessControl/package.json b/contracts/shieldedAccessControl/package.json new file mode 100644 index 00000000..65238178 --- /dev/null +++ b/contracts/shieldedAccessControl/package.json @@ -0,0 +1,32 @@ +{ + "name": "@openzeppelin-compact/shielded-access-control", + "private": true, + "type": "module", + "main": "dist/index.js", + "module": "dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "require": "./dist/index.js", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "scripts": { + "compact": "compact-compiler", + "build": "compact-builder && tsc", + "test": "vitest run", + "types": "tsc -p tsconfig.json --noEmit", + "clean": "git clean -fXd" + }, + "dependencies": { + "@openzeppelin-compact/compact": "workspace:^" + }, + "devDependencies": { + "@types/node": "22.14.0", + "ts-node": "^10.9.2", + "typescript": "^5.2.2", + "vitest": "^3.1.3" + } +} \ No newline at end of file diff --git a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact new file mode 100644 index 00000000..25aca710 --- /dev/null +++ b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: MIT + +pragma language_version >= 0.15.0; + +/** + * @module Shielded AccessControl + * @description A Shielded AccessControl library. + * This module provides a role-based access control mechanism, where roles can be used to + * represent a set of permissions. Roles are stored as MerkleTree commitments to avoid + * disclosing information regarding the roles an account may have. Commitments are created + * with SHA256(PublicKey | roleIdentifier | nonce). + * + * @notice Using the SHA256 hashing function comes at a significant performace cost. In the future, we + * plan on migrating to a ZK-friendly hashing function like Poseidon when an implementation is available. + * + * Roles are referred to by their `Bytes<32>` identifier. These should be exposed + * in the top-level contract and be unique. One way to achieve this is by + * using `export sealed ledger` hash digests that are initialized in the top-level contract: + * + * ```typescript + * import CompactStandardLibrary; + * import "./node_modules/@openzeppelin-compact/accessControl/src/ShieldedAccessControl" prefix ShieldedAccessControl_; + * + * export sealed ledger MY_ROLE: Bytes<32>; + * + * constructor() { + * MY_ROLE = persistent_hash>(pad(32, "MY_ROLE")); + * } + * ``` + * + * To restrict access to a circuit, use {assertOnlyRole}: + * + * ```typescript + * circuit foo(): [] { + * assertOnlyRole(MY_ROLE); + * ... + * } + * ``` + * + * Roles can be granted and revoked dynamically via the {grantRole} and + * {revokeRole} circuits. Each role has an associated admin role, and only + * accounts that have a role's admin role can call {grantRole} and {revokeRole}. + * + * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means + * that only accounts with this role will be able to grant or revoke other + * roles. More complex role relationships can be created by using + * {_setRoleAdmin}. To set a custom `DEFAULT_ADMIN_ROLE`, implement the `Initializable` + * module and set `DEFAULT_ADMIN_ROLE` in the `initialize()` circuit. + * + * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to + * grant and revoke this role. Extra precautions should be taken to secure + * accounts that have been granted it. + * + * @notice Roles can only be granted to ZswapCoinPublicKeys + * through the main role approval circuits (`grantRole` and `_grantRole`). + * In other words, role approvals to contract addresses are disallowed through these + * circuits. + * This is because Compact currently does not support contract-to-contract calls which means + * if a contract is granted a role, the contract cannot directly call the protected + * circuit. + * + * @notice This module does offer an experimental circuit that allows roles to be granted + * to contract addresses (`_unsafeGrantRole`). + * Note that the circuit name is very explicit ("unsafe") with this experimental circuit. + * Until contract-to-contract calls are supported, + * there is no direct way for a contract to call protected circuits. + * + * @notice The unsafe circuits are planned to become deprecated once contract-to-contract calls + * are supported. + * + * @notice Missing Features and Improvements: + * + * - Role events + * - An ERC165-like interface + */ +module ShieldedAccessControl { + import CompactStandardLibrary; + import "../../node_modules/@openzeppelin-compact/utils/src/Utils" prefix Utils_; + + /** + * @description A MerkleTree of role commitments stored as H(PK | role | nonce) + * @type {Bytes<32>} roleCommitment - A roleCommitment created by the following hash: SHA256(PK | role | nonce). + * @type {MerkleTree<4, roleCommitment>} + * @type {MerkleTree<4, Bytes<32>>} _operatorRoles +  */ + export ledger _operatorRoles: MerkleTree<4, Bytes<32>; + + /** + * @description Mapping from a role identifier to an admin role identifier. + * @type {Bytes<32>} roleId - A hash representing a role identifier. + * @type {Bytes<32>} adminId - A hash representing an admin identifier. + * @type {Map} + * @type {Map, Bytes<32>>} _adminRoles +  */ + export ledger _adminRoles: Map, Bytes<32>>; + + export ledger DEFAULT_ADMIN_ROLE: Bytes<32>; + + /** + * @description Returns `true` if `account` has been granted `roleId`. + * + * @circuitInfo + * + * @param {Either} account - The account to query. + * @param {Bytes<32>} roleId - The role identifier. + * @param {Bytes<16>} nonce - The nonce - H(SK | "role-nonce" | role | PK) - used to generate + * the role commitment stored in `_operatorRoles` + * @return {Boolean} - Whether the account has the specified role. +   */ + export circuit hasRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): Boolean { + const authPath = persistent_hash>>(roleId, account.left.bytes, pad(32, nonce)); + return _operatorRoles.checkRoot(merkleTreePathRoot<4, Bytes<32>>(authPath)) + } + + /** + * @description Reverts if `own_public_key()` is missing `roleId`. + * + * @circuitInfo + * + * Requirements: + * + * - The caller must have `roleId`. + * - The caller must not be a ContractAddress + * + * @param {Bytes<32>} roleId - The role identifier. + * @return {[]} - Empty tuple. + */ + export circuit assertOnlyRole(roleId: Bytes<32>, nonce: Bytes<16>): [] { + _checkRole( + roleId, + left(own_public_key(), + nonce + )); + } + + /** + * @description Reverts if `account` is missing `roleId`. + * + * @circuitInfo + * + * Requirements: + * + * - `account` must have `roleId`. + * + * @param {Bytes<32>} roleId - The role identifier. + * @param {Either} account - The account to query. + * @return {[]} - Empty tuple. + */ + export circuit _checkRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): [] { + assert hasRole(roleId, account, nonce) "ShieldedAccessControl: unauthorized account"; + } + + /** + * @description Returns the admin role that controls `roleId` or + * a byte array with all zero bytes if `roleId` doesn't exist. See {grantRole} and {revokeRole}. + * + * To change a role’s admin use {_setRoleAdmin}. + * + * @circuitInfo + * + * @param {Bytes<32>} roleId - The role identifier. + * @return {Bytes<32>} roleAdmin - The admin role that controls `roleId`. + */ + export circuit getRoleAdmin(roleId: Bytes<32>): Bytes<32> { + if (_adminRoles.member(roleId)) { + return _adminRoles.lookup(roleId); + } + return default>; + } + + /** + * @description Grants `roleId` to `account`. + * + * @circuitInfo + * + * Requirements: + * + * - `account` must not be a ContractAddress. + * - The caller must have `roleId`'s admin role. + * + * @param {Bytes<32>} roleId - The role identifier. + * @param {Either} account - A ZswapCoinPublicKey or ContractAddress. + * @return {[]} - Empty tuple. + */ + export circuit grantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): [] { + assertOnlyRole(getRoleAdmin(roleId), nonce); + _grantRole(roleId, account); + } + + /** + * @description Revokes `roleId` from `account`. + * + * @circuitInfo + * + * Requirements: + * + * - The caller must have `roleId`'s admin role. + * + * @param {Bytes<32>} roleId - The role identifier. + * @param {Either} account - A ZswapCoinPublicKey or ContractAddress. + * @return {[]} - Empty tuple. + */ + export circuit revokeRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): [] { + assertOnlyRole(getRoleAdmin(roleId), nonce); + _revokeRole(roleId, account, nonce); + } + + /** + * @description Revokes `roleId` from the calling account. + * + * @notice Roles are often managed via {grantRole} and {revokeRole}: this circuit's + * purpose is to provide a mechanism for accounts to lose their privileges + * if they are compromised (such as when a trusted device is misplaced). + * + * @circuitInfo + * + * Requirements: + * + * - The caller must be `callerConfirmation`. + * - The caller must not be a `ContractAddress`. + * + * @param {Bytes<32>} roleId - The role identifier. + * @param {Either} callerConfirmation - A ZswapCoinPublicKey or ContractAddress. + * @return {[]} - Empty tuple. + */ + export circuit renounceRole(roleId: Bytes<32>, callerConfirmation: Either): [] { + assert callerConfirmation == left(own_public_key()) "AccessControl: bad confirmation"; + + _revokeRole(roleId, callerConfirmation); + } + + /** + * @description Sets `adminRole` as `roleId`'s admin role. + * + * @circuitInfo + * + * @param {Bytes<32>} roleId - The role identifier. + * @param {Bytes<32>} adminRole - The admin role identifier. + * @return {[]} - Empty tuple. + */ + export circuit _setRoleAdmin(roleId: Bytes<32>, adminRole: Bytes<32>): [] { + _adminRoles.insert(roleId, adminRole); + } + + /** + * @description Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted. + * Internal circuit without access restriction. + * + * @circuitInfo + * + * Requirements: + * + * - `account` must not be a ContractAddress. + * + * @param {Bytes<32>} roleId - The role identifier. + * @param {Either} account - A ZswapCoinPublicKey or ContractAddress. + * @return {Boolean} roleGranted - A boolean indicating if `roleId` was granted. + */ + export circuit _grantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): Boolean { + assert !Utils_isContractAddress(account) "AccessControl: unsafe role approval"; + return _unsafeGrantRole(roleId, account, nonce); + } + + /** + * @description Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted. + * Internal circuit without access restriction. It does NOT check if the role is granted to a ContractAddress. + * + * @circuitInfo + * + * @notice External smart contracts cannot call the token contract at this time, so granting a role to an ContractAddress may + * render a circuit permanently inaccessible. + * + * @param {Bytes<32>} roleId - The role identifier. + * @param {Either} account - A ZswapCoinPublicKey or ContractAddress. + * @return {Boolean} roleGranted - A boolean indicating if `role` was granted. + */ + export circuit _unsafeGrantRole(roleId: Bytes<32>, account: Either): Boolean { + if (hasRole(roleId, account, nonce)) { + return false; + } + + if (account.isLeft) { + const commitment = persistent_hash>>([roleId, account.left.bytes, pad(32, nonce)]) + _operatorRoles.insertHash(commitment); + return true; + } + + const commitment = persistent_hash>>([roleId, account.right.bytes, pad(32, nonce)]) + _operatorRoles.insertHash(commitment); + return true; + } + + /** + * @description Attempts to revoke `roleId` from `account` and returns a boolean indicating if `roleId` was revoked. + * Internal circuit without access restriction. + * + * @circuitInfo + * + * @param {Bytes<32>} roleId - The role identifier. + * @param {Bytes<32>} adminRole - The admin role identifier. + * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. + */ + export circuit _revokeRole(roleId: Bytes<32>, account: Either): Boolean { + if (!hasRole(roleId, account)) { + return false; + } + + _operatorRoles + .lookup(roleId) + .insert(account, false); + return true; + } +} diff --git a/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact new file mode 100644 index 00000000..2324c6a8 --- /dev/null +++ b/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT + +pragma language_version >= 0.15.0; + +import CompactStandardLibrary; + +import "../../ShieldedAccessControl" prefix ShieldedAccessControl_; + +export { ZswapCoinPublicKey, ContractAddress, Either, Maybe, ShieldedAccessControl_DEFAULT_ADMIN_ROLE }; + +export circuit hasRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): Boolean { + return ShieldedAccessControl_hasRole(roleId, account); +} + +export circuit assertOnlyRole(roleId: Bytes<32>, nonce: Bytes<16>): [] { + ShieldedAccessControl_assertOnlyRole(roleId, nonce); +} + +export circuit _checkRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): [] { + ShieldedAccessControl__checkRole(roleId, account, nonce); +} + +export circuit getRoleAdmin(roleId: Bytes<32>): Bytes<32> { + return ShieldedAccessControl_getRoleAdmin(roleId); +} + +export circuit grantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): [] { + ShieldedAccessControl_grantRole(roleId, account, nonce); +} + +export circuit revokeRole(roleId: Bytes<32>, account: Either): [] { + ShieldedAccessControl_revokeRole(roleId, account); +} + +export circuit renounceRole(roleId: Bytes<32>, callerConfirmation: Either): [] { + ShieldedAccessControl_renounceRole(roleId, callerConfirmation); +} + +export circuit _setRoleAdmin(roleId: Bytes<32>, adminRole: Bytes<32>): [] { + ShieldedAccessControl__setRoleAdmin(roleId, adminRole); +} + +export circuit _grantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): Boolean { + return ShieldedAccessControl__grantRole(roleId, account, nonce); +} + +export circuit _unsafeGrantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): Boolean { + return ShieldedAccessControl__unsafeGrantRole(roleId, account, nonce); +} + +export circuit _revokeRole(roleId: Bytes<32>, account: Either): Boolean { + return ShieldedAccessControl__revokeRole(roleId, account); +} diff --git a/contracts/shieldedAccessControl/tsconfig.build.json b/contracts/shieldedAccessControl/tsconfig.build.json new file mode 100644 index 00000000..f1132509 --- /dev/null +++ b/contracts/shieldedAccessControl/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["src/test/**/*.ts"], + "compilerOptions": {} +} diff --git a/contracts/shieldedAccessControl/tsconfig.json b/contracts/shieldedAccessControl/tsconfig.json new file mode 100644 index 00000000..4ae082c4 --- /dev/null +++ b/contracts/shieldedAccessControl/tsconfig.json @@ -0,0 +1,25 @@ +{ + "include": [ + "src/**/*.ts" + ], + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "declaration": true, + "lib": [ + "ES2022" + ], + "target": "ES2022", + "module": "nodenext", + "moduleResolution": "nodenext", + "allowJs": true, + "forceConsistentCasingInFileNames": true, + "noImplicitAny": true, + "strict": true, + "isolatedModules": true, + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "skipLibCheck": true + } +} diff --git a/contracts/shieldedAccessControl/vitest.config.ts b/contracts/shieldedAccessControl/vitest.config.ts new file mode 100644 index 00000000..785b792e --- /dev/null +++ b/contracts/shieldedAccessControl/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['src/test/**/*.test.ts'], + reporters: 'verbose', + }, +}); diff --git a/docs/modules/ROOT/pages/api/shieldedAccessControl.adoc b/docs/modules/ROOT/pages/api/shieldedAccessControl.adoc new file mode 100644 index 00000000..e69de29b diff --git a/docs/modules/ROOT/pages/shieldedAccessControl.adoc b/docs/modules/ROOT/pages/shieldedAccessControl.adoc new file mode 100644 index 00000000..e69de29b From 5f9268e2a4897abb016f5feafc5a49094a6f42e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 29 Jul 2025 19:50:17 -0400 Subject: [PATCH 051/202] Update ShieldedAccessControl to 0.24.0 --- .../src/ShieldedAccessControl.compact | 118 +++++++++++------- .../mocks/MockShieldedAccessControl.compact | 26 ++-- yarn.lock | 12 ++ 3 files changed, 96 insertions(+), 60 deletions(-) diff --git a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact index 25aca710..e43f6380 100644 --- a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact +++ b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma language_version >= 0.15.0; +pragma language_version >= 0.16.0; /** * @module Shielded AccessControl @@ -24,7 +24,7 @@ pragma language_version >= 0.15.0; * export sealed ledger MY_ROLE: Bytes<32>; * * constructor() { - * MY_ROLE = persistent_hash>(pad(32, "MY_ROLE")); + * MY_ROLE = persistentHash>(pad(32, "MY_ROLE")); * } * ``` * @@ -80,10 +80,17 @@ module ShieldedAccessControl { /** * @description A MerkleTree of role commitments stored as H(PK | role | nonce) * @type {Bytes<32>} roleCommitment - A roleCommitment created by the following hash: SHA256(PK | role | nonce). - * @type {MerkleTree<4, roleCommitment>} - * @type {MerkleTree<4, Bytes<32>>} _operatorRoles + * @type {MerkleTree<10, roleCommitment>} + * @type {MerkleTree<10, Bytes<32>>} _operatorRoles  */ - export ledger _operatorRoles: MerkleTree<4, Bytes<32>; + export ledger _operatorRoles: MerkleTree<10, Bytes<32>>; + + /** + * @description A set of nullifiers used to revoke permissions from a role + * @type {Bytes<32>} roleCommitment - A roleCommitment created by the following hash: SHA256(PK | role | nonce). + * @type {Set} _roleNullifiers +  */ + export ledger _roleNullifiers: Set>; /** * @description Mapping from a role identifier to an admin role identifier. @@ -96,24 +103,28 @@ module ShieldedAccessControl { export ledger DEFAULT_ADMIN_ROLE: Bytes<32>; - /** - * @description Returns `true` if `account` has been granted `roleId`. - * - * @circuitInfo - * - * @param {Either} account - The account to query. - * @param {Bytes<32>} roleId - The role identifier. - * @param {Bytes<16>} nonce - The nonce - H(SK | "role-nonce" | role | PK) - used to generate - * the role commitment stored in `_operatorRoles` - * @return {Boolean} - Whether the account has the specified role. -   */ - export circuit hasRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): Boolean { - const authPath = persistent_hash>>(roleId, account.left.bytes, pad(32, nonce)); - return _operatorRoles.checkRoot(merkleTreePathRoot<4, Bytes<32>>(authPath)) + witness getRoleCommitmentPath(roleCommitment: Bytes<32>): MerkleTreePath<10, Bytes<32>>; + + /** + * @description Returns `true` if `account` has been granted `roleId`. + * + * @circuitInfo + * + * @param {Either} account - The account to query. + * @param {Bytes<32>} roleId - The role identifier. + * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) + * @return {Boolean} - Whether the account has the specified role. +  */ + export circuit hasRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { + const roleCommitment = persistentHash>>([roleId, account.left.bytes, nonce]); + const authPath = getRoleCommitmentPath(roleCommitment); + const isNullified = _roleNullifiers.member(disclose(roleCommitment)); + return _operatorRoles + .checkRoot(merkleTreePathRoot<10, Bytes<32>>(disclose(authPath))) && !isNullified; } /** - * @description Reverts if `own_public_key()` is missing `roleId`. + * @description Reverts if `ownPublicKey()` is missing `roleId`. * * @circuitInfo * @@ -123,14 +134,15 @@ module ShieldedAccessControl { * - The caller must not be a ContractAddress * * @param {Bytes<32>} roleId - The role identifier. + * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {[]} - Empty tuple. */ - export circuit assertOnlyRole(roleId: Bytes<32>, nonce: Bytes<16>): [] { + export circuit assertOnlyRole(roleId: Bytes<32>, nonce: Bytes<32>): [] { _checkRole( roleId, - left(own_public_key(), + left(ownPublicKey()), nonce - )); + ); } /** @@ -144,10 +156,11 @@ module ShieldedAccessControl { * * @param {Bytes<32>} roleId - The role identifier. * @param {Either} account - The account to query. + * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {[]} - Empty tuple. */ - export circuit _checkRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): [] { - assert hasRole(roleId, account, nonce) "ShieldedAccessControl: unauthorized account"; + export circuit _checkRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): [] { + assert(hasRole(roleId, account, nonce), "ShieldedAccessControl: unauthorized account"); } /** @@ -162,8 +175,8 @@ module ShieldedAccessControl { * @return {Bytes<32>} roleAdmin - The admin role that controls `roleId`. */ export circuit getRoleAdmin(roleId: Bytes<32>): Bytes<32> { - if (_adminRoles.member(roleId)) { - return _adminRoles.lookup(roleId); + if (_adminRoles.member(disclose(roleId))) { + return _adminRoles.lookup(disclose(roleId)); } return default>; } @@ -180,11 +193,12 @@ module ShieldedAccessControl { * * @param {Bytes<32>} roleId - The role identifier. * @param {Either} account - A ZswapCoinPublicKey or ContractAddress. + * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {[]} - Empty tuple. */ - export circuit grantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): [] { + export circuit grantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): [] { assertOnlyRole(getRoleAdmin(roleId), nonce); - _grantRole(roleId, account); + _grantRole(roleId, account, nonce); } /** @@ -198,9 +212,10 @@ module ShieldedAccessControl { * * @param {Bytes<32>} roleId - The role identifier. * @param {Either} account - A ZswapCoinPublicKey or ContractAddress. + * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {[]} - Empty tuple. */ - export circuit revokeRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): [] { + export circuit revokeRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): [] { assertOnlyRole(getRoleAdmin(roleId), nonce); _revokeRole(roleId, account, nonce); } @@ -221,12 +236,13 @@ module ShieldedAccessControl { * * @param {Bytes<32>} roleId - The role identifier. * @param {Either} callerConfirmation - A ZswapCoinPublicKey or ContractAddress. + * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {[]} - Empty tuple. */ - export circuit renounceRole(roleId: Bytes<32>, callerConfirmation: Either): [] { - assert callerConfirmation == left(own_public_key()) "AccessControl: bad confirmation"; + export circuit renounceRole(roleId: Bytes<32>, callerConfirmation: Either, nonce: Bytes<32>): [] { + assert(callerConfirmation == left(ownPublicKey()), "ShieldedAccessControl: bad confirmation"); - _revokeRole(roleId, callerConfirmation); + _revokeRole(roleId, callerConfirmation, nonce); } /** @@ -239,7 +255,7 @@ module ShieldedAccessControl { * @return {[]} - Empty tuple. */ export circuit _setRoleAdmin(roleId: Bytes<32>, adminRole: Bytes<32>): [] { - _adminRoles.insert(roleId, adminRole); + _adminRoles.insert(disclose(roleId), disclose(adminRole)); } /** @@ -254,10 +270,11 @@ module ShieldedAccessControl { * * @param {Bytes<32>} roleId - The role identifier. * @param {Either} account - A ZswapCoinPublicKey or ContractAddress. + * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {Boolean} roleGranted - A boolean indicating if `roleId` was granted. */ - export circuit _grantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): Boolean { - assert !Utils_isContractAddress(account) "AccessControl: unsafe role approval"; + export circuit _grantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { + assert(!Utils_isContractAddress(account), "ShieldedAccessControl: unsafe role approval"); return _unsafeGrantRole(roleId, account, nonce); } @@ -272,21 +289,22 @@ module ShieldedAccessControl { * * @param {Bytes<32>} roleId - The role identifier. * @param {Either} account - A ZswapCoinPublicKey or ContractAddress. + * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {Boolean} roleGranted - A boolean indicating if `role` was granted. */ - export circuit _unsafeGrantRole(roleId: Bytes<32>, account: Either): Boolean { + export circuit _unsafeGrantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { if (hasRole(roleId, account, nonce)) { return false; } - if (account.isLeft) { - const commitment = persistent_hash>>([roleId, account.left.bytes, pad(32, nonce)]) - _operatorRoles.insertHash(commitment); + if (!Utils_isContractAddress(account)) { + const roleCommitment = persistentHash>>([roleId, account.left.bytes, nonce]); + _operatorRoles.insertHash(disclose(roleCommitment)); return true; } - const commitment = persistent_hash>>([roleId, account.right.bytes, pad(32, nonce)]) - _operatorRoles.insertHash(commitment); + const roleCommitment = persistentHash>>([roleId, account.right.bytes, nonce]); + _operatorRoles.insertHash(disclose(roleCommitment)); return true; } @@ -298,16 +316,22 @@ module ShieldedAccessControl { * * @param {Bytes<32>} roleId - The role identifier. * @param {Bytes<32>} adminRole - The admin role identifier. + * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. */ - export circuit _revokeRole(roleId: Bytes<32>, account: Either): Boolean { - if (!hasRole(roleId, account)) { + export circuit _revokeRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { + if (!hasRole(roleId, account, nonce)) { return false; } - _operatorRoles - .lookup(roleId) - .insert(account, false); + if(!Utils_isContractAddress(account)) { + const roleCommitment = persistentHash>>([roleId, account.left.bytes, nonce]); + _roleNullifiers.insert(disclose(roleCommitment)); + return true; + } + + const roleCommitment = persistentHash>>([roleId, account.right.bytes, nonce]); + _roleNullifiers.insert(disclose(roleCommitment)); return true; } } diff --git a/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact index 2324c6a8..406bca34 100644 --- a/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact +++ b/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact @@ -8,15 +8,15 @@ import "../../ShieldedAccessControl" prefix ShieldedAccessControl_; export { ZswapCoinPublicKey, ContractAddress, Either, Maybe, ShieldedAccessControl_DEFAULT_ADMIN_ROLE }; -export circuit hasRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): Boolean { - return ShieldedAccessControl_hasRole(roleId, account); +export circuit hasRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { + return ShieldedAccessControl_hasRole(roleId, account, nonce); } -export circuit assertOnlyRole(roleId: Bytes<32>, nonce: Bytes<16>): [] { +export circuit assertOnlyRole(roleId: Bytes<32>, nonce: Bytes<32>): [] { ShieldedAccessControl_assertOnlyRole(roleId, nonce); } -export circuit _checkRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): [] { +export circuit _checkRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): [] { ShieldedAccessControl__checkRole(roleId, account, nonce); } @@ -24,30 +24,30 @@ export circuit getRoleAdmin(roleId: Bytes<32>): Bytes<32> { return ShieldedAccessControl_getRoleAdmin(roleId); } -export circuit grantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): [] { +export circuit grantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): [] { ShieldedAccessControl_grantRole(roleId, account, nonce); } -export circuit revokeRole(roleId: Bytes<32>, account: Either): [] { - ShieldedAccessControl_revokeRole(roleId, account); +export circuit revokeRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): [] { + ShieldedAccessControl_revokeRole(roleId, account, nonce); } -export circuit renounceRole(roleId: Bytes<32>, callerConfirmation: Either): [] { - ShieldedAccessControl_renounceRole(roleId, callerConfirmation); +export circuit renounceRole(roleId: Bytes<32>, callerConfirmation: Either, nonce: Bytes<32>): [] { + ShieldedAccessControl_renounceRole(roleId, callerConfirmation, nonce); } export circuit _setRoleAdmin(roleId: Bytes<32>, adminRole: Bytes<32>): [] { ShieldedAccessControl__setRoleAdmin(roleId, adminRole); } -export circuit _grantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): Boolean { +export circuit _grantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { return ShieldedAccessControl__grantRole(roleId, account, nonce); } -export circuit _unsafeGrantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<16>): Boolean { +export circuit _unsafeGrantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { return ShieldedAccessControl__unsafeGrantRole(roleId, account, nonce); } -export circuit _revokeRole(roleId: Bytes<32>, account: Either): Boolean { - return ShieldedAccessControl__revokeRole(roleId, account); +export circuit _revokeRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { + return ShieldedAccessControl__revokeRole(roleId, account, nonce); } diff --git a/yarn.lock b/yarn.lock index bfe8e4de..8510acb8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -483,6 +483,18 @@ __metadata: languageName: unknown linkType: soft +"@openzeppelin-compact/shielded-access-control@workspace:contracts/shieldedAccessControl": + version: 0.0.0-use.local + resolution: "@openzeppelin-compact/shielded-access-control@workspace:contracts/shieldedAccessControl" + dependencies: + "@openzeppelin-compact/compact": "workspace:^" + "@types/node": "npm:22.14.0" + ts-node: "npm:^10.9.2" + typescript: "npm:^5.2.2" + vitest: "npm:^3.1.3" + languageName: unknown + linkType: soft + "@openzeppelin-compact/utils@workspace:contracts/utils": version: 0.0.0-use.local resolution: "@openzeppelin-compact/utils@workspace:contracts/utils" From bd81f55d3ed56f1cb1542e2c3be7b8a791155b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 29 Jul 2025 21:11:21 -0400 Subject: [PATCH 052/202] Add disclosure closer to disclosure point --- contracts/utils/src/Utils.compact | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/src/Utils.compact b/contracts/utils/src/Utils.compact index 3528e10e..01e013a0 100644 --- a/contracts/utils/src/Utils.compact +++ b/contracts/utils/src/Utils.compact @@ -63,7 +63,7 @@ module Utils { * @return {Boolean} - Returns true if `keyOrAddress` is a ContractAddress. */ export pure circuit isContractAddress(keyOrAddress: Either): Boolean { - return !keyOrAddress.is_left; + return disclose(!keyOrAddress.is_left); } /** From 01c0b6a0d9e578acc7c09608817ce7b6476ef425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Thu, 31 Jul 2025 19:26:48 -0400 Subject: [PATCH 053/202] Revert "Add disclosure closer to disclosure point" This reverts commit bd81f55d3ed56f1cb1542e2c3be7b8a791155b72. --- contracts/utils/src/Utils.compact | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/src/Utils.compact b/contracts/utils/src/Utils.compact index 01e013a0..3528e10e 100644 --- a/contracts/utils/src/Utils.compact +++ b/contracts/utils/src/Utils.compact @@ -63,7 +63,7 @@ module Utils { * @return {Boolean} - Returns true if `keyOrAddress` is a ContractAddress. */ export pure circuit isContractAddress(keyOrAddress: Either): Boolean { - return disclose(!keyOrAddress.is_left); + return !keyOrAddress.is_left; } /** From 04ff4f41c5af9d9252371ac38dd299bdd404e167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:52:45 -0400 Subject: [PATCH 054/202] Update contract design --- .../src/ShieldedAccessControl.compact | 317 +++++++++++++++--- .../src/ShieldedAccessControlUtils.compact | 25 ++ .../mocks/MockShieldedAccessControl.compact | 6 +- 3 files changed, 306 insertions(+), 42 deletions(-) create mode 100644 contracts/shieldedAccessControl/src/ShieldedAccessControlUtils.compact diff --git a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact index e43f6380..ee39d49c 100644 --- a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact +++ b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact @@ -75,11 +75,11 @@ pragma language_version >= 0.16.0; */ module ShieldedAccessControl { import CompactStandardLibrary; - import "../../node_modules/@openzeppelin-compact/utils/src/Utils" prefix Utils_; + import "ShieldedAccessControlUtils" prefix Utils_; /** - * @description A MerkleTree of role commitments stored as H(PK | role | nonce) - * @type {Bytes<32>} roleCommitment - A roleCommitment created by the following hash: SHA256(PK | role | nonce). + * @description A MerkleTree of role commitments stored as SHA256(PK | role | nonce | index) + * @type {Bytes<32>} finalRoleCommitment - A roleCommitment created by the following hash: SHA256(PK | role | nonce | index). * @type {MerkleTree<10, roleCommitment>} * @type {MerkleTree<10, Bytes<32>>} _operatorRoles  */ @@ -87,10 +87,10 @@ module ShieldedAccessControl { /** * @description A set of nullifiers used to revoke permissions from a role - * @type {Bytes<32>} roleCommitment - A roleCommitment created by the following hash: SHA256(PK | role | nonce). - * @type {Set} _roleNullifiers + * @type {Bytes<32> roleCommitment - A roleCommitment created by the following hash: SHA256(PK | role | nonce | index). + * @type {Set} _roleCommitmentNullifiers  */ - export ledger _roleNullifiers: Set>; + export ledger _roleCommitmentNullifiers: Set>; /** * @description Mapping from a role identifier to an admin role identifier. @@ -101,37 +101,80 @@ module ShieldedAccessControl {  */ export ledger _adminRoles: Map, Bytes<32>>; + /** + * @description Mapping from an intermediateRoleCommitment to an index in the `_operatorRoles` MerkleTree. + * @type {Bytes<32>} intermediateRoleCommitment - An intermediateRoleCommitment created by the following hash: SHA256(PK | role | nonce). + * @type {Uint<64>} index - The index of a finalRoleCommitment in the `_operatorRoles` MerkleTree created by the following hash: SHA256(PK | role | nonce | index). + * @type {Map} + * @type {Map, Uint<64>>} _roleCommitmentIndex +  */ + export ledger _roleCommitmentIndex: Map, Uint<64>>; + + export ledger _nextIndex: Counter; + export ledger DEFAULT_ADMIN_ROLE: Bytes<32>; - witness getRoleCommitmentPath(roleCommitment: Bytes<32>): MerkleTreePath<10, Bytes<32>>; + witness getRoleCommitmentPath(roleCommitment: Bytes<32>, index: Uint<64>): MerkleTreePath<10, Bytes<32>>; /** * @description Returns `true` if `account` has been granted `roleId`. * - * @circuitInfo + * @circuitInfo k=16, rows=60076 + * + * Requirements: + * + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * must exist in the `_roleCommitmentIndex` Map. + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) + * must not exist in the `_roleCommitmentNullifiers` Set. + * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must + * exist at `index` in the `_operatorRoles` MerkleTree. + * + * Disclosures: + * + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce | index). + * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` + * MerkleTree. + * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. * - * @param {Either} account - The account to query. * @param {Bytes<32>} roleId - The role identifier. + * @param {Either} account - The account to check. * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) - * @return {Boolean} - Whether the account has the specified role. + * @return {Boolean} - A boolean determining if the account has the specified role.  */ export circuit hasRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { - const roleCommitment = persistentHash>>([roleId, account.left.bytes, nonce]); - const authPath = getRoleCommitmentPath(roleCommitment); - const isNullified = _roleNullifiers.member(disclose(roleCommitment)); - return _operatorRoles - .checkRoot(merkleTreePathRoot<10, Bytes<32>>(disclose(authPath))) && !isNullified; + if (!Utils_isContractAddress(account)) { + const zswapPubKey = account.left.bytes; + return _checkMerkleTree(roleId, zswapPubKey, nonce); + } + + const contractAddress = account.right.bytes; + return _checkMerkleTree(roleId, contractAddress, nonce); } /** * @description Reverts if `ownPublicKey()` is missing `roleId`. * - * @circuitInfo + * @circuitInfo k=15, rows=29786 * * Requirements: * - * - The caller must have `roleId`. - * - The caller must not be a ContractAddress + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * must exist in the `_roleCommitmentIndex` Map. + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) + * must not exist in the `_roleCommitmentNullifiers` Set. + * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must + * exist at `index` in the `_operatorRoles` MerkleTree. + * - The caller must not be a ContractAddress. + * + * Disclosures: + * + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce | index). + * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` + * MerkleTree. + * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. * * @param {Bytes<32>} roleId - The role identifier. * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) @@ -140,7 +183,7 @@ module ShieldedAccessControl { export circuit assertOnlyRole(roleId: Bytes<32>, nonce: Bytes<32>): [] { _checkRole( roleId, - left(ownPublicKey()), + left(ownPublicKey()), nonce ); } @@ -148,14 +191,27 @@ module ShieldedAccessControl { /** * @description Reverts if `account` is missing `roleId`. * - * @circuitInfo + * @circuitInfo k=16, rows=60055 * * Requirements: * - * - `account` must have `roleId`. + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * must exist in the `_roleCommitmentIndex` Map. + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) + * must not exist in the `_roleCommitmentNullifiers` Set. + * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must + * exist at `index` in the `_operatorRoles` MerkleTree. + * + * Disclosures: + * + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce | index). + * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` + * MerkleTree. + * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. * * @param {Bytes<32>} roleId - The role identifier. - * @param {Either} account - The account to query. + * @param {Either} account - The account to check. * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {[]} - Empty tuple. */ @@ -163,13 +219,53 @@ module ShieldedAccessControl { assert(hasRole(roleId, account, nonce), "ShieldedAccessControl: unauthorized account"); } + /** + * @description Checks if a path exists for a role commitment. + * + * @circuitInfo k=15, rows=29807 + * + * Requirements: + * + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * must exist in the `_roleCommitmentIndex` Map. + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) + * must not exist in the `_roleCommitmentNullifiers` Set. + * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must + * exist at `index` in the `_operatorRoles` MerkleTree. + * + * Disclosures: + * + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce | index). + * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` + * MerkleTree. + * + * @param {Bytes<32>} roleId - The role identifier. + * @param {Bytes<32>} account - The account to check represented as a Bytes<32>. + * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) + * @return {Boolean} - A boolean determining if a path for for the role commitment + * produced by SHA256(roleId | account | nonce | index) exists in the `_operatorRoles` MerkleTree + */ + export circuit _checkMerkleTree(roleId: Bytes<32>, account: Bytes<32>, nonce: Bytes<32>): Boolean { + const intermediateRoleCommitment = persistentHash>>([roleId, account, nonce]); + assert(_roleCommitmentIndex.member(disclose(intermediateRoleCommitment)), "ShieldedAccessControl: role commitment index not found"); + + const index = _roleCommitmentIndex.lookup(disclose(intermediateRoleCommitment)); + const finalRoleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Bytes<32>]); + assert(!_roleCommitmentNullifiers.member(disclose(finalRoleCommitment)), "ShieldedAccessControl: role commitment access revoked"); + + const authPath = getRoleCommitmentPath(finalRoleCommitment, index); + return _operatorRoles + .checkRoot(merkleTreePathRoot<10, Bytes<32>>(disclose(authPath))); + } + /** * @description Returns the admin role that controls `roleId` or * a byte array with all zero bytes if `roleId` doesn't exist. See {grantRole} and {revokeRole}. * * To change a role’s admin use {_setRoleAdmin}. * - * @circuitInfo + * @circuitInfo k=10, rows=212 * * @param {Bytes<32>} roleId - The role identifier. * @return {Bytes<32>} roleAdmin - The admin role that controls `roleId`. @@ -184,12 +280,25 @@ module ShieldedAccessControl { /** * @description Grants `roleId` to `account`. * - * @circuitInfo + * @circuitInfo k=18, rows=138635 * * Requirements: * * - `account` must not be a ContractAddress. - * - The caller must have `roleId`'s admin role. + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * must exist in the `_roleCommitmentIndex` Map. + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) + * must not exist in the `_roleCommitmentNullifiers` Set. + * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must + * exist at `index` in the `_operatorRoles` MerkleTree. + * + * Disclosures: + * + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce | index). + * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` + * MerkleTree. + * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. * * @param {Bytes<32>} roleId - The role identifier. * @param {Either} account - A ZswapCoinPublicKey or ContractAddress. @@ -204,11 +313,25 @@ module ShieldedAccessControl { /** * @description Revokes `roleId` from `account`. * - * @circuitInfo + * @circuitInfo k=18, rows=138383 * * Requirements: * - * - The caller must have `roleId`'s admin role. + * - `account` must not be a ContractAddress. + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * must exist in the `_roleCommitmentIndex` Map. + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) + * must not exist in the `_roleCommitmentNullifiers` Set. + * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must + * exist at `index` in the `_operatorRoles` MerkleTree. + * + * Disclosures: + * + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce | index). + * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` + * MerkleTree. + * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. * * @param {Bytes<32>} roleId - The role identifier. * @param {Either} account - A ZswapCoinPublicKey or ContractAddress. @@ -227,12 +350,26 @@ module ShieldedAccessControl { * purpose is to provide a mechanism for accounts to lose their privileges * if they are compromised (such as when a trusted device is misplaced). * - * @circuitInfo + * @circuitInfo k=17, rows=108846 * * Requirements: * * - The caller must be `callerConfirmation`. * - The caller must not be a `ContractAddress`. + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * must exist in the `_roleCommitmentIndex` Map. + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) + * must not exist in the `_roleCommitmentNullifiers` Set. + * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must + * exist at `index` in the `_operatorRoles` MerkleTree. + * + * Disclosures: + * + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce | index). + * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` + * MerkleTree. + * - The type data of `callerConfirmation` - a ZswapCoinPublicKey or ContractAddress. * * @param {Bytes<32>} roleId - The role identifier. * @param {Either} callerConfirmation - A ZswapCoinPublicKey or ContractAddress. @@ -248,7 +385,7 @@ module ShieldedAccessControl { /** * @description Sets `adminRole` as `roleId`'s admin role. * - * @circuitInfo + * @circuitInfo k=10, rows=210 * * @param {Bytes<32>} roleId - The role identifier. * @param {Bytes<32>} adminRole - The admin role identifier. @@ -262,11 +399,25 @@ module ShieldedAccessControl { * @description Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted. * Internal circuit without access restriction. * - * @circuitInfo + * @circuitInfo k=17, rows=109025 * * Requirements: * * - `account` must not be a ContractAddress. + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * must exist in the `_roleCommitmentIndex` Map. + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) + * must not exist in the `_roleCommitmentNullifiers` Set. + * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must + * exist at `index` in the `_operatorRoles` MerkleTree. + * + * Disclosures: + * + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce | index). + * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` + * MerkleTree. + * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. * * @param {Bytes<32>} roleId - The role identifier. * @param {Either} account - A ZswapCoinPublicKey or ContractAddress. @@ -282,11 +433,28 @@ module ShieldedAccessControl { * @description Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted. * Internal circuit without access restriction. It does NOT check if the role is granted to a ContractAddress. * - * @circuitInfo + * @circuitInfo k=17, rows=109024 * * @notice External smart contracts cannot call the token contract at this time, so granting a role to an ContractAddress may * render a circuit permanently inaccessible. * + * Requirements: + * + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * must exist in the `_roleCommitmentIndex` Map. + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) + * must not exist in the `_roleCommitmentNullifiers` Set. + * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must + * exist at `index` in the `_operatorRoles` MerkleTree. + * + * Disclosures: + * + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce | index). + * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` + * MerkleTree. + * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. + * * @param {Bytes<32>} roleId - The role identifier. * @param {Either} account - A ZswapCoinPublicKey or ContractAddress. * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) @@ -298,13 +466,13 @@ module ShieldedAccessControl { } if (!Utils_isContractAddress(account)) { - const roleCommitment = persistentHash>>([roleId, account.left.bytes, nonce]); - _operatorRoles.insertHash(disclose(roleCommitment)); + const zswapPubKey = account.left.bytes; + _addRoleCommitmentToLedger(roleId, zswapPubKey, nonce); return true; } - const roleCommitment = persistentHash>>([roleId, account.right.bytes, nonce]); - _operatorRoles.insertHash(disclose(roleCommitment)); + const contractAddress = account.right.bytes; + _addRoleCommitmentToLedger(roleId, contractAddress, nonce); return true; } @@ -312,10 +480,27 @@ module ShieldedAccessControl { * @description Attempts to revoke `roleId` from `account` and returns a boolean indicating if `roleId` was revoked. * Internal circuit without access restriction. * - * @circuitInfo + * @circuitInfo k=17, rows=108770 + * + * Requirements: + * + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * must exist in the `_roleCommitmentIndex` Map. + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) + * must not exist in the `_roleCommitmentNullifiers` Set. + * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must + * exist at `index` in the `_operatorRoles` MerkleTree. + * + * Disclosures: + * + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce | index). + * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` + * MerkleTree. + * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. * * @param {Bytes<32>} roleId - The role identifier. - * @param {Bytes<32>} adminRole - The admin role identifier. + * @param {Either} account - A ZswapCoinPublicKey or ContractAddress. * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. */ @@ -325,13 +510,63 @@ module ShieldedAccessControl { } if(!Utils_isContractAddress(account)) { - const roleCommitment = persistentHash>>([roleId, account.left.bytes, nonce]); - _roleNullifiers.insert(disclose(roleCommitment)); + const zswapPubKey = account.left.bytes; + _nullifyRoleCommitment(roleId, zswapPubKey, nonce); return true; } - const roleCommitment = persistentHash>>([roleId, account.right.bytes, nonce]); - _roleNullifiers.insert(disclose(roleCommitment)); + const contractAddress = account.right.bytes; + _nullifyRoleCommitment(roleId, contractAddress, nonce); return true; } + + /** + * @description Adds a role commitment to the `_operatorRoles` MerkleTree. + * + * WARNING: Exposing this circuit in the implementing contract would allow anyone to add roles. + * + * @circuitInfo k=15, rows=24571 + * + * Disclosures: + * + * - The role commitment produced by SHA256(roleId | account | nonce | index). + * + * @param {Bytes<32>} roleId - The role identifier. + * @param {Bytes<32>} account - The account to add represented as a Bytes<32>. + * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) + * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. + */ + circuit _addRoleCommitmentToLedger(roleId: Bytes<32>, account: Bytes<32>, nonce: Bytes<32>): [] { + const intermediateRoleCommitment = persistentHash>>([roleId, account, nonce]); + const index = _nextIndex.read(); + const finalRoleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Bytes<32>]); + + _operatorRoles.insertHashIndex(disclose(finalRoleCommitment), index); + _roleCommitmentIndex.insert(disclose(finalRoleCommitment), index); + _nextIndex.increment(1); + } + + /** + * @description Adds a role commitment to the `_roleNullifiers` nullifer set. + * + * WARNING: Exposing this circuit in the implementing contract would allow anyone to revoke roles. + * + * @circuitInfo k=15, rows=24563 + * + * Disclosures: + * + * - The role commitment produced by SHA256(roleId | account | nonce | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * + * @param {Bytes<32>} roleId - The role identifier. + * @param {Bytes<32>} account - The account to add represented as a Bytes<32>. + * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) + * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. + */ + circuit _nullifyRoleCommitment(roleId: Bytes<32>, account: Bytes<32>, nonce: Bytes<32>): [] { + const intermediateRoleCommitment = persistentHash>>([roleId, account, nonce]); + const index = _roleCommitmentIndex.lookup(disclose(intermediateRoleCommitment)); + const finalRoleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Bytes<32>]); + _roleCommitmentNullifiers.insert(disclose(finalRoleCommitment)); + } } diff --git a/contracts/shieldedAccessControl/src/ShieldedAccessControlUtils.compact b/contracts/shieldedAccessControl/src/ShieldedAccessControlUtils.compact new file mode 100644 index 00000000..5c08f0f6 --- /dev/null +++ b/contracts/shieldedAccessControl/src/ShieldedAccessControlUtils.compact @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma language_version >= 0.16.0; + +/** + * @module ShieldedAccessControlUtils. + * @description A library for common utilities used in the Shielded Access Control module. + */ +module ShieldedAccessControlUtils { + import CompactStandardLibrary; + + /** + * @description Returns whether `keyOrAddress` is a ContractAddress type. + * + * Disclosures: + * + * - The type data of `keyOrAddress` - a ZswapCoinPublicKey or ContractAddress. + * + * @param {Either} keyOrAddress - The target value to check, either a ZswapCoinPublicKey or a ContractAddress. + * @return {Boolean} - Returns true if `keyOrAddress` is a ContractAddress. + */ + export pure circuit isContractAddress(keyOrAddress: Either): Boolean { + return disclose(!keyOrAddress.is_left); + } +} diff --git a/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact index 406bca34..85c25042 100644 --- a/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact +++ b/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma language_version >= 0.15.0; +pragma language_version >= 0.16.0; import CompactStandardLibrary; @@ -20,6 +20,10 @@ export circuit _checkRole(roleId: Bytes<32>, account: Either, account: Bytes<32>, nonce: Bytes<32>): Boolean { + return ShieldedAccessControl__checkMerkleTree(roleId, account, nonce); +} + export circuit getRoleAdmin(roleId: Bytes<32>): Bytes<32> { return ShieldedAccessControl_getRoleAdmin(roleId); } From 8fca267c7d102788d926e4ca354fce857e6f406a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:54:30 -0400 Subject: [PATCH 055/202] expose merkletree ledger var in TS --- .../src/test/mocks/MockShieldedAccessControl.compact | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact index 85c25042..f8710b91 100644 --- a/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact +++ b/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact @@ -6,7 +6,14 @@ import CompactStandardLibrary; import "../../ShieldedAccessControl" prefix ShieldedAccessControl_; -export { ZswapCoinPublicKey, ContractAddress, Either, Maybe, ShieldedAccessControl_DEFAULT_ADMIN_ROLE }; +export { + ZswapCoinPublicKey, + ContractAddress, + Either, + Maybe, + ShieldedAccessControl_DEFAULT_ADMIN_ROLE, + ShieldedAccessControl__operatorRoles +}; export circuit hasRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { return ShieldedAccessControl_hasRole(roleId, account, nonce); From b4cc0e026bf268835090a907ed1cc7ec4a35703b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 5 Aug 2025 19:51:31 -0400 Subject: [PATCH 056/202] Update contract interface --- .../src/ShieldedAccessControl.compact | 16 ++++++++++++++++ .../test/mocks/MockShieldedAccessControl.compact | 11 ++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact index ee39d49c..0e40989e 100644 --- a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact +++ b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact @@ -110,11 +110,15 @@ module ShieldedAccessControl {  */ export ledger _roleCommitmentIndex: Map, Uint<64>>; + export ledger _roleIds: Set>; + export ledger _nextIndex: Counter; export ledger DEFAULT_ADMIN_ROLE: Bytes<32>; witness getRoleCommitmentPath(roleCommitment: Bytes<32>, index: Uint<64>): MerkleTreePath<10, Bytes<32>>; + witness _requestRole(roleId: Bytes<32>): []; + witness _recoverNonce(): []; /** * @description Returns `true` if `account` has been granted `roleId`. @@ -537,6 +541,10 @@ module ShieldedAccessControl { * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. */ circuit _addRoleCommitmentToLedger(roleId: Bytes<32>, account: Bytes<32>, nonce: Bytes<32>): [] { + if (!_roleIds.member(disclose(roleId))) { + _roleIds.insert(disclose(roleId)); + } + const intermediateRoleCommitment = persistentHash>>([roleId, account, nonce]); const index = _nextIndex.read(); const finalRoleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Bytes<32>]); @@ -569,4 +577,12 @@ module ShieldedAccessControl { const finalRoleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Bytes<32>]); _roleCommitmentNullifiers.insert(disclose(finalRoleCommitment)); } + + export circuit requestRole(roleId: Bytes<32>): [] { + _requestRole(roleId); + } + + export circuit recoverNonce(): [] { + _recoverNonce(); + } } diff --git a/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact index f8710b91..1f8c048b 100644 --- a/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact +++ b/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact @@ -12,7 +12,8 @@ export { Either, Maybe, ShieldedAccessControl_DEFAULT_ADMIN_ROLE, - ShieldedAccessControl__operatorRoles + ShieldedAccessControl__operatorRoles, + ShieldedAccessControl__roleIds }; export circuit hasRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { @@ -62,3 +63,11 @@ export circuit _unsafeGrantRole(roleId: Bytes<32>, account: Either, account: Either, nonce: Bytes<32>): Boolean { return ShieldedAccessControl__revokeRole(roleId, account, nonce); } + +export circuit recoverNonce(): [] { + ShieldedAccessControl_recoverNonce(); +} + +export circuit requestRole(roleId: Bytes<32>): [] { + ShieldedAccessControl_requestRole(roleId); +} From 73ffd2bdee8d270d06888a69755f5bbbcbad63a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 5 Aug 2025 19:56:25 -0400 Subject: [PATCH 057/202] Remove _roleIds --- .../shieldedAccessControl/src/ShieldedAccessControl.compact | 6 ------ 1 file changed, 6 deletions(-) diff --git a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact index 0e40989e..7183d954 100644 --- a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact +++ b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact @@ -110,8 +110,6 @@ module ShieldedAccessControl {  */ export ledger _roleCommitmentIndex: Map, Uint<64>>; - export ledger _roleIds: Set>; - export ledger _nextIndex: Counter; export ledger DEFAULT_ADMIN_ROLE: Bytes<32>; @@ -541,10 +539,6 @@ module ShieldedAccessControl { * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. */ circuit _addRoleCommitmentToLedger(roleId: Bytes<32>, account: Bytes<32>, nonce: Bytes<32>): [] { - if (!_roleIds.member(disclose(roleId))) { - _roleIds.insert(disclose(roleId)); - } - const intermediateRoleCommitment = persistentHash>>([roleId, account, nonce]); const index = _nextIndex.read(); const finalRoleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Bytes<32>]); From 8af10e28f8c98c35642d76efaed9c66bfd3e7f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 5 Aug 2025 20:27:26 -0400 Subject: [PATCH 058/202] revert changes --- .../src/ShieldedAccessControl.compact | 26 ++++++++++++------- .../mocks/MockShieldedAccessControl.compact | 13 ++-------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact index 7183d954..4d321892 100644 --- a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact +++ b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact @@ -110,13 +110,27 @@ module ShieldedAccessControl {  */ export ledger _roleCommitmentIndex: Map, Uint<64>>; + /** + * @description A counter tracking the next available index in the `_operatorRoles` MerkleTree + */ export ledger _nextIndex: Counter; export ledger DEFAULT_ADMIN_ROLE: Bytes<32>; + /** + * @description Returns the Merkle path, given the knowledge that a `roleCommitment` is at the given index. + * + * Requirements: + * + * - It is an error to call this if this if `roleCommitment` is not contained at the given index. + * + * @circuitInfo + * + * @param {Bytes<32>} roleCommitment - The role identifier. + * @param {Uint<64>} index - An index in the `_operatorRoles` MerkleTree + * @return {MerkleTreePath<10, Bytes<32>>} - The Merkle path of `roleCommitment` in the `_operatorRoles` Merkle Tree +  */ witness getRoleCommitmentPath(roleCommitment: Bytes<32>, index: Uint<64>): MerkleTreePath<10, Bytes<32>>; - witness _requestRole(roleId: Bytes<32>): []; - witness _recoverNonce(): []; /** * @description Returns `true` if `account` has been granted `roleId`. @@ -571,12 +585,4 @@ module ShieldedAccessControl { const finalRoleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Bytes<32>]); _roleCommitmentNullifiers.insert(disclose(finalRoleCommitment)); } - - export circuit requestRole(roleId: Bytes<32>): [] { - _requestRole(roleId); - } - - export circuit recoverNonce(): [] { - _recoverNonce(); - } } diff --git a/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact index 1f8c048b..e1fe345d 100644 --- a/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact +++ b/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact @@ -12,8 +12,7 @@ export { Either, Maybe, ShieldedAccessControl_DEFAULT_ADMIN_ROLE, - ShieldedAccessControl__operatorRoles, - ShieldedAccessControl__roleIds + ShieldedAccessControl__operatorRoles }; export circuit hasRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { @@ -62,12 +61,4 @@ export circuit _unsafeGrantRole(roleId: Bytes<32>, account: Either, account: Either, nonce: Bytes<32>): Boolean { return ShieldedAccessControl__revokeRole(roleId, account, nonce); -} - -export circuit recoverNonce(): [] { - ShieldedAccessControl_recoverNonce(); -} - -export circuit requestRole(roleId: Bytes<32>): [] { - ShieldedAccessControl_requestRole(roleId); -} +} \ No newline at end of file From ecdc1d83f24843791a1830f3c4c7c9e64d20c230 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 6 Aug 2025 01:06:57 -0300 Subject: [PATCH 059/202] update syntax to compact 0.24.0 --- contracts/ownable/src/OwnablePK.compact | 18 +++++------ contracts/ownable/src/test/OwnablePK.test.ts | 30 +++++++++---------- .../src/test/mocks/MockOwnablePK.compact | 4 +-- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/contracts/ownable/src/OwnablePK.compact b/contracts/ownable/src/OwnablePK.compact index 3c4ab389..60352b47 100644 --- a/contracts/ownable/src/OwnablePK.compact +++ b/contracts/ownable/src/OwnablePK.compact @@ -42,9 +42,9 @@ module OwnablePK { * @returns {[]} - None. */ export circuit initializer(initOwner: ZswapCoinPublicKey): [] { - assert initOwner != burn_address().left "OwnablePK: new owner cannot be zero"; + assert(initOwner != burnAddress().left, "OwnablePK: new owner cannot be zero"); const nextInstance = _instance + 1 as Field as Bytes<32>; - const shieldedOwner = shieldOwner(initOwner, nextInstance); + const shieldedOwner = shieldOwner(disclose(initOwner), nextInstance); _transferOwnership(shieldedOwner); } @@ -93,10 +93,10 @@ module OwnablePK { * @returns {[]} - None. */ export circuit acceptOwnership(): [] { - const caller = own_public_key(); + const caller = ownPublicKey(); const nextInstance = _instance + 1 as Field as Bytes<32>; const shieldedOwner = shieldOwner(caller, nextInstance); - assert shieldedOwner == _pendingOwner "OwnablePK: caller is not pending owner"; + assert(shieldedOwner == _pendingOwner, "OwnablePK: caller is not pending owner"); // Reset pending owner and assign new owner _transferOwnership(shieldedOwner); @@ -125,8 +125,8 @@ module OwnablePK { * @returns {[]} - None. */ export circuit assertOnlyOwner(): [] { - const caller = own_public_key(); - assert _owner == shieldOwner(caller, _instance as Field as Bytes<32>) "OwnablePK: not owner"; + const caller = ownPublicKey(); + assert(_owner == shieldOwner(caller, _instance as Field as Bytes<32>), "OwnablePK: not owner"); } /** @@ -136,7 +136,7 @@ module OwnablePK { * @returns {Bytes<32>} - The shielded hash of the owner and instance. */ export circuit shieldOwner(ownerPK: ZswapCoinPublicKey, instance: Bytes<32>): Bytes<32> { - return persistent_hash>>([pad(32, "OwnablePK:shield:"), instance, ownerPK.bytes]); + return persistentHash>>([pad(32, "OwnablePK:shield:"), instance, ownerPK.bytes]); } /** @@ -150,7 +150,7 @@ module OwnablePK { export circuit _transferOwnership(newOwner: Bytes<32>): [] { _pendingOwner = default>; _instance.increment(1); - _owner = newOwner; + _owner = disclose(newOwner); } /** @@ -162,7 +162,7 @@ module OwnablePK { * @returns {[]} - None. */ export circuit _proposeOwner(newOwner: ZswapCoinPublicKey): [] { - if (newOwner == burn_address().left) { + if (newOwner == burnAddress().left) { _pendingOwner = pad(32, ""); } else { const nextInstance = _instance + 1 as Field as Bytes<32>; diff --git a/contracts/ownable/src/test/OwnablePK.test.ts b/contracts/ownable/src/test/OwnablePK.test.ts index a9fba560..fb2ab94d 100644 --- a/contracts/ownable/src/test/OwnablePK.test.ts +++ b/contracts/ownable/src/test/OwnablePK.test.ts @@ -31,7 +31,7 @@ describe('OwnablePK', () => { ownable = new OwnablePKSimulator(Z_OWNER, OWNER); // Check instance - const instance = ownable.getCurrentPublicState().ownablePK_Instance; + const instance = ownable.getCurrentPublicState().OwnablePK__instance; expect(instance).toEqual(1n); // Check shielded owner @@ -43,7 +43,7 @@ describe('OwnablePK', () => { // Check pending owner const pendingOwner = - ownable.getCurrentPublicState().ownablePK_PendingOwner; + ownable.getCurrentPublicState().OwnablePK__pendingOwner; expect(pendingOwner).toEqual(EMPTY_BYTES); }); @@ -62,7 +62,7 @@ describe('OwnablePK', () => { describe('owner', () => { it('should return correct owner', () => { expect(ownable.owner()).toEqual( - ownable.getCurrentPublicState().ownablePK_Owner, + ownable.getCurrentPublicState().OwnablePK__owner, ); }); @@ -76,7 +76,7 @@ describe('OwnablePK', () => { describe('pendingOwner', () => { it('should return pending owner', () => { const nextInstance = - ownable.getCurrentPublicState().ownablePK_Instance + 1n; + ownable.getCurrentPublicState().OwnablePK__instance + 1n; const expPending = ownable.shieldOwner( Z_NEW_OWNER, convert_bigint_to_Uint8Array(32, nextInstance), @@ -98,7 +98,7 @@ describe('OwnablePK', () => { // Check pending owner const nextInstance = - ownable.getCurrentPublicState().ownablePK_Instance + 1n; + ownable.getCurrentPublicState().OwnablePK__instance + 1n; const expPending = ownable.shieldOwner( Z_NEW_OWNER, convert_bigint_to_Uint8Array(32, nextInstance), @@ -106,7 +106,7 @@ describe('OwnablePK', () => { expect(ownable.pendingOwner()).toEqual(expPending); // Check current owner - const thisInstance = ownable.getCurrentPublicState().ownablePK_Instance; + const thisInstance = ownable.getCurrentPublicState().OwnablePK__instance; const expOwner = ownable.shieldOwner( Z_OWNER, convert_bigint_to_Uint8Array(32, thisInstance), @@ -140,7 +140,7 @@ describe('OwnablePK', () => { // Check new pending owner const nextInstance = - ownable.getCurrentPublicState().ownablePK_Instance + 1n; + ownable.getCurrentPublicState().OwnablePK__instance + 1n; const expPending = ownable.shieldOwner( Z_NEW_NEW_OWNER, convert_bigint_to_Uint8Array(32, nextInstance), @@ -158,13 +158,13 @@ describe('OwnablePK', () => { it('should accept ownership from pending owner', () => { caller = NEW_OWNER; const beforeInstance = - ownable.getCurrentPublicState().ownablePK_Instance; + ownable.getCurrentPublicState().OwnablePK__instance; ownable.acceptOwnership(caller); // Check instance is bumped const afterInstance = - ownable.getCurrentPublicState().ownablePK_Instance; + ownable.getCurrentPublicState().OwnablePK__instance; expect(afterInstance).toEqual(beforeInstance + 1n); // Check new owner @@ -215,12 +215,12 @@ describe('OwnablePK', () => { it('should renounce ownership', () => { caller = OWNER; const beforeInstance = - ownable.getCurrentPublicState().ownablePK_Instance; + ownable.getCurrentPublicState().OwnablePK__instance; ownable.renounceOwnership(caller); expect(ownable.owner()).toEqual(EMPTY_BYTES); expect(ownable.pendingOwner()).toEqual(EMPTY_BYTES); - expect(ownable.getCurrentPublicState().ownablePK_Instance).toEqual( + expect(ownable.getCurrentPublicState().OwnablePK__instance).toEqual( beforeInstance + 1n, ); }); @@ -258,7 +258,7 @@ describe('OwnablePK', () => { // Transfer to new owner const nextInstance = - ownable.getCurrentPublicState().ownablePK_Instance + 1n; + ownable.getCurrentPublicState().OwnablePK__instance + 1n; const newOwner = ownable.shieldOwner( Z_NEW_OWNER, convert_bigint_to_Uint8Array(32, nextInstance), @@ -286,7 +286,7 @@ describe('OwnablePK', () => { describe('_transferOwnership', () => { it('should transfer ownership', () => { const beforeInstance = - ownable.getCurrentPublicState().ownablePK_Instance; + ownable.getCurrentPublicState().OwnablePK__instance; ownable._proposeOwner(Z_NEW_NEW_OWNER); ownable._transferOwnership(Z_NEW_OWNER.bytes); @@ -294,7 +294,7 @@ describe('OwnablePK', () => { // _transferownership does not shield the input so it should be a == a expect(ownable.owner()).toEqual(Z_NEW_OWNER.bytes); // Check instance is bumped - expect(ownable.getCurrentPublicState().ownablePK_Instance).toEqual( + expect(ownable.getCurrentPublicState().OwnablePK__instance).toEqual( beforeInstance + 1n, ); // Check pending owner is reset @@ -313,7 +313,7 @@ describe('OwnablePK', () => { ownable._proposeOwner(Z_NEW_OWNER); const nextInstance = - ownable.getCurrentPublicState().ownablePK_Instance + 1n; + ownable.getCurrentPublicState().OwnablePK__instance + 1n; const expOwner = ownable.shieldOwner( Z_NEW_OWNER, convert_bigint_to_Uint8Array(32, nextInstance), diff --git a/contracts/ownable/src/test/mocks/MockOwnablePK.compact b/contracts/ownable/src/test/mocks/MockOwnablePK.compact index 85ebcc76..1ab40018 100644 --- a/contracts/ownable/src/test/mocks/MockOwnablePK.compact +++ b/contracts/ownable/src/test/mocks/MockOwnablePK.compact @@ -21,7 +21,7 @@ export circuit pendingOwner(): Bytes<32> { } export circuit transferOwnership(newOwner: ZswapCoinPublicKey): [] { - return OwnablePK_transferOwnership(newOwner); + return OwnablePK_transferOwnership(disclose(newOwner)); } export circuit acceptOwnership(): [] { @@ -45,5 +45,5 @@ export circuit _transferOwnership(newOwner: Bytes<32>): [] { } export circuit _proposeOwner(newOwner: ZswapCoinPublicKey): [] { - return OwnablePK__proposeOwner(newOwner); + return OwnablePK__proposeOwner(disclose(newOwner)); } From cdb21bcb5a1e0f53f00e73f7baba43dd99763452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Thu, 7 Aug 2025 15:26:37 -0400 Subject: [PATCH 060/202] Circuit performance speedup --- .../src/ShieldedAccessControl.compact | 244 +++++++++--------- 1 file changed, 122 insertions(+), 122 deletions(-) diff --git a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact index 4d321892..686c6f42 100644 --- a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact +++ b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact @@ -5,10 +5,10 @@ pragma language_version >= 0.16.0; /** * @module Shielded AccessControl * @description A Shielded AccessControl library. - * This module provides a role-based access control mechanism, where roles can be used to - * represent a set of permissions. Roles are stored as MerkleTree commitments to avoid - * disclosing information regarding the roles an account may have. Commitments are created - * with SHA256(PublicKey | roleIdentifier | nonce). + * This module provides a shielded role-based access control mechanism, where roles can be used to + * represent a set of permissions. Roles are stored as Merkle tree commitments to avoid + * disclosing information about role holder. Role commitments are created with the following + * hashing scheme SHA256( SHA256(PublicKey | roleIdentifier | nonce) | index). * * @notice Using the SHA256 hashing function comes at a significant performace cost. In the future, we * plan on migrating to a ZK-friendly hashing function like Poseidon when an implementation is available. @@ -78,20 +78,13 @@ module ShieldedAccessControl { import "ShieldedAccessControlUtils" prefix Utils_; /** - * @description A MerkleTree of role commitments stored as SHA256(PK | role | nonce | index) - * @type {Bytes<32>} finalRoleCommitment - A roleCommitment created by the following hash: SHA256(PK | role | nonce | index). + * @description A Merkle tree of role commitments stored as SHA256( SHA256(PK | role | nonce) | index) + * @type {Bytes<32>} roleCommitment - A commitment created by the following hash: SHA256( SHA256(PK | role | nonce) | index). * @type {MerkleTree<10, roleCommitment>} * @type {MerkleTree<10, Bytes<32>>} _operatorRoles  */ export ledger _operatorRoles: MerkleTree<10, Bytes<32>>; - /** - * @description A set of nullifiers used to revoke permissions from a role - * @type {Bytes<32> roleCommitment - A roleCommitment created by the following hash: SHA256(PK | role | nonce | index). - * @type {Set} _roleCommitmentNullifiers -  */ - export ledger _roleCommitmentNullifiers: Set>; - /** * @description Mapping from a role identifier to an admin role identifier. * @type {Bytes<32>} roleId - A hash representing a role identifier. @@ -102,9 +95,16 @@ module ShieldedAccessControl { export ledger _adminRoles: Map, Bytes<32>>; /** - * @description Mapping from an intermediateRoleCommitment to an index in the `_operatorRoles` MerkleTree. - * @type {Bytes<32>} intermediateRoleCommitment - An intermediateRoleCommitment created by the following hash: SHA256(PK | role | nonce). - * @type {Uint<64>} index - The index of a finalRoleCommitment in the `_operatorRoles` MerkleTree created by the following hash: SHA256(PK | role | nonce | index). + * @description A set of nullifiers used to revoke the permissions of a role + * @type {Bytes<32> roleCommitment - A roleCommitment created by the following hash: SHA256( SHA256(PK | role | nonce) | index). + * @type {Set} _roleCommitmentNullifiers +  */ + export ledger _roleCommitmentNullifiers: Set>; + + /** + * @description Mapping from an intermediate role commitment hash to an index in the `_operatorRoles` Merkle tree. + * @type {Bytes<32>} intermediateRoleCommitment - An intermediate role commitment hash created by the following hashing scheme: SHA256(PK | role | nonce). + * @type {Uint<64>} index - The index of a role commitment in the `_operatorRoles` Merkle tree. * @type {Map} * @type {Map, Uint<64>>} _roleCommitmentIndex  */ @@ -118,7 +118,7 @@ module ShieldedAccessControl { export ledger DEFAULT_ADMIN_ROLE: Bytes<32>; /** - * @description Returns the Merkle path, given the knowledge that a `roleCommitment` is at the given index. + * @description Returns a Merkle path in the `_operatorRoles` Merkle tree, given the knowledge that a `roleCommitment` is at the given index. * * Requirements: * @@ -126,37 +126,37 @@ module ShieldedAccessControl { * * @circuitInfo * - * @param {Bytes<32>} roleCommitment - The role identifier. - * @param {Uint<64>} index - An index in the `_operatorRoles` MerkleTree - * @return {MerkleTreePath<10, Bytes<32>>} - The Merkle path of `roleCommitment` in the `_operatorRoles` Merkle Tree + * @param {Bytes<32>} roleCommitment - A commitment created by the following hash: SHA256( SHA256(PK | role | nonce) | index). + * @param {Uint<64>} index - An index in the `_operatorRoles` Merkle tree + * @return {MerkleTreePath<10, Bytes<32>>} - The Merkle path of `roleCommitment` in the `_operatorRoles` Merkle tree  */ witness getRoleCommitmentPath(roleCommitment: Bytes<32>, index: Uint<64>): MerkleTreePath<10, Bytes<32>>; /** * @description Returns `true` if `account` has been granted `roleId`. * - * @circuitInfo k=16, rows=60076 + * @circuitInfo k=16, rows=50605 * * Requirements: * * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) - * must exist in the `_roleCommitmentIndex` Map. - * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) - * must not exist in the `_roleCommitmentNullifiers` Set. - * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must - * exist at `index` in the `_operatorRoles` MerkleTree. + * must exist in the `_roleCommitmentIndex` map. + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * must not exist in the `_roleCommitmentNullifiers` set. + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256(roleId | account | nonce | index). - * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` - * MerkleTree. + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` + * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. * * @param {Bytes<32>} roleId - The role identifier. * @param {Either} account - The account to check. - * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) + * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK). * @return {Boolean} - A boolean determining if the account has the specified role.  */ export circuit hasRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { @@ -172,24 +172,24 @@ module ShieldedAccessControl { /** * @description Reverts if `ownPublicKey()` is missing `roleId`. * - * @circuitInfo k=15, rows=29786 + * @circuitInfo k=15, rows=25046 * * Requirements: * * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) - * must exist in the `_roleCommitmentIndex` Map. - * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) - * must not exist in the `_roleCommitmentNullifiers` Set. - * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must - * exist at `index` in the `_operatorRoles` MerkleTree. + * must exist in the `_roleCommitmentIndex` map. + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * must not exist in the `_roleCommitmentNullifiers` set. + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * exist at `index` in the `_operatorRoles` Merkle tree. * - The caller must not be a ContractAddress. * * Disclosures: * * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256(roleId | account | nonce | index). - * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` - * MerkleTree. + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` + * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. * * @param {Bytes<32>} roleId - The role identifier. @@ -207,23 +207,23 @@ module ShieldedAccessControl { /** * @description Reverts if `account` is missing `roleId`. * - * @circuitInfo k=16, rows=60055 + * @circuitInfo k=16, rows=50584 * * Requirements: * * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) - * must exist in the `_roleCommitmentIndex` Map. - * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) - * must not exist in the `_roleCommitmentNullifiers` Set. - * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must - * exist at `index` in the `_operatorRoles` MerkleTree. + * must exist in the `_roleCommitmentIndex` map. + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * must not exist in the `_roleCommitmentNullifiers` set. + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256(roleId | account | nonce | index). - * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` - * MerkleTree. + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` + * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. * * @param {Bytes<32>} roleId - The role identifier. @@ -238,36 +238,36 @@ module ShieldedAccessControl { /** * @description Checks if a path exists for a role commitment. * - * @circuitInfo k=15, rows=29807 + * @circuitInfo k=15, rows=25067 * * Requirements: * * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) - * must exist in the `_roleCommitmentIndex` Map. - * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) - * must not exist in the `_roleCommitmentNullifiers` Set. - * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must - * exist at `index` in the `_operatorRoles` MerkleTree. + * must exist in the `_roleCommitmentIndex` map. + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * must not exist in the `_roleCommitmentNullifiers` set. + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256(roleId | account | nonce | index). - * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` - * MerkleTree. + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` + * Merkle tree. * * @param {Bytes<32>} roleId - The role identifier. * @param {Bytes<32>} account - The account to check represented as a Bytes<32>. * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {Boolean} - A boolean determining if a path for for the role commitment - * produced by SHA256(roleId | account | nonce | index) exists in the `_operatorRoles` MerkleTree + * produced by SHA256( SHA256(roleId | account | nonce) | index) exists in the `_operatorRoles` Merkle tree */ export circuit _checkMerkleTree(roleId: Bytes<32>, account: Bytes<32>, nonce: Bytes<32>): Boolean { const intermediateRoleCommitment = persistentHash>>([roleId, account, nonce]); assert(_roleCommitmentIndex.member(disclose(intermediateRoleCommitment)), "ShieldedAccessControl: role commitment index not found"); const index = _roleCommitmentIndex.lookup(disclose(intermediateRoleCommitment)); - const finalRoleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Bytes<32>]); + const finalRoleCommitment = persistentHash>>([intermediateRoleCommitment, index as Field as Bytes<32>]); assert(!_roleCommitmentNullifiers.member(disclose(finalRoleCommitment)), "ShieldedAccessControl: role commitment access revoked"); const authPath = getRoleCommitmentPath(finalRoleCommitment, index); @@ -281,7 +281,7 @@ module ShieldedAccessControl { * * To change a role’s admin use {_setRoleAdmin}. * - * @circuitInfo k=10, rows=212 + * @circuitInfo k=10, rows=207 * * @param {Bytes<32>} roleId - The role identifier. * @return {Bytes<32>} roleAdmin - The admin role that controls `roleId`. @@ -296,24 +296,24 @@ module ShieldedAccessControl { /** * @description Grants `roleId` to `account`. * - * @circuitInfo k=18, rows=138635 + * @circuitInfo k=17, rows=114944 * * Requirements: * * - `account` must not be a ContractAddress. * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) - * must exist in the `_roleCommitmentIndex` Map. - * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) - * must not exist in the `_roleCommitmentNullifiers` Set. - * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must - * exist at `index` in the `_operatorRoles` MerkleTree. + * must exist in the `_roleCommitmentIndex` map. + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * must not exist in the `_roleCommitmentNullifiers` set. + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256(roleId | account | nonce | index). - * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` - * MerkleTree. + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` + * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. * * @param {Bytes<32>} roleId - The role identifier. @@ -329,24 +329,24 @@ module ShieldedAccessControl { /** * @description Revokes `roleId` from `account`. * - * @circuitInfo k=18, rows=138383 + * @circuitInfo k=17, rows=114699 * * Requirements: * * - `account` must not be a ContractAddress. * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) - * must exist in the `_roleCommitmentIndex` Map. - * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) - * must not exist in the `_roleCommitmentNullifiers` Set. - * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must - * exist at `index` in the `_operatorRoles` MerkleTree. + * must exist in the `_roleCommitmentIndex` map. + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * must not exist in the `_roleCommitmentNullifiers` set. + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256(roleId | account | nonce | index). - * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` - * MerkleTree. + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` + * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. * * @param {Bytes<32>} roleId - The role identifier. @@ -366,25 +366,25 @@ module ShieldedAccessControl { * purpose is to provide a mechanism for accounts to lose their privileges * if they are compromised (such as when a trusted device is misplaced). * - * @circuitInfo k=17, rows=108846 + * @circuitInfo k=17, rows=89905 * * Requirements: * * - The caller must be `callerConfirmation`. * - The caller must not be a `ContractAddress`. * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) - * must exist in the `_roleCommitmentIndex` Map. - * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) - * must not exist in the `_roleCommitmentNullifiers` Set. - * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must - * exist at `index` in the `_operatorRoles` MerkleTree. + * must exist in the `_roleCommitmentIndex` map. + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * must not exist in the `_roleCommitmentNullifiers` set. + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256(roleId | account | nonce | index). - * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` - * MerkleTree. + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` + * Merkle tree. * - The type data of `callerConfirmation` - a ZswapCoinPublicKey or ContractAddress. * * @param {Bytes<32>} roleId - The role identifier. @@ -401,7 +401,7 @@ module ShieldedAccessControl { /** * @description Sets `adminRole` as `roleId`'s admin role. * - * @circuitInfo k=10, rows=210 + * @circuitInfo k=10, rows=209 * * @param {Bytes<32>} roleId - The role identifier. * @param {Bytes<32>} adminRole - The admin role identifier. @@ -415,24 +415,24 @@ module ShieldedAccessControl { * @description Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted. * Internal circuit without access restriction. * - * @circuitInfo k=17, rows=109025 + * @circuitInfo k=17, rows=90077 * * Requirements: * * - `account` must not be a ContractAddress. * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) - * must exist in the `_roleCommitmentIndex` Map. - * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) - * must not exist in the `_roleCommitmentNullifiers` Set. - * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must - * exist at `index` in the `_operatorRoles` MerkleTree. + * must exist in the `_roleCommitmentIndex` map. + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * must not exist in the `_roleCommitmentNullifiers` set. + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256(roleId | account | nonce | index). - * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` - * MerkleTree. + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` + * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. * * @param {Bytes<32>} roleId - The role identifier. @@ -449,7 +449,7 @@ module ShieldedAccessControl { * @description Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted. * Internal circuit without access restriction. It does NOT check if the role is granted to a ContractAddress. * - * @circuitInfo k=17, rows=109024 + * @circuitInfo k=17, rows=90076 * * @notice External smart contracts cannot call the token contract at this time, so granting a role to an ContractAddress may * render a circuit permanently inaccessible. @@ -457,18 +457,18 @@ module ShieldedAccessControl { * Requirements: * * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) - * must exist in the `_roleCommitmentIndex` Map. - * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) - * must not exist in the `_roleCommitmentNullifiers` Set. - * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must - * exist at `index` in the `_operatorRoles` MerkleTree. + * must exist in the `_roleCommitmentIndex` map. + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * must not exist in the `_roleCommitmentNullifiers` set. + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256(roleId | account | nonce | index). - * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` - * MerkleTree. + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` + * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. * * @param {Bytes<32>} roleId - The role identifier. @@ -496,23 +496,23 @@ module ShieldedAccessControl { * @description Attempts to revoke `roleId` from `account` and returns a boolean indicating if `roleId` was revoked. * Internal circuit without access restriction. * - * @circuitInfo k=17, rows=108770 + * @circuitInfo k=17, rows=89829 * * Requirements: * * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) - * must exist in the `_roleCommitmentIndex` Map. - * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce | index) - * must not exist in the `_roleCommitmentNullifiers` Set. - * - A path for the role commitment produced by SHA256(roleId | account | nonce | index) must - * exist at `index` in the `_operatorRoles` MerkleTree. + * must exist in the `_roleCommitmentIndex` map. + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * must not exist in the `_roleCommitmentNullifiers` set. + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256(roleId | account | nonce | index). - * - The MerkleTree path for the role commitment stored at `index` in the `_operatorRoles` - * MerkleTree. + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` + * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. * * @param {Bytes<32>} roleId - The role identifier. @@ -537,15 +537,15 @@ module ShieldedAccessControl { } /** - * @description Adds a role commitment to the `_operatorRoles` MerkleTree. + * @description Adds a role commitment to the `_operatorRoles` Merkle tree. * * WARNING: Exposing this circuit in the implementing contract would allow anyone to add roles. * - * @circuitInfo k=15, rows=24571 + * @circuitInfo k=15, rows=19832 * * Disclosures: * - * - The role commitment produced by SHA256(roleId | account | nonce | index). + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). * * @param {Bytes<32>} roleId - The role identifier. * @param {Bytes<32>} account - The account to add represented as a Bytes<32>. @@ -555,7 +555,7 @@ module ShieldedAccessControl { circuit _addRoleCommitmentToLedger(roleId: Bytes<32>, account: Bytes<32>, nonce: Bytes<32>): [] { const intermediateRoleCommitment = persistentHash>>([roleId, account, nonce]); const index = _nextIndex.read(); - const finalRoleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Bytes<32>]); + const finalRoleCommitment = persistentHash>>([intermediateRoleCommitment, index as Field as Bytes<32>]); _operatorRoles.insertHashIndex(disclose(finalRoleCommitment), index); _roleCommitmentIndex.insert(disclose(finalRoleCommitment), index); @@ -567,11 +567,11 @@ module ShieldedAccessControl { * * WARNING: Exposing this circuit in the implementing contract would allow anyone to revoke roles. * - * @circuitInfo k=15, rows=24563 + * @circuitInfo k=15, rows=19824 * * Disclosures: * - * - The role commitment produced by SHA256(roleId | account | nonce | index). + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). * - The intermediate role commitment produced by SHA256(roleId | account | nonce). * * @param {Bytes<32>} roleId - The role identifier. @@ -582,7 +582,7 @@ module ShieldedAccessControl { circuit _nullifyRoleCommitment(roleId: Bytes<32>, account: Bytes<32>, nonce: Bytes<32>): [] { const intermediateRoleCommitment = persistentHash>>([roleId, account, nonce]); const index = _roleCommitmentIndex.lookup(disclose(intermediateRoleCommitment)); - const finalRoleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Bytes<32>]); + const finalRoleCommitment = persistentHash>>([intermediateRoleCommitment, index as Field as Bytes<32>]); _roleCommitmentNullifiers.insert(disclose(finalRoleCommitment)); } } From 20b14b8ca94ce4c67355ec03ba2af5411fe20ba2 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 7 Aug 2025 23:46:20 -0300 Subject: [PATCH 061/202] add initial pk design with init tests, sim, and witnesses --- contracts/ownable/src/Z_OwnablePK.compact | 75 +++++++ .../ownable/src/test/Z_OwnablePK.test.ts | 95 +++++++++ .../src/test/mocks/MockZ_OwnablePK.compact | 37 ++++ .../test/simulators/Z_OwnablePKSimulator.ts | 190 ++++++++++++++++++ .../src/witnesses/Z_OwnablePKWitnesses.ts | 38 ++++ contracts/ownable/src/witnesses/interface.ts | 15 +- 6 files changed, 449 insertions(+), 1 deletion(-) create mode 100644 contracts/ownable/src/Z_OwnablePK.compact create mode 100644 contracts/ownable/src/test/Z_OwnablePK.test.ts create mode 100644 contracts/ownable/src/test/mocks/MockZ_OwnablePK.compact create mode 100644 contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts create mode 100644 contracts/ownable/src/witnesses/Z_OwnablePKWitnesses.ts diff --git a/contracts/ownable/src/Z_OwnablePK.compact b/contracts/ownable/src/Z_OwnablePK.compact new file mode 100644 index 00000000..22a55e06 --- /dev/null +++ b/contracts/ownable/src/Z_OwnablePK.compact @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT + +pragma language_version >= 0.16.0; + +module Z_OwnablePK { + import CompactStandardLibrary; + + export ledger _ownerCommitment: Bytes<32>; + export ledger _instance: Counter; + + export witness offchainNonce(): Bytes<32>; + + export circuit initialize(initCommitment: Bytes<32>): [] { + assert(initCommitment != default>, "Invalid parameters"); + _transferOwnership(initCommitment); + } + + export circuit owner(): Bytes<32> { + return _ownerCommitment; + } + + export circuit transferOwnership(newOwnerCommitment: Bytes<32>): [] { + assertOnlyOwner(); + assert(newOwnerCommitment != default>, "Invalid parameters"); + _transferOwnership(newOwnerCommitment); + } + + export circuit renounceOwnership(): [] { + assertOnlyOwner(); + _transferOwnership(default>); + } + + export circuit renounceOwnershipObfuscated(): [] { + assertOnlyOwner(); + const nonce = offchainNonce(); + const obfuscatedCommitment = persistentHash>>( + [ + pad(32, "Z_OwnablePK:renounced:"), + default>, + _instance as Field as Bytes<32>, + nonce + ] + ); + + _transferOwnership(obfuscatedCommitment); + } + + export circuit assertOnlyOwner(): [] { + const caller = ownPublicKey(); + const nonce = offchainNonce(); + assert( + _ownerCommitment == shieldPK(caller, _instance, nonce + ), "Forbidden"); + } + + export circuit shieldPK( + pk: ZswapCoinPublicKey, + instance: Uint<64>, + nonce: Bytes<32> + ): Bytes<32> { + return persistentHash>>( + [ + pad(32, "Z_OwnablePK:shield:"), + pk.bytes, + instance as Field as Bytes<32>, + nonce + ] + ); + } + + export circuit _transferOwnership(newOwnerCommitment: Bytes<32>): [] { + _instance.increment(1); + _ownerCommitment = disclose(newOwnerCommitment); + } +} diff --git a/contracts/ownable/src/test/Z_OwnablePK.test.ts b/contracts/ownable/src/test/Z_OwnablePK.test.ts new file mode 100644 index 00000000..e2bb0ab8 --- /dev/null +++ b/contracts/ownable/src/test/Z_OwnablePK.test.ts @@ -0,0 +1,95 @@ +import { + type CoinPublicKey, + convert_bigint_to_Uint8Array, + persistentHash, + CompactTypeVector, + CompactTypeBytes +} from '@midnight-ntwrk/compact-runtime'; +import { beforeEach, describe, expect, it } from 'vitest'; +import { Z_OwnablePKSimulator } from './simulators/Z_OwnablePKSimulator.js'; +import * as utils from './utils/address.js'; +import { ZswapCoinPublicKey } from '../artifacts/MockOwnable/contract/index.cjs'; + +const OWNER = String(Buffer.from('OWNER', 'ascii').toString('hex')).padStart( + 64, + '0', +); +const NEW_OWNER = String( + Buffer.from('NEW_OWNER', 'ascii').toString('hex'), +).padStart(64, '0'); +const UNAUTHORIZED = String( + Buffer.from('UNAUTHORIZED', 'ascii').toString('hex'), +).padStart(64, '0'); +const Z_ZERO = utils.encodeToPK(''); +const Z_OWNER = utils.encodeToPK('OWNER'); +const Z_NEW_OWNER = utils.encodeToPK('NEW_OWNER'); +const Z_NEW_NEW_OWNER = utils.encodeToPK('Z_NEW_NEW_OWNER'); +const EMPTY_BYTES = utils.ZERO_KEY.left.bytes; + +// Commitments +const DOMAIN = "Z_OwnablePK:shield:"; +const INIT_COUNTER = 1n; +const STATIC_NONCE = new Uint8Array(32).fill(0xab); + +let ownable: Z_OwnablePKSimulator; +let caller: CoinPublicKey; + +const createZPKCommitment = ( + domain: string, + pk: ZswapCoinPublicKey, + counter: bigint, + nonce: Uint8Array +): Uint8Array => { + const rt_type = new CompactTypeVector(4, new CompactTypeBytes(32)); + const encoder = new TextEncoder(); + const bDomain = encoder.encode(domain); + const bPK = pk.bytes; + const bCounter = convert_bigint_to_Uint8Array(32, counter); + return persistentHash(rt_type, [bDomain, bPK, bCounter, nonce]); +} + +describe('Z_OwnablePK', () => { + describe('before initialize', () => { + it('should fail when setting owner commitment as 0', () => { + expect(() => { + const badCommitment = new Uint8Array(32).fill(0); + new Z_OwnablePKSimulator(badCommitment, OWNER); + }).toThrow('Invalid parameters'); + }); + + it('should initialize with non-zero commitment', () => { + const nonZeroCommitment = new Uint8Array(32).fill(1); + ownable = new Z_OwnablePKSimulator(nonZeroCommitment, OWNER); + + expect(ownable.owner()).toEqual(nonZeroCommitment); + }); + }); + + describe('after initialization', () => { + beforeEach(() => { + const ownerCommitment = createZPKCommitment(DOMAIN, Z_OWNER, INIT_COUNTER, STATIC_NONCE); + ownable = new Z_OwnablePKSimulator(ownerCommitment, OWNER); + }); + + describe('owner', () => { + it('should return the correct owner commitment', () => { + const expCommitment = createZPKCommitment(DOMAIN, Z_OWNER, INIT_COUNTER, STATIC_NONCE); + expect(ownable.owner()).toEqual(expCommitment); + }); + }); + + describe('assertOnlyOwner', () => { + it('should allow the authorized caller with correct nonce to call', () => { + caller = OWNER; + expect(ownable.assertOnlyOwner(caller)).to.not.throw; + }); + + it('should fail when called by unauthorized with correct nonce', () => { + caller = UNAUTHORIZED; + expect(() => { + ownable.assertOnlyOwner(caller); + }).toThrow('Forbidden'); + }); + }); + }); +}); diff --git a/contracts/ownable/src/test/mocks/MockZ_OwnablePK.compact b/contracts/ownable/src/test/mocks/MockZ_OwnablePK.compact new file mode 100644 index 00000000..e719e6c0 --- /dev/null +++ b/contracts/ownable/src/test/mocks/MockZ_OwnablePK.compact @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT + +pragma language_version >= 0.15.0; + +import CompactStandardLibrary; +import "../../Z_OwnablePK" prefix Z_OwnablePK_; + +export { ZswapCoinPublicKey, ContractAddress, Either, Maybe }; +export { Z_OwnablePK__ownerCommitment, Z_OwnablePK__instance }; + +constructor(initOwnerCommitment: Bytes<32>) { + Z_OwnablePK_initialize(initOwnerCommitment); +} + +export circuit owner(): Bytes<32> { + return Z_OwnablePK_owner(); +} + +export circuit transferOwnership(newOwnerCommitment: Bytes<32>): [] { + return Z_OwnablePK_transferOwnership(disclose(newOwnerCommitment)); +} + +export circuit renounceOwnership(): [] { + return Z_OwnablePK_renounceOwnership(); +} + +export circuit assertOnlyOwner(): [] { + return Z_OwnablePK_assertOnlyOwner(); +} + +export circuit shieldPK(ownerPK: ZswapCoinPublicKey, instance: Uint<64>, nonce: Bytes<32>): Bytes<32> { + return Z_OwnablePK_shieldPK(ownerPK, instance, nonce); +} + +export circuit _transferOwnership(newOwnerCommitment: Bytes<32>): [] { + return Z_OwnablePK__transferOwnership(newOwnerCommitment); +} diff --git a/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts new file mode 100644 index 00000000..dd187759 --- /dev/null +++ b/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts @@ -0,0 +1,190 @@ +import { + type CircuitContext, + type CoinPublicKey, + type ContractState, + constructorContext, + emptyZswapLocalState, + QueryContext, +} from '@midnight-ntwrk/compact-runtime'; +import { sampleContractAddress } from '@midnight-ntwrk/zswap'; +import type { ZswapCoinPublicKey } from '../../artifacts/MockZ_OwnablePK/contract/index.cjs'; +import { + type Ledger, + ledger, + Contract as MockOwnable, +} from '../../artifacts/MockZ_OwnablePK/contract/index.cjs'; // Combined imports +import { +Z_OwnablePKPrivateState, + Z_OwnablePKWitnesses, +} from '../../witnesses/Z_OwnablePKWitnesses.js'; +import type { IContractSimulator } from '../types/test.js'; +//import { types, padBytes, fieldToBytes } from 'compact-runtime'; + +/** + * @description A simulator implementation of a contract for testing purposes. + * @template P - The private state type, fixed to Z_OwnablePKPrivateState. + * @template L - The ledger type, fixed to Contract.Ledger. + */ +export class Z_OwnablePKSimulator + implements IContractSimulator +{ + /** @description The underlying contract instance managing contract logic. */ + readonly contract: MockOwnable; + + /** @description The deployed address of the contract. */ + readonly contractAddress: string; + + /** @description The deployer address of the contract. */ + readonly deployer: CoinPublicKey; + + /** @description The current circuit context, updated by contract operations. */ + circuitContext: CircuitContext; + + /** + * @description Initializes the mock contract. + */ + constructor(initOwner: Uint8Array, deployer: CoinPublicKey) { + this.contract = new MockOwnable(Z_OwnablePKWitnesses()); + this.deployer = deployer; + + const { + currentPrivateState, + currentContractState, + currentZswapLocalState, + } = this.contract.initialState( + constructorContext(Z_OwnablePKPrivateState.generate(), deployer), + initOwner + ); + this.circuitContext = { + currentPrivateState, + currentZswapLocalState, + originalState: currentContractState, + transactionContext: new QueryContext( + currentContractState.data, + sampleContractAddress(), + ), + }; + this.contractAddress = this.circuitContext.transactionContext.address; + } + + /** + * @description Retrieves the current public ledger state of the contract. + * @returns The ledger state as defined by the contract. + */ + public getCurrentPublicState(): Ledger { + return ledger(this.circuitContext.transactionContext.state); + } + + /** + * @description Retrieves the current private state of the contract. + * @returns The private state of type Z_OwnablePKPrivateState. + */ + public getCurrentPrivateState(): Z_OwnablePKPrivateState { + return this.circuitContext.currentPrivateState; + } + + /** + * @description Retrieves the current contract state. + * @returns The contract state object. + */ + public getCurrentContractState(): ContractState { + return this.circuitContext.originalState; + } + + /** + * @description Returns the shielded owner. + * @returns The shielded owner. + */ + public owner(): Uint8Array { + return this.contract.impureCircuits.owner(this.circuitContext).result; + } + + /** + * @description Initiates the two-step ownership transfer to `newOwner`. + */ + public transferOwnership( + newOwner: Uint8Array, + sender: CoinPublicKey, + ): CircuitContext { + const res = this.contract.impureCircuits.transferOwnership( + { + ...this.circuitContext, + currentZswapLocalState: sender + ? emptyZswapLocalState(sender) + : this.circuitContext.currentZswapLocalState, + }, + newOwner, + ); + + this.circuitContext = res.context; + return this.circuitContext; + } + + /** + * @description Leaves the contract without an owner. It will not be + * possible to call `assertOnlyOnwer` circuits anymore. Can only be + * called by the current owner. + */ + public renounceOwnership( + sender: CoinPublicKey, + ): CircuitContext { + const res = this.contract.impureCircuits.renounceOwnership({ + ...this.circuitContext, + currentZswapLocalState: sender + ? emptyZswapLocalState(sender) + : this.circuitContext.currentZswapLocalState, + }); + + this.circuitContext = res.context; + return this.circuitContext; + } + + /** + * @description Throws if called by any account other than the owner. + * Use this to restrict access to sensitive circuits. + */ + public assertOnlyOwner( + sender: CoinPublicKey, + ): CircuitContext { + const res = this.contract.impureCircuits.assertOnlyOwner({ + ...this.circuitContext, + currentZswapLocalState: sender + ? emptyZswapLocalState(sender) + : this.circuitContext.currentZswapLocalState, + }); + + this.circuitContext = res.context; + return this.circuitContext; + } + + /** + * @description Obfuscates the `ownerPK` be hashing it with a domain separator and + * the passed `instance`. + * @returns The shielded hash of the owner and instance. + */ + public shieldPK( + ownerPK: ZswapCoinPublicKey, + instance: bigint, + nonce: Uint8Array + ): Uint8Array { + return this.contract.circuits.shieldPK( + this.circuitContext, + ownerPK, + instance, + nonce + ).result; + } + + /** + * @description Internal circuit that transfers ownership of the contract to `newOwner`. + */ + public _transferOwnership( + newOwner: Uint8Array, + ): CircuitContext { + this.circuitContext = this.contract.impureCircuits._transferOwnership( + this.circuitContext, + newOwner, + ).context; + return this.circuitContext; + } +} diff --git a/contracts/ownable/src/witnesses/Z_OwnablePKWitnesses.ts b/contracts/ownable/src/witnesses/Z_OwnablePKWitnesses.ts new file mode 100644 index 00000000..78a096a0 --- /dev/null +++ b/contracts/ownable/src/witnesses/Z_OwnablePKWitnesses.ts @@ -0,0 +1,38 @@ +import { getRandomValues } from 'node:crypto'; +import type { Ledger } from '../artifacts/MockZ_OwnablePK/contract/index.cjs'; +import type { WitnessContext } from '@midnight-ntwrk/compact-runtime'; +import { IZ_OwnablePKWitnesses } from './interface.js' + +/** + * @description Represents the private state of an ownable contract, storing a secret nonce. + */ +export type Z_OwnablePKPrivateState = { + /** @description A 32-byte secret nonce used as a privacy additive. */ + offchainNonce: Buffer; +}; + +/** + * @description Utility object for managing the private state of an Ownable contract. + */ +export const Z_OwnablePKPrivateState = { + /** + * @description Generates a new private state with a random secret nonce. + * @returns A fresh Z_OwnablePKPrivateState instance. + */ + generate: (): Z_OwnablePKPrivateState => { + //return { offchainNonce: getRandomValues(Buffer.alloc(32))}; + return { offchainNonce: Buffer.from(Array(32).fill(0xab))}; + } +}; + +/** + * @description Factory function creating witness implementations for Ownable operations. + * @returns An object implementing the Witnesses interface for Z_OwnablePKPrivateState. + */ +export const Z_OwnablePKWitnesses = (): IZ_OwnablePKWitnesses => ({ + offchainNonce( + context: WitnessContext, + ): [Z_OwnablePKPrivateState, Uint8Array] { + return [context.privateState, context.privateState.offchainNonce]; + }, +}); \ No newline at end of file diff --git a/contracts/ownable/src/witnesses/interface.ts b/contracts/ownable/src/witnesses/interface.ts index 8ce2f273..3faedeb8 100644 --- a/contracts/ownable/src/witnesses/interface.ts +++ b/contracts/ownable/src/witnesses/interface.ts @@ -1,5 +1,5 @@ import type { WitnessContext } from '@midnight-ntwrk/compact-runtime'; -import type { Ledger } from '../artifacts/MockOwnablePK/contract/index.cjs'; // Combined imports +import type { Ledger } from '../artifacts/MockZ_OwnablePK/contract/index.cjs'; // Combined imports /** * @description Interface defining the witness methods for ownable operations. @@ -13,3 +13,16 @@ export interface IOwnableWitnesses

{ */ localSecretKey(context: WitnessContext): [P, Uint8Array]; } + +/** + * @description Interface defining the witness methods for Ownable operations. + * @template P - The private state type. + */ +export interface IZ_OwnablePKWitnesses

{ + /** + * Retrieves the secret nonce from the private state. + * @param context - The witness context containing the private state. + * @returns A tuple of the private state and the secret nonce as a Uint8Array. + */ + offchainNonce(context: WitnessContext): [P, Uint8Array]; +} From 39df0c60b80ec484f34b6c8a4cc3f7351c6b1e81 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 7 Aug 2025 23:46:48 -0300 Subject: [PATCH 062/202] remove comment --- contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts index dd187759..1ee73716 100644 --- a/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts @@ -18,7 +18,6 @@ Z_OwnablePKPrivateState, Z_OwnablePKWitnesses, } from '../../witnesses/Z_OwnablePKWitnesses.js'; import type { IContractSimulator } from '../types/test.js'; -//import { types, padBytes, fieldToBytes } from 'compact-runtime'; /** * @description A simulator implementation of a contract for testing purposes. From 7d748703715bbc5e3c55c52bdcff8aa6b538b4d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Fri, 8 Aug 2025 18:22:32 -0400 Subject: [PATCH 063/202] Update design and create witness implementations --- .../src/ShieldedAccessControl.compact | 70 ++++++- .../mocks/MockShieldedAccessControl.compact | 9 + .../ShieldedAccessControlWitnesses.ts | 181 ++++++++++++++++++ 3 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts diff --git a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact index 686c6f42..a46ee9d0 100644 --- a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact +++ b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact @@ -51,6 +51,12 @@ pragma language_version >= 0.16.0; * grant and revoke this role. Extra precautions should be taken to secure * accounts that have been granted it. * + * By default, the salt value used to generate nonce values in the `requestRole` witness + * is set to 0. The use of a random salt value adds significantly to the strength of the + * underlying HKDF function and is highly encouraged. A random salt value can be set + * by implementing the `Initializable` module and setting `_salt` in the `initialize() + * circuit. + * * @notice Roles can only be granted to ZswapCoinPublicKeys * through the main role approval circuits (`grantRole` and `_grantRole`). * In other words, role approvals to contract addresses are disallowed through these @@ -115,6 +121,11 @@ module ShieldedAccessControl { */ export ledger _nextIndex: Counter; + /** + * @description A random salt value used to strengthen the HKDF function used in the `requestRole` witness function. + */ + export ledger _salt: Bytes<32>; + export ledger DEFAULT_ADMIN_ROLE: Bytes<32>; /** @@ -122,7 +133,7 @@ module ShieldedAccessControl { * * Requirements: * - * - It is an error to call this if this if `roleCommitment` is not contained at the given index. + * - It is an error to call this if `roleCommitment` is not contained at the given index. * * @circuitInfo * @@ -132,6 +143,30 @@ module ShieldedAccessControl {  */ witness getRoleCommitmentPath(roleCommitment: Bytes<32>, index: Uint<64>): MerkleTreePath<10, Bytes<32>>; + /** + * @description Locally creates and stores a nonce value using the HKDF function and the associated role identifier. + * + * @dev Developers must provide an implementation to privately send the account's public key, roleId, and nonce to an admin. One + * possible solution is by using an HTTP API. + * + * @param {Bytes<32>} roleId - A hash representing a role identifier. + * @param {Bytes<32>} account - The account requesting a role. + * @param {Bytes<32>} salt - A salt value for the underlying HKDF function. + * @return {[]} - Empty tuple. +  */ + witness requestRole(roleId: Bytes<32>, account: Bytes<32>, salt: Bytes<32>): []; + + /** + * @description Used to recover roles in the event of data loss. + * + * @dev Developers must export publicly declared roles from the top-level contract to generate possible roles for each. + * + * @param {Bytes<32>} account - The account requesting a role. + * @param {Bytes<32>}salt - A salt value for the underlying HKDF function. + * @return {[]} - Empty tuple. +  */ + witness recoverRoles(account: Bytes<32>, salt: Bytes<32>): []; + /** * @description Returns `true` if `account` has been granted `roleId`. * @@ -585,4 +620,37 @@ module ShieldedAccessControl { const finalRoleCommitment = persistentHash>>([intermediateRoleCommitment, index as Field as Bytes<32>]); _roleCommitmentNullifiers.insert(disclose(finalRoleCommitment)); } + + /** + * @description A wrapper circuit for the `requestRole` witness. + * + * @circuitInfo k=10, rows=188 + * + * Requirements: + * + * - The caller must not be a ContractAddress. + * + * @param {Bytes<32>} roleId - A hash representing a role identifier. + * @return {[]} - Empty tuple. +  */ + export circuit _requestRole(roleId: Bytes<32>): [] { + const publicKey = left(ownPublicKey()); + requestRole(roleId, publicKey.left.bytes, _salt); + } + + /** + * @description A wrapper circuit for the `recoverRoles` witness. + * + * @circuitInfo k=10, rows=99 + * + * Requirements: + * + * - The caller must not be a ContractAddress. + * + * @return {[]} - Empty tuple. +  */ + export circuit _recoverRoles(): [] { + const publicKey = left(ownPublicKey()); + recoverRoles(publicKey.left.bytes, _salt); + } } diff --git a/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact index e1fe345d..24f996a0 100644 --- a/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact +++ b/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact @@ -12,6 +12,7 @@ export { Either, Maybe, ShieldedAccessControl_DEFAULT_ADMIN_ROLE, + ShieldedAccessControl__salt, ShieldedAccessControl__operatorRoles }; @@ -61,4 +62,12 @@ export circuit _unsafeGrantRole(roleId: Bytes<32>, account: Either, account: Either, nonce: Bytes<32>): Boolean { return ShieldedAccessControl__revokeRole(roleId, account, nonce); +} + +export circuit _requestRole(roleId: Bytes<32>): [] { + ShieldedAccessControl__requestRole(roleId); +} + +export circuit _recoverRoles(): [] { + ShieldedAccessControl__recoverRoles(); } \ No newline at end of file diff --git a/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts new file mode 100644 index 00000000..ec345fb2 --- /dev/null +++ b/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts @@ -0,0 +1,181 @@ +import { + type WitnessContext, + type MerkleTreePath, + constructorContext, + decodeCoinPublicKey, + QueryContext +} from '@midnight-ntwrk/compact-runtime'; +import { encodeContractAddress } from '@midnight-ntwrk/ledger'; +import { + type Either, + type Ledger, + Contract as MockShieldedAccessControl, + type ZswapCoinPublicKey, + type ContractAddress +} from '../artifacts/MockShieldedAccessControl/contract/index.cjs'; // Combined imports +import { Buffer } from 'node:buffer'; +import { sampleContractAddress } from '@midnight-ntwrk/zswap'; + +const { + hkdfSync, +} = await import('node:crypto'); + +const KEYLEN = 32; + +/** + * @description The respective `nonce` value for a given `roleId` should be at the same index + * for each array of `Buffer`s + */ +export type ShieldedAccessControlPrivateState = { + secretKey: Buffer; + nonces: Buffer[]; + roleIds: Buffer[]; +}; + +/** + * @description Generates a nonce value using the following scheme: HKDF-SHA256(SK, "role-nonce" | roleId | PK) + * @param secretKey - The secret key associated with the contract. + * @param roleId - The role identifier. + * @param salt - A salt value. + * @param account - The public key of an account. + * + * @returns A unique nonce value for `roleId` + */ +function generateNonce(secretKey: Buffer, roleId: Buffer, salt: Buffer, account: Buffer): + Buffer { + const domainString = Buffer.from('role-nonce'); + const info = Buffer.concat([domainString, roleId, account]); + const nonce = hkdfSync('sha512', secretKey, salt, info, KEYLEN); + + return Buffer.from(nonce); +} + +/** + * @description A stub function that simulates a successful role approval + * @param account - The public key of an account. + * @param roleId - The role identifier. + * @param nonce - The nonce associated with `roleId`. + * + * @returns Whether the account was approved for a role + */ +function sendRoleRequestToAdmin(account: Buffer, roleId: Buffer, nonce: Buffer) { + return true; +} + +export const ShieldedAccessControlWitnesses = { + /** + * @description Typescript implementation of the `getRoleCommitmentPath` witness function. + * @param privateState - The current private state. + * @param ledger - A snapshot of the current ledger state. + * @param roleCommitment - The role commitment to query. + * @param index - The index of `roleCommitment`in the Merkle tree. + * + * @returns An array of the private state and the Merkle tree path of `roleCommitment` + * in the `_operatorRoles` Merkle tree. + */ + getRoleCommitmentPath: ({ + ledger, + privateState, + }: WitnessContext, roleCommitment: Uint8Array, index: bigint): [ + ShieldedAccessControlPrivateState, + MerkleTreePath, + ] => { + const merkleTreePath = ledger.ShieldedAccessControl__operatorRoles.pathForLeaf(index, roleCommitment); + return [privateState, merkleTreePath] + }, + /** + * @description Typescript implementation of the `recoverNonce` witness function. Simulates calls to the `hasRole` circuit + * to determine if the account has the specified role. Updates the private state with any found roles. + * @param privateState - The current private state. + * @param ledger - A snapshot of the current ledger state. + * @param contractAddress - The address of the contract. + * @param account - The public key associated with a role. + * @param salt - A salt value. + * + * @returns An array of the new private state and the empty tuple + */ + recoverRoles: ({ + ledger, + privateState, + contractAddress + }: WitnessContext, account: Uint8Array, salt: Uint8Array): [ + ShieldedAccessControlPrivateState, + [], + ] => { + const roles = [ledger.ShieldedAccessControl_DEFAULT_ADMIN_ROLE]; + const coinPubKey = decodeCoinPublicKey(account); + let newPrivateState: ShieldedAccessControlPrivateState = { + secretKey: privateState.secretKey, + roleIds: [], + nonces: [] + }; + + const contract = new MockShieldedAccessControl( + ShieldedAccessControlWitnesses, + ); + const { + currentPrivateState, + currentContractState, + currentZswapLocalState, + } = contract.initialState(constructorContext({ secretKey: privateState.secretKey, nonces: [], roleIds: [] }, coinPubKey)); + const circuitContext = { + currentPrivateState, + currentZswapLocalState, + originalState: currentContractState, + transactionContext: new QueryContext( + currentContractState.data, + contractAddress, + ), + }; + + for (let i = 0; i < roles.length; i++) { + let role = roles[i]; + const nonce = generateNonce(privateState.secretKey, Buffer.from(role), Buffer.from(salt), Buffer.from(account)); + const eitherAccount: Either = { + is_left: true, + left: { bytes: account }, + right: { bytes: encodeContractAddress(sampleContractAddress()) }, + } + + try { + const hasRole = contract.impureCircuits.hasRole(circuitContext, role, eitherAccount, nonce); + if (hasRole) { + newPrivateState.nonces.push(nonce); + newPrivateState.roleIds.push(Buffer.from(role)) + } + } catch (err) { + console.log(err); + } + } + + return [newPrivateState, []] + }, + /** + * @description Typescript implementation of the `requestRole` witness function. + * @param privateState - The current private state. + * @param roleId - The role identifier. + * @param account - The public key requesting a role. + * @param salt - A salt value. + * + * @returns An array of the new private state and an empty array + */ + requestRole: ({ + privateState, + }: WitnessContext, roleId: Uint8Array, account: Uint8Array, salt: Uint8Array): [ + ShieldedAccessControlPrivateState, + [], + ] => { + const saltBuff = Buffer.from(salt); + const roleIdBuff = Buffer.from(roleId); + const accountBuff = Buffer.from(account); + const nonce = generateNonce(privateState.secretKey, roleIdBuff, saltBuff, accountBuff); + const isApproved = sendRoleRequestToAdmin(accountBuff, roleIdBuff, nonce); + + if (isApproved) { + privateState.nonces.push(nonce); + privateState.roleIds.push(roleIdBuff); + } + + return [privateState, []] + }, +}; From b28361dea90f45ae8aa122324024da3293279fb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Fri, 8 Aug 2025 18:22:57 -0400 Subject: [PATCH 064/202] fmt file --- .../ShieldedAccessControlWitnesses.ts | 131 +++++++++++------- 1 file changed, 82 insertions(+), 49 deletions(-) diff --git a/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts index ec345fb2..80663f1e 100644 --- a/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts @@ -1,24 +1,22 @@ +import { Buffer } from 'node:buffer'; import { - type WitnessContext, - type MerkleTreePath, constructorContext, decodeCoinPublicKey, - QueryContext + type MerkleTreePath, + QueryContext, + type WitnessContext, } from '@midnight-ntwrk/compact-runtime'; import { encodeContractAddress } from '@midnight-ntwrk/ledger'; +import { sampleContractAddress } from '@midnight-ntwrk/zswap'; import { + type ContractAddress, type Either, type Ledger, Contract as MockShieldedAccessControl, type ZswapCoinPublicKey, - type ContractAddress } from '../artifacts/MockShieldedAccessControl/contract/index.cjs'; // Combined imports -import { Buffer } from 'node:buffer'; -import { sampleContractAddress } from '@midnight-ntwrk/zswap'; -const { - hkdfSync, -} = await import('node:crypto'); +const { hkdfSync } = await import('node:crypto'); const KEYLEN = 32; @@ -41,8 +39,12 @@ export type ShieldedAccessControlPrivateState = { * * @returns A unique nonce value for `roleId` */ -function generateNonce(secretKey: Buffer, roleId: Buffer, salt: Buffer, account: Buffer): - Buffer { +function generateNonce( + secretKey: Buffer, + roleId: Buffer, + salt: Buffer, + account: Buffer, +): Buffer { const domainString = Buffer.from('role-nonce'); const info = Buffer.concat([domainString, roleId, account]); const nonce = hkdfSync('sha512', secretKey, salt, info, KEYLEN); @@ -58,7 +60,11 @@ function generateNonce(secretKey: Buffer, roleId: Buffer, salt: Buffer, account: * * @returns Whether the account was approved for a role */ -function sendRoleRequestToAdmin(account: Buffer, roleId: Buffer, nonce: Buffer) { +function sendRoleRequestToAdmin( + account: Buffer, + roleId: Buffer, + nonce: Buffer, +) { return true; } @@ -73,15 +79,20 @@ export const ShieldedAccessControlWitnesses = { * @returns An array of the private state and the Merkle tree path of `roleCommitment` * in the `_operatorRoles` Merkle tree. */ - getRoleCommitmentPath: ({ - ledger, - privateState, - }: WitnessContext, roleCommitment: Uint8Array, index: bigint): [ - ShieldedAccessControlPrivateState, - MerkleTreePath, - ] => { - const merkleTreePath = ledger.ShieldedAccessControl__operatorRoles.pathForLeaf(index, roleCommitment); - return [privateState, merkleTreePath] + getRoleCommitmentPath: ( + { + ledger, + privateState, + }: WitnessContext, + roleCommitment: Uint8Array, + index: bigint, + ): [ShieldedAccessControlPrivateState, MerkleTreePath] => { + const merkleTreePath = + ledger.ShieldedAccessControl__operatorRoles.pathForLeaf( + index, + roleCommitment, + ); + return [privateState, merkleTreePath]; }, /** * @description Typescript implementation of the `recoverNonce` witness function. Simulates calls to the `hasRole` circuit @@ -94,30 +105,37 @@ export const ShieldedAccessControlWitnesses = { * * @returns An array of the new private state and the empty tuple */ - recoverRoles: ({ - ledger, - privateState, - contractAddress - }: WitnessContext, account: Uint8Array, salt: Uint8Array): [ - ShieldedAccessControlPrivateState, - [], - ] => { + recoverRoles: ( + { + ledger, + privateState, + contractAddress, + }: WitnessContext, + account: Uint8Array, + salt: Uint8Array, + ): [ShieldedAccessControlPrivateState, []] => { const roles = [ledger.ShieldedAccessControl_DEFAULT_ADMIN_ROLE]; const coinPubKey = decodeCoinPublicKey(account); - let newPrivateState: ShieldedAccessControlPrivateState = { + const newPrivateState: ShieldedAccessControlPrivateState = { secretKey: privateState.secretKey, roleIds: [], - nonces: [] + nonces: [], }; - const contract = new MockShieldedAccessControl( - ShieldedAccessControlWitnesses, - ); + const contract = + new MockShieldedAccessControl( + ShieldedAccessControlWitnesses, + ); const { currentPrivateState, currentContractState, currentZswapLocalState, - } = contract.initialState(constructorContext({ secretKey: privateState.secretKey, nonces: [], roleIds: [] }, coinPubKey)); + } = contract.initialState( + constructorContext( + { secretKey: privateState.secretKey, nonces: [], roleIds: [] }, + coinPubKey, + ), + ); const circuitContext = { currentPrivateState, currentZswapLocalState, @@ -129,26 +147,36 @@ export const ShieldedAccessControlWitnesses = { }; for (let i = 0; i < roles.length; i++) { - let role = roles[i]; - const nonce = generateNonce(privateState.secretKey, Buffer.from(role), Buffer.from(salt), Buffer.from(account)); + const role = roles[i]; + const nonce = generateNonce( + privateState.secretKey, + Buffer.from(role), + Buffer.from(salt), + Buffer.from(account), + ); const eitherAccount: Either = { is_left: true, left: { bytes: account }, right: { bytes: encodeContractAddress(sampleContractAddress()) }, - } + }; try { - const hasRole = contract.impureCircuits.hasRole(circuitContext, role, eitherAccount, nonce); + const hasRole = contract.impureCircuits.hasRole( + circuitContext, + role, + eitherAccount, + nonce, + ); if (hasRole) { newPrivateState.nonces.push(nonce); - newPrivateState.roleIds.push(Buffer.from(role)) + newPrivateState.roleIds.push(Buffer.from(role)); } } catch (err) { console.log(err); } } - return [newPrivateState, []] + return [newPrivateState, []]; }, /** * @description Typescript implementation of the `requestRole` witness function. @@ -159,16 +187,21 @@ export const ShieldedAccessControlWitnesses = { * * @returns An array of the new private state and an empty array */ - requestRole: ({ - privateState, - }: WitnessContext, roleId: Uint8Array, account: Uint8Array, salt: Uint8Array): [ - ShieldedAccessControlPrivateState, - [], - ] => { + requestRole: ( + { privateState }: WitnessContext, + roleId: Uint8Array, + account: Uint8Array, + salt: Uint8Array, + ): [ShieldedAccessControlPrivateState, []] => { const saltBuff = Buffer.from(salt); const roleIdBuff = Buffer.from(roleId); const accountBuff = Buffer.from(account); - const nonce = generateNonce(privateState.secretKey, roleIdBuff, saltBuff, accountBuff); + const nonce = generateNonce( + privateState.secretKey, + roleIdBuff, + saltBuff, + accountBuff, + ); const isApproved = sendRoleRequestToAdmin(accountBuff, roleIdBuff, nonce); if (isApproved) { @@ -176,6 +209,6 @@ export const ShieldedAccessControlWitnesses = { privateState.roleIds.push(roleIdBuff); } - return [privateState, []] + return [privateState, []]; }, }; From 5acfaf8f6611cb049a0e354923f369b601421495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Fri, 8 Aug 2025 18:24:32 -0400 Subject: [PATCH 065/202] Fix lints --- .../src/witnesses/ShieldedAccessControlWitnesses.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts index 80663f1e..88e1c589 100644 --- a/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts @@ -61,9 +61,9 @@ function generateNonce( * @returns Whether the account was approved for a role */ function sendRoleRequestToAdmin( - account: Buffer, - roleId: Buffer, - nonce: Buffer, + _account: Buffer, + _roleId: Buffer, + _nonce: Buffer, ) { return true; } From 5f9a9eb6668d02be3cf26ccdcf3d0e365bdcf22a Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 8 Aug 2025 21:08:00 -0300 Subject: [PATCH 066/202] remove old pk module, add abstract class and state manager for simulator --- contracts/ownable/src/OwnablePK.compact | 172 --------- contracts/ownable/src/test/OwnablePK.test.ts | 331 ------------------ .../ownable/src/test/Z_OwnablePK.test.ts | 17 +- .../src/test/mocks/MockOwnablePK.compact | 49 --- .../src/test/simulators/OwnablePKSimulator.ts | 223 ------------ .../test/simulators/Z_OwnablePKSimulator.ts | 243 ++++++++----- .../test/types/AbstractContractSimulator.ts | 131 +++++++ .../src/test/types/SimualatorStateManager.ts | 114 ++++++ contracts/ownable/src/test/types/test.ts | 91 ++++- .../src/witnesses/OwnablePKWitnesses.ts | 3 - 10 files changed, 486 insertions(+), 888 deletions(-) delete mode 100644 contracts/ownable/src/OwnablePK.compact delete mode 100644 contracts/ownable/src/test/OwnablePK.test.ts delete mode 100644 contracts/ownable/src/test/mocks/MockOwnablePK.compact delete mode 100644 contracts/ownable/src/test/simulators/OwnablePKSimulator.ts create mode 100644 contracts/ownable/src/test/types/AbstractContractSimulator.ts create mode 100644 contracts/ownable/src/test/types/SimualatorStateManager.ts delete mode 100644 contracts/ownable/src/witnesses/OwnablePKWitnesses.ts diff --git a/contracts/ownable/src/OwnablePK.compact b/contracts/ownable/src/OwnablePK.compact deleted file mode 100644 index 60352b47..00000000 --- a/contracts/ownable/src/OwnablePK.compact +++ /dev/null @@ -1,172 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma language_version >= 0.15.0; - -/** - * @module Shielded Ownable Public Key module - * @description The OwnablePK module provides a basic access control mechanism, - * where there is an account (an owner) that can be granted exclusive access - * to specific circuits. - * - * The initial owner can be set by using the `initializer` circuit during - * construction. The owner's public key will be obfuscated in the ledger - * (thus shielded) by the `shieldOwner` circuit. - * - * This module enforces a two-step ownership transfer mechanism. The mechanism - * flow starts with the current owner calling `transferOwnership` and passing - * the new owner's ZswapCoinPublicKey. The proposed owner's key is obfuscated - * similarly via the `shieldOwner` circuit. After the owner proposes the new - * owner, the new owner must accept ownership by calling `acceptOwnership`. - * This circuit validates that the caller is the proposed owner. Thereafter, - * the new owner may call `assertOnlyOwner` circuits. - * - * The reason this module enforces the two-step mechanism is for safety. - * If the owner transferred ownership to the wrong pubkey without the mechanism, - * it's likely that the ownership privileges will be lost for the contract forever. - * With the two-step mechanism, the current owner can overwrite the pending - * owner by calling `transferOwnership` with a different pubkey or passing - * zero to cancel the transfer. - */ -module OwnablePK { - import CompactStandardLibrary; - - /** Public state */ - export ledger _owner: Bytes<32>; - export ledger _pendingOwner: Bytes<32>; - export ledger _instance: Counter; - - /** - * @description Initializes the contract by setting `initOwner` as the - * (shielded) contract owner. - * - * @returns {[]} - None. - */ - export circuit initializer(initOwner: ZswapCoinPublicKey): [] { - assert(initOwner != burnAddress().left, "OwnablePK: new owner cannot be zero"); - const nextInstance = _instance + 1 as Field as Bytes<32>; - const shieldedOwner = shieldOwner(disclose(initOwner), nextInstance); - _transferOwnership(shieldedOwner); - } - - /** - * @description Returns the shielded owner. - * - * @returns {Bytes<32>} - The shielded owner. - */ - export circuit owner(): Bytes<32> { - return _owner; - } - - /** - * @description Returns the shielded pending owner. - * - * @returns {Bytes<32>} - The shielded proposed owner. - */ - export circuit pendingOwner(): Bytes<32> { - return _pendingOwner; - } - - /** - * @description Initiates the two-step ownership transfer to `newOwner`. - * To cancel an ownership transfer, the current owner can call this circuit - * and pass zero as `newOwner`. - * - * Requirements: - * - * - The caller must be the current contract owner. - * - * @returns {[]} - None. - */ - export circuit transferOwnership(newOwner: ZswapCoinPublicKey): [] { - assertOnlyOwner(); - _proposeOwner(newOwner); - } - - /** - * @description Finishes the two-step ownership transfer process by accepting - * the ownership. Can only be called by the pending owner. - * - * Requirements: - * - * - The caller is the pending owner. - * - * @returns {[]} - None. - */ - export circuit acceptOwnership(): [] { - const caller = ownPublicKey(); - const nextInstance = _instance + 1 as Field as Bytes<32>; - const shieldedOwner = shieldOwner(caller, nextInstance); - assert(shieldedOwner == _pendingOwner, "OwnablePK: caller is not pending owner"); - - // Reset pending owner and assign new owner - _transferOwnership(shieldedOwner); - } - - /** - * @description Leaves the contract without an owner. It will not be - * possible to call `assertOnlyOnwer` circuits anymore. Can only be - * called by the current owner. - * - * Requirements: - * - * - The caller is the contract owner. - * - * @returns {[]} - None. - */ - export circuit renounceOwnership(): [] { - assertOnlyOwner(); - _transferOwnership(default>); - } - - /** - * @description Throws if called by any account other than the owner. - * Use this to restrict access to sensitive circuits. - * - * @returns {[]} - None. - */ - export circuit assertOnlyOwner(): [] { - const caller = ownPublicKey(); - assert(_owner == shieldOwner(caller, _instance as Field as Bytes<32>), "OwnablePK: not owner"); - } - - /** - * @description Obfuscates the `ownerPK` be hashing it with a domain separator and - * the passed `instance`. - * - * @returns {Bytes<32>} - The shielded hash of the owner and instance. - */ - export circuit shieldOwner(ownerPK: ZswapCoinPublicKey, instance: Bytes<32>): Bytes<32> { - return persistentHash>>([pad(32, "OwnablePK:shield:"), instance, ownerPK.bytes]); - } - - /** - * @description Internal circuit that transfers ownership of the contract to `newOwner`. - * This circuit does not have access control and thus should not be exposed. - * - * Be careful with this circuit. `newOwner` will be stored in the ledger as it's - * passed meaning that `newOwner` must be shielded via `shieldOwner` beforehand. - * Maybe include `shieldOwner()` in logic so it's difficult to misuse? - */ - export circuit _transferOwnership(newOwner: Bytes<32>): [] { - _pendingOwner = default>; - _instance.increment(1); - _owner = disclose(newOwner); - } - - /** - * @description Internal circuit that sets the pending owner. - * Passing `newOwner` as zero cancels the two-step ownership - * transfer. Otherwise, this circuit shields `newOwner` and - * sets it in the ledger. - * - * @returns {[]} - None. - */ - export circuit _proposeOwner(newOwner: ZswapCoinPublicKey): [] { - if (newOwner == burnAddress().left) { - _pendingOwner = pad(32, ""); - } else { - const nextInstance = _instance + 1 as Field as Bytes<32>; - _pendingOwner = shieldOwner(newOwner, nextInstance); - } - } -} diff --git a/contracts/ownable/src/test/OwnablePK.test.ts b/contracts/ownable/src/test/OwnablePK.test.ts deleted file mode 100644 index fb2ab94d..00000000 --- a/contracts/ownable/src/test/OwnablePK.test.ts +++ /dev/null @@ -1,331 +0,0 @@ -import { - type CoinPublicKey, - convert_bigint_to_Uint8Array, -} from '@midnight-ntwrk/compact-runtime'; -import { beforeEach, describe, expect, it } from 'vitest'; -import { OwnablePKSimulator } from './simulators/OwnablePKSimulator.js'; -import * as utils from './utils/address.js'; - -const OWNER = String(Buffer.from('OWNER', 'ascii').toString('hex')).padStart( - 64, - '0', -); -const NEW_OWNER = String( - Buffer.from('NEW_OWNER', 'ascii').toString('hex'), -).padStart(64, '0'); -const UNAUTHORIZED = String( - Buffer.from('UNAUTHORIZED', 'ascii').toString('hex'), -).padStart(64, '0'); -const Z_ZERO = utils.encodeToPK(''); -const Z_OWNER = utils.encodeToPK('OWNER'); -const Z_NEW_OWNER = utils.encodeToPK('NEW_OWNER'); -const Z_NEW_NEW_OWNER = utils.encodeToPK('Z_NEW_NEW_OWNER'); -const EMPTY_BYTES = utils.ZERO_KEY.left.bytes; - -let ownable: OwnablePKSimulator; -let caller: CoinPublicKey; - -describe('OwnablePK', () => { - describe('initializer', () => { - it('should initialize and set the shielded owner', () => { - ownable = new OwnablePKSimulator(Z_OWNER, OWNER); - - // Check instance - const instance = ownable.getCurrentPublicState().OwnablePK__instance; - expect(instance).toEqual(1n); - - // Check shielded owner - const expOwner = ownable.shieldOwner( - Z_OWNER, - convert_bigint_to_Uint8Array(32, instance), - ); - expect(ownable.owner()).toEqual(expOwner); - - // Check pending owner - const pendingOwner = - ownable.getCurrentPublicState().OwnablePK__pendingOwner; - expect(pendingOwner).toEqual(EMPTY_BYTES); - }); - - it('should fail when initializing owner as zero', () => { - expect(() => { - ownable = new OwnablePKSimulator(utils.ZERO_KEY.left, OWNER); - }).toThrow('OwnablePK: new owner cannot be zero'); - }); - }); - - describe('with owner set', () => { - beforeEach(() => { - ownable = new OwnablePKSimulator(Z_OWNER, OWNER); - }); - - describe('owner', () => { - it('should return correct owner', () => { - expect(ownable.owner()).toEqual( - ownable.getCurrentPublicState().OwnablePK__owner, - ); - }); - - it('should return no owner', () => { - // Set owner to zero - ownable._transferOwnership(EMPTY_BYTES); - expect(ownable.owner()).toEqual(EMPTY_BYTES); - }); - }); - - describe('pendingOwner', () => { - it('should return pending owner', () => { - const nextInstance = - ownable.getCurrentPublicState().OwnablePK__instance + 1n; - const expPending = ownable.shieldOwner( - Z_NEW_OWNER, - convert_bigint_to_Uint8Array(32, nextInstance), - ); - ownable._proposeOwner(Z_NEW_OWNER); - expect(ownable.pendingOwner()).toEqual(expPending); - }); - - it('should return no pending owner', () => { - expect(ownable.pendingOwner()).toEqual(EMPTY_BYTES); - }); - }); - - describe('transferOwnership', () => { - it('should start two-step transfer', () => { - caller = OWNER; - - ownable.transferOwnership(Z_NEW_OWNER, caller); - - // Check pending owner - const nextInstance = - ownable.getCurrentPublicState().OwnablePK__instance + 1n; - const expPending = ownable.shieldOwner( - Z_NEW_OWNER, - convert_bigint_to_Uint8Array(32, nextInstance), - ); - expect(ownable.pendingOwner()).toEqual(expPending); - - // Check current owner - const thisInstance = ownable.getCurrentPublicState().OwnablePK__instance; - const expOwner = ownable.shieldOwner( - Z_OWNER, - convert_bigint_to_Uint8Array(32, thisInstance), - ); - expect(ownable.owner()).toEqual(expOwner); - }); - - it('should cancel two-step transfer', () => { - caller = OWNER; - - // Start transfer process - ownable.transferOwnership(Z_NEW_OWNER, caller); - // Cancel transfer by transferring to zero - ownable.transferOwnership(Z_ZERO, caller); - expect(ownable.pendingOwner()).toEqual(Z_ZERO.bytes); - }); - - it('should not transfer owner from unauthorized caller', () => { - caller = UNAUTHORIZED; - - expect(() => { - ownable.transferOwnership(Z_NEW_OWNER, caller); - }).toThrow('OwnablePK: not owner'); - }); - - it('should overwrite pending owner with new owner', () => { - caller = OWNER; - - ownable.transferOwnership(Z_NEW_OWNER, caller); - ownable.transferOwnership(Z_NEW_NEW_OWNER, caller); - - // Check new pending owner - const nextInstance = - ownable.getCurrentPublicState().OwnablePK__instance + 1n; - const expPending = ownable.shieldOwner( - Z_NEW_NEW_OWNER, - convert_bigint_to_Uint8Array(32, nextInstance), - ); - expect(ownable.pendingOwner()).toEqual(expPending); - }); - }); - - describe('acceptOwnership', () => { - describe('when owner is pending', () => { - beforeEach(() => { - ownable._proposeOwner(Z_NEW_OWNER); - }); - - it('should accept ownership from pending owner', () => { - caller = NEW_OWNER; - const beforeInstance = - ownable.getCurrentPublicState().OwnablePK__instance; - - ownable.acceptOwnership(caller); - - // Check instance is bumped - const afterInstance = - ownable.getCurrentPublicState().OwnablePK__instance; - expect(afterInstance).toEqual(beforeInstance + 1n); - - // Check new owner - const expOwner = ownable.shieldOwner( - Z_NEW_OWNER, - convert_bigint_to_Uint8Array(32, afterInstance), - ); - expect(ownable.owner()).toEqual(expOwner); - - // Check pending owner is reset - expect(ownable.pendingOwner()).toEqual(EMPTY_BYTES); - }); - - it('should not accept ownership from unauthorized', () => { - caller = UNAUTHORIZED; - - expect(() => { - ownable.acceptOwnership(caller); - }).toThrow('OwnablePK: caller is not pending owner'); - }); - - it('should not accept ownership from current owner', () => { - caller = OWNER; - - expect(() => { - ownable.acceptOwnership(caller); - }).toThrow('OwnablePK: caller is not pending owner'); - }); - - it('should not accept ownership from previous owner', () => { - caller = NEW_OWNER; - // Sets new owner - ownable.acceptOwnership(caller); - - // New owner proposes another new owner - ownable.transferOwnership(Z_NEW_NEW_OWNER, caller); - - // Initial owner tries to accept - caller = OWNER; - expect(() => { - ownable.acceptOwnership(caller); - }).toThrow('OwnablePK: caller is not pending owner'); - }); - }); - }); - - describe('renounceOwnership', () => { - it('should renounce ownership', () => { - caller = OWNER; - const beforeInstance = - ownable.getCurrentPublicState().OwnablePK__instance; - ownable.renounceOwnership(caller); - - expect(ownable.owner()).toEqual(EMPTY_BYTES); - expect(ownable.pendingOwner()).toEqual(EMPTY_BYTES); - expect(ownable.getCurrentPublicState().OwnablePK__instance).toEqual( - beforeInstance + 1n, - ); - }); - - it('should not renounce from unauthorized', () => { - caller = UNAUTHORIZED; - expect(() => { - ownable.renounceOwnership(caller); - }).toThrow('OwnablePK: not owner'); - }); - }); - - describe('assertOnlyOwner', () => { - it('should allow owner to call', () => { - caller = OWNER; - - ownable.assertOnlyOwner(caller); - }); - - it('should not allow unauthorized to call', () => { - caller = UNAUTHORIZED; - expect(() => { - ownable.assertOnlyOwner(caller); - }).toThrow('OwnablePK: not owner'); - }); - - it('should update who can and cannot call', () => { - caller = OWNER; - ownable.assertOnlyOwner(caller); - - caller = NEW_OWNER; - expect(() => { - ownable.assertOnlyOwner(caller); - }).toThrow('OwnablePK: not owner'); - - // Transfer to new owner - const nextInstance = - ownable.getCurrentPublicState().OwnablePK__instance + 1n; - const newOwner = ownable.shieldOwner( - Z_NEW_OWNER, - convert_bigint_to_Uint8Array(32, nextInstance), - ); - ownable._transferOwnership(newOwner); - - caller = NEW_OWNER; - ownable.assertOnlyOwner(caller); - - caller = OWNER; - expect(() => { - ownable.assertOnlyOwner(caller); - }).toThrow('OwnablePK: not owner'); - }); - }); - - describe('shieldOwner', () => { - it.skip('should hash owner correctly', () => { - const instance = convert_bigint_to_Uint8Array(32, 123n); - const _expHash = ownable.shieldOwner(Z_OWNER, instance); - // TODO add matching algo in js - }); - }); - - describe('_transferOwnership', () => { - it('should transfer ownership', () => { - const beforeInstance = - ownable.getCurrentPublicState().OwnablePK__instance; - ownable._proposeOwner(Z_NEW_NEW_OWNER); - - ownable._transferOwnership(Z_NEW_OWNER.bytes); - - // _transferownership does not shield the input so it should be a == a - expect(ownable.owner()).toEqual(Z_NEW_OWNER.bytes); - // Check instance is bumped - expect(ownable.getCurrentPublicState().OwnablePK__instance).toEqual( - beforeInstance + 1n, - ); - // Check pending owner is reset - expect(ownable.pendingOwner()).toEqual(EMPTY_BYTES); - }); - - it('should transfer ownership to zero', () => { - // _transfer does not shield the input so it should be a == a - ownable._transferOwnership(EMPTY_BYTES); - expect(ownable.owner()).toEqual(EMPTY_BYTES); - }); - }); - - describe('proposeOwner', () => { - it('should propose owner', () => { - ownable._proposeOwner(Z_NEW_OWNER); - - const nextInstance = - ownable.getCurrentPublicState().OwnablePK__instance + 1n; - const expOwner = ownable.shieldOwner( - Z_NEW_OWNER, - convert_bigint_to_Uint8Array(32, nextInstance), - ); - expect(ownable.pendingOwner()).toEqual(expOwner); - }); - - it('should propose owner and cancel', () => { - ownable._proposeOwner(Z_NEW_OWNER); - ownable._proposeOwner(utils.ZERO_KEY.left); - expect(ownable.pendingOwner()).toEqual(Z_ZERO.bytes); - }); - }); - }); -}); diff --git a/contracts/ownable/src/test/Z_OwnablePK.test.ts b/contracts/ownable/src/test/Z_OwnablePK.test.ts index e2bb0ab8..72a8763b 100644 --- a/contracts/ownable/src/test/Z_OwnablePK.test.ts +++ b/contracts/ownable/src/test/Z_OwnablePK.test.ts @@ -32,7 +32,6 @@ const INIT_COUNTER = 1n; const STATIC_NONCE = new Uint8Array(32).fill(0xab); let ownable: Z_OwnablePKSimulator; -let caller: CoinPublicKey; const createZPKCommitment = ( domain: string, @@ -42,6 +41,7 @@ const createZPKCommitment = ( ): Uint8Array => { const rt_type = new CompactTypeVector(4, new CompactTypeBytes(32)); const encoder = new TextEncoder(); + const bDomain = encoder.encode(domain); const bPK = pk.bytes; const bCounter = convert_bigint_to_Uint8Array(32, counter); @@ -53,13 +53,13 @@ describe('Z_OwnablePK', () => { it('should fail when setting owner commitment as 0', () => { expect(() => { const badCommitment = new Uint8Array(32).fill(0); - new Z_OwnablePKSimulator(badCommitment, OWNER); + new Z_OwnablePKSimulator(badCommitment); }).toThrow('Invalid parameters'); }); it('should initialize with non-zero commitment', () => { const nonZeroCommitment = new Uint8Array(32).fill(1); - ownable = new Z_OwnablePKSimulator(nonZeroCommitment, OWNER); + ownable = new Z_OwnablePKSimulator(nonZeroCommitment); expect(ownable.owner()).toEqual(nonZeroCommitment); }); @@ -68,7 +68,7 @@ describe('Z_OwnablePK', () => { describe('after initialization', () => { beforeEach(() => { const ownerCommitment = createZPKCommitment(DOMAIN, Z_OWNER, INIT_COUNTER, STATIC_NONCE); - ownable = new Z_OwnablePKSimulator(ownerCommitment, OWNER); + ownable = new Z_OwnablePKSimulator(ownerCommitment); }); describe('owner', () => { @@ -80,14 +80,15 @@ describe('Z_OwnablePK', () => { describe('assertOnlyOwner', () => { it('should allow the authorized caller with correct nonce to call', () => { - caller = OWNER; - expect(ownable.assertOnlyOwner(caller)).to.not.throw; + ownable.setCaller(OWNER); + expect(ownable.assertOnlyOwner()).to.not.throw; }); it('should fail when called by unauthorized with correct nonce', () => { - caller = UNAUTHORIZED; + ownable.setCaller(UNAUTHORIZED); + expect(() => { - ownable.assertOnlyOwner(caller); + ownable.assertOnlyOwner(); }).toThrow('Forbidden'); }); }); diff --git a/contracts/ownable/src/test/mocks/MockOwnablePK.compact b/contracts/ownable/src/test/mocks/MockOwnablePK.compact deleted file mode 100644 index 1ab40018..00000000 --- a/contracts/ownable/src/test/mocks/MockOwnablePK.compact +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma language_version >= 0.15.0; - -import CompactStandardLibrary; -import "../../OwnablePK" prefix OwnablePK_; - -export { ZswapCoinPublicKey, ContractAddress, Either, Maybe }; -export { OwnablePK__owner, OwnablePK__pendingOwner, OwnablePK__instance }; - -constructor(initOwner: ZswapCoinPublicKey) { - OwnablePK_initializer(initOwner); -} - -export circuit owner(): Bytes<32> { - return OwnablePK_owner(); -} - -export circuit pendingOwner(): Bytes<32> { - return OwnablePK_pendingOwner(); -} - -export circuit transferOwnership(newOwner: ZswapCoinPublicKey): [] { - return OwnablePK_transferOwnership(disclose(newOwner)); -} - -export circuit acceptOwnership(): [] { - return OwnablePK_acceptOwnership(); -} - -export circuit renounceOwnership(): [] { - return OwnablePK_renounceOwnership(); -} - -export circuit assertOnlyOwner(): [] { - return OwnablePK_assertOnlyOwner(); -} - -export circuit shieldOwner(ownerPK: ZswapCoinPublicKey, instance: Bytes<32>): Bytes<32> { - return OwnablePK_shieldOwner(ownerPK, instance); -} - -export circuit _transferOwnership(newOwner: Bytes<32>): [] { - return OwnablePK__transferOwnership(newOwner); -} - -export circuit _proposeOwner(newOwner: ZswapCoinPublicKey): [] { - return OwnablePK__proposeOwner(disclose(newOwner)); -} diff --git a/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts deleted file mode 100644 index 60731080..00000000 --- a/contracts/ownable/src/test/simulators/OwnablePKSimulator.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { - type CircuitContext, - type CoinPublicKey, - type ContractState, - constructorContext, - emptyZswapLocalState, - QueryContext, -} from '@midnight-ntwrk/compact-runtime'; -import { sampleContractAddress } from '@midnight-ntwrk/zswap'; -import type { ZswapCoinPublicKey } from '../../artifacts/MockOwnablePK/contract/index.cjs'; -import { - type Ledger, - ledger, - Contract as MockOwnable, -} from '../../artifacts/MockOwnablePK/contract/index.cjs'; // Combined imports -import { - type OwnablePKPrivateState, - OwnablePKWitnesses, -} from '../../witnesses/OwnablePKWitnesses.js'; -import type { IContractSimulator } from '../types/test.js'; - -/** - * @description A simulator implementation of a contract for testing purposes. - * @template P - The private state type, fixed to OwnablePKPrivateState. - * @template L - The ledger type, fixed to Contract.Ledger. - */ -export class OwnablePKSimulator - implements IContractSimulator -{ - /** @description The underlying contract instance managing contract logic. */ - readonly contract: MockOwnable; - - /** @description The deployed address of the contract. */ - readonly contractAddress: string; - - /** @description The deployer address of the contract. */ - readonly deployer: CoinPublicKey; - - /** @description The current circuit context, updated by contract operations. */ - circuitContext: CircuitContext; - - /** - * @description Initializes the mock contract. - */ - constructor(initOwner: ZswapCoinPublicKey, deployer: CoinPublicKey) { - this.contract = new MockOwnable(OwnablePKWitnesses); - this.deployer = deployer; - const { - currentPrivateState, - currentContractState, - currentZswapLocalState, - } = this.contract.initialState(constructorContext({}, deployer), initOwner); - this.circuitContext = { - currentPrivateState, - currentZswapLocalState, - originalState: currentContractState, - transactionContext: new QueryContext( - currentContractState.data, - sampleContractAddress(), - ), - }; - this.contractAddress = this.circuitContext.transactionContext.address; - } - - /** - * @description Retrieves the current public ledger state of the contract. - * @returns The ledger state as defined by the contract. - */ - public getCurrentPublicState(): Ledger { - return ledger(this.circuitContext.transactionContext.state); - } - - /** - * @description Retrieves the current private state of the contract. - * @returns The private state of type OwnablePKPrivateState. - */ - public getCurrentPrivateState(): OwnablePKPrivateState { - return this.circuitContext.currentPrivateState; - } - - /** - * @description Retrieves the current contract state. - * @returns The contract state object. - */ - public getCurrentContractState(): ContractState { - return this.circuitContext.originalState; - } - - /** - * @description Returns the shielded owner. - * @returns The shielded owner. - */ - public owner(): Uint8Array { - return this.contract.impureCircuits.owner(this.circuitContext).result; - } - - /** - * @description Returns the shielded pending owner. - * @returns The shielded proposed owner. - */ - public pendingOwner(): Uint8Array { - return this.contract.impureCircuits.pendingOwner(this.circuitContext) - .result; - } - - /** - * @description Initiates the two-step ownership transfer to `newOwner`. - */ - public transferOwnership( - newOwner: ZswapCoinPublicKey, - sender: CoinPublicKey, - ): CircuitContext { - const res = this.contract.impureCircuits.transferOwnership( - { - ...this.circuitContext, - currentZswapLocalState: sender - ? emptyZswapLocalState(sender) - : this.circuitContext.currentZswapLocalState, - }, - newOwner, - ); - - this.circuitContext = res.context; - return this.circuitContext; - } - - /** - * @description Finishes the two-step ownership transfer process by accepting - * the ownership. Can only be called by the pending owner. - */ - public acceptOwnership( - sender: CoinPublicKey, - ): CircuitContext { - const res = this.contract.impureCircuits.acceptOwnership({ - ...this.circuitContext, - currentZswapLocalState: sender - ? emptyZswapLocalState(sender) - : this.circuitContext.currentZswapLocalState, - }); - - this.circuitContext = res.context; - return this.circuitContext; - } - - /** - * @description Leaves the contract without an owner. It will not be - * possible to call `assertOnlyOnwer` circuits anymore. Can only be - * called by the current owner. - */ - public renounceOwnership( - sender: CoinPublicKey, - ): CircuitContext { - const res = this.contract.impureCircuits.renounceOwnership({ - ...this.circuitContext, - currentZswapLocalState: sender - ? emptyZswapLocalState(sender) - : this.circuitContext.currentZswapLocalState, - }); - - this.circuitContext = res.context; - return this.circuitContext; - } - - /** - * @description Throws if called by any account other than the owner. - * Use this to restrict access to sensitive circuits. - */ - public assertOnlyOwner( - sender: CoinPublicKey, - ): CircuitContext { - const res = this.contract.impureCircuits.assertOnlyOwner({ - ...this.circuitContext, - currentZswapLocalState: sender - ? emptyZswapLocalState(sender) - : this.circuitContext.currentZswapLocalState, - }); - - this.circuitContext = res.context; - return this.circuitContext; - } - - /** - * @description Obfuscates the `ownerPK` be hashing it with a domain separator and - * the passed `instance`. - * @returns The shielded hash of the owner and instance. - */ - public shieldOwner( - ownerPK: ZswapCoinPublicKey, - instance: Uint8Array, - ): Uint8Array { - return this.contract.circuits.shieldOwner( - this.circuitContext, - ownerPK, - instance, - ).result; - } - - /** - * @description Internal circuit that transfers ownership of the contract to `newOwner`. - */ - public _transferOwnership( - newOwner: Uint8Array, - ): CircuitContext { - this.circuitContext = this.contract.impureCircuits._transferOwnership( - this.circuitContext, - newOwner, - ).context; - return this.circuitContext; - } - - /** - * @description Internal circuit that sets the pending owner. - */ - public _proposeOwner( - newOwner: ZswapCoinPublicKey, - ): CircuitContext { - this.circuitContext = this.contract.impureCircuits._proposeOwner( - this.circuitContext, - newOwner, - ).context; - return this.circuitContext; - } -} diff --git a/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts index 1ee73716..c2b6ecc0 100644 --- a/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts @@ -2,9 +2,7 @@ import { type CircuitContext, type CoinPublicKey, type ContractState, - constructorContext, emptyZswapLocalState, - QueryContext, } from '@midnight-ntwrk/compact-runtime'; import { sampleContractAddress } from '@midnight-ntwrk/zswap'; import type { ZswapCoinPublicKey } from '../../artifacts/MockZ_OwnablePK/contract/index.cjs'; @@ -17,53 +15,159 @@ import { Z_OwnablePKPrivateState, Z_OwnablePKWitnesses, } from '../../witnesses/Z_OwnablePKWitnesses.js'; -import type { IContractSimulator } from '../types/test.js'; +import { AbstractContractSimulator } from '../types/AbstractContractSimulator.js'; +import { SimulatorStateManager } from '../types/SimualatorStateManager.js'; +import { ContextlessCircuits, ExtractImpureCircuits, ExtractPureCircuits } from '../types/test.js'; + /** * @description A simulator implementation of a contract for testing purposes. * @template P - The private state type, fixed to Z_OwnablePKPrivateState. * @template L - The ledger type, fixed to Contract.Ledger. */ -export class Z_OwnablePKSimulator - implements IContractSimulator -{ - /** @description The underlying contract instance managing contract logic. */ +export class Z_OwnablePKSimulator extends AbstractContractSimulator< + Z_OwnablePKPrivateState, + Ledger +> { readonly contract: MockOwnable; - - /** @description The deployed address of the contract. */ readonly contractAddress: string; + private stateManager: SimulatorStateManager; + private callerOverride: CoinPublicKey | null = null; + + private _pureCircuitProxy?: ContextlessCircuits< + ExtractPureCircuits>, + Z_OwnablePKPrivateState + >; + + private _impureCircuitProxy?: ContextlessCircuits< + ExtractImpureCircuits>, + Z_OwnablePKPrivateState + >; + + constructor(initOwner: Uint8Array) { + super(); + this.contract = new MockOwnable( + Z_OwnablePKWitnesses(), + ); + // Setup initial state + const privateState: Z_OwnablePKPrivateState = Z_OwnablePKPrivateState.generate(); + const coinPK = '0'.repeat(64); + const address = sampleContractAddress(); + const constructorArgs = [initOwner]; + + this.stateManager = new SimulatorStateManager( + this.contract, + privateState, + coinPK, + address, + ...constructorArgs, + ); + this.contractAddress = this.circuitContext.transactionContext.address; + } - /** @description The deployer address of the contract. */ - readonly deployer: CoinPublicKey; + get circuitContext() { + return this.stateManager.getContext(); + } + + set circuitContext(ctx) { + this.stateManager.setContext(ctx); + } - /** @description The current circuit context, updated by contract operations. */ - circuitContext: CircuitContext; + getPublicState(): Ledger { + return ledger(this.circuitContext.transactionContext.state); + } /** - * @description Initializes the mock contract. + * @description Constructs a caller-specific circuit context. + * If a caller override is present, it replaces the current Zswap local state with an empty one + * scoped to the overridden caller. Otherwise, the existing context is reused as-is. + * @returns A circuit context adjusted for the current simulated caller. */ - constructor(initOwner: Uint8Array, deployer: CoinPublicKey) { - this.contract = new MockOwnable(Z_OwnablePKWitnesses()); - this.deployer = deployer; - - const { - currentPrivateState, - currentContractState, - currentZswapLocalState, - } = this.contract.initialState( - constructorContext(Z_OwnablePKPrivateState.generate(), deployer), - initOwner - ); - this.circuitContext = { - currentPrivateState, - currentZswapLocalState, - originalState: currentContractState, - transactionContext: new QueryContext( - currentContractState.data, - sampleContractAddress(), - ), + protected getCallerContext(): CircuitContext { + return { + ...this.circuitContext, + currentZswapLocalState: this.callerOverride + ? emptyZswapLocalState(this.callerOverride) + : this.circuitContext.currentZswapLocalState, + }; + } + + /** + * @description Initializes and returns a proxy to pure contract circuits. + * The proxy automatically injects the current circuit context into each call, + * and returns only the result portion of each circuit's output. + * @notice The proxy is created only when first accessed a.k.a lazy initialization. + * This approach is efficient in cases where only pure or only impure circuits are used, + * avoiding unnecessary proxy creation. + * @returns A proxy object exposing pure circuit functions without requiring explicit context. + */ + protected get pureCircuit(): ContextlessCircuits< + ExtractPureCircuits>, + Z_OwnablePKPrivateState + > { + if (!this._pureCircuitProxy) { + this._pureCircuitProxy = this.createPureCircuitProxy< + MockOwnable['circuits'] + >(this.contract.circuits, () => this.circuitContext); + } + return this._pureCircuitProxy; + } + + /** + * @description Initializes and returns a proxy to impure contract circuits. + * The proxy automatically injects the current (possibly caller-modified) context into each call, + * and updates the circuit context with the one returned by the circuit after execution. + * @notice The proxy is created only when first accessed a.k.a. lazy initialization. + * This approach is efficient in cases where only pure or only impure circuits are used, + * avoiding unnecessary proxy creation. + * @returns A proxy object exposing impure circuit functions without requiring explicit context management. + */ + protected get impureCircuit(): ContextlessCircuits< + ExtractImpureCircuits>, + Z_OwnablePKPrivateState + > { + if (!this._impureCircuitProxy) { + this._impureCircuitProxy = this.createImpureCircuitProxy< + MockOwnable['impureCircuits'] + >( + this.contract.impureCircuits, + () => this.getCallerContext(), + (ctx: any) => { + this.circuitContext = ctx; + }, + ); + } + return this._impureCircuitProxy; + } + + /** + * @description Sets the caller context. + * @param caller The caller in context of the proceeding circuit calls. + */ + public setCaller(caller: CoinPublicKey | null): void { + this.callerOverride = caller; + } + + /** + * @description Resets the cached circuit proxy instances. + * This is useful if the underlying contract state or circuit context has changed, + * and you want to ensure the proxies are recreated with updated context on next access. + */ + public resetCircuitProxies(): void { + this._pureCircuitProxy = undefined; + this._impureCircuitProxy = undefined; + } + + /** + * @description Helper method that provides access to both pure and impure circuit proxies. + * These proxies automatically inject the appropriate circuit context when invoked. + * @returns An object containing `pure` and `impure` circuit proxy interfaces. + */ + public get circuits() { + return { + pure: this.pureCircuit, + impure: this.impureCircuit, }; - this.contractAddress = this.circuitContext.transactionContext.address; } /** @@ -95,28 +199,16 @@ export class Z_OwnablePKSimulator * @returns The shielded owner. */ public owner(): Uint8Array { - return this.contract.impureCircuits.owner(this.circuitContext).result; + return this.circuits.impure.owner(); } /** - * @description Initiates the two-step ownership transfer to `newOwner`. + * @description */ public transferOwnership( newOwner: Uint8Array, - sender: CoinPublicKey, - ): CircuitContext { - const res = this.contract.impureCircuits.transferOwnership( - { - ...this.circuitContext, - currentZswapLocalState: sender - ? emptyZswapLocalState(sender) - : this.circuitContext.currentZswapLocalState, - }, - newOwner, - ); - - this.circuitContext = res.context; - return this.circuitContext; + ) { + this.circuits.impure.transferOwnership(newOwner); } /** @@ -124,66 +216,35 @@ export class Z_OwnablePKSimulator * possible to call `assertOnlyOnwer` circuits anymore. Can only be * called by the current owner. */ - public renounceOwnership( - sender: CoinPublicKey, - ): CircuitContext { - const res = this.contract.impureCircuits.renounceOwnership({ - ...this.circuitContext, - currentZswapLocalState: sender - ? emptyZswapLocalState(sender) - : this.circuitContext.currentZswapLocalState, - }); - - this.circuitContext = res.context; - return this.circuitContext; + public renounceOwnership() { + this.circuits.impure.renounceOwnership(); } /** * @description Throws if called by any account other than the owner. * Use this to restrict access to sensitive circuits. */ - public assertOnlyOwner( - sender: CoinPublicKey, - ): CircuitContext { - const res = this.contract.impureCircuits.assertOnlyOwner({ - ...this.circuitContext, - currentZswapLocalState: sender - ? emptyZswapLocalState(sender) - : this.circuitContext.currentZswapLocalState, - }); - - this.circuitContext = res.context; - return this.circuitContext; + public assertOnlyOwner() { + this.circuits.impure.assertOnlyOwner(); } /** - * @description Obfuscates the `ownerPK` be hashing it with a domain separator and + * @description Obfuscates the `pk` be hashing it with a domain separator and * the passed `instance`. * @returns The shielded hash of the owner and instance. */ public shieldPK( - ownerPK: ZswapCoinPublicKey, + pk: ZswapCoinPublicKey, instance: bigint, nonce: Uint8Array ): Uint8Array { - return this.contract.circuits.shieldPK( - this.circuitContext, - ownerPK, - instance, - nonce - ).result; + return this.circuits.pure.shieldPK(pk, instance, nonce); } /** * @description Internal circuit that transfers ownership of the contract to `newOwner`. */ - public _transferOwnership( - newOwner: Uint8Array, - ): CircuitContext { - this.circuitContext = this.contract.impureCircuits._transferOwnership( - this.circuitContext, - newOwner, - ).context; - return this.circuitContext; + public _transferOwnership(newOwnerCommitment: Uint8Array) { + this.circuits.impure._transferOwnership(newOwnerCommitment); } } diff --git a/contracts/ownable/src/test/types/AbstractContractSimulator.ts b/contracts/ownable/src/test/types/AbstractContractSimulator.ts new file mode 100644 index 00000000..8aec6a28 --- /dev/null +++ b/contracts/ownable/src/test/types/AbstractContractSimulator.ts @@ -0,0 +1,131 @@ +import type { + CircuitContext, + ContractState, +} from '@midnight-ntwrk/compact-runtime'; +import type { ContextlessCircuits, IContractSimulator } from './test.js'; + +/** + * Abstract base class for simulating contract behavior. + * Provides common functionality for managing circuit contexts and creating proxies + * for pure and impure circuit functions. + * + * @template P - The type representing the private state of the contract. + * @template L - The type representing the public ledger (contract) state. + */ +export abstract class AbstractContractSimulator + implements IContractSimulator +{ + /** + * The deployed contract's address. + * Must be implemented by concrete subclasses. + */ + abstract readonly contractAddress: string; + + /** + * The current circuit context containing private state, contract state, and transaction context. + * Must be implemented by concrete subclasses. + */ + abstract circuitContext: CircuitContext

; + + /** + * Retrieves the current public ledger state of the contract. + * Must be implemented by concrete subclasses. + * + * @returns The current public ledger state. + */ + abstract getPublicState(): L; + + /** + * Retrieves the current private state from the circuit context. + * + * @returns The current private state of the contract. + */ + public getPrivateState(): P { + return this.circuitContext.currentPrivateState; + } + + /** + * Retrieves the original contract state from the circuit context. + * + * @returns The original contract state. + */ + public getContractState(): ContractState { + return this.circuitContext.originalState; + } + + /** + * Creates a proxy wrapper around pure circuits. + * Pure circuits do not modify contract state, so only the result is returned. + * + * @template Circuits - The type of the circuits object to proxy. + * @param circuits - The original circuits object containing functions accepting a CircuitContext. + * @param context - A function returning the current CircuitContext to pass to circuit functions. + * @returns A proxy with contextless circuits that accept the original arguments and return only results. + */ + protected createPureCircuitProxy( + circuits: Circuits, + context: () => CircuitContext

, + ): ContextlessCircuits { + return new Proxy(circuits, { + get(target, prop, receiver) { + const original = Reflect.get(target, prop, receiver); + + if (typeof original !== 'function') return original; + + return (...args: any[]) => { + const ctx = context(); + + const fn = original as ( + ctx: CircuitContext

, + ...args: any[] + ) => { result: any }; + + return fn(ctx, ...args).result; + }; + }, + }) as ContextlessCircuits; + } + + /** + * Creates a proxy wrapper around impure circuits. + * Impure circuits can modify contract state, so the circuit context is updated accordingly. + * + * @template Circuits - The type of the circuits object to proxy. + * @param circuits - The original circuits object containing functions accepting a CircuitContext. + * @param context - A function returning the current CircuitContext to pass to circuit functions. + * @param updateContext - A callback to update the circuit context with the new context returned by the circuit. + * @returns A proxy with contextless circuits that accept the original arguments, update context, and return results. + */ + protected createImpureCircuitProxy( + circuits: Circuits, + context: () => CircuitContext

, + updateContext: (ctx: CircuitContext

) => void, + ): ContextlessCircuits { + return new Proxy(circuits, { + get(target, prop, receiver) { + const original = Reflect.get(target, prop, receiver); + + if (typeof original !== 'function') return original; + + return (...args: any[]) => { + const ctx = context(); + + const fn = original as ( + ctx: CircuitContext

, + ...args: any[] + ) => { result: any; context: CircuitContext

}; + + const { result, context: newCtx } = fn(ctx, ...args); + updateContext(newCtx); + return result; + }; + }, + }) as ContextlessCircuits; + } + + /** + * Optional method to reset any cached circuit proxies. + * Concrete subclasses can override this to clear proxies if needed. + */ + public resetCircuitProxies?(): void {} +} diff --git a/contracts/ownable/src/test/types/SimualatorStateManager.ts b/contracts/ownable/src/test/types/SimualatorStateManager.ts new file mode 100644 index 00000000..943ecf35 --- /dev/null +++ b/contracts/ownable/src/test/types/SimualatorStateManager.ts @@ -0,0 +1,114 @@ +import { + type CircuitContext, + type ConstructorContext, + type ContractState, + constructorContext, + QueryContext, +} from '@midnight-ntwrk/compact-runtime'; + +/** + * A composable utility class for managing Compact contract state in simulations. + * + * This class handles initialization and lifecycle management of the `CircuitContext`, + * which includes private state, public (ledger) state, zswap local state, and transaction context. + * + * It is designed to be embedded compositionally inside contract simulator classes + * (e.g., `FooSimulator`), enabling better separation of concerns and easier test setup. + * + * @template P - The type of the contract's private state. + * + * ### Responsibilities + * - Initializes the contract state using the compiled contract's `.initialState` method. + * - Stores and exposes the `CircuitContext` via getters/setters. + * - Supports injection of private state and contract constructor arguments. + * - Allows the owning simulator to update private state manually during testing. + * + * ### Example Usage: + * ```ts + * const contract = new MyContract(witnesses); + * const manager = new SimulatorStateManager( + * contract, + * { foo: 1n }, // initial private state + * '0'.repeat(64), // coin public key + * undefined, // optional contract address + * arg1, arg2 // additional constructor args + * ); + * + * const context = manager.getContext(); + * ``` + */ +export class SimulatorStateManager

{ + private context: CircuitContext

; + + /** + * Creates an instance of `SimulatorStateManager`. + * + * @param contract - A compiled Compact contract instance (from artifacts), exposing `initialState()`. + * @param privateState - The initial private state to inject into the contract. + * @param coinPK - The caller's coin public key (used to create the constructor context and as default address). + * @param contractAddress - Optional override for the contract's address. Defaults to `coinPK` if not provided. + * @param contractArgs - Additional arguments to pass to the contract constructor (e.g., circuit params). + */ + constructor( + contract: { + initialState: ( + ctx: ConstructorContext

, + ...args: any[] + ) => { + currentPrivateState: P; + currentContractState: ContractState; + currentZswapLocalState: any; + }; + }, + privateState: P, + coinPK: string, + contractAddress?: string, + ...contractArgs: any[] + ) { + const initCtx = constructorContext(privateState, coinPK); + + const { + currentPrivateState, + currentContractState, + currentZswapLocalState, + } = contract.initialState(initCtx, ...contractArgs); + + this.context = { + currentPrivateState, + currentZswapLocalState, + originalState: currentContractState, + transactionContext: new QueryContext( + currentContractState.data, + contractAddress ?? coinPK, + ), + }; + } + + /** + * Retrieves the current `CircuitContext`, including private state, + * zswap state, contract state, and transaction context. + */ + getContext(): CircuitContext

{ + return this.context; + } + + /** + * Replaces the internal `CircuitContext` with a new one. + * + * Useful when circuits mutate state and return an updated context. + */ + setContext(newContext: CircuitContext

) { + this.context = newContext; + } + + /** + * Updates just the private state inside the existing context. + * + * This is a lightweight way to simulate local state changes without reconstructing the full context. + * + * @param newPrivateState - The new private state object to apply. + */ + updatePrivateState(newPrivateState: P) { + this.context.currentPrivateState = newPrivateState; + } +} diff --git a/contracts/ownable/src/test/types/test.ts b/contracts/ownable/src/test/types/test.ts index 7a909543..c5511715 100644 --- a/contracts/ownable/src/test/types/test.ts +++ b/contracts/ownable/src/test/types/test.ts @@ -4,23 +4,92 @@ import type { } from '@midnight-ntwrk/compact-runtime'; /** - * Generic interface for mock contract implementations. - * @template P - The type of the contract's private state. - * @template L - The type of the contract's ledger (public state). + * Interface defining a generic contract simulator. + * + * @template P - Type representing the private contract state. + * @template L - Type representing the public ledger state. */ export interface IContractSimulator { - /** The contract's deployed address. */ + /** + * The deployed contract's address. + */ readonly contractAddress: string; - /** The current circuit context. */ + /** + * The current circuit context holding the contract state. + */ circuitContext: CircuitContext

; - /** Retrieves the current ledger state. */ - getCurrentPublicState(): L; + /** + * Returns the current public ledger state. + * + * @returns The current ledger state of type L. + */ + getPublicState(): L; - /** Retrieves the current private state. */ - getCurrentPrivateState(): P; + /** + * Returns the current private contract state. + * + * @returns The current private state of type P. + */ + getPrivateState(): P; - /** Retrieves the current contract state. */ - getCurrentContractState(): ContractState; + /** + * Returns the original contract state. + * + * @returns The current contract state. + */ + getContractState(): ContractState; } + +/** + * Extracts pure circuits from a contract type. + * + * Pure circuits are those in `circuits` but not in `impureCircuits`. + * + * @template TContract - Contract type with `circuits` and `impureCircuits`. + */ +export type ExtractPureCircuits = TContract extends { + circuits: infer TCircuits; + impureCircuits: infer TImpureCircuits; +} + ? Omit + : never; + +/** + * Extracts impure circuits from a contract type. + * + * Impure circuits are those in `impureCircuits`. + * + * @template TContract - Contract type with `circuits` and `impureCircuits`. + */ +export type ExtractImpureCircuits = TContract extends { + impureCircuits: infer TImpureCircuits; +} + ? TImpureCircuits + : never; + +/** + * Transforms a collection of circuit functions by removing the explicit `CircuitContext` parameter, + * producing a version of each function that can be called without passing the context explicitly. + * + * Each original circuit function is expected to have the signature: + * `(ctx: CircuitContext, ...args) => { result: R; context: CircuitContext }` + * or a compatible shape. + * + * The transformed type maps each key `K` of the input `Circuits` type to a new function + * that takes the same parameters as the original, *except* the first `CircuitContext` argument, + * and returns the `result` part `R` directly. + * + * @template Circuits - An object type whose values are circuit functions accepting a `CircuitContext` + * and returning an object with `result` and optionally `context`. + * @template TState - The type representing the private or contract state passed inside `CircuitContext`. + */ +export type ContextlessCircuits = { + [K in keyof Circuits]: Circuits[K] extends ( + ctx: CircuitContext, + ...args: infer P + ) => { result: infer R; context: CircuitContext } + ? (...args: P) => R + : never; +}; diff --git a/contracts/ownable/src/witnesses/OwnablePKWitnesses.ts b/contracts/ownable/src/witnesses/OwnablePKWitnesses.ts deleted file mode 100644 index 4976e327..00000000 --- a/contracts/ownable/src/witnesses/OwnablePKWitnesses.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This is how we type an empty object. -export type OwnablePKPrivateState = Record; -export const OwnablePKWitnesses = {}; From 97cc94ef48d9c19c894ff7719a459dca232953c9 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 8 Aug 2025 21:41:20 -0300 Subject: [PATCH 067/202] move helper test files --- .../AbstractContractSimulator.ts | 2 +- .../SimualatorStateManager.ts | 0 .../src/test/utils/createCircuitProxies.ts | 49 +++++++++++++++++++ contracts/ownable/src/test/utils/test.ts | 4 +- 4 files changed, 52 insertions(+), 3 deletions(-) rename contracts/ownable/src/test/{types => utils}/AbstractContractSimulator.ts (98%) rename contracts/ownable/src/test/{types => utils}/SimualatorStateManager.ts (100%) create mode 100644 contracts/ownable/src/test/utils/createCircuitProxies.ts diff --git a/contracts/ownable/src/test/types/AbstractContractSimulator.ts b/contracts/ownable/src/test/utils/AbstractContractSimulator.ts similarity index 98% rename from contracts/ownable/src/test/types/AbstractContractSimulator.ts rename to contracts/ownable/src/test/utils/AbstractContractSimulator.ts index 8aec6a28..36ac5d73 100644 --- a/contracts/ownable/src/test/types/AbstractContractSimulator.ts +++ b/contracts/ownable/src/test/utils/AbstractContractSimulator.ts @@ -2,7 +2,7 @@ import type { CircuitContext, ContractState, } from '@midnight-ntwrk/compact-runtime'; -import type { ContextlessCircuits, IContractSimulator } from './test.js'; +import type { ContextlessCircuits, IContractSimulator } from '../types/test.js'; /** * Abstract base class for simulating contract behavior. diff --git a/contracts/ownable/src/test/types/SimualatorStateManager.ts b/contracts/ownable/src/test/utils/SimualatorStateManager.ts similarity index 100% rename from contracts/ownable/src/test/types/SimualatorStateManager.ts rename to contracts/ownable/src/test/utils/SimualatorStateManager.ts diff --git a/contracts/ownable/src/test/utils/createCircuitProxies.ts b/contracts/ownable/src/test/utils/createCircuitProxies.ts new file mode 100644 index 00000000..b4a14ab0 --- /dev/null +++ b/contracts/ownable/src/test/utils/createCircuitProxies.ts @@ -0,0 +1,49 @@ +import type { CircuitContext } from '@midnight-ntwrk/compact-runtime'; +import type { + ContextlessCircuits, + ExtractImpureCircuits, + ExtractPureCircuits +} from '../types/test.js'; + +/** + * Creates lazily-initialized circuit proxies for pure and impure contract functions. + */ +export function createCircuitProxies( + contract: ContractType, + getContext: () => CircuitContext

, + getCallerContext: () => CircuitContext

, + updateContext: (ctx: CircuitContext

) => void, + createPureProxy: ( + circuits: C, + context: () => CircuitContext

, + ) => ContextlessCircuits, + createImpureProxy: ( + circuits: C, + context: () => CircuitContext

, + updateContext: (ctx: CircuitContext

) => void, + ) => ContextlessCircuits, +) { + let pureProxy: ContextlessCircuits, P> | undefined; + let impureProxy: ContextlessCircuits, P> | undefined; + + return { + get circuits() { + return { + pure: + pureProxy ?? + (pureProxy = createPureProxy(contract.circuits, getContext)), + impure: + impureProxy ?? + (impureProxy = createImpureProxy( + contract.impureCircuits, + getCallerContext, + updateContext, + )), + }; + }, + resetProxies() { + pureProxy = undefined; + impureProxy = undefined; + }, + }; +} diff --git a/contracts/ownable/src/test/utils/test.ts b/contracts/ownable/src/test/utils/test.ts index 2fd5a504..52e92528 100644 --- a/contracts/ownable/src/test/utils/test.ts +++ b/contracts/ownable/src/test/utils/test.ts @@ -55,8 +55,8 @@ export function useCircuitContextSender< L, C extends IContractSimulator, >(contract: C, sender: CoinPublicKey): CircuitContext

{ - const currentPrivateState = contract.getCurrentPrivateState(); - const originalState = contract.getCurrentContractState(); + const currentPrivateState = contract.getPrivateState(); + const originalState = contract.getContractState(); const contractAddress = contract.contractAddress; return { From 74945e0bbd93b53914be0516654fb292468ab062 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 8 Aug 2025 21:41:35 -0300 Subject: [PATCH 068/202] update simulators --- .../src/test/simulators/OwnableSimulator.ts | 6 ++-- .../test/simulators/Z_OwnablePKSimulator.ts | 28 ++----------------- 2 files changed, 5 insertions(+), 29 deletions(-) diff --git a/contracts/ownable/src/test/simulators/OwnableSimulator.ts b/contracts/ownable/src/test/simulators/OwnableSimulator.ts index 31e18127..1fbd380e 100644 --- a/contracts/ownable/src/test/simulators/OwnableSimulator.ts +++ b/contracts/ownable/src/test/simulators/OwnableSimulator.ts @@ -71,7 +71,7 @@ export class OwnableSimulator * @description Retrieves the current public ledger state of the contract. * @returns The ledger state as defined by the contract. */ - public getCurrentPublicState(): Ledger { + public getPublicState(): Ledger { return ledger(this.circuitContext.transactionContext.state); } @@ -79,7 +79,7 @@ export class OwnableSimulator * @description Retrieves the current private state of the contract. * @returns The private state of type OwnablePrivateState. */ - public getCurrentPrivateState(): OwnablePrivateState { + public getPrivateState(): OwnablePrivateState { return this.circuitContext.currentPrivateState; } @@ -87,7 +87,7 @@ export class OwnableSimulator * @description Retrieves the current contract state. * @returns The contract state object. */ - public getCurrentContractState(): ContractState { + public getContractState(): ContractState { return this.circuitContext.originalState; } diff --git a/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts index c2b6ecc0..e6fd0dfd 100644 --- a/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts @@ -15,8 +15,8 @@ import { Z_OwnablePKPrivateState, Z_OwnablePKWitnesses, } from '../../witnesses/Z_OwnablePKWitnesses.js'; -import { AbstractContractSimulator } from '../types/AbstractContractSimulator.js'; -import { SimulatorStateManager } from '../types/SimualatorStateManager.js'; +import { AbstractContractSimulator } from '../utils/AbstractContractSimulator.js'; +import { SimulatorStateManager } from '../utils/SimualatorStateManager.js'; import { ContextlessCircuits, ExtractImpureCircuits, ExtractPureCircuits } from '../types/test.js'; @@ -170,30 +170,6 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< }; } - /** - * @description Retrieves the current public ledger state of the contract. - * @returns The ledger state as defined by the contract. - */ - public getCurrentPublicState(): Ledger { - return ledger(this.circuitContext.transactionContext.state); - } - - /** - * @description Retrieves the current private state of the contract. - * @returns The private state of type Z_OwnablePKPrivateState. - */ - public getCurrentPrivateState(): Z_OwnablePKPrivateState { - return this.circuitContext.currentPrivateState; - } - - /** - * @description Retrieves the current contract state. - * @returns The contract state object. - */ - public getCurrentContractState(): ContractState { - return this.circuitContext.originalState; - } - /** * @description Returns the shielded owner. * @returns The shielded owner. From 6698dcccb8182427fdfde293aa588361eef8fdd5 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 8 Aug 2025 22:24:07 -0300 Subject: [PATCH 069/202] fix import --- contracts/ownable/src/test/simulators/OwnableSimulator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/ownable/src/test/simulators/OwnableSimulator.ts b/contracts/ownable/src/test/simulators/OwnableSimulator.ts index 1fbd380e..82778ad6 100644 --- a/contracts/ownable/src/test/simulators/OwnableSimulator.ts +++ b/contracts/ownable/src/test/simulators/OwnableSimulator.ts @@ -19,7 +19,7 @@ import { type OwnablePrivateState, OwnableWitnesses, } from '../../witnesses/OwnableWitnesses.js'; -import type { IContractSimulator } from '../types/test.js'; +import { IContractSimulator } from '../types/test.js'; /** * @description A simulator implementation of a Ownable contract for testing purposes. From 0e657a1e0fa4d981fff33f54755334cb88379e8d Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 9 Aug 2025 16:01:37 -0300 Subject: [PATCH 070/202] add sim options type --- contracts/ownable/src/test/types/test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/contracts/ownable/src/test/types/test.ts b/contracts/ownable/src/test/types/test.ts index c5511715..48da9c81 100644 --- a/contracts/ownable/src/test/types/test.ts +++ b/contracts/ownable/src/test/types/test.ts @@ -93,3 +93,10 @@ export type ContextlessCircuits = { ? (...args: P) => R : never; }; + +export type SimulatorOptions any> = { + address?: string; + coinPK?: string; + privateState?: PS; + witnesses?: ReturnType; +}; From 5eb449978e7ff617e8bf69fe128f62614f62ebec Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 9 Aug 2025 16:02:11 -0300 Subject: [PATCH 071/202] fix generate nonce in witness --- contracts/ownable/src/witnesses/Z_OwnablePKWitnesses.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/ownable/src/witnesses/Z_OwnablePKWitnesses.ts b/contracts/ownable/src/witnesses/Z_OwnablePKWitnesses.ts index 78a096a0..5203cf23 100644 --- a/contracts/ownable/src/witnesses/Z_OwnablePKWitnesses.ts +++ b/contracts/ownable/src/witnesses/Z_OwnablePKWitnesses.ts @@ -20,8 +20,7 @@ export const Z_OwnablePKPrivateState = { * @returns A fresh Z_OwnablePKPrivateState instance. */ generate: (): Z_OwnablePKPrivateState => { - //return { offchainNonce: getRandomValues(Buffer.alloc(32))}; - return { offchainNonce: Buffer.from(Array(32).fill(0xab))}; + return { offchainNonce: getRandomValues(Buffer.alloc(32))}; } }; From 2ddadc88190e2fbd050b72ac5197b69e2362cef4 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 9 Aug 2025 16:02:45 -0300 Subject: [PATCH 072/202] improve simulator with options in constructor --- .../test/simulators/Z_OwnablePKSimulator.ts | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts index e6fd0dfd..8fcaa473 100644 --- a/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts @@ -17,8 +17,9 @@ Z_OwnablePKPrivateState, } from '../../witnesses/Z_OwnablePKWitnesses.js'; import { AbstractContractSimulator } from '../utils/AbstractContractSimulator.js'; import { SimulatorStateManager } from '../utils/SimualatorStateManager.js'; -import { ContextlessCircuits, ExtractImpureCircuits, ExtractPureCircuits } from '../types/test.js'; +import { ContextlessCircuits, ExtractImpureCircuits, ExtractPureCircuits, SimulatorOptions } from '../types/test.js'; +type OwnableSimOptions = SimulatorOptions; /** * @description A simulator implementation of a contract for testing purposes. @@ -44,17 +45,22 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< Z_OwnablePKPrivateState >; - constructor(initOwner: Uint8Array) { + constructor(initOwner: Uint8Array, options: OwnableSimOptions = {}, ) { super(); - this.contract = new MockOwnable( - Z_OwnablePKWitnesses(), - ); + // Setup initial state - const privateState: Z_OwnablePKPrivateState = Z_OwnablePKPrivateState.generate(); - const coinPK = '0'.repeat(64); - const address = sampleContractAddress(); + const { + privateState = Z_OwnablePKPrivateState.generate(), + witnesses = Z_OwnablePKWitnesses(), + coinPK = '0'.repeat(64), + address = sampleContractAddress(), + } = options; const constructorArgs = [initOwner]; + this.contract = new MockOwnable( + witnesses, + ); + this.stateManager = new SimulatorStateManager( this.contract, privateState, @@ -223,4 +229,20 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< public _transferOwnership(newOwnerCommitment: Uint8Array) { this.circuits.impure._transferOwnership(newOwnerCommitment); } + + public readonly privateState = { + /** + * @description Stubs a new nonce into the private state. + */ + injectSecretNonce: (newNonce: Buffer): Z_OwnablePKPrivateState => { + const currentState = this.stateManager.getContext().currentPrivateState; + const updatedState = { ...currentState, offchainNonce: newNonce }; + this.stateManager.updatePrivateState(updatedState); + return updatedState; + }, + + getCurrentSecretNonce: (): Uint8Array => { + return this.stateManager.getContext().currentPrivateState.offchainNonce; + } + } } From de93d4828cc6fdc86477051c27f0888c7bb0c799 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 9 Aug 2025 16:03:01 -0300 Subject: [PATCH 073/202] add transferOwnership tests --- .../ownable/src/test/Z_OwnablePK.test.ts | 142 +++++++++++++++++- 1 file changed, 134 insertions(+), 8 deletions(-) diff --git a/contracts/ownable/src/test/Z_OwnablePK.test.ts b/contracts/ownable/src/test/Z_OwnablePK.test.ts index 72a8763b..dbfe9445 100644 --- a/contracts/ownable/src/test/Z_OwnablePK.test.ts +++ b/contracts/ownable/src/test/Z_OwnablePK.test.ts @@ -9,6 +9,7 @@ import { beforeEach, describe, expect, it } from 'vitest'; import { Z_OwnablePKSimulator } from './simulators/Z_OwnablePKSimulator.js'; import * as utils from './utils/address.js'; import { ZswapCoinPublicKey } from '../artifacts/MockOwnable/contract/index.cjs'; +import { Z_OwnablePKPrivateState } from '../witnesses/Z_OwnablePKWitnesses.js'; const OWNER = String(Buffer.from('OWNER', 'ascii').toString('hex')).padStart( 64, @@ -26,11 +27,10 @@ const Z_NEW_OWNER = utils.encodeToPK('NEW_OWNER'); const Z_NEW_NEW_OWNER = utils.encodeToPK('Z_NEW_NEW_OWNER'); const EMPTY_BYTES = utils.ZERO_KEY.left.bytes; -// Commitments const DOMAIN = "Z_OwnablePK:shield:"; const INIT_COUNTER = 1n; -const STATIC_NONCE = new Uint8Array(32).fill(0xab); +let secretNonce: Uint8Array; let ownable: Z_OwnablePKSimulator; const createZPKCommitment = ( @@ -67,30 +67,156 @@ describe('Z_OwnablePK', () => { describe('after initialization', () => { beforeEach(() => { - const ownerCommitment = createZPKCommitment(DOMAIN, Z_OWNER, INIT_COUNTER, STATIC_NONCE); - ownable = new Z_OwnablePKSimulator(ownerCommitment); + // Create private state object and generate nonce + const PS = Z_OwnablePKPrivateState.generate(); + // Bind nonce for convenience + secretNonce = PS.offchainNonce; + // Prepare initial owner commitment with gen nonce + const ownerCommitment = createZPKCommitment(DOMAIN, Z_OWNER, INIT_COUNTER, secretNonce); + // Deploy contract with derived owner commitment and PS + ownable = new Z_OwnablePKSimulator(ownerCommitment, {privateState: PS}); }); describe('owner', () => { it('should return the correct owner commitment', () => { - const expCommitment = createZPKCommitment(DOMAIN, Z_OWNER, INIT_COUNTER, STATIC_NONCE); + const expCommitment = createZPKCommitment(DOMAIN, Z_OWNER, INIT_COUNTER, secretNonce); expect(ownable.owner()).toEqual(expCommitment); }); }); describe('assertOnlyOwner', () => { - it('should allow the authorized caller with correct nonce to call', () => { + it('should allow authorized caller with correct nonce to call', () => { + // Check nonce is correct + expect(ownable.privateState.getCurrentSecretNonce()).toEqual(secretNonce); + ownable.setCaller(OWNER); expect(ownable.assertOnlyOwner()).to.not.throw; }); - it('should fail when called by unauthorized with correct nonce', () => { - ownable.setCaller(UNAUTHORIZED); + it('should fail when the authorized caller has the wrong nonce', () => { + // Inject bad nonce + const badNonce = Buffer.alloc(32, "badNonce"); + ownable.privateState.injectSecretNonce(badNonce); + // Check nonce does not match + expect(ownable.privateState.getCurrentSecretNonce()).not.toEqual(secretNonce); + + // Set caller and call circuit + ownable.setCaller(OWNER); expect(() => { ownable.assertOnlyOwner(); }).toThrow('Forbidden'); }); + + it('should fail when unauthorized caller has the correct nonce', () => { + // Check nonce is correct + expect(ownable.privateState.getCurrentSecretNonce()).toEqual(secretNonce); + + ownable.setCaller(UNAUTHORIZED); + expect(() => { + ownable.assertOnlyOwner() + }).toThrow('Forbidden'); + }); + + it('should fail when unauthorized caller has the wrong nonce', () => { + // Inject bad nonce + const badNonce = Buffer.alloc(32, "badNonce"); + ownable.privateState.injectSecretNonce(badNonce); + + // Check nonce does not match + expect(ownable.privateState.getCurrentSecretNonce()).not.toEqual(secretNonce); + + // Set unauthorized caller and call circuit + ownable.setCaller(UNAUTHORIZED); + expect(() => { + ownable.assertOnlyOwner() + }).toThrow('Forbidden'); + }); + }); + + describe('transferOwnership', () => { + let newOwnerCommitment: Uint8Array; + let newOwnerNonce: Uint8Array; + + beforeEach(() => { + // Prepare new owner commitment + newOwnerNonce = Z_OwnablePKPrivateState.generate().offchainNonce; + const newOwnerCounter = INIT_COUNTER + 1n; + newOwnerCommitment = createZPKCommitment(DOMAIN, Z_NEW_OWNER, newOwnerCounter, newOwnerNonce); + }); + + it('should transfer ownership', () => { + ownable.setCaller(OWNER); + ownable.transferOwnership(newOwnerCommitment); + expect(ownable.owner()).toEqual(newOwnerCommitment); + + // Old owner + ownable.setCaller(OWNER); + expect(() => { + ownable.assertOnlyOwner() + }).toThrow('Forbidden'); + + // Unauthorized + ownable.setCaller(UNAUTHORIZED); + expect(() => { + ownable.assertOnlyOwner() + }).toThrow('Forbidden'); + + // New owner + ownable.setCaller(NEW_OWNER); + ownable.privateState.injectSecretNonce(Buffer.from(newOwnerNonce)) + expect(ownable.assertOnlyOwner()).not.to.throw; + }); + + it('should fail when transferring to zero', () => { + ownable.setCaller(OWNER); + const badCommitment = new Uint8Array(32).fill(0); + expect(() => { + ownable.transferOwnership(badCommitment); + }).toThrow('Invalid parameters'); + }); + + it('should fail when unauthorized transfers ownership', () => { + ownable.setCaller(UNAUTHORIZED); + expect(() => { + ownable.transferOwnership(newOwnerCommitment); + }).toThrow('Forbidden'); + }); + + /** + * @description More thoroughly tested in `_transferOwnership` + * */ + it('should bump instance after transfer', () => { + let beforeInstance = ownable.getPublicState().Z_OwnablePK__instance; + + // Transfer + ownable.setCaller(OWNER); + ownable.transferOwnership(newOwnerCommitment); + + // Check counter + let afterInstance = ownable.getPublicState().Z_OwnablePK__instance; + expect(afterInstance).toEqual(beforeInstance + 1n); + }); + + it('should change hash when transferring ownership to commitment with same pk and nonce', () => { + // Confirm current commitment + const initCommitment = ownable.owner(); + const calcInitCommitment = createZPKCommitment(DOMAIN, Z_OWNER, INIT_COUNTER, secretNonce); + expect(initCommitment).toEqual(calcInitCommitment); + + // Create new commitment by bumping the counter + const bumpedCounter = INIT_COUNTER + 1n; + const newCommitment = createZPKCommitment(DOMAIN, Z_OWNER, bumpedCounter, secretNonce); + + // Transfer ownership to self + ownable.setCaller(OWNER); + ownable.transferOwnership(newCommitment); + + // Check owner and permissions + const newOwner = ownable.owner(); + expect(newOwner).toEqual(newCommitment); + ownable.assertOnlyOwner(); + }) }); }); }); From d7fe2a70d8a4367ad720dafe2becb7abe9181d3c Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 10 Aug 2025 00:54:21 -0300 Subject: [PATCH 074/202] start hash chaining --- contracts/ownable/src/Z_OwnablePK.compact | 41 +++--- .../ownable/src/test/Z_OwnablePK.test.ts | 123 +++++++++++++----- .../src/test/mocks/MockZ_OwnablePK.compact | 6 +- .../test/simulators/Z_OwnablePKSimulator.ts | 21 ++- 4 files changed, 123 insertions(+), 68 deletions(-) diff --git a/contracts/ownable/src/Z_OwnablePK.compact b/contracts/ownable/src/Z_OwnablePK.compact index 22a55e06..730b4db6 100644 --- a/contracts/ownable/src/Z_OwnablePK.compact +++ b/contracts/ownable/src/Z_OwnablePK.compact @@ -6,7 +6,7 @@ module Z_OwnablePK { import CompactStandardLibrary; export ledger _ownerCommitment: Bytes<32>; - export ledger _instance: Counter; + export ledger _counter: Counter; export witness offchainNonce(): Bytes<32>; @@ -19,10 +19,10 @@ module Z_OwnablePK { return _ownerCommitment; } - export circuit transferOwnership(newOwnerCommitment: Bytes<32>): [] { + export circuit transferOwnership(newOwnerIdHash: Bytes<32>): [] { assertOnlyOwner(); - assert(newOwnerCommitment != default>, "Invalid parameters"); - _transferOwnership(newOwnerCommitment); + assert(newOwnerIdHash != default>, "Invalid parameters"); + _transferOwnership(newOwnerIdHash); } export circuit renounceOwnership(): [] { @@ -37,7 +37,7 @@ module Z_OwnablePK { [ pad(32, "Z_OwnablePK:renounced:"), default>, - _instance as Field as Bytes<32>, + _counter as Field as Bytes<32>, nonce ] ); @@ -48,28 +48,23 @@ module Z_OwnablePK { export circuit assertOnlyOwner(): [] { const caller = ownPublicKey(); const nonce = offchainNonce(); - assert( - _ownerCommitment == shieldPK(caller, _instance, nonce - ), "Forbidden"); + const idHash = persistentHash>>([caller.bytes, nonce]); + assert(_ownerCommitment == hashCommitment(idHash, _counter), "Forbidden"); } - export circuit shieldPK( - pk: ZswapCoinPublicKey, - instance: Uint<64>, - nonce: Bytes<32> + export circuit hashCommitment( + idHash: Bytes<32>, + counter: Uint<64>, ): Bytes<32> { - return persistentHash>>( - [ - pad(32, "Z_OwnablePK:shield:"), - pk.bytes, - instance as Field as Bytes<32>, - nonce - ] - ); + //const contextHash = persistentHash>>([kernel.self().bytes, idHash]); + //const counterHash = persistentHash>>([counter as Field as Bytes<32>, contextHash]); + const counterHash = persistentHash>>([counter as Field as Bytes<32>, idHash]); + const commitment = persistentHash>>([pad(32, "Z_OwnablePK:shield:"), counterHash]); + return commitment; } - export circuit _transferOwnership(newOwnerCommitment: Bytes<32>): [] { - _instance.increment(1); - _ownerCommitment = disclose(newOwnerCommitment); + export circuit _transferOwnership(newOwnerIdHash: Bytes<32>): [] { + _counter.increment(1); + _ownerCommitment = hashCommitment(disclose(newOwnerIdHash), _counter); } } diff --git a/contracts/ownable/src/test/Z_OwnablePK.test.ts b/contracts/ownable/src/test/Z_OwnablePK.test.ts index dbfe9445..dc881e85 100644 --- a/contracts/ownable/src/test/Z_OwnablePK.test.ts +++ b/contracts/ownable/src/test/Z_OwnablePK.test.ts @@ -8,7 +8,7 @@ import { import { beforeEach, describe, expect, it } from 'vitest'; import { Z_OwnablePKSimulator } from './simulators/Z_OwnablePKSimulator.js'; import * as utils from './utils/address.js'; -import { ZswapCoinPublicKey } from '../artifacts/MockOwnable/contract/index.cjs'; +import { ZswapCoinPublicKey, ContractAddress } from '../artifacts/MockOwnable/contract/index.cjs'; import { Z_OwnablePKPrivateState } from '../witnesses/Z_OwnablePKWitnesses.js'; const OWNER = String(Buffer.from('OWNER', 'ascii').toString('hex')).padStart( @@ -32,20 +32,69 @@ const INIT_COUNTER = 1n; let secretNonce: Uint8Array; let ownable: Z_OwnablePKSimulator; +let bOwnableAddress: Uint8Array; + +//const createZPKCommitment = ( +// domain: string, +// pk: ZswapCoinPublicKey, +// counter: bigint, +// nonce: Uint8Array +//): Uint8Array => { +// const rt_type = new CompactTypeVector(4, new CompactTypeBytes(32)); +// const encoder = new TextEncoder(); +// +// const bDomain = encoder.encode(domain); +// const bPK = pk.bytes; +// const bCounter = convert_bigint_to_Uint8Array(32, counter); +// return persistentHash(rt_type, [bDomain, bPK, bCounter, nonce]); +//} + +const createIdHash = ( + pk: ZswapCoinPublicKey, + nonce: Uint8Array +): Uint8Array => { + const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32)); + + const bPK = pk.bytes; + return persistentHash(rt_type, [bPK, nonce]); +} + +const buildCommitmentFromId = ( + id: Uint8Array, + counter: bigint +): Uint8Array => { + const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32)); + const encoder = new TextEncoder(); + + //const contextHash = persistentHash(rt_type, [address, id]); + + const bCounter = convert_bigint_to_Uint8Array(32, counter); + const innerHash = persistentHash(rt_type, [bCounter, id]); + + const bDomain = encoder.encode(DOMAIN); + const outerHash = persistentHash(rt_type, [bDomain, innerHash]); + return outerHash; +} -const createZPKCommitment = ( +const buildCommitment = ( domain: string, pk: ZswapCoinPublicKey, counter: bigint, - nonce: Uint8Array + nonce: Uint8Array, ): Uint8Array => { - const rt_type = new CompactTypeVector(4, new CompactTypeBytes(32)); + const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32)); const encoder = new TextEncoder(); - const bDomain = encoder.encode(domain); - const bPK = pk.bytes; + const idHash = createIdHash(pk, nonce); + //const contextHash = persistentHash(rt_type, [address, idHash]); + const bCounter = convert_bigint_to_Uint8Array(32, counter); - return persistentHash(rt_type, [bDomain, bPK, bCounter, nonce]); + const counterHash = persistentHash(rt_type, [bCounter, idHash]); + + const bDomain = encoder.encode(domain); + const outerHash = persistentHash(rt_type, [bDomain, counterHash]); + return outerHash; + //return innerHash; } describe('Z_OwnablePK', () => { @@ -58,9 +107,12 @@ describe('Z_OwnablePK', () => { }); it('should initialize with non-zero commitment', () => { - const nonZeroCommitment = new Uint8Array(32).fill(1); - ownable = new Z_OwnablePKSimulator(nonZeroCommitment); + const notZeroPK = utils.encodeToPK('NOT_ZERO'); + const notZeroNonce = new Uint8Array(32).fill(1); + const nonZeroId = createIdHash(notZeroPK, notZeroNonce); + ownable = new Z_OwnablePKSimulator(nonZeroId); + const nonZeroCommitment = buildCommitment(DOMAIN, notZeroPK, INIT_COUNTER, notZeroNonce); expect(ownable.owner()).toEqual(nonZeroCommitment); }); }); @@ -71,15 +123,18 @@ describe('Z_OwnablePK', () => { const PS = Z_OwnablePKPrivateState.generate(); // Bind nonce for convenience secretNonce = PS.offchainNonce; - // Prepare initial owner commitment with gen nonce - const ownerCommitment = createZPKCommitment(DOMAIN, Z_OWNER, INIT_COUNTER, secretNonce); + // Prepare owner ID with gen nonce + const ownerCommitment = createIdHash(Z_OWNER, secretNonce); // Deploy contract with derived owner commitment and PS ownable = new Z_OwnablePKSimulator(ownerCommitment, {privateState: PS}); + + const encoder = new TextEncoder(); + bOwnableAddress = encoder.encode(ownable.contractAddress); }); describe('owner', () => { it('should return the correct owner commitment', () => { - const expCommitment = createZPKCommitment(DOMAIN, Z_OWNER, INIT_COUNTER, secretNonce); + const expCommitment = buildCommitment(DOMAIN, Z_OWNER, INIT_COUNTER, secretNonce); expect(ownable.owner()).toEqual(expCommitment); }); }); @@ -137,17 +192,20 @@ describe('Z_OwnablePK', () => { describe('transferOwnership', () => { let newOwnerCommitment: Uint8Array; let newOwnerNonce: Uint8Array; + let newIdHash: Uint8Array; + let newCounter: bigint; beforeEach(() => { // Prepare new owner commitment newOwnerNonce = Z_OwnablePKPrivateState.generate().offchainNonce; - const newOwnerCounter = INIT_COUNTER + 1n; - newOwnerCommitment = createZPKCommitment(DOMAIN, Z_NEW_OWNER, newOwnerCounter, newOwnerNonce); + newCounter = INIT_COUNTER + 1n; + newIdHash = createIdHash(Z_NEW_OWNER, newOwnerNonce); + newOwnerCommitment = buildCommitment(DOMAIN, Z_NEW_OWNER, newCounter, newOwnerNonce); }); it('should transfer ownership', () => { ownable.setCaller(OWNER); - ownable.transferOwnership(newOwnerCommitment); + ownable.transferOwnership(newIdHash); expect(ownable.owner()).toEqual(newOwnerCommitment); // Old owner @@ -187,36 +245,41 @@ describe('Z_OwnablePK', () => { * @description More thoroughly tested in `_transferOwnership` * */ it('should bump instance after transfer', () => { - let beforeInstance = ownable.getPublicState().Z_OwnablePK__instance; + let beforeInstance = ownable.getPublicState().Z_OwnablePK__counter; // Transfer ownable.setCaller(OWNER); ownable.transferOwnership(newOwnerCommitment); // Check counter - let afterInstance = ownable.getPublicState().Z_OwnablePK__instance; + let afterInstance = ownable.getPublicState().Z_OwnablePK__counter; expect(afterInstance).toEqual(beforeInstance + 1n); }); - it('should change hash when transferring ownership to commitment with same pk and nonce', () => { + it('should change commitment when transferring ownership to self with same pk + nonce)', () => { // Confirm current commitment + const repeatedId = createIdHash(Z_OWNER, secretNonce); const initCommitment = ownable.owner(); - const calcInitCommitment = createZPKCommitment(DOMAIN, Z_OWNER, INIT_COUNTER, secretNonce); - expect(initCommitment).toEqual(calcInitCommitment); + const expInitCommitment = buildCommitmentFromId(repeatedId, INIT_COUNTER); + expect(initCommitment).toEqual(expInitCommitment); + + // Transfer ownership to self with the same id -> `H(pk, nonce)` + ownable.setCaller(OWNER); + ownable.transferOwnership(repeatedId); + + // Check commitments don't match + const newCommitment = ownable.owner(); + expect(initCommitment).not.toEqual(newCommitment); - // Create new commitment by bumping the counter + // Build commitment locally and validate new commitment == expected const bumpedCounter = INIT_COUNTER + 1n; - const newCommitment = createZPKCommitment(DOMAIN, Z_OWNER, bumpedCounter, secretNonce); + const expNewCommitment = buildCommitmentFromId(repeatedId, bumpedCounter); + expect(newCommitment).toEqual(expNewCommitment); - // Transfer ownership to self + // Check same owner maintains permissions after transfer ownable.setCaller(OWNER); - ownable.transferOwnership(newCommitment); - - // Check owner and permissions - const newOwner = ownable.owner(); - expect(newOwner).toEqual(newCommitment); - ownable.assertOnlyOwner(); - }) + expect(ownable.assertOnlyOwner()).not.to.throw; + }); }); }); }); diff --git a/contracts/ownable/src/test/mocks/MockZ_OwnablePK.compact b/contracts/ownable/src/test/mocks/MockZ_OwnablePK.compact index e719e6c0..92658e3e 100644 --- a/contracts/ownable/src/test/mocks/MockZ_OwnablePK.compact +++ b/contracts/ownable/src/test/mocks/MockZ_OwnablePK.compact @@ -6,7 +6,7 @@ import CompactStandardLibrary; import "../../Z_OwnablePK" prefix Z_OwnablePK_; export { ZswapCoinPublicKey, ContractAddress, Either, Maybe }; -export { Z_OwnablePK__ownerCommitment, Z_OwnablePK__instance }; +export { Z_OwnablePK__ownerCommitment, Z_OwnablePK__counter }; constructor(initOwnerCommitment: Bytes<32>) { Z_OwnablePK_initialize(initOwnerCommitment); @@ -28,8 +28,8 @@ export circuit assertOnlyOwner(): [] { return Z_OwnablePK_assertOnlyOwner(); } -export circuit shieldPK(ownerPK: ZswapCoinPublicKey, instance: Uint<64>, nonce: Bytes<32>): Bytes<32> { - return Z_OwnablePK_shieldPK(ownerPK, instance, nonce); +export circuit hashCommitment(idHash: Bytes<32>, counter: Uint<64>): Bytes<32> { + return Z_OwnablePK_hashCommitment(idHash, counter); } export circuit _transferOwnership(newOwnerCommitment: Bytes<32>): [] { diff --git a/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts index 8fcaa473..e441dfa6 100644 --- a/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts @@ -188,9 +188,9 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< * @description */ public transferOwnership( - newOwner: Uint8Array, + newOwnerId: Uint8Array, ) { - this.circuits.impure.transferOwnership(newOwner); + this.circuits.impure.transferOwnership(newOwnerId); } /** @@ -211,23 +211,20 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< } /** - * @description Obfuscates the `pk` be hashing it with a domain separator and - * the passed `instance`. - * @returns The shielded hash of the owner and instance. + * @description */ - public shieldPK( - pk: ZswapCoinPublicKey, - instance: bigint, - nonce: Uint8Array + public hashCommitment( + idHash: Uint8Array, + counter: bigint, ): Uint8Array { - return this.circuits.pure.shieldPK(pk, instance, nonce); + return this.circuits.pure.hashCommitment(idHash, counter); } /** * @description Internal circuit that transfers ownership of the contract to `newOwner`. */ - public _transferOwnership(newOwnerCommitment: Uint8Array) { - this.circuits.impure._transferOwnership(newOwnerCommitment); + public _transferOwnership(newOwnerId: Uint8Array) { + this.circuits.impure._transferOwnership(newOwnerId); } public readonly privateState = { From 47d7480da96af5943f2b6910e9c66e33787150e7 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 11 Aug 2025 02:47:40 -0300 Subject: [PATCH 075/202] integrate instance salt to hash --- contracts/ownable/src/Z_OwnablePK.compact | 72 +++++++++++++++++------ 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/contracts/ownable/src/Z_OwnablePK.compact b/contracts/ownable/src/Z_OwnablePK.compact index 730b4db6..aa03f457 100644 --- a/contracts/ownable/src/Z_OwnablePK.compact +++ b/contracts/ownable/src/Z_OwnablePK.compact @@ -2,69 +2,103 @@ pragma language_version >= 0.16.0; +/** + * @module Z_OwnablePK + * @description A shielded Ownable library. + */ module Z_OwnablePK { import CompactStandardLibrary; export ledger _ownerCommitment: Bytes<32>; export ledger _counter: Counter; + export sealed ledger _instanceSalt: Bytes<32>; export witness offchainNonce(): Bytes<32>; - export circuit initialize(initCommitment: Bytes<32>): [] { - assert(initCommitment != default>, "Invalid parameters"); - _transferOwnership(initCommitment); + /** + * @description Add me!!! + */ + export circuit initialize(ownerId: Bytes<32>, instanceSalt: Bytes<32>): [] { + assert(ownerId != default>, "Invalid parameters"); + _instanceSalt = disclose(instanceSalt); + _transferOwnership(ownerId); } + /** + * @description Add me!!! + */ export circuit owner(): Bytes<32> { return _ownerCommitment; } - export circuit transferOwnership(newOwnerIdHash: Bytes<32>): [] { + /** + * @description Add me!!! + */ + export circuit transferOwnership(newOwnerId: Bytes<32>): [] { assertOnlyOwner(); - assert(newOwnerIdHash != default>, "Invalid parameters"); - _transferOwnership(newOwnerIdHash); + assert(newOwnerId != default>, "Invalid parameters"); + _transferOwnership(newOwnerId); } + /** + * @description Add me!!! + */ export circuit renounceOwnership(): [] { assertOnlyOwner(); _transferOwnership(default>); } + /** + * @description Add me!!! + */ export circuit renounceOwnershipObfuscated(): [] { assertOnlyOwner(); const nonce = offchainNonce(); const obfuscatedCommitment = persistentHash>>( [ - pad(32, "Z_OwnablePK:renounced:"), - default>, + persistentHash>>([default>, nonce]), + _instanceSalt, _counter as Field as Bytes<32>, - nonce + pad(32, "Z_OwnablePK:renounced:"), ] ); _transferOwnership(obfuscatedCommitment); } + /** + * @description Add me!!! + */ export circuit assertOnlyOwner(): [] { const caller = ownPublicKey(); const nonce = offchainNonce(); - const idHash = persistentHash>>([caller.bytes, nonce]); - assert(_ownerCommitment == hashCommitment(idHash, _counter), "Forbidden"); + const id = persistentHash>>([caller.bytes, nonce]); + assert(_ownerCommitment == hashCommitment(id, _counter), "Forbidden"); } + // computePKCommitment || generateCommitment + /** + * @description Add me!!! + */ export circuit hashCommitment( - idHash: Bytes<32>, + id: Bytes<32>, counter: Uint<64>, ): Bytes<32> { - //const contextHash = persistentHash>>([kernel.self().bytes, idHash]); - //const counterHash = persistentHash>>([counter as Field as Bytes<32>, contextHash]); - const counterHash = persistentHash>>([counter as Field as Bytes<32>, idHash]); - const commitment = persistentHash>>([pad(32, "Z_OwnablePK:shield:"), counterHash]); - return commitment; + return persistentHash>>( + [ + id, + _instanceSalt, + counter as Field as Bytes<32>, + pad(32, "Z_OwnablePK:shield:") + ] + ); } - export circuit _transferOwnership(newOwnerIdHash: Bytes<32>): [] { + /** + * @description Add me!!! + */ + export circuit _transferOwnership(newOwnerId: Bytes<32>): [] { _counter.increment(1); - _ownerCommitment = hashCommitment(disclose(newOwnerIdHash), _counter); + _ownerCommitment = hashCommitment(disclose(newOwnerId), _counter); } } From cf582c0c887e7e2e86e0a2e5288515ef065071b8 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 11 Aug 2025 02:48:06 -0300 Subject: [PATCH 076/202] integrate instance salt to hash --- contracts/ownable/src/test/mocks/MockZ_OwnablePK.compact | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/ownable/src/test/mocks/MockZ_OwnablePK.compact b/contracts/ownable/src/test/mocks/MockZ_OwnablePK.compact index 92658e3e..4d425dbf 100644 --- a/contracts/ownable/src/test/mocks/MockZ_OwnablePK.compact +++ b/contracts/ownable/src/test/mocks/MockZ_OwnablePK.compact @@ -8,8 +8,8 @@ import "../../Z_OwnablePK" prefix Z_OwnablePK_; export { ZswapCoinPublicKey, ContractAddress, Either, Maybe }; export { Z_OwnablePK__ownerCommitment, Z_OwnablePK__counter }; -constructor(initOwnerCommitment: Bytes<32>) { - Z_OwnablePK_initialize(initOwnerCommitment); +constructor(initOwnerCommitment: Bytes<32>, instanceSalt: Bytes<32>) { + Z_OwnablePK_initialize(initOwnerCommitment, instanceSalt); } export circuit owner(): Bytes<32> { @@ -28,8 +28,8 @@ export circuit assertOnlyOwner(): [] { return Z_OwnablePK_assertOnlyOwner(); } -export circuit hashCommitment(idHash: Bytes<32>, counter: Uint<64>): Bytes<32> { - return Z_OwnablePK_hashCommitment(idHash, counter); +export circuit hashCommitment(id: Bytes<32>, counter: Uint<64>): Bytes<32> { + return Z_OwnablePK_hashCommitment(id, counter); } export circuit _transferOwnership(newOwnerCommitment: Bytes<32>): [] { From f5f58d24d1ec940678b47e826caa6fbf8342e7df Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 11 Aug 2025 02:48:42 -0300 Subject: [PATCH 077/202] update constructor in sim --- .../ownable/src/test/simulators/Z_OwnablePKSimulator.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts index e441dfa6..fac56eb6 100644 --- a/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts @@ -45,7 +45,7 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< Z_OwnablePKPrivateState >; - constructor(initOwner: Uint8Array, options: OwnableSimOptions = {}, ) { + constructor(initOwner: Uint8Array, instanceSalt: Uint8Array, options: OwnableSimOptions = {}, ) { super(); // Setup initial state @@ -55,7 +55,7 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< coinPK = '0'.repeat(64), address = sampleContractAddress(), } = options; - const constructorArgs = [initOwner]; + const constructorArgs = [initOwner, instanceSalt]; this.contract = new MockOwnable( witnesses, @@ -214,10 +214,10 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< * @description */ public hashCommitment( - idHash: Uint8Array, + id: Uint8Array, counter: bigint, ): Uint8Array { - return this.circuits.pure.hashCommitment(idHash, counter); + return this.circuits.impure.hashCommitment(id, counter); } /** From 6f233b10fb5ee8bbd32ab66d0db6f1b76b089b33 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 11 Aug 2025 02:49:07 -0300 Subject: [PATCH 078/202] update tests with salt --- .../ownable/src/test/Z_OwnablePK.test.ts | 108 ++++++++---------- 1 file changed, 50 insertions(+), 58 deletions(-) diff --git a/contracts/ownable/src/test/Z_OwnablePK.test.ts b/contracts/ownable/src/test/Z_OwnablePK.test.ts index dc881e85..eed5e4b8 100644 --- a/contracts/ownable/src/test/Z_OwnablePK.test.ts +++ b/contracts/ownable/src/test/Z_OwnablePK.test.ts @@ -1,14 +1,13 @@ import { - type CoinPublicKey, convert_bigint_to_Uint8Array, persistentHash, CompactTypeVector, - CompactTypeBytes + CompactTypeBytes, } from '@midnight-ntwrk/compact-runtime'; import { beforeEach, describe, expect, it } from 'vitest'; import { Z_OwnablePKSimulator } from './simulators/Z_OwnablePKSimulator.js'; import * as utils from './utils/address.js'; -import { ZswapCoinPublicKey, ContractAddress } from '../artifacts/MockOwnable/contract/index.cjs'; +import { ZswapCoinPublicKey } from '../artifacts/MockOwnable/contract/index.cjs'; import { Z_OwnablePKPrivateState } from '../witnesses/Z_OwnablePKWitnesses.js'; const OWNER = String(Buffer.from('OWNER', 'ascii').toString('hex')).padStart( @@ -21,34 +20,17 @@ const NEW_OWNER = String( const UNAUTHORIZED = String( Buffer.from('UNAUTHORIZED', 'ascii').toString('hex'), ).padStart(64, '0'); -const Z_ZERO = utils.encodeToPK(''); const Z_OWNER = utils.encodeToPK('OWNER'); const Z_NEW_OWNER = utils.encodeToPK('NEW_OWNER'); -const Z_NEW_NEW_OWNER = utils.encodeToPK('Z_NEW_NEW_OWNER'); -const EMPTY_BYTES = utils.ZERO_KEY.left.bytes; +const INSTANCE_SALT = new Uint8Array(32).fill(8675309); const DOMAIN = "Z_OwnablePK:shield:"; const INIT_COUNTER = 1n; let secretNonce: Uint8Array; let ownable: Z_OwnablePKSimulator; -let bOwnableAddress: Uint8Array; - -//const createZPKCommitment = ( -// domain: string, -// pk: ZswapCoinPublicKey, -// counter: bigint, -// nonce: Uint8Array -//): Uint8Array => { -// const rt_type = new CompactTypeVector(4, new CompactTypeBytes(32)); -// const encoder = new TextEncoder(); -// -// const bDomain = encoder.encode(domain); -// const bPK = pk.bytes; -// const bCounter = convert_bigint_to_Uint8Array(32, counter); -// return persistentHash(rt_type, [bDomain, bPK, bCounter, nonce]); -//} +/** Helpers */ const createIdHash = ( pk: ZswapCoinPublicKey, nonce: Uint8Array @@ -61,40 +43,32 @@ const createIdHash = ( const buildCommitmentFromId = ( id: Uint8Array, - counter: bigint + instanceSalt: Uint8Array, + counter: bigint, ): Uint8Array => { - const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32)); - const encoder = new TextEncoder(); - - //const contextHash = persistentHash(rt_type, [address, id]); - + const rt_type = new CompactTypeVector(4, new CompactTypeBytes(32)); const bCounter = convert_bigint_to_Uint8Array(32, counter); - const innerHash = persistentHash(rt_type, [bCounter, id]); + const bDomain = new TextEncoder().encode(DOMAIN); - const bDomain = encoder.encode(DOMAIN); - const outerHash = persistentHash(rt_type, [bDomain, innerHash]); - return outerHash; + const commitment = persistentHash(rt_type, [id, instanceSalt, bCounter, bDomain]); + return commitment; } const buildCommitment = ( - domain: string, - pk: ZswapCoinPublicKey, - counter: bigint, - nonce: Uint8Array, + pk: ZswapCoinPublicKey, + nonce: Uint8Array, + instanceSalt: Uint8Array, + counter: bigint, + domain: string ): Uint8Array => { - const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32)); - const encoder = new TextEncoder(); - - const idHash = createIdHash(pk, nonce); - //const contextHash = persistentHash(rt_type, [address, idHash]); + const id = createIdHash(pk, nonce); + const rt_type = new CompactTypeVector(4, new CompactTypeBytes(32)); const bCounter = convert_bigint_to_Uint8Array(32, counter); - const counterHash = persistentHash(rt_type, [bCounter, idHash]); + const bDomain = new TextEncoder().encode(domain); - const bDomain = encoder.encode(domain); - const outerHash = persistentHash(rt_type, [bDomain, counterHash]); - return outerHash; - //return innerHash; + const commitment = persistentHash(rt_type, [id, instanceSalt, bCounter, bDomain]); + return commitment; } describe('Z_OwnablePK', () => { @@ -102,7 +76,7 @@ describe('Z_OwnablePK', () => { it('should fail when setting owner commitment as 0', () => { expect(() => { const badCommitment = new Uint8Array(32).fill(0); - new Z_OwnablePKSimulator(badCommitment); + new Z_OwnablePKSimulator(badCommitment, INSTANCE_SALT); }).toThrow('Invalid parameters'); }); @@ -110,9 +84,9 @@ describe('Z_OwnablePK', () => { const notZeroPK = utils.encodeToPK('NOT_ZERO'); const notZeroNonce = new Uint8Array(32).fill(1); const nonZeroId = createIdHash(notZeroPK, notZeroNonce); - ownable = new Z_OwnablePKSimulator(nonZeroId); + ownable = new Z_OwnablePKSimulator(nonZeroId, INSTANCE_SALT); - const nonZeroCommitment = buildCommitment(DOMAIN, notZeroPK, INIT_COUNTER, notZeroNonce); + const nonZeroCommitment = buildCommitmentFromId(nonZeroId, INSTANCE_SALT, INIT_COUNTER); expect(ownable.owner()).toEqual(nonZeroCommitment); }); }); @@ -124,17 +98,35 @@ describe('Z_OwnablePK', () => { // Bind nonce for convenience secretNonce = PS.offchainNonce; // Prepare owner ID with gen nonce - const ownerCommitment = createIdHash(Z_OWNER, secretNonce); + const ownerId = createIdHash(Z_OWNER, secretNonce); // Deploy contract with derived owner commitment and PS - ownable = new Z_OwnablePKSimulator(ownerCommitment, {privateState: PS}); - - const encoder = new TextEncoder(); - bOwnableAddress = encoder.encode(ownable.contractAddress); + ownable = new Z_OwnablePKSimulator(ownerId, INSTANCE_SALT, {privateState: PS}); }); + /** + * @TODO parameterize + */ + describe('hashCommitment', () => { + it('should match local and contract commitment algorithms', () => { + const address = ownable.contractAddress; + const id = createIdHash(Z_OWNER, secretNonce); + const counter = INIT_COUNTER; + + // Check buildCommitmentFromId + const hashFromContract = ownable.hashCommitment(id, counter); + const hashFromHelper1 = buildCommitmentFromId(id, INSTANCE_SALT, counter); + expect(hashFromContract).toEqual(hashFromHelper1); + + // Check buildCommitment + const hashFromHelper2 = buildCommitment(Z_OWNER, secretNonce, INSTANCE_SALT, counter, DOMAIN); + expect(hashFromContract).toEqual(hashFromHelper1); + expect(hashFromHelper1).toEqual(hashFromHelper2); + }) + }) + describe('owner', () => { it('should return the correct owner commitment', () => { - const expCommitment = buildCommitment(DOMAIN, Z_OWNER, INIT_COUNTER, secretNonce); + const expCommitment = buildCommitment(Z_OWNER, secretNonce, INSTANCE_SALT, INIT_COUNTER, DOMAIN); expect(ownable.owner()).toEqual(expCommitment); }); }); @@ -200,7 +192,7 @@ describe('Z_OwnablePK', () => { newOwnerNonce = Z_OwnablePKPrivateState.generate().offchainNonce; newCounter = INIT_COUNTER + 1n; newIdHash = createIdHash(Z_NEW_OWNER, newOwnerNonce); - newOwnerCommitment = buildCommitment(DOMAIN, Z_NEW_OWNER, newCounter, newOwnerNonce); + newOwnerCommitment = buildCommitment(Z_NEW_OWNER, newOwnerNonce, INSTANCE_SALT, newCounter, DOMAIN); }); it('should transfer ownership', () => { @@ -260,7 +252,7 @@ describe('Z_OwnablePK', () => { // Confirm current commitment const repeatedId = createIdHash(Z_OWNER, secretNonce); const initCommitment = ownable.owner(); - const expInitCommitment = buildCommitmentFromId(repeatedId, INIT_COUNTER); + const expInitCommitment = buildCommitmentFromId(repeatedId, INSTANCE_SALT, INIT_COUNTER); expect(initCommitment).toEqual(expInitCommitment); // Transfer ownership to self with the same id -> `H(pk, nonce)` @@ -273,7 +265,7 @@ describe('Z_OwnablePK', () => { // Build commitment locally and validate new commitment == expected const bumpedCounter = INIT_COUNTER + 1n; - const expNewCommitment = buildCommitmentFromId(repeatedId, bumpedCounter); + const expNewCommitment = buildCommitmentFromId(repeatedId, INSTANCE_SALT, bumpedCounter); expect(newCommitment).toEqual(expNewCommitment); // Check same owner maintains permissions after transfer From 9a643145efacb0e96f4d14ea4c3cb628e6bbf18a Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 11 Aug 2025 02:59:26 -0300 Subject: [PATCH 079/202] fix fmt --- .../test/simulators/AccessControlSimulator.ts | 4 +- .../ownable/src/test/Z_OwnablePK.test.ts | 127 +++++++++++++----- .../src/test/simulators/OwnableSimulator.ts | 2 +- .../test/simulators/Z_OwnablePKSimulator.ts | 43 +++--- .../src/test/utils/createCircuitProxies.ts | 37 +++-- .../src/witnesses/Z_OwnablePKWitnesses.ts | 13 +- contracts/ownable/src/witnesses/interface.ts | 2 +- 7 files changed, 148 insertions(+), 80 deletions(-) diff --git a/contracts/accessControl/src/test/simulators/AccessControlSimulator.ts b/contracts/accessControl/src/test/simulators/AccessControlSimulator.ts index b1eac3b2..5566c3fa 100644 --- a/contracts/accessControl/src/test/simulators/AccessControlSimulator.ts +++ b/contracts/accessControl/src/test/simulators/AccessControlSimulator.ts @@ -2,18 +2,18 @@ import { type CircuitContext, type CoinPublicKey, type ContractState, - QueryContext, constructorContext, emptyZswapLocalState, + QueryContext, } from '@midnight-ntwrk/compact-runtime'; import { sampleContractAddress } from '@midnight-ntwrk/zswap'; import { type ContractAddress, type Either, type Ledger, + ledger, Contract as MockAccessControl, type ZswapCoinPublicKey, - ledger, } from '../../artifacts/MockAccessControl/contract/index.cjs'; // Combined imports import { type AccessControlPrivateState, diff --git a/contracts/ownable/src/test/Z_OwnablePK.test.ts b/contracts/ownable/src/test/Z_OwnablePK.test.ts index eed5e4b8..152ed3a0 100644 --- a/contracts/ownable/src/test/Z_OwnablePK.test.ts +++ b/contracts/ownable/src/test/Z_OwnablePK.test.ts @@ -1,14 +1,14 @@ import { + CompactTypeBytes, + CompactTypeVector, convert_bigint_to_Uint8Array, persistentHash, - CompactTypeVector, - CompactTypeBytes, } from '@midnight-ntwrk/compact-runtime'; import { beforeEach, describe, expect, it } from 'vitest'; +import type { ZswapCoinPublicKey } from '../artifacts/MockOwnable/contract/index.cjs'; +import { Z_OwnablePKPrivateState } from '../witnesses/Z_OwnablePKWitnesses.js'; import { Z_OwnablePKSimulator } from './simulators/Z_OwnablePKSimulator.js'; import * as utils from './utils/address.js'; -import { ZswapCoinPublicKey } from '../artifacts/MockOwnable/contract/index.cjs'; -import { Z_OwnablePKPrivateState } from '../witnesses/Z_OwnablePKWitnesses.js'; const OWNER = String(Buffer.from('OWNER', 'ascii').toString('hex')).padStart( 64, @@ -24,7 +24,7 @@ const Z_OWNER = utils.encodeToPK('OWNER'); const Z_NEW_OWNER = utils.encodeToPK('NEW_OWNER'); const INSTANCE_SALT = new Uint8Array(32).fill(8675309); -const DOMAIN = "Z_OwnablePK:shield:"; +const DOMAIN = 'Z_OwnablePK:shield:'; const INIT_COUNTER = 1n; let secretNonce: Uint8Array; @@ -32,14 +32,14 @@ let ownable: Z_OwnablePKSimulator; /** Helpers */ const createIdHash = ( - pk: ZswapCoinPublicKey, - nonce: Uint8Array + pk: ZswapCoinPublicKey, + nonce: Uint8Array, ): Uint8Array => { const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32)); const bPK = pk.bytes; return persistentHash(rt_type, [bPK, nonce]); -} +}; const buildCommitmentFromId = ( id: Uint8Array, @@ -50,16 +50,21 @@ const buildCommitmentFromId = ( const bCounter = convert_bigint_to_Uint8Array(32, counter); const bDomain = new TextEncoder().encode(DOMAIN); - const commitment = persistentHash(rt_type, [id, instanceSalt, bCounter, bDomain]); + const commitment = persistentHash(rt_type, [ + id, + instanceSalt, + bCounter, + bDomain, + ]); return commitment; -} +}; const buildCommitment = ( pk: ZswapCoinPublicKey, nonce: Uint8Array, instanceSalt: Uint8Array, counter: bigint, - domain: string + domain: string, ): Uint8Array => { const id = createIdHash(pk, nonce); @@ -67,9 +72,14 @@ const buildCommitment = ( const bCounter = convert_bigint_to_Uint8Array(32, counter); const bDomain = new TextEncoder().encode(domain); - const commitment = persistentHash(rt_type, [id, instanceSalt, bCounter, bDomain]); + const commitment = persistentHash(rt_type, [ + id, + instanceSalt, + bCounter, + bDomain, + ]); return commitment; -} +}; describe('Z_OwnablePK', () => { describe('before initialize', () => { @@ -86,7 +96,11 @@ describe('Z_OwnablePK', () => { const nonZeroId = createIdHash(notZeroPK, notZeroNonce); ownable = new Z_OwnablePKSimulator(nonZeroId, INSTANCE_SALT); - const nonZeroCommitment = buildCommitmentFromId(nonZeroId, INSTANCE_SALT, INIT_COUNTER); + const nonZeroCommitment = buildCommitmentFromId( + nonZeroId, + INSTANCE_SALT, + INIT_COUNTER, + ); expect(ownable.owner()).toEqual(nonZeroCommitment); }); }); @@ -100,7 +114,9 @@ describe('Z_OwnablePK', () => { // Prepare owner ID with gen nonce const ownerId = createIdHash(Z_OWNER, secretNonce); // Deploy contract with derived owner commitment and PS - ownable = new Z_OwnablePKSimulator(ownerId, INSTANCE_SALT, {privateState: PS}); + ownable = new Z_OwnablePKSimulator(ownerId, INSTANCE_SALT, { + privateState: PS, + }); }); /** @@ -108,25 +124,40 @@ describe('Z_OwnablePK', () => { */ describe('hashCommitment', () => { it('should match local and contract commitment algorithms', () => { - const address = ownable.contractAddress; const id = createIdHash(Z_OWNER, secretNonce); const counter = INIT_COUNTER; // Check buildCommitmentFromId const hashFromContract = ownable.hashCommitment(id, counter); - const hashFromHelper1 = buildCommitmentFromId(id, INSTANCE_SALT, counter); + const hashFromHelper1 = buildCommitmentFromId( + id, + INSTANCE_SALT, + counter, + ); expect(hashFromContract).toEqual(hashFromHelper1); // Check buildCommitment - const hashFromHelper2 = buildCommitment(Z_OWNER, secretNonce, INSTANCE_SALT, counter, DOMAIN); + const hashFromHelper2 = buildCommitment( + Z_OWNER, + secretNonce, + INSTANCE_SALT, + counter, + DOMAIN, + ); expect(hashFromContract).toEqual(hashFromHelper1); expect(hashFromHelper1).toEqual(hashFromHelper2); - }) - }) + }); + }); describe('owner', () => { it('should return the correct owner commitment', () => { - const expCommitment = buildCommitment(Z_OWNER, secretNonce, INSTANCE_SALT, INIT_COUNTER, DOMAIN); + const expCommitment = buildCommitment( + Z_OWNER, + secretNonce, + INSTANCE_SALT, + INIT_COUNTER, + DOMAIN, + ); expect(ownable.owner()).toEqual(expCommitment); }); }); @@ -134,7 +165,9 @@ describe('Z_OwnablePK', () => { describe('assertOnlyOwner', () => { it('should allow authorized caller with correct nonce to call', () => { // Check nonce is correct - expect(ownable.privateState.getCurrentSecretNonce()).toEqual(secretNonce); + expect(ownable.privateState.getCurrentSecretNonce()).toEqual( + secretNonce, + ); ownable.setCaller(OWNER); expect(ownable.assertOnlyOwner()).to.not.throw; @@ -142,11 +175,13 @@ describe('Z_OwnablePK', () => { it('should fail when the authorized caller has the wrong nonce', () => { // Inject bad nonce - const badNonce = Buffer.alloc(32, "badNonce"); + const badNonce = Buffer.alloc(32, 'badNonce'); ownable.privateState.injectSecretNonce(badNonce); // Check nonce does not match - expect(ownable.privateState.getCurrentSecretNonce()).not.toEqual(secretNonce); + expect(ownable.privateState.getCurrentSecretNonce()).not.toEqual( + secretNonce, + ); // Set caller and call circuit ownable.setCaller(OWNER); @@ -157,26 +192,30 @@ describe('Z_OwnablePK', () => { it('should fail when unauthorized caller has the correct nonce', () => { // Check nonce is correct - expect(ownable.privateState.getCurrentSecretNonce()).toEqual(secretNonce); + expect(ownable.privateState.getCurrentSecretNonce()).toEqual( + secretNonce, + ); ownable.setCaller(UNAUTHORIZED); expect(() => { - ownable.assertOnlyOwner() + ownable.assertOnlyOwner(); }).toThrow('Forbidden'); }); it('should fail when unauthorized caller has the wrong nonce', () => { // Inject bad nonce - const badNonce = Buffer.alloc(32, "badNonce"); + const badNonce = Buffer.alloc(32, 'badNonce'); ownable.privateState.injectSecretNonce(badNonce); // Check nonce does not match - expect(ownable.privateState.getCurrentSecretNonce()).not.toEqual(secretNonce); + expect(ownable.privateState.getCurrentSecretNonce()).not.toEqual( + secretNonce, + ); // Set unauthorized caller and call circuit ownable.setCaller(UNAUTHORIZED); expect(() => { - ownable.assertOnlyOwner() + ownable.assertOnlyOwner(); }).toThrow('Forbidden'); }); }); @@ -192,7 +231,13 @@ describe('Z_OwnablePK', () => { newOwnerNonce = Z_OwnablePKPrivateState.generate().offchainNonce; newCounter = INIT_COUNTER + 1n; newIdHash = createIdHash(Z_NEW_OWNER, newOwnerNonce); - newOwnerCommitment = buildCommitment(Z_NEW_OWNER, newOwnerNonce, INSTANCE_SALT, newCounter, DOMAIN); + newOwnerCommitment = buildCommitment( + Z_NEW_OWNER, + newOwnerNonce, + INSTANCE_SALT, + newCounter, + DOMAIN, + ); }); it('should transfer ownership', () => { @@ -203,18 +248,18 @@ describe('Z_OwnablePK', () => { // Old owner ownable.setCaller(OWNER); expect(() => { - ownable.assertOnlyOwner() + ownable.assertOnlyOwner(); }).toThrow('Forbidden'); // Unauthorized ownable.setCaller(UNAUTHORIZED); expect(() => { - ownable.assertOnlyOwner() + ownable.assertOnlyOwner(); }).toThrow('Forbidden'); // New owner ownable.setCaller(NEW_OWNER); - ownable.privateState.injectSecretNonce(Buffer.from(newOwnerNonce)) + ownable.privateState.injectSecretNonce(Buffer.from(newOwnerNonce)); expect(ownable.assertOnlyOwner()).not.to.throw; }); @@ -237,14 +282,14 @@ describe('Z_OwnablePK', () => { * @description More thoroughly tested in `_transferOwnership` * */ it('should bump instance after transfer', () => { - let beforeInstance = ownable.getPublicState().Z_OwnablePK__counter; + const beforeInstance = ownable.getPublicState().Z_OwnablePK__counter; // Transfer ownable.setCaller(OWNER); ownable.transferOwnership(newOwnerCommitment); // Check counter - let afterInstance = ownable.getPublicState().Z_OwnablePK__counter; + const afterInstance = ownable.getPublicState().Z_OwnablePK__counter; expect(afterInstance).toEqual(beforeInstance + 1n); }); @@ -252,7 +297,11 @@ describe('Z_OwnablePK', () => { // Confirm current commitment const repeatedId = createIdHash(Z_OWNER, secretNonce); const initCommitment = ownable.owner(); - const expInitCommitment = buildCommitmentFromId(repeatedId, INSTANCE_SALT, INIT_COUNTER); + const expInitCommitment = buildCommitmentFromId( + repeatedId, + INSTANCE_SALT, + INIT_COUNTER, + ); expect(initCommitment).toEqual(expInitCommitment); // Transfer ownership to self with the same id -> `H(pk, nonce)` @@ -265,7 +314,11 @@ describe('Z_OwnablePK', () => { // Build commitment locally and validate new commitment == expected const bumpedCounter = INIT_COUNTER + 1n; - const expNewCommitment = buildCommitmentFromId(repeatedId, INSTANCE_SALT, bumpedCounter); + const expNewCommitment = buildCommitmentFromId( + repeatedId, + INSTANCE_SALT, + bumpedCounter, + ); expect(newCommitment).toEqual(expNewCommitment); // Check same owner maintains permissions after transfer diff --git a/contracts/ownable/src/test/simulators/OwnableSimulator.ts b/contracts/ownable/src/test/simulators/OwnableSimulator.ts index 82778ad6..1fbd380e 100644 --- a/contracts/ownable/src/test/simulators/OwnableSimulator.ts +++ b/contracts/ownable/src/test/simulators/OwnableSimulator.ts @@ -19,7 +19,7 @@ import { type OwnablePrivateState, OwnableWitnesses, } from '../../witnesses/OwnableWitnesses.js'; -import { IContractSimulator } from '../types/test.js'; +import type { IContractSimulator } from '../types/test.js'; /** * @description A simulator implementation of a Ownable contract for testing purposes. diff --git a/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts index fac56eb6..c8520211 100644 --- a/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts @@ -1,25 +1,31 @@ import { type CircuitContext, type CoinPublicKey, - type ContractState, emptyZswapLocalState, } from '@midnight-ntwrk/compact-runtime'; import { sampleContractAddress } from '@midnight-ntwrk/zswap'; -import type { ZswapCoinPublicKey } from '../../artifacts/MockZ_OwnablePK/contract/index.cjs'; import { type Ledger, ledger, Contract as MockOwnable, } from '../../artifacts/MockZ_OwnablePK/contract/index.cjs'; // Combined imports import { -Z_OwnablePKPrivateState, + Z_OwnablePKPrivateState, Z_OwnablePKWitnesses, } from '../../witnesses/Z_OwnablePKWitnesses.js'; +import type { + ContextlessCircuits, + ExtractImpureCircuits, + ExtractPureCircuits, + SimulatorOptions, +} from '../types/test.js'; import { AbstractContractSimulator } from '../utils/AbstractContractSimulator.js'; import { SimulatorStateManager } from '../utils/SimualatorStateManager.js'; -import { ContextlessCircuits, ExtractImpureCircuits, ExtractPureCircuits, SimulatorOptions } from '../types/test.js'; -type OwnableSimOptions = SimulatorOptions; +type OwnableSimOptions = SimulatorOptions< + Z_OwnablePKPrivateState, + typeof Z_OwnablePKWitnesses +>; /** * @description A simulator implementation of a contract for testing purposes. @@ -45,7 +51,11 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< Z_OwnablePKPrivateState >; - constructor(initOwner: Uint8Array, instanceSalt: Uint8Array, options: OwnableSimOptions = {}, ) { + constructor( + initOwner: Uint8Array, + instanceSalt: Uint8Array, + options: OwnableSimOptions = {}, + ) { super(); // Setup initial state @@ -57,9 +67,7 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< } = options; const constructorArgs = [initOwner, instanceSalt]; - this.contract = new MockOwnable( - witnesses, - ); + this.contract = new MockOwnable(witnesses); this.stateManager = new SimulatorStateManager( this.contract, @@ -187,9 +195,7 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< /** * @description */ - public transferOwnership( - newOwnerId: Uint8Array, - ) { + public transferOwnership(newOwnerId: Uint8Array) { this.circuits.impure.transferOwnership(newOwnerId); } @@ -213,10 +219,7 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< /** * @description */ - public hashCommitment( - id: Uint8Array, - counter: bigint, - ): Uint8Array { + public hashCommitment(id: Uint8Array, counter: bigint): Uint8Array { return this.circuits.impure.hashCommitment(id, counter); } @@ -231,7 +234,9 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< /** * @description Stubs a new nonce into the private state. */ - injectSecretNonce: (newNonce: Buffer): Z_OwnablePKPrivateState => { + injectSecretNonce: ( + newNonce: Buffer, + ): Z_OwnablePKPrivateState => { const currentState = this.stateManager.getContext().currentPrivateState; const updatedState = { ...currentState, offchainNonce: newNonce }; this.stateManager.updatePrivateState(updatedState); @@ -240,6 +245,6 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< getCurrentSecretNonce: (): Uint8Array => { return this.stateManager.getContext().currentPrivateState.offchainNonce; - } - } + }, + }; } diff --git a/contracts/ownable/src/test/utils/createCircuitProxies.ts b/contracts/ownable/src/test/utils/createCircuitProxies.ts index b4a14ab0..ad4211b6 100644 --- a/contracts/ownable/src/test/utils/createCircuitProxies.ts +++ b/contracts/ownable/src/test/utils/createCircuitProxies.ts @@ -2,13 +2,16 @@ import type { CircuitContext } from '@midnight-ntwrk/compact-runtime'; import type { ContextlessCircuits, ExtractImpureCircuits, - ExtractPureCircuits + ExtractPureCircuits, } from '../types/test.js'; /** * Creates lazily-initialized circuit proxies for pure and impure contract functions. */ -export function createCircuitProxies( +export function createCircuitProxies< + P, + ContractType extends { circuits: any; impureCircuits: any }, +>( contract: ContractType, getContext: () => CircuitContext

, getCallerContext: () => CircuitContext

, @@ -23,22 +26,28 @@ export function createCircuitProxies) => void, ) => ContextlessCircuits, ) { - let pureProxy: ContextlessCircuits, P> | undefined; - let impureProxy: ContextlessCircuits, P> | undefined; + let pureProxy: + | ContextlessCircuits, P> + | undefined; + let impureProxy: + | ContextlessCircuits, P> + | undefined; return { get circuits() { + if (!pureProxy) { + pureProxy = createPureProxy(contract.circuits, getContext); + } + if (!impureProxy) { + impureProxy = createImpureProxy( + contract.impureCircuits, + getCallerContext, + updateContext, + ); + } return { - pure: - pureProxy ?? - (pureProxy = createPureProxy(contract.circuits, getContext)), - impure: - impureProxy ?? - (impureProxy = createImpureProxy( - contract.impureCircuits, - getCallerContext, - updateContext, - )), + pure: pureProxy, + impure: impureProxy, }; }, resetProxies() { diff --git a/contracts/ownable/src/witnesses/Z_OwnablePKWitnesses.ts b/contracts/ownable/src/witnesses/Z_OwnablePKWitnesses.ts index 5203cf23..4bfcf082 100644 --- a/contracts/ownable/src/witnesses/Z_OwnablePKWitnesses.ts +++ b/contracts/ownable/src/witnesses/Z_OwnablePKWitnesses.ts @@ -1,7 +1,7 @@ import { getRandomValues } from 'node:crypto'; -import type { Ledger } from '../artifacts/MockZ_OwnablePK/contract/index.cjs'; import type { WitnessContext } from '@midnight-ntwrk/compact-runtime'; -import { IZ_OwnablePKWitnesses } from './interface.js' +import type { Ledger } from '../artifacts/MockZ_OwnablePK/contract/index.cjs'; +import type { IZ_OwnablePKWitnesses } from './interface.js'; /** * @description Represents the private state of an ownable contract, storing a secret nonce. @@ -20,18 +20,19 @@ export const Z_OwnablePKPrivateState = { * @returns A fresh Z_OwnablePKPrivateState instance. */ generate: (): Z_OwnablePKPrivateState => { - return { offchainNonce: getRandomValues(Buffer.alloc(32))}; - } + return { offchainNonce: getRandomValues(Buffer.alloc(32)) }; + }, }; /** * @description Factory function creating witness implementations for Ownable operations. * @returns An object implementing the Witnesses interface for Z_OwnablePKPrivateState. */ -export const Z_OwnablePKWitnesses = (): IZ_OwnablePKWitnesses => ({ +export const Z_OwnablePKWitnesses = + (): IZ_OwnablePKWitnesses => ({ offchainNonce( context: WitnessContext, ): [Z_OwnablePKPrivateState, Uint8Array] { return [context.privateState, context.privateState.offchainNonce]; }, -}); \ No newline at end of file + }); diff --git a/contracts/ownable/src/witnesses/interface.ts b/contracts/ownable/src/witnesses/interface.ts index 3faedeb8..94431006 100644 --- a/contracts/ownable/src/witnesses/interface.ts +++ b/contracts/ownable/src/witnesses/interface.ts @@ -24,5 +24,5 @@ export interface IZ_OwnablePKWitnesses

{ * @param context - The witness context containing the private state. * @returns A tuple of the private state and the secret nonce as a Uint8Array. */ - offchainNonce(context: WitnessContext): [P, Uint8Array]; + offchainNonce(context: WitnessContext): [P, Uint8Array]; } From 0bafd7b6284c22db6055498614879564741da1db Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 11 Aug 2025 03:14:12 -0300 Subject: [PATCH 080/202] remove underscore in module name --- ...Z_OwnablePK.compact => ZOwnablePK.compact} | 8 +-- ...Z_OwnablePK.test.ts => ZOwnablePK.test.ts} | 24 ++++----- ...nablePK.compact => MockZOwnablePK.compact} | 18 +++---- ...ePKSimulator.ts => ZOwnablePKSimulator.ts} | 52 +++++++++---------- ...ePKWitnesses.ts => ZOwnablePKWitnesses.ts} | 22 ++++---- contracts/ownable/src/witnesses/interface.ts | 17 +----- 6 files changed, 64 insertions(+), 77 deletions(-) rename contracts/ownable/src/{Z_OwnablePK.compact => ZOwnablePK.compact} (94%) rename contracts/ownable/src/test/{Z_OwnablePK.test.ts => ZOwnablePK.test.ts} (92%) rename contracts/ownable/src/test/mocks/{MockZ_OwnablePK.compact => MockZOwnablePK.compact} (55%) rename contracts/ownable/src/test/simulators/{Z_OwnablePKSimulator.ts => ZOwnablePKSimulator.ts} (83%) rename contracts/ownable/src/witnesses/{Z_OwnablePKWitnesses.ts => ZOwnablePKWitnesses.ts} (60%) diff --git a/contracts/ownable/src/Z_OwnablePK.compact b/contracts/ownable/src/ZOwnablePK.compact similarity index 94% rename from contracts/ownable/src/Z_OwnablePK.compact rename to contracts/ownable/src/ZOwnablePK.compact index aa03f457..935bcb74 100644 --- a/contracts/ownable/src/Z_OwnablePK.compact +++ b/contracts/ownable/src/ZOwnablePK.compact @@ -3,10 +3,10 @@ pragma language_version >= 0.16.0; /** - * @module Z_OwnablePK + * @module ZOwnablePK * @description A shielded Ownable library. */ -module Z_OwnablePK { +module ZOwnablePK { import CompactStandardLibrary; export ledger _ownerCommitment: Bytes<32>; @@ -59,7 +59,7 @@ module Z_OwnablePK { persistentHash>>([default>, nonce]), _instanceSalt, _counter as Field as Bytes<32>, - pad(32, "Z_OwnablePK:renounced:"), + pad(32, "ZOwnablePK:renounced:"), ] ); @@ -89,7 +89,7 @@ module Z_OwnablePK { id, _instanceSalt, counter as Field as Bytes<32>, - pad(32, "Z_OwnablePK:shield:") + pad(32, "ZOwnablePK:shield:") ] ); } diff --git a/contracts/ownable/src/test/Z_OwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts similarity index 92% rename from contracts/ownable/src/test/Z_OwnablePK.test.ts rename to contracts/ownable/src/test/ZOwnablePK.test.ts index 152ed3a0..1995b651 100644 --- a/contracts/ownable/src/test/Z_OwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -6,8 +6,8 @@ import { } from '@midnight-ntwrk/compact-runtime'; import { beforeEach, describe, expect, it } from 'vitest'; import type { ZswapCoinPublicKey } from '../artifacts/MockOwnable/contract/index.cjs'; -import { Z_OwnablePKPrivateState } from '../witnesses/Z_OwnablePKWitnesses.js'; -import { Z_OwnablePKSimulator } from './simulators/Z_OwnablePKSimulator.js'; +import { ZOwnablePKPrivateState } from '../witnesses/ZOwnablePKWitnesses.js'; +import { ZOwnablePKSimulator } from './simulators/ZOwnablePKSimulator.js'; import * as utils from './utils/address.js'; const OWNER = String(Buffer.from('OWNER', 'ascii').toString('hex')).padStart( @@ -24,11 +24,11 @@ const Z_OWNER = utils.encodeToPK('OWNER'); const Z_NEW_OWNER = utils.encodeToPK('NEW_OWNER'); const INSTANCE_SALT = new Uint8Array(32).fill(8675309); -const DOMAIN = 'Z_OwnablePK:shield:'; +const DOMAIN = 'ZOwnablePK:shield:'; const INIT_COUNTER = 1n; let secretNonce: Uint8Array; -let ownable: Z_OwnablePKSimulator; +let ownable: ZOwnablePKSimulator; /** Helpers */ const createIdHash = ( @@ -81,12 +81,12 @@ const buildCommitment = ( return commitment; }; -describe('Z_OwnablePK', () => { +describe('ZOwnablePK', () => { describe('before initialize', () => { it('should fail when setting owner commitment as 0', () => { expect(() => { const badCommitment = new Uint8Array(32).fill(0); - new Z_OwnablePKSimulator(badCommitment, INSTANCE_SALT); + new ZOwnablePKSimulator(badCommitment, INSTANCE_SALT); }).toThrow('Invalid parameters'); }); @@ -94,7 +94,7 @@ describe('Z_OwnablePK', () => { const notZeroPK = utils.encodeToPK('NOT_ZERO'); const notZeroNonce = new Uint8Array(32).fill(1); const nonZeroId = createIdHash(notZeroPK, notZeroNonce); - ownable = new Z_OwnablePKSimulator(nonZeroId, INSTANCE_SALT); + ownable = new ZOwnablePKSimulator(nonZeroId, INSTANCE_SALT); const nonZeroCommitment = buildCommitmentFromId( nonZeroId, @@ -108,13 +108,13 @@ describe('Z_OwnablePK', () => { describe('after initialization', () => { beforeEach(() => { // Create private state object and generate nonce - const PS = Z_OwnablePKPrivateState.generate(); + const PS = ZOwnablePKPrivateState.generate(); // Bind nonce for convenience secretNonce = PS.offchainNonce; // Prepare owner ID with gen nonce const ownerId = createIdHash(Z_OWNER, secretNonce); // Deploy contract with derived owner commitment and PS - ownable = new Z_OwnablePKSimulator(ownerId, INSTANCE_SALT, { + ownable = new ZOwnablePKSimulator(ownerId, INSTANCE_SALT, { privateState: PS, }); }); @@ -228,7 +228,7 @@ describe('Z_OwnablePK', () => { beforeEach(() => { // Prepare new owner commitment - newOwnerNonce = Z_OwnablePKPrivateState.generate().offchainNonce; + newOwnerNonce = ZOwnablePKPrivateState.generate().offchainNonce; newCounter = INIT_COUNTER + 1n; newIdHash = createIdHash(Z_NEW_OWNER, newOwnerNonce); newOwnerCommitment = buildCommitment( @@ -282,14 +282,14 @@ describe('Z_OwnablePK', () => { * @description More thoroughly tested in `_transferOwnership` * */ it('should bump instance after transfer', () => { - const beforeInstance = ownable.getPublicState().Z_OwnablePK__counter; + const beforeInstance = ownable.getPublicState().ZOwnablePK__counter; // Transfer ownable.setCaller(OWNER); ownable.transferOwnership(newOwnerCommitment); // Check counter - const afterInstance = ownable.getPublicState().Z_OwnablePK__counter; + const afterInstance = ownable.getPublicState().ZOwnablePK__counter; expect(afterInstance).toEqual(beforeInstance + 1n); }); diff --git a/contracts/ownable/src/test/mocks/MockZ_OwnablePK.compact b/contracts/ownable/src/test/mocks/MockZOwnablePK.compact similarity index 55% rename from contracts/ownable/src/test/mocks/MockZ_OwnablePK.compact rename to contracts/ownable/src/test/mocks/MockZOwnablePK.compact index 4d425dbf..f4cf6505 100644 --- a/contracts/ownable/src/test/mocks/MockZ_OwnablePK.compact +++ b/contracts/ownable/src/test/mocks/MockZOwnablePK.compact @@ -3,35 +3,35 @@ pragma language_version >= 0.15.0; import CompactStandardLibrary; -import "../../Z_OwnablePK" prefix Z_OwnablePK_; +import "../../ZOwnablePK" prefix ZOwnablePK_; export { ZswapCoinPublicKey, ContractAddress, Either, Maybe }; -export { Z_OwnablePK__ownerCommitment, Z_OwnablePK__counter }; +export { ZOwnablePK__ownerCommitment, ZOwnablePK__counter }; constructor(initOwnerCommitment: Bytes<32>, instanceSalt: Bytes<32>) { - Z_OwnablePK_initialize(initOwnerCommitment, instanceSalt); + ZOwnablePK_initialize(initOwnerCommitment, instanceSalt); } export circuit owner(): Bytes<32> { - return Z_OwnablePK_owner(); + return ZOwnablePK_owner(); } export circuit transferOwnership(newOwnerCommitment: Bytes<32>): [] { - return Z_OwnablePK_transferOwnership(disclose(newOwnerCommitment)); + return ZOwnablePK_transferOwnership(disclose(newOwnerCommitment)); } export circuit renounceOwnership(): [] { - return Z_OwnablePK_renounceOwnership(); + return ZOwnablePK_renounceOwnership(); } export circuit assertOnlyOwner(): [] { - return Z_OwnablePK_assertOnlyOwner(); + return ZOwnablePK_assertOnlyOwner(); } export circuit hashCommitment(id: Bytes<32>, counter: Uint<64>): Bytes<32> { - return Z_OwnablePK_hashCommitment(id, counter); + return ZOwnablePK_hashCommitment(id, counter); } export circuit _transferOwnership(newOwnerCommitment: Bytes<32>): [] { - return Z_OwnablePK__transferOwnership(newOwnerCommitment); + return ZOwnablePK__transferOwnership(newOwnerCommitment); } diff --git a/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts similarity index 83% rename from contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts rename to contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts index c8520211..abc9e1b7 100644 --- a/contracts/ownable/src/test/simulators/Z_OwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts @@ -8,11 +8,11 @@ import { type Ledger, ledger, Contract as MockOwnable, -} from '../../artifacts/MockZ_OwnablePK/contract/index.cjs'; // Combined imports +} from '../../artifacts/MockZOwnablePK/contract/index.cjs'; // Combined imports import { - Z_OwnablePKPrivateState, - Z_OwnablePKWitnesses, -} from '../../witnesses/Z_OwnablePKWitnesses.js'; + ZOwnablePKPrivateState, + ZOwnablePKWitnesses, +} from '../../witnesses/ZOwnablePKWitnesses.js'; import type { ContextlessCircuits, ExtractImpureCircuits, @@ -23,32 +23,32 @@ import { AbstractContractSimulator } from '../utils/AbstractContractSimulator.js import { SimulatorStateManager } from '../utils/SimualatorStateManager.js'; type OwnableSimOptions = SimulatorOptions< - Z_OwnablePKPrivateState, - typeof Z_OwnablePKWitnesses + ZOwnablePKPrivateState, + typeof ZOwnablePKWitnesses >; /** * @description A simulator implementation of a contract for testing purposes. - * @template P - The private state type, fixed to Z_OwnablePKPrivateState. + * @template P - The private state type, fixed to ZOwnablePKPrivateState. * @template L - The ledger type, fixed to Contract.Ledger. */ -export class Z_OwnablePKSimulator extends AbstractContractSimulator< - Z_OwnablePKPrivateState, +export class ZOwnablePKSimulator extends AbstractContractSimulator< + ZOwnablePKPrivateState, Ledger > { - readonly contract: MockOwnable; + readonly contract: MockOwnable; readonly contractAddress: string; - private stateManager: SimulatorStateManager; + private stateManager: SimulatorStateManager; private callerOverride: CoinPublicKey | null = null; private _pureCircuitProxy?: ContextlessCircuits< - ExtractPureCircuits>, - Z_OwnablePKPrivateState + ExtractPureCircuits>, + ZOwnablePKPrivateState >; private _impureCircuitProxy?: ContextlessCircuits< - ExtractImpureCircuits>, - Z_OwnablePKPrivateState + ExtractImpureCircuits>, + ZOwnablePKPrivateState >; constructor( @@ -60,14 +60,14 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< // Setup initial state const { - privateState = Z_OwnablePKPrivateState.generate(), - witnesses = Z_OwnablePKWitnesses(), + privateState = ZOwnablePKPrivateState.generate(), + witnesses = ZOwnablePKWitnesses(), coinPK = '0'.repeat(64), address = sampleContractAddress(), } = options; const constructorArgs = [initOwner, instanceSalt]; - this.contract = new MockOwnable(witnesses); + this.contract = new MockOwnable(witnesses); this.stateManager = new SimulatorStateManager( this.contract, @@ -97,7 +97,7 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< * scoped to the overridden caller. Otherwise, the existing context is reused as-is. * @returns A circuit context adjusted for the current simulated caller. */ - protected getCallerContext(): CircuitContext { + protected getCallerContext(): CircuitContext { return { ...this.circuitContext, currentZswapLocalState: this.callerOverride @@ -116,12 +116,12 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< * @returns A proxy object exposing pure circuit functions without requiring explicit context. */ protected get pureCircuit(): ContextlessCircuits< - ExtractPureCircuits>, - Z_OwnablePKPrivateState + ExtractPureCircuits>, + ZOwnablePKPrivateState > { if (!this._pureCircuitProxy) { this._pureCircuitProxy = this.createPureCircuitProxy< - MockOwnable['circuits'] + MockOwnable['circuits'] >(this.contract.circuits, () => this.circuitContext); } return this._pureCircuitProxy; @@ -137,12 +137,12 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< * @returns A proxy object exposing impure circuit functions without requiring explicit context management. */ protected get impureCircuit(): ContextlessCircuits< - ExtractImpureCircuits>, - Z_OwnablePKPrivateState + ExtractImpureCircuits>, + ZOwnablePKPrivateState > { if (!this._impureCircuitProxy) { this._impureCircuitProxy = this.createImpureCircuitProxy< - MockOwnable['impureCircuits'] + MockOwnable['impureCircuits'] >( this.contract.impureCircuits, () => this.getCallerContext(), @@ -236,7 +236,7 @@ export class Z_OwnablePKSimulator extends AbstractContractSimulator< */ injectSecretNonce: ( newNonce: Buffer, - ): Z_OwnablePKPrivateState => { + ): ZOwnablePKPrivateState => { const currentState = this.stateManager.getContext().currentPrivateState; const updatedState = { ...currentState, offchainNonce: newNonce }; this.stateManager.updatePrivateState(updatedState); diff --git a/contracts/ownable/src/witnesses/Z_OwnablePKWitnesses.ts b/contracts/ownable/src/witnesses/ZOwnablePKWitnesses.ts similarity index 60% rename from contracts/ownable/src/witnesses/Z_OwnablePKWitnesses.ts rename to contracts/ownable/src/witnesses/ZOwnablePKWitnesses.ts index 4bfcf082..f4682cc6 100644 --- a/contracts/ownable/src/witnesses/Z_OwnablePKWitnesses.ts +++ b/contracts/ownable/src/witnesses/ZOwnablePKWitnesses.ts @@ -1,12 +1,12 @@ import { getRandomValues } from 'node:crypto'; import type { WitnessContext } from '@midnight-ntwrk/compact-runtime'; -import type { Ledger } from '../artifacts/MockZ_OwnablePK/contract/index.cjs'; -import type { IZ_OwnablePKWitnesses } from './interface.js'; +import type { Ledger } from '../artifacts/MockZOwnablePK/contract/index.cjs'; +import type { IZOwnablePKWitnesses } from './interface.js'; /** * @description Represents the private state of an ownable contract, storing a secret nonce. */ -export type Z_OwnablePKPrivateState = { +export type ZOwnablePKPrivateState = { /** @description A 32-byte secret nonce used as a privacy additive. */ offchainNonce: Buffer; }; @@ -14,25 +14,25 @@ export type Z_OwnablePKPrivateState = { /** * @description Utility object for managing the private state of an Ownable contract. */ -export const Z_OwnablePKPrivateState = { +export const ZOwnablePKPrivateState = { /** * @description Generates a new private state with a random secret nonce. - * @returns A fresh Z_OwnablePKPrivateState instance. + * @returns A fresh ZOwnablePKPrivateState instance. */ - generate: (): Z_OwnablePKPrivateState => { + generate: (): ZOwnablePKPrivateState => { return { offchainNonce: getRandomValues(Buffer.alloc(32)) }; }, }; /** * @description Factory function creating witness implementations for Ownable operations. - * @returns An object implementing the Witnesses interface for Z_OwnablePKPrivateState. + * @returns An object implementing the Witnesses interface for ZOwnablePKPrivateState. */ -export const Z_OwnablePKWitnesses = - (): IZ_OwnablePKWitnesses => ({ +export const ZOwnablePKWitnesses = + (): IZOwnablePKWitnesses => ({ offchainNonce( - context: WitnessContext, - ): [Z_OwnablePKPrivateState, Uint8Array] { + context: WitnessContext, + ): [ZOwnablePKPrivateState, Uint8Array] { return [context.privateState, context.privateState.offchainNonce]; }, }); diff --git a/contracts/ownable/src/witnesses/interface.ts b/contracts/ownable/src/witnesses/interface.ts index 94431006..7799c8ef 100644 --- a/contracts/ownable/src/witnesses/interface.ts +++ b/contracts/ownable/src/witnesses/interface.ts @@ -1,24 +1,11 @@ import type { WitnessContext } from '@midnight-ntwrk/compact-runtime'; -import type { Ledger } from '../artifacts/MockZ_OwnablePK/contract/index.cjs'; // Combined imports - -/** - * @description Interface defining the witness methods for ownable operations. - * @template P - The private state type. - */ -export interface IOwnableWitnesses

{ - /** - * Retrieves the secret key from the private state. - * @param context - The witness context containing the private state. - * @returns A tuple of the private state and the secret key as a Uint8Array. - */ - localSecretKey(context: WitnessContext): [P, Uint8Array]; -} +import type { Ledger } from '../artifacts/MockZOwnablePK/contract/index.cjs'; // Combined imports /** * @description Interface defining the witness methods for Ownable operations. * @template P - The private state type. */ -export interface IZ_OwnablePKWitnesses

{ +export interface IZOwnablePKWitnesses

{ /** * Retrieves the secret nonce from the private state. * @param context - The witness context containing the private state. From dee9254e0e22c96b4905542a037436511f77540d Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 11 Aug 2025 03:14:45 -0300 Subject: [PATCH 081/202] remove unused file --- contracts/ownable/src/test/types/string.ts | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 contracts/ownable/src/test/types/string.ts diff --git a/contracts/ownable/src/test/types/string.ts b/contracts/ownable/src/test/types/string.ts deleted file mode 100644 index 430a139e..00000000 --- a/contracts/ownable/src/test/types/string.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type MaybeString = { - is_some: boolean; - value: string; -}; From a47ea19499bdc7a50c251c222d37e3d5dbf4f2b0 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Aug 2025 00:36:08 -0300 Subject: [PATCH 082/202] fix var names --- contracts/ownable/src/test/ZOwnablePK.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/ownable/src/test/ZOwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts index 1995b651..38554643 100644 --- a/contracts/ownable/src/test/ZOwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -85,8 +85,8 @@ describe('ZOwnablePK', () => { describe('before initialize', () => { it('should fail when setting owner commitment as 0', () => { expect(() => { - const badCommitment = new Uint8Array(32).fill(0); - new ZOwnablePKSimulator(badCommitment, INSTANCE_SALT); + const badId = new Uint8Array(32).fill(0); + new ZOwnablePKSimulator(badId, INSTANCE_SALT); }).toThrow('Invalid parameters'); }); @@ -263,11 +263,11 @@ describe('ZOwnablePK', () => { expect(ownable.assertOnlyOwner()).not.to.throw; }); - it('should fail when transferring to zero', () => { + it('should fail when transferring to id zero', () => { ownable.setCaller(OWNER); - const badCommitment = new Uint8Array(32).fill(0); + const badId = new Uint8Array(32).fill(0); expect(() => { - ownable.transferOwnership(badCommitment); + ownable.transferOwnership(badId); }).toThrow('Invalid parameters'); }); From 9233b473d9ae5b4b687fdc33d166b16ed1ca0c92 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Aug 2025 00:36:54 -0300 Subject: [PATCH 083/202] add witness injection to sim --- .../test/simulators/ZOwnablePKSimulator.ts | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts index abc9e1b7..5f3cd893 100644 --- a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts @@ -8,7 +8,7 @@ import { type Ledger, ledger, Contract as MockOwnable, -} from '../../artifacts/MockZOwnablePK/contract/index.cjs'; // Combined imports +} from '../../artifacts/MockZOwnablePK/contract/index.cjs'; import { ZOwnablePKPrivateState, ZOwnablePKWitnesses, @@ -36,10 +36,11 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< ZOwnablePKPrivateState, Ledger > { - readonly contract: MockOwnable; + contract: MockOwnable; readonly contractAddress: string; private stateManager: SimulatorStateManager; private callerOverride: CoinPublicKey | null = null; + private _witnesses: ReturnType; private _pureCircuitProxy?: ContextlessCircuits< ExtractPureCircuits>, @@ -77,6 +78,8 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< ...constructorArgs, ); this.contractAddress = this.circuitContext.transactionContext.address; + this._witnesses = witnesses; + this.contract = new MockOwnable(this._witnesses); } get circuitContext() { @@ -184,6 +187,25 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< }; } + public get witnesses(): ReturnType { + return this._witnesses; + } + + public set witnesses(newWitnesses: ReturnType) { + this._witnesses = newWitnesses; + this.contract = new MockOwnable(this._witnesses); + } + + public overrideWitness( + key: K, + fn: typeof this._witnesses[K], + ) { + this.witnesses = { + ...this._witnesses, + [key]: fn, + }; + } + /** * @description Returns the shielded owner. * @returns The shielded owner. From 4ed11233fe47a3308dbdba06418bfc4c3645e750 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Aug 2025 00:49:38 -0300 Subject: [PATCH 084/202] add callerCtx --- contracts/ownable/src/test/ZOwnablePK.test.ts | 32 +++++++++++-------- .../test/simulators/ZOwnablePKSimulator.ts | 18 ++++++----- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/contracts/ownable/src/test/ZOwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts index 38554643..2189b6b6 100644 --- a/contracts/ownable/src/test/ZOwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -169,7 +169,7 @@ describe('ZOwnablePK', () => { secretNonce, ); - ownable.setCaller(OWNER); + ownable.callerCtx.setCaller(OWNER); expect(ownable.assertOnlyOwner()).to.not.throw; }); @@ -184,7 +184,7 @@ describe('ZOwnablePK', () => { ); // Set caller and call circuit - ownable.setCaller(OWNER); + ownable.callerCtx.setCaller(OWNER); expect(() => { ownable.assertOnlyOwner(); }).toThrow('Forbidden'); @@ -196,7 +196,7 @@ describe('ZOwnablePK', () => { secretNonce, ); - ownable.setCaller(UNAUTHORIZED); + ownable.callerCtx.setCaller(UNAUTHORIZED); expect(() => { ownable.assertOnlyOwner(); }).toThrow('Forbidden'); @@ -213,7 +213,7 @@ describe('ZOwnablePK', () => { ); // Set unauthorized caller and call circuit - ownable.setCaller(UNAUTHORIZED); + ownable.callerCtx.setCaller(UNAUTHORIZED); expect(() => { ownable.assertOnlyOwner(); }).toThrow('Forbidden'); @@ -241,30 +241,30 @@ describe('ZOwnablePK', () => { }); it('should transfer ownership', () => { - ownable.setCaller(OWNER); + ownable.callerCtx.setCaller(OWNER); ownable.transferOwnership(newIdHash); expect(ownable.owner()).toEqual(newOwnerCommitment); // Old owner - ownable.setCaller(OWNER); + ownable.callerCtx.setCaller(OWNER); expect(() => { ownable.assertOnlyOwner(); }).toThrow('Forbidden'); // Unauthorized - ownable.setCaller(UNAUTHORIZED); + ownable.callerCtx.setCaller(UNAUTHORIZED); expect(() => { ownable.assertOnlyOwner(); }).toThrow('Forbidden'); // New owner - ownable.setCaller(NEW_OWNER); + ownable.callerCtx.setCaller(NEW_OWNER); ownable.privateState.injectSecretNonce(Buffer.from(newOwnerNonce)); expect(ownable.assertOnlyOwner()).not.to.throw; }); it('should fail when transferring to id zero', () => { - ownable.setCaller(OWNER); + ownable.callerCtx.setCaller(OWNER); const badId = new Uint8Array(32).fill(0); expect(() => { ownable.transferOwnership(badId); @@ -272,7 +272,7 @@ describe('ZOwnablePK', () => { }); it('should fail when unauthorized transfers ownership', () => { - ownable.setCaller(UNAUTHORIZED); + ownable.callerCtx.setCaller(UNAUTHORIZED); expect(() => { ownable.transferOwnership(newOwnerCommitment); }).toThrow('Forbidden'); @@ -285,7 +285,7 @@ describe('ZOwnablePK', () => { const beforeInstance = ownable.getPublicState().ZOwnablePK__counter; // Transfer - ownable.setCaller(OWNER); + ownable.callerCtx.setCaller(OWNER); ownable.transferOwnership(newOwnerCommitment); // Check counter @@ -305,7 +305,7 @@ describe('ZOwnablePK', () => { expect(initCommitment).toEqual(expInitCommitment); // Transfer ownership to self with the same id -> `H(pk, nonce)` - ownable.setCaller(OWNER); + ownable.callerCtx.setCaller(OWNER); ownable.transferOwnership(repeatedId); // Check commitments don't match @@ -322,9 +322,15 @@ describe('ZOwnablePK', () => { expect(newCommitment).toEqual(expNewCommitment); // Check same owner maintains permissions after transfer - ownable.setCaller(OWNER); + ownable.callerCtx.setCaller(OWNER); expect(ownable.assertOnlyOwner()).not.to.throw; }); }); + + describe('renounceOwnership', () => { + it('should renounce ownership', () => { + + }) + }) }); }); diff --git a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts index 5f3cd893..f55a3715 100644 --- a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts @@ -157,14 +157,6 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< return this._impureCircuitProxy; } - /** - * @description Sets the caller context. - * @param caller The caller in context of the proceeding circuit calls. - */ - public setCaller(caller: CoinPublicKey | null): void { - this.callerOverride = caller; - } - /** * @description Resets the cached circuit proxy instances. * This is useful if the underlying contract state or circuit context has changed, @@ -269,4 +261,14 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< return this.stateManager.getContext().currentPrivateState.offchainNonce; }, }; + + public callerCtx = { + /** + * @description Sets the caller context. + * @param caller The caller in context of the proceeding circuit calls. + */ + setCaller: (caller: CoinPublicKey) => { + this.callerOverride = caller; + }, + } } From 876f9efa9fa29aeee20a4bf43e700aa6a9d4b83c Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Aug 2025 01:57:59 -0300 Subject: [PATCH 085/202] add revealKey to renounce obfuscated --- contracts/ownable/src/ZOwnablePK.compact | 7 ++- contracts/ownable/src/test/ZOwnablePK.test.ts | 50 ++++++++++++++++--- .../src/test/mocks/MockZOwnablePK.compact | 4 ++ .../test/simulators/ZOwnablePKSimulator.ts | 8 +++ 4 files changed, 59 insertions(+), 10 deletions(-) diff --git a/contracts/ownable/src/ZOwnablePK.compact b/contracts/ownable/src/ZOwnablePK.compact index 935bcb74..a8a2b392 100644 --- a/contracts/ownable/src/ZOwnablePK.compact +++ b/contracts/ownable/src/ZOwnablePK.compact @@ -45,18 +45,17 @@ module ZOwnablePK { */ export circuit renounceOwnership(): [] { assertOnlyOwner(); - _transferOwnership(default>); + _ownerCommitment.resetToDefault(); } /** * @description Add me!!! */ - export circuit renounceOwnershipObfuscated(): [] { + export circuit renounceOwnershipObfuscated(revealKey: Bytes<32>): [] { assertOnlyOwner(); - const nonce = offchainNonce(); const obfuscatedCommitment = persistentHash>>( [ - persistentHash>>([default>, nonce]), + persistentHash>>([default>, revealKey]), _instanceSalt, _counter as Field as Bytes<32>, pad(32, "ZOwnablePK:renounced:"), diff --git a/contracts/ownable/src/test/ZOwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts index 2189b6b6..8b57d86c 100644 --- a/contracts/ownable/src/test/ZOwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -23,6 +23,7 @@ const UNAUTHORIZED = String( const Z_OWNER = utils.encodeToPK('OWNER'); const Z_NEW_OWNER = utils.encodeToPK('NEW_OWNER'); const INSTANCE_SALT = new Uint8Array(32).fill(8675309); +const BAD_NONCE = Buffer.from(Buffer.alloc(32, 'BAD_NONCE')); const DOMAIN = 'ZOwnablePK:shield:'; const INIT_COUNTER = 1n; @@ -175,8 +176,7 @@ describe('ZOwnablePK', () => { it('should fail when the authorized caller has the wrong nonce', () => { // Inject bad nonce - const badNonce = Buffer.alloc(32, 'badNonce'); - ownable.privateState.injectSecretNonce(badNonce); + ownable.privateState.injectSecretNonce(BAD_NONCE); // Check nonce does not match expect(ownable.privateState.getCurrentSecretNonce()).not.toEqual( @@ -204,8 +204,7 @@ describe('ZOwnablePK', () => { it('should fail when unauthorized caller has the wrong nonce', () => { // Inject bad nonce - const badNonce = Buffer.alloc(32, 'badNonce'); - ownable.privateState.injectSecretNonce(badNonce); + ownable.privateState.injectSecretNonce(BAD_NONCE); // Check nonce does not match expect(ownable.privateState.getCurrentSecretNonce()).not.toEqual( @@ -329,8 +328,47 @@ describe('ZOwnablePK', () => { describe('renounceOwnership', () => { it('should renounce ownership', () => { + ownable.callerCtx.setCaller(OWNER); + ownable.renounceOwnership(); + + // Check owner is reset + expect(ownable.owner()).toEqual(new Uint8Array(32).fill(0)); + + // Check revoked permissions + expect(() => { + ownable.assertOnlyOwner() + }).toThrow('Forbidden'); + }); + + it('should fail when renouncing from unauthorized', () => { + ownable.callerCtx.setCaller(UNAUTHORIZED); + expect(() => { + ownable.renounceOwnership(); + }); + }); - }) - }) + it('should fail when renouncing from authorized with bad nonce', () => { + ownable.callerCtx.setCaller(OWNER); + ownable.privateState.injectSecretNonce(BAD_NONCE); + expect(() => { + ownable.renounceOwnership(); + }); + }); + + it('should fail when renouncing from unauthorized with bad nonce', () => { + ownable.callerCtx.setCaller(UNAUTHORIZED); + ownable.privateState.injectSecretNonce(BAD_NONCE); + expect(() => { + ownable.renounceOwnership(); + }); + }); + + //describe('renounceOwnershipObfuscated', () => { + // it('should renounce and obfuscate', () => { + // ownable.callerCtx.setCaller(OWNER); + // + // }) + //}) + }); }); }); diff --git a/contracts/ownable/src/test/mocks/MockZOwnablePK.compact b/contracts/ownable/src/test/mocks/MockZOwnablePK.compact index f4cf6505..4a41eb69 100644 --- a/contracts/ownable/src/test/mocks/MockZOwnablePK.compact +++ b/contracts/ownable/src/test/mocks/MockZOwnablePK.compact @@ -24,6 +24,10 @@ export circuit renounceOwnership(): [] { return ZOwnablePK_renounceOwnership(); } +export circuit renounceOwnershipObfuscated(revealKey: Bytes<32>): [] { + return ZOwnablePK_renounceOwnershipObfuscated(revealKey); +} + export circuit assertOnlyOwner(): [] { return ZOwnablePK_assertOnlyOwner(); } diff --git a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts index f55a3715..130ed0d3 100644 --- a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts @@ -222,6 +222,14 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< this.circuits.impure.renounceOwnership(); } + + /** + * @description + */ + public renounceOwnershipObfuscated(revealKey: Uint8Array) { + this.circuits.impure.renounceOwnershipObfuscated(revealKey); + } + /** * @description Throws if called by any account other than the owner. * Use this to restrict access to sensitive circuits. From f9c52d99753d576f119e1daf8b9e3ed4e4d7cb81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:48:44 -0400 Subject: [PATCH 086/202] fmt docs --- .../shieldedAccessControl/src/ShieldedAccessControl.compact | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact index a46ee9d0..925c7ffb 100644 --- a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact +++ b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact @@ -162,7 +162,7 @@ module ShieldedAccessControl { * @dev Developers must export publicly declared roles from the top-level contract to generate possible roles for each. * * @param {Bytes<32>} account - The account requesting a role. - * @param {Bytes<32>}salt - A salt value for the underlying HKDF function. + * @param {Bytes<32>} salt - A salt value for the underlying HKDF function. * @return {[]} - Empty tuple.  */ witness recoverRoles(account: Bytes<32>, salt: Bytes<32>): []; From fb01e61c0416a09ff2091ba8704b3acb54841af5 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Aug 2025 14:11:04 -0300 Subject: [PATCH 087/202] remove obfuscated renounce --- contracts/ownable/src/ZOwnablePK.compact | 17 ----------------- contracts/ownable/src/test/ZOwnablePK.test.ts | 7 ------- .../src/test/mocks/MockZOwnablePK.compact | 4 ---- .../src/test/simulators/ZOwnablePKSimulator.ts | 8 -------- 4 files changed, 36 deletions(-) diff --git a/contracts/ownable/src/ZOwnablePK.compact b/contracts/ownable/src/ZOwnablePK.compact index a8a2b392..d093f45b 100644 --- a/contracts/ownable/src/ZOwnablePK.compact +++ b/contracts/ownable/src/ZOwnablePK.compact @@ -48,23 +48,6 @@ module ZOwnablePK { _ownerCommitment.resetToDefault(); } - /** - * @description Add me!!! - */ - export circuit renounceOwnershipObfuscated(revealKey: Bytes<32>): [] { - assertOnlyOwner(); - const obfuscatedCommitment = persistentHash>>( - [ - persistentHash>>([default>, revealKey]), - _instanceSalt, - _counter as Field as Bytes<32>, - pad(32, "ZOwnablePK:renounced:"), - ] - ); - - _transferOwnership(obfuscatedCommitment); - } - /** * @description Add me!!! */ diff --git a/contracts/ownable/src/test/ZOwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts index 8b57d86c..e2b93d4b 100644 --- a/contracts/ownable/src/test/ZOwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -362,13 +362,6 @@ describe('ZOwnablePK', () => { ownable.renounceOwnership(); }); }); - - //describe('renounceOwnershipObfuscated', () => { - // it('should renounce and obfuscate', () => { - // ownable.callerCtx.setCaller(OWNER); - // - // }) - //}) }); }); }); diff --git a/contracts/ownable/src/test/mocks/MockZOwnablePK.compact b/contracts/ownable/src/test/mocks/MockZOwnablePK.compact index 4a41eb69..f4cf6505 100644 --- a/contracts/ownable/src/test/mocks/MockZOwnablePK.compact +++ b/contracts/ownable/src/test/mocks/MockZOwnablePK.compact @@ -24,10 +24,6 @@ export circuit renounceOwnership(): [] { return ZOwnablePK_renounceOwnership(); } -export circuit renounceOwnershipObfuscated(revealKey: Bytes<32>): [] { - return ZOwnablePK_renounceOwnershipObfuscated(revealKey); -} - export circuit assertOnlyOwner(): [] { return ZOwnablePK_assertOnlyOwner(); } diff --git a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts index 130ed0d3..f55a3715 100644 --- a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts @@ -222,14 +222,6 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< this.circuits.impure.renounceOwnership(); } - - /** - * @description - */ - public renounceOwnershipObfuscated(revealKey: Uint8Array) { - this.circuits.impure.renounceOwnershipObfuscated(revealKey); - } - /** * @description Throws if called by any account other than the owner. * Use this to restrict access to sensitive circuits. From c05509f41f157705b1dc2e432b9881adef3e6f04 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Aug 2025 14:15:38 -0300 Subject: [PATCH 088/202] fix circuit name, fmt and lint --- contracts/ownable/src/ZOwnablePK.compact | 6 +++--- contracts/ownable/src/test/ZOwnablePK.test.ts | 6 +++--- contracts/ownable/src/test/mocks/MockZOwnablePK.compact | 4 ++-- .../ownable/src/test/simulators/ZOwnablePKSimulator.ts | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/contracts/ownable/src/ZOwnablePK.compact b/contracts/ownable/src/ZOwnablePK.compact index d093f45b..248eeb1a 100644 --- a/contracts/ownable/src/ZOwnablePK.compact +++ b/contracts/ownable/src/ZOwnablePK.compact @@ -55,14 +55,14 @@ module ZOwnablePK { const caller = ownPublicKey(); const nonce = offchainNonce(); const id = persistentHash>>([caller.bytes, nonce]); - assert(_ownerCommitment == hashCommitment(id, _counter), "Forbidden"); + assert(_ownerCommitment == computeOwnerCommitment(id, _counter), "Forbidden"); } // computePKCommitment || generateCommitment /** * @description Add me!!! */ - export circuit hashCommitment( + export circuit computeOwnerCommitment( id: Bytes<32>, counter: Uint<64>, ): Bytes<32> { @@ -81,6 +81,6 @@ module ZOwnablePK { */ export circuit _transferOwnership(newOwnerId: Bytes<32>): [] { _counter.increment(1); - _ownerCommitment = hashCommitment(disclose(newOwnerId), _counter); + _ownerCommitment = computeOwnerCommitment(disclose(newOwnerId), _counter); } } diff --git a/contracts/ownable/src/test/ZOwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts index e2b93d4b..62a4a0ae 100644 --- a/contracts/ownable/src/test/ZOwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -123,13 +123,13 @@ describe('ZOwnablePK', () => { /** * @TODO parameterize */ - describe('hashCommitment', () => { + describe('computeOwnerCommitment', () => { it('should match local and contract commitment algorithms', () => { const id = createIdHash(Z_OWNER, secretNonce); const counter = INIT_COUNTER; // Check buildCommitmentFromId - const hashFromContract = ownable.hashCommitment(id, counter); + const hashFromContract = ownable.computeOwnerCommitment(id, counter); const hashFromHelper1 = buildCommitmentFromId( id, INSTANCE_SALT, @@ -336,7 +336,7 @@ describe('ZOwnablePK', () => { // Check revoked permissions expect(() => { - ownable.assertOnlyOwner() + ownable.assertOnlyOwner(); }).toThrow('Forbidden'); }); diff --git a/contracts/ownable/src/test/mocks/MockZOwnablePK.compact b/contracts/ownable/src/test/mocks/MockZOwnablePK.compact index f4cf6505..b7c3548f 100644 --- a/contracts/ownable/src/test/mocks/MockZOwnablePK.compact +++ b/contracts/ownable/src/test/mocks/MockZOwnablePK.compact @@ -28,8 +28,8 @@ export circuit assertOnlyOwner(): [] { return ZOwnablePK_assertOnlyOwner(); } -export circuit hashCommitment(id: Bytes<32>, counter: Uint<64>): Bytes<32> { - return ZOwnablePK_hashCommitment(id, counter); +export circuit computeOwnerCommitment(id: Bytes<32>, counter: Uint<64>): Bytes<32> { + return ZOwnablePK_computeOwnerCommitment(id, counter); } export circuit _transferOwnership(newOwnerCommitment: Bytes<32>): [] { diff --git a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts index f55a3715..dc6543d6 100644 --- a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts @@ -190,7 +190,7 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< public overrideWitness( key: K, - fn: typeof this._witnesses[K], + fn: (typeof this._witnesses)[K], ) { this.witnesses = { ...this._witnesses, @@ -233,8 +233,8 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< /** * @description */ - public hashCommitment(id: Uint8Array, counter: bigint): Uint8Array { - return this.circuits.impure.hashCommitment(id, counter); + public computeOwnerCommitment(id: Uint8Array, counter: bigint): Uint8Array { + return this.circuits.impure.computeOwnerCommitment(id, counter); } /** @@ -270,5 +270,5 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< setCaller: (caller: CoinPublicKey) => { this.callerOverride = caller; }, - } + }; } From 249663b7246b962306d591ba00047d6c89fb8ab4 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Aug 2025 15:56:22 -0300 Subject: [PATCH 089/202] fix error msg --- contracts/ownable/src/ZOwnablePK.compact | 7 +- contracts/ownable/src/test/ZOwnablePK.test.ts | 226 +++++++++++------- 2 files changed, 137 insertions(+), 96 deletions(-) diff --git a/contracts/ownable/src/ZOwnablePK.compact b/contracts/ownable/src/ZOwnablePK.compact index 248eeb1a..4f157372 100644 --- a/contracts/ownable/src/ZOwnablePK.compact +++ b/contracts/ownable/src/ZOwnablePK.compact @@ -19,7 +19,7 @@ module ZOwnablePK { * @description Add me!!! */ export circuit initialize(ownerId: Bytes<32>, instanceSalt: Bytes<32>): [] { - assert(ownerId != default>, "Invalid parameters"); + assert(ownerId != default>, "ZOwnablePK: invalid id"); _instanceSalt = disclose(instanceSalt); _transferOwnership(ownerId); } @@ -36,7 +36,7 @@ module ZOwnablePK { */ export circuit transferOwnership(newOwnerId: Bytes<32>): [] { assertOnlyOwner(); - assert(newOwnerId != default>, "Invalid parameters"); + assert(newOwnerId != default>, "ZOwnablePK: invalid id"); _transferOwnership(newOwnerId); } @@ -55,10 +55,9 @@ module ZOwnablePK { const caller = ownPublicKey(); const nonce = offchainNonce(); const id = persistentHash>>([caller.bytes, nonce]); - assert(_ownerCommitment == computeOwnerCommitment(id, _counter), "Forbidden"); + assert(_ownerCommitment == computeOwnerCommitment(id, _counter), "ZOwnablePK: caller is not the owner"); } - // computePKCommitment || generateCommitment /** * @description Add me!!! */ diff --git a/contracts/ownable/src/test/ZOwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts index 62a4a0ae..dbb63417 100644 --- a/contracts/ownable/src/test/ZOwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -88,7 +88,7 @@ describe('ZOwnablePK', () => { expect(() => { const badId = new Uint8Array(32).fill(0); new ZOwnablePKSimulator(badId, INSTANCE_SALT); - }).toThrow('Invalid parameters'); + }).toThrow('ZOwnablePK: invalid id'); }); it('should initialize with non-zero commitment', () => { @@ -120,36 +120,6 @@ describe('ZOwnablePK', () => { }); }); - /** - * @TODO parameterize - */ - describe('computeOwnerCommitment', () => { - it('should match local and contract commitment algorithms', () => { - const id = createIdHash(Z_OWNER, secretNonce); - const counter = INIT_COUNTER; - - // Check buildCommitmentFromId - const hashFromContract = ownable.computeOwnerCommitment(id, counter); - const hashFromHelper1 = buildCommitmentFromId( - id, - INSTANCE_SALT, - counter, - ); - expect(hashFromContract).toEqual(hashFromHelper1); - - // Check buildCommitment - const hashFromHelper2 = buildCommitment( - Z_OWNER, - secretNonce, - INSTANCE_SALT, - counter, - DOMAIN, - ); - expect(hashFromContract).toEqual(hashFromHelper1); - expect(hashFromHelper1).toEqual(hashFromHelper2); - }); - }); - describe('owner', () => { it('should return the correct owner commitment', () => { const expCommitment = buildCommitment( @@ -163,62 +133,6 @@ describe('ZOwnablePK', () => { }); }); - describe('assertOnlyOwner', () => { - it('should allow authorized caller with correct nonce to call', () => { - // Check nonce is correct - expect(ownable.privateState.getCurrentSecretNonce()).toEqual( - secretNonce, - ); - - ownable.callerCtx.setCaller(OWNER); - expect(ownable.assertOnlyOwner()).to.not.throw; - }); - - it('should fail when the authorized caller has the wrong nonce', () => { - // Inject bad nonce - ownable.privateState.injectSecretNonce(BAD_NONCE); - - // Check nonce does not match - expect(ownable.privateState.getCurrentSecretNonce()).not.toEqual( - secretNonce, - ); - - // Set caller and call circuit - ownable.callerCtx.setCaller(OWNER); - expect(() => { - ownable.assertOnlyOwner(); - }).toThrow('Forbidden'); - }); - - it('should fail when unauthorized caller has the correct nonce', () => { - // Check nonce is correct - expect(ownable.privateState.getCurrentSecretNonce()).toEqual( - secretNonce, - ); - - ownable.callerCtx.setCaller(UNAUTHORIZED); - expect(() => { - ownable.assertOnlyOwner(); - }).toThrow('Forbidden'); - }); - - it('should fail when unauthorized caller has the wrong nonce', () => { - // Inject bad nonce - ownable.privateState.injectSecretNonce(BAD_NONCE); - - // Check nonce does not match - expect(ownable.privateState.getCurrentSecretNonce()).not.toEqual( - secretNonce, - ); - - // Set unauthorized caller and call circuit - ownable.callerCtx.setCaller(UNAUTHORIZED); - expect(() => { - ownable.assertOnlyOwner(); - }).toThrow('Forbidden'); - }); - }); - describe('transferOwnership', () => { let newOwnerCommitment: Uint8Array; let newOwnerNonce: Uint8Array; @@ -248,13 +162,13 @@ describe('ZOwnablePK', () => { ownable.callerCtx.setCaller(OWNER); expect(() => { ownable.assertOnlyOwner(); - }).toThrow('Forbidden'); + }).toThrow('ZOwnablePK: caller is not the owner'); // Unauthorized ownable.callerCtx.setCaller(UNAUTHORIZED); expect(() => { ownable.assertOnlyOwner(); - }).toThrow('Forbidden'); + }).toThrow('ZOwnablePK: caller is not the owner'); // New owner ownable.callerCtx.setCaller(NEW_OWNER); @@ -267,14 +181,14 @@ describe('ZOwnablePK', () => { const badId = new Uint8Array(32).fill(0); expect(() => { ownable.transferOwnership(badId); - }).toThrow('Invalid parameters'); + }).toThrow('ZOwnablePK: invalid id'); }); it('should fail when unauthorized transfers ownership', () => { ownable.callerCtx.setCaller(UNAUTHORIZED); expect(() => { ownable.transferOwnership(newOwnerCommitment); - }).toThrow('Forbidden'); + }).toThrow('ZOwnablePK: caller is not the owner'); }); /** @@ -337,7 +251,7 @@ describe('ZOwnablePK', () => { // Check revoked permissions expect(() => { ownable.assertOnlyOwner(); - }).toThrow('Forbidden'); + }).toThrow('ZOwnablePK: caller is not the owner'); }); it('should fail when renouncing from unauthorized', () => { @@ -363,5 +277,133 @@ describe('ZOwnablePK', () => { }); }); }); + + describe('assertOnlyOwner', () => { + it('should allow authorized caller with correct nonce to call', () => { + // Check nonce is correct + expect(ownable.privateState.getCurrentSecretNonce()).toEqual( + secretNonce, + ); + + ownable.callerCtx.setCaller(OWNER); + expect(ownable.assertOnlyOwner()).to.not.throw; + }); + + it('should fail when the authorized caller has the wrong nonce', () => { + // Inject bad nonce + ownable.privateState.injectSecretNonce(BAD_NONCE); + + // Check nonce does not match + expect(ownable.privateState.getCurrentSecretNonce()).not.toEqual( + secretNonce, + ); + + // Set caller and call circuit + ownable.callerCtx.setCaller(OWNER); + expect(() => { + ownable.assertOnlyOwner(); + }).toThrow('ZOwnablePK: caller is not the owner'); + }); + + it('should fail when unauthorized caller has the correct nonce', () => { + // Check nonce is correct + expect(ownable.privateState.getCurrentSecretNonce()).toEqual( + secretNonce, + ); + + ownable.callerCtx.setCaller(UNAUTHORIZED); + expect(() => { + ownable.assertOnlyOwner(); + }).toThrow('ZOwnablePK: caller is not the owner'); + }); + + it('should fail when unauthorized caller has the wrong nonce', () => { + // Inject bad nonce + ownable.privateState.injectSecretNonce(BAD_NONCE); + + // Check nonce does not match + expect(ownable.privateState.getCurrentSecretNonce()).not.toEqual( + secretNonce, + ); + + // Set unauthorized caller and call circuit + ownable.callerCtx.setCaller(UNAUTHORIZED); + expect(() => { + ownable.assertOnlyOwner(); + }).toThrow('ZOwnablePK: caller is not the owner'); + }); + }); + + /** + * @TODO parameterize + */ + describe('computeOwnerCommitment', () => { + it('should match local and contract commitment algorithms', () => { + const id = createIdHash(Z_OWNER, secretNonce); + const counter = INIT_COUNTER; + + // Check buildCommitmentFromId + const hashFromContract = ownable.computeOwnerCommitment(id, counter); + const hashFromHelper1 = buildCommitmentFromId( + id, + INSTANCE_SALT, + counter, + ); + expect(hashFromContract).toEqual(hashFromHelper1); + + // Check buildCommitment + const hashFromHelper2 = buildCommitment( + Z_OWNER, + secretNonce, + INSTANCE_SALT, + counter, + DOMAIN, + ); + expect(hashFromContract).toEqual(hashFromHelper1); + expect(hashFromHelper1).toEqual(hashFromHelper2); + }); + }); + + describe('_transferOwnership', () => { + it('should transfer ownership', () => { + const id = createIdHash(Z_OWNER, secretNonce); + ownable._transferOwnership(id); + + const nextCounter = INIT_COUNTER + 1n; + const expCommitment = buildCommitmentFromId(id, INSTANCE_SALT, nextCounter); + expect(ownable.owner()).toEqual(expCommitment); + }); + + it('should bump the counter with each transfer', () => { + const nTransfers = 10; + const counterStart = 2; // count starts at 2 bc the constructor bumps the count to 1 + for (let i = counterStart; i <= nTransfers; i++) { + const pk = utils.encodeToPK(`Id${i}`); + const nonce = new Uint8Array(32).fill(i) + const id = createIdHash(pk, nonce); + ownable._transferOwnership(id); + + expect(ownable.getPublicState().ZOwnablePK__counter).toEqual(BigInt(i)) + } + }); + + it('should allow transfer to all zeroes id', () => { + const zerosId = new Uint8Array(32).fill(0); + ownable._transferOwnership(zerosId); + + const nextCounter = INIT_COUNTER + 1n; + const expCommitment = buildCommitmentFromId(zerosId, INSTANCE_SALT, nextCounter); + expect(ownable.owner()).toEqual(expCommitment); + }); + + it('should allow anyone to transfer', () => { + ownable.callerCtx.setCaller(OWNER); + const id = createIdHash(Z_OWNER, secretNonce); + expect(ownable._transferOwnership(id)).not.to.throw; + + ownable.callerCtx.setCaller(UNAUTHORIZED); + expect(ownable._transferOwnership(id)).not.to.throw; + }); + }); }); }); From a5289fd89c9476ac14972ee3ab89544ed2af4fad Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Aug 2025 16:18:10 -0300 Subject: [PATCH 090/202] tidy up test --- contracts/ownable/src/test/ZOwnablePK.test.ts | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/contracts/ownable/src/test/ZOwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts index dbb63417..d8cd5a0b 100644 --- a/contracts/ownable/src/test/ZOwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -10,28 +10,24 @@ import { ZOwnablePKPrivateState } from '../witnesses/ZOwnablePKWitnesses.js'; import { ZOwnablePKSimulator } from './simulators/ZOwnablePKSimulator.js'; import * as utils from './utils/address.js'; -const OWNER = String(Buffer.from('OWNER', 'ascii').toString('hex')).padStart( - 64, - '0', -); -const NEW_OWNER = String( - Buffer.from('NEW_OWNER', 'ascii').toString('hex'), -).padStart(64, '0'); -const UNAUTHORIZED = String( - Buffer.from('UNAUTHORIZED', 'ascii').toString('hex'), -).padStart(64, '0'); +// Callers +const OWNER = utils.toHexPadded('OWNER'); +const NEW_OWNER = utils.toHexPadded('NEW_OWNER'); +const UNAUTHORIZED = utils.toHexPadded('UNAUTHORIZED'); + +// ZPKs const Z_OWNER = utils.encodeToPK('OWNER'); const Z_NEW_OWNER = utils.encodeToPK('NEW_OWNER'); + const INSTANCE_SALT = new Uint8Array(32).fill(8675309); const BAD_NONCE = Buffer.from(Buffer.alloc(32, 'BAD_NONCE')); - const DOMAIN = 'ZOwnablePK:shield:'; const INIT_COUNTER = 1n; let secretNonce: Uint8Array; let ownable: ZOwnablePKSimulator; -/** Helpers */ +// Helpers const createIdHash = ( pk: ZswapCoinPublicKey, nonce: Uint8Array, From 4adaf86c118a54e8d4eca358096c41e52cc6ec50 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Aug 2025 16:45:56 -0300 Subject: [PATCH 091/202] add initialize checks, add underscore to hash circuit --- contracts/ownable/src/ZOwnablePK.compact | 19 ++++++++++++++++--- contracts/ownable/src/test/ZOwnablePK.test.ts | 4 ++-- .../src/test/mocks/MockZOwnablePK.compact | 4 ++-- .../test/simulators/ZOwnablePKSimulator.ts | 4 ++-- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/contracts/ownable/src/ZOwnablePK.compact b/contracts/ownable/src/ZOwnablePK.compact index 4f157372..67193082 100644 --- a/contracts/ownable/src/ZOwnablePK.compact +++ b/contracts/ownable/src/ZOwnablePK.compact @@ -8,6 +8,7 @@ pragma language_version >= 0.16.0; */ module ZOwnablePK { import CompactStandardLibrary; + import "../../node_modules/@openzeppelin-compact/utils/src/Initializable" prefix Initializable_; export ledger _ownerCommitment: Bytes<32>; export ledger _counter: Counter; @@ -19,6 +20,8 @@ module ZOwnablePK { * @description Add me!!! */ export circuit initialize(ownerId: Bytes<32>, instanceSalt: Bytes<32>): [] { + Initializable_initialize(); + assert(ownerId != default>, "ZOwnablePK: invalid id"); _instanceSalt = disclose(instanceSalt); _transferOwnership(ownerId); @@ -28,6 +31,7 @@ module ZOwnablePK { * @description Add me!!! */ export circuit owner(): Bytes<32> { + Initializable_assertInitialized(); return _ownerCommitment; } @@ -35,6 +39,8 @@ module ZOwnablePK { * @description Add me!!! */ export circuit transferOwnership(newOwnerId: Bytes<32>): [] { + Initializable_assertInitialized(); + assertOnlyOwner(); assert(newOwnerId != default>, "ZOwnablePK: invalid id"); _transferOwnership(newOwnerId); @@ -44,6 +50,8 @@ module ZOwnablePK { * @description Add me!!! */ export circuit renounceOwnership(): [] { + Initializable_assertInitialized(); + assertOnlyOwner(); _ownerCommitment.resetToDefault(); } @@ -52,19 +60,22 @@ module ZOwnablePK { * @description Add me!!! */ export circuit assertOnlyOwner(): [] { + Initializable_assertInitialized(); + const caller = ownPublicKey(); const nonce = offchainNonce(); const id = persistentHash>>([caller.bytes, nonce]); - assert(_ownerCommitment == computeOwnerCommitment(id, _counter), "ZOwnablePK: caller is not the owner"); + assert(_ownerCommitment == _computeOwnerCommitment(id, _counter), "ZOwnablePK: caller is not the owner"); } /** * @description Add me!!! */ - export circuit computeOwnerCommitment( + export circuit _computeOwnerCommitment( id: Bytes<32>, counter: Uint<64>, ): Bytes<32> { + Initializable_assertInitialized(); return persistentHash>>( [ id, @@ -79,7 +90,9 @@ module ZOwnablePK { * @description Add me!!! */ export circuit _transferOwnership(newOwnerId: Bytes<32>): [] { + Initializable_assertInitialized(); + _counter.increment(1); - _ownerCommitment = computeOwnerCommitment(disclose(newOwnerId), _counter); + _ownerCommitment = _computeOwnerCommitment(disclose(newOwnerId), _counter); } } diff --git a/contracts/ownable/src/test/ZOwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts index d8cd5a0b..c3d6ff15 100644 --- a/contracts/ownable/src/test/ZOwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -333,13 +333,13 @@ describe('ZOwnablePK', () => { /** * @TODO parameterize */ - describe('computeOwnerCommitment', () => { + describe('_computeOwnerCommitment', () => { it('should match local and contract commitment algorithms', () => { const id = createIdHash(Z_OWNER, secretNonce); const counter = INIT_COUNTER; // Check buildCommitmentFromId - const hashFromContract = ownable.computeOwnerCommitment(id, counter); + const hashFromContract = ownable._computeOwnerCommitment(id, counter); const hashFromHelper1 = buildCommitmentFromId( id, INSTANCE_SALT, diff --git a/contracts/ownable/src/test/mocks/MockZOwnablePK.compact b/contracts/ownable/src/test/mocks/MockZOwnablePK.compact index b7c3548f..5d64d43e 100644 --- a/contracts/ownable/src/test/mocks/MockZOwnablePK.compact +++ b/contracts/ownable/src/test/mocks/MockZOwnablePK.compact @@ -28,8 +28,8 @@ export circuit assertOnlyOwner(): [] { return ZOwnablePK_assertOnlyOwner(); } -export circuit computeOwnerCommitment(id: Bytes<32>, counter: Uint<64>): Bytes<32> { - return ZOwnablePK_computeOwnerCommitment(id, counter); +export circuit _computeOwnerCommitment(id: Bytes<32>, counter: Uint<64>): Bytes<32> { + return ZOwnablePK__computeOwnerCommitment(id, counter); } export circuit _transferOwnership(newOwnerCommitment: Bytes<32>): [] { diff --git a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts index dc6543d6..6a1337a1 100644 --- a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts @@ -233,8 +233,8 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< /** * @description */ - public computeOwnerCommitment(id: Uint8Array, counter: bigint): Uint8Array { - return this.circuits.impure.computeOwnerCommitment(id, counter); + public _computeOwnerCommitment(id: Uint8Array, counter: bigint): Uint8Array { + return this.circuits.impure._computeOwnerCommitment(id, counter); } /** From 954d63fa8e5d59257bf444049f6ffebeececd70a Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Aug 2025 16:50:28 -0300 Subject: [PATCH 092/202] change PS/witness val name to secretNonce --- contracts/ownable/src/ZOwnablePK.compact | 4 ++-- contracts/ownable/src/test/ZOwnablePK.test.ts | 22 ++++++++++++++----- .../test/simulators/ZOwnablePKSimulator.ts | 4 ++-- .../src/witnesses/ZOwnablePKWitnesses.ts | 8 +++---- contracts/ownable/src/witnesses/interface.ts | 2 +- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/contracts/ownable/src/ZOwnablePK.compact b/contracts/ownable/src/ZOwnablePK.compact index 67193082..237ef6f6 100644 --- a/contracts/ownable/src/ZOwnablePK.compact +++ b/contracts/ownable/src/ZOwnablePK.compact @@ -14,7 +14,7 @@ module ZOwnablePK { export ledger _counter: Counter; export sealed ledger _instanceSalt: Bytes<32>; - export witness offchainNonce(): Bytes<32>; + export witness secretNonce(): Bytes<32>; /** * @description Add me!!! @@ -63,7 +63,7 @@ module ZOwnablePK { Initializable_assertInitialized(); const caller = ownPublicKey(); - const nonce = offchainNonce(); + const nonce = secretNonce(); const id = persistentHash>>([caller.bytes, nonce]); assert(_ownerCommitment == _computeOwnerCommitment(id, _counter), "ZOwnablePK: caller is not the owner"); } diff --git a/contracts/ownable/src/test/ZOwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts index c3d6ff15..5759a853 100644 --- a/contracts/ownable/src/test/ZOwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -107,7 +107,7 @@ describe('ZOwnablePK', () => { // Create private state object and generate nonce const PS = ZOwnablePKPrivateState.generate(); // Bind nonce for convenience - secretNonce = PS.offchainNonce; + secretNonce = PS.secretNonce; // Prepare owner ID with gen nonce const ownerId = createIdHash(Z_OWNER, secretNonce); // Deploy contract with derived owner commitment and PS @@ -137,7 +137,7 @@ describe('ZOwnablePK', () => { beforeEach(() => { // Prepare new owner commitment - newOwnerNonce = ZOwnablePKPrivateState.generate().offchainNonce; + newOwnerNonce = ZOwnablePKPrivateState.generate().secretNonce; newCounter = INIT_COUNTER + 1n; newIdHash = createIdHash(Z_NEW_OWNER, newOwnerNonce); newOwnerCommitment = buildCommitment( @@ -366,7 +366,11 @@ describe('ZOwnablePK', () => { ownable._transferOwnership(id); const nextCounter = INIT_COUNTER + 1n; - const expCommitment = buildCommitmentFromId(id, INSTANCE_SALT, nextCounter); + const expCommitment = buildCommitmentFromId( + id, + INSTANCE_SALT, + nextCounter, + ); expect(ownable.owner()).toEqual(expCommitment); }); @@ -375,11 +379,13 @@ describe('ZOwnablePK', () => { const counterStart = 2; // count starts at 2 bc the constructor bumps the count to 1 for (let i = counterStart; i <= nTransfers; i++) { const pk = utils.encodeToPK(`Id${i}`); - const nonce = new Uint8Array(32).fill(i) + const nonce = new Uint8Array(32).fill(i); const id = createIdHash(pk, nonce); ownable._transferOwnership(id); - expect(ownable.getPublicState().ZOwnablePK__counter).toEqual(BigInt(i)) + expect(ownable.getPublicState().ZOwnablePK__counter).toEqual( + BigInt(i), + ); } }); @@ -388,7 +394,11 @@ describe('ZOwnablePK', () => { ownable._transferOwnership(zerosId); const nextCounter = INIT_COUNTER + 1n; - const expCommitment = buildCommitmentFromId(zerosId, INSTANCE_SALT, nextCounter); + const expCommitment = buildCommitmentFromId( + zerosId, + INSTANCE_SALT, + nextCounter, + ); expect(ownable.owner()).toEqual(expCommitment); }); diff --git a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts index 6a1337a1..c4e58c25 100644 --- a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts @@ -252,13 +252,13 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< newNonce: Buffer, ): ZOwnablePKPrivateState => { const currentState = this.stateManager.getContext().currentPrivateState; - const updatedState = { ...currentState, offchainNonce: newNonce }; + const updatedState = { ...currentState, secretNonce: newNonce }; this.stateManager.updatePrivateState(updatedState); return updatedState; }, getCurrentSecretNonce: (): Uint8Array => { - return this.stateManager.getContext().currentPrivateState.offchainNonce; + return this.stateManager.getContext().currentPrivateState.secretNonce; }, }; diff --git a/contracts/ownable/src/witnesses/ZOwnablePKWitnesses.ts b/contracts/ownable/src/witnesses/ZOwnablePKWitnesses.ts index f4682cc6..2a3d2028 100644 --- a/contracts/ownable/src/witnesses/ZOwnablePKWitnesses.ts +++ b/contracts/ownable/src/witnesses/ZOwnablePKWitnesses.ts @@ -8,7 +8,7 @@ import type { IZOwnablePKWitnesses } from './interface.js'; */ export type ZOwnablePKPrivateState = { /** @description A 32-byte secret nonce used as a privacy additive. */ - offchainNonce: Buffer; + secretNonce: Buffer; }; /** @@ -20,7 +20,7 @@ export const ZOwnablePKPrivateState = { * @returns A fresh ZOwnablePKPrivateState instance. */ generate: (): ZOwnablePKPrivateState => { - return { offchainNonce: getRandomValues(Buffer.alloc(32)) }; + return { secretNonce: getRandomValues(Buffer.alloc(32)) }; }, }; @@ -30,9 +30,9 @@ export const ZOwnablePKPrivateState = { */ export const ZOwnablePKWitnesses = (): IZOwnablePKWitnesses => ({ - offchainNonce( + secretNonce( context: WitnessContext, ): [ZOwnablePKPrivateState, Uint8Array] { - return [context.privateState, context.privateState.offchainNonce]; + return [context.privateState, context.privateState.secretNonce]; }, }); diff --git a/contracts/ownable/src/witnesses/interface.ts b/contracts/ownable/src/witnesses/interface.ts index 7799c8ef..1898410c 100644 --- a/contracts/ownable/src/witnesses/interface.ts +++ b/contracts/ownable/src/witnesses/interface.ts @@ -11,5 +11,5 @@ export interface IZOwnablePKWitnesses

{ * @param context - The witness context containing the private state. * @returns A tuple of the private state and the secret nonce as a Uint8Array. */ - offchainNonce(context: WitnessContext): [P, Uint8Array]; + secretNonce(context: WitnessContext): [P, Uint8Array]; } From e1ee7e45e73e0bf038816fa3c4c4bcda6ae96bb5 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Aug 2025 16:51:33 -0300 Subject: [PATCH 093/202] improve test description --- contracts/ownable/src/test/ZOwnablePK.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/ownable/src/test/ZOwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts index 5759a853..e628eceb 100644 --- a/contracts/ownable/src/test/ZOwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -334,7 +334,7 @@ describe('ZOwnablePK', () => { * @TODO parameterize */ describe('_computeOwnerCommitment', () => { - it('should match local and contract commitment algorithms', () => { + it('should match local and contract commitment', () => { const id = createIdHash(Z_OWNER, secretNonce); const counter = INIT_COUNTER; From 6bbe434964742e6e65ccd3c3c00f7d590cc4eda2 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Aug 2025 16:52:26 -0300 Subject: [PATCH 094/202] remove unnecessary line --- contracts/ownable/src/test/ZOwnablePK.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/ownable/src/test/ZOwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts index e628eceb..07daf67e 100644 --- a/contracts/ownable/src/test/ZOwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -355,7 +355,6 @@ describe('ZOwnablePK', () => { counter, DOMAIN, ); - expect(hashFromContract).toEqual(hashFromHelper1); expect(hashFromHelper1).toEqual(hashFromHelper2); }); }); From 54116e29d3b33bd953b9f359047458c628683b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 12 Aug 2025 19:52:00 -0400 Subject: [PATCH 095/202] Add contractAddress to nonce generation scheme --- .../ShieldedAccessControlWitnesses.ts | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts index 88e1c589..08260719 100644 --- a/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts @@ -7,7 +7,6 @@ import { type WitnessContext, } from '@midnight-ntwrk/compact-runtime'; import { encodeContractAddress } from '@midnight-ntwrk/ledger'; -import { sampleContractAddress } from '@midnight-ntwrk/zswap'; import { type ContractAddress, type Either, @@ -21,13 +20,14 @@ const { hkdfSync } = await import('node:crypto'); const KEYLEN = 32; /** - * @description The respective `nonce` value for a given `roleId` should be at the same index + * @description The respective nonce and contract value for a given `roleId` should be at the same index * for each array of `Buffer`s */ export type ShieldedAccessControlPrivateState = { secretKey: Buffer; nonces: Buffer[]; roleIds: Buffer[]; + contractAddress: Buffer; }; /** @@ -36,6 +36,7 @@ export type ShieldedAccessControlPrivateState = { * @param roleId - The role identifier. * @param salt - A salt value. * @param account - The public key of an account. + * @param contractAddress - The contract address of the contract being called. * * @returns A unique nonce value for `roleId` */ @@ -44,9 +45,10 @@ function generateNonce( roleId: Buffer, salt: Buffer, account: Buffer, + contractAddress: Buffer, ): Buffer { const domainString = Buffer.from('role-nonce'); - const info = Buffer.concat([domainString, roleId, account]); + const info = Buffer.concat([domainString, roleId, account, contractAddress]); const nonce = hkdfSync('sha512', secretKey, salt, info, KEYLEN); return Buffer.from(nonce); @@ -57,6 +59,7 @@ function generateNonce( * @param account - The public key of an account. * @param roleId - The role identifier. * @param nonce - The nonce associated with `roleId`. + * @param contractAddress - The address of the contract being called. * * @returns Whether the account was approved for a role */ @@ -64,6 +67,7 @@ function sendRoleRequestToAdmin( _account: Buffer, _roleId: Buffer, _nonce: Buffer, + _contractAddress: Buffer, ) { return true; } @@ -120,6 +124,7 @@ export const ShieldedAccessControlWitnesses = { secretKey: privateState.secretKey, roleIds: [], nonces: [], + contractAddress: privateState.contractAddress, }; const contract = @@ -132,7 +137,12 @@ export const ShieldedAccessControlWitnesses = { currentZswapLocalState, } = contract.initialState( constructorContext( - { secretKey: privateState.secretKey, nonces: [], roleIds: [] }, + { + secretKey: privateState.secretKey, + nonces: [], + roleIds: [], + contractAddress: privateState.contractAddress, + }, coinPubKey, ), ); @@ -147,17 +157,18 @@ export const ShieldedAccessControlWitnesses = { }; for (let i = 0; i < roles.length; i++) { - const role = roles[i]; + const role = Buffer.from(roles[i]); const nonce = generateNonce( privateState.secretKey, - Buffer.from(role), + role, Buffer.from(salt), Buffer.from(account), + privateState.contractAddress, ); const eitherAccount: Either = { is_left: true, left: { bytes: account }, - right: { bytes: encodeContractAddress(sampleContractAddress()) }, + right: { bytes: new Uint8Array(32) }, }; try { @@ -169,7 +180,7 @@ export const ShieldedAccessControlWitnesses = { ); if (hasRole) { newPrivateState.nonces.push(nonce); - newPrivateState.roleIds.push(Buffer.from(role)); + newPrivateState.roleIds.push(role); } } catch (err) { console.log(err); @@ -184,11 +195,15 @@ export const ShieldedAccessControlWitnesses = { * @param roleId - The role identifier. * @param account - The public key requesting a role. * @param salt - A salt value. + * @param contractAddress - The address of the contract being called. * * @returns An array of the new private state and an empty array */ requestRole: ( - { privateState }: WitnessContext, + { + privateState, + contractAddress, + }: WitnessContext, roleId: Uint8Array, account: Uint8Array, salt: Uint8Array, @@ -196,13 +211,22 @@ export const ShieldedAccessControlWitnesses = { const saltBuff = Buffer.from(salt); const roleIdBuff = Buffer.from(roleId); const accountBuff = Buffer.from(account); + const contractAddressBuff = Buffer.from( + encodeContractAddress(contractAddress), + ); const nonce = generateNonce( privateState.secretKey, roleIdBuff, saltBuff, accountBuff, + contractAddressBuff, + ); + const isApproved = sendRoleRequestToAdmin( + accountBuff, + roleIdBuff, + nonce, + contractAddressBuff, ); - const isApproved = sendRoleRequestToAdmin(accountBuff, roleIdBuff, nonce); if (isApproved) { privateState.nonces.push(nonce); From 44019818cdf260b0b3fffecaad744c1b0583c707 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Aug 2025 23:11:40 -0300 Subject: [PATCH 096/202] add in-code docs, add _computeOwnerId --- contracts/ownable/src/ZOwnablePK.compact | 186 ++++++++++++++++++++++- 1 file changed, 178 insertions(+), 8 deletions(-) diff --git a/contracts/ownable/src/ZOwnablePK.compact b/contracts/ownable/src/ZOwnablePK.compact index 237ef6f6..7470abee 100644 --- a/contracts/ownable/src/ZOwnablePK.compact +++ b/contracts/ownable/src/ZOwnablePK.compact @@ -4,7 +4,42 @@ pragma language_version >= 0.16.0; /** * @module ZOwnablePK - * @description A shielded Ownable library. + * @description A shielded, public key-derived Ownable module. + * + * `ZOwnablePK` provides a privacy-preserving access control mechanism + * for contracts with a single administrative user. Unlike traditional + * `Ownable` implementations that store or expose the owner's public key + * on-chain, this module stores only a commitment to a hashed identifier + * derived from the owner's public key and a secret nonce. + * + * @notice This module explicitly supports commitments derived from public keys; + * however, it may be possible to use contract addresses when contract-to-contract + * calls become available. This will be revisited when it is know if/how witnesses + * are used from a contract address context. + * + * @dev Features: + * - Obfuscated owner identity: The owner's public key is never revealed on-chain. + * - Stateless verification: The contract never needs access to the full public key. + * - Built-in support for transfer and renounce functionality. + * - Instance-specific salts to prevent cross-contract correlation. + * - Deterministic hashing with `persistentHash` to support zero-knowledge verification. + * + * @dev Commitment structure: + * ``` + * id = H(pk, secretNonce) + * commitment = H(id, instanceSalt, counter, "ZOwnablePK:shield:") + * ``` + * The commitment changes on each transfer due to the incrementing `counter`, + * providing unlinkability across ownership changes. + * + * @dev Security Considerations: + * - The `secretNonce` must be kept private. Loss of the nonce prevents the + * owner from proving ownership or transferring it. + * - Ownership validation is entirely circuit-based using witness-provided values. + * - The `_instanceSalt` is immutable and used to differentiate deployments. + * + * @notice Best used for single-admin contracts with privacy requirements. + * It is not designed for multi-owner or role-based access control. */ module ZOwnablePK { import CompactStandardLibrary; @@ -17,7 +52,23 @@ module ZOwnablePK { export witness secretNonce(): Bytes<32>; /** - * @description Add me!!! + * @description Initializes the contract by setting the initial owner via `ownerId` + * and storing the `instanceSalt` that acts as a privacy additive for preventing + * duplicate commitments among other contracts implementing ZOwnablePK. + * + * @dev The `ownerId` must be calculated prior to contract deployment. + * + * @circuitInfo k=???, rows=??? + * + * Requirements: + * + * - Contract is not initialized. + * - `ownerId` is not all zeroes. + * + * @param {Bytes<32>} ownerId - The owner's unique identifier H(pk, nonce). + * @param {Bytes<32>} instanceSalt - Contract salt to prevent duplicate commitments if + * users reuse their PK and secretNonce witness (not recommended). + * @returns {[]} Empty tuple. */ export circuit initialize(ownerId: Bytes<32>, instanceSalt: Bytes<32>): [] { Initializable_initialize(); @@ -28,7 +79,16 @@ module ZOwnablePK { } /** - * @description Add me!!! + * @description Returns the current commitment representing the contract owner. + * The full commitment is: `H(H(pk, nonce), instanceSalt, counter, domain)`. + * + * @circuitInfo k=???, rows=??? + * + * Requirements: + * + * - Contract is initialized. + * + * @returns {Bytes<32>} The current owner's commitment. */ export circuit owner(): Bytes<32> { Initializable_assertInitialized(); @@ -36,7 +96,19 @@ module ZOwnablePK { } /** - * @description Add me!!! + * @description Transfers ownership to `newOwnerId`. + * `newOwnerId` must be precalculated and given to the current owner off chain. + * + * @circuitInfo k=???, rows=??? + * + * Requirements: + * + * - Contract is initialized. + * - Caller must be the current owner. + * - `newOwnerId` must not be all zeroes. + * + * @param {Bytes<32>} newOwnerId - The new owner's unique identifier (`H(pk, nonce)`). + * @returns {[]} Empty tuple. */ export circuit transferOwnership(newOwnerId: Bytes<32>): [] { Initializable_assertInitialized(); @@ -47,7 +119,18 @@ module ZOwnablePK { } /** - * @description Add me!!! + * @description Leaves the contract without an owner. + * It will not be possible to call `assertOnlyOnwer` circuits anymore. + * Can only be called by the current owner. + * + * @circuitInfo k=???, rows=??? + * + * Requirements: + * + * - Contract is initialized. + * - Caller must be the current owner. + * + * @returns {[]} Empty tuple. */ export circuit renounceOwnership(): [] { Initializable_assertInitialized(); @@ -57,7 +140,19 @@ module ZOwnablePK { } /** - * @description Add me!!! + * @description Throws if called by any account whose id hash `H(pk, nonce)` does not match + * the stored owner commitment. + * Use this to restrict access of specific circuits to the owner. + * + * @circuitInfo k=???, rows=??? + * + * Requirements: + * + * - Contract is initialized. + * - Caller's id (`H(pk, nonce)`) when used in `_computeOwnerCommitment` must equal + * the stored `_ownerCommitment`, thus verifying themselves as the owner. + * + * @returns {[]} Empty tuple. */ export circuit assertOnlyOwner(): [] { Initializable_assertInitialized(); @@ -69,7 +164,33 @@ module ZOwnablePK { } /** - * @description Add me!!! + * @description Computes the owner commitment from the given `id` and `counter`. + * + * ## Owner ID (`id`) + * The `id` is expected to be computed off-chain as: + * `id = H(pk, nonce)` + * + * - `pk`: The owner's public key. + * - `nonce`: A secret nonce scoped to the instance, ideally rotated with each transfer. + * + * ## Commitment Derivation + * `commitment = H(id, instanceSalt, counter, domain)` + * + * - `id`: See above. + * - `instanceSalt`: A unique per-deployment salt, stored during initialization. + * This prevents commitment collisions across deployments. + * - `counter`: Incremented with each ownership transfer, ensuring uniqueness + * even with repeated `id` values. + * - `domain`: A domain separator to prevent hash collisions when extending the module. + * + * Requirements: + * + * - Contract is initialized. + * + * @param {Bytes<32>} id - The unique identifier of the owner calculated by `H(pk, nonce)`. + * @param {Uint<64>} counter - The current counter or round. This increments by `1` + * after every transfer to prevent duplicate commitments given the same `id`. + * @returns {Bytes<32>} The commitment derived from `id` and `counter`. */ export circuit _computeOwnerCommitment( id: Bytes<32>, @@ -87,7 +208,56 @@ module ZOwnablePK { } /** - * @description Add me!!! + * @description Computes the unique identifier (`id`) of the owner from their + * public key and a secret nonce. + * + * ## ID Derivation + * `id = H(pk, nonce)` + * + * - `pk`: The public key of the caller. This is passed explicitly to allow + * for off-chain derivation, testing, or scenarios where the caller is + * different from the subject of the computation. + * - `nonce`: A secret nonce tied to the identity. This value should be + * randomly generated and kept private. It may be rotated periodically + * for enhanced unlinkability. + * + * The result is a 32-byte commitment that uniquely identifies the owner. + * This value is later used in owner commitment hashing, and acts as a privacy-preserving + * alternative to a raw public key. + * + * @notice This module allows ownership to be tied to an identity commitment derived + * from a public key and secret nonce. + * While typically used with user public keys, this mechanism may also + * support contract addresses as identifiers in future contract-to-contract + * interactions. Both are treated as 32-byte values (`Bytes<32>`). + * + * @circuitInfo k=???, rows=??? + * + * @param {Bytes<32>} pk - The public key of the identity being committed. + * @param {Bytes<32>} nonce - A private nonce to scope the commitment. + * @returns {Bytes<32>} The computed owner ID. + */ + export pure circuit _computeOwnerId(pk: Either, nonce: Bytes<32>): Bytes<32> { + if (!pk.is_left) { + assert(false, "ZOwnablePK: contract address owners are not yet supported"); + } + + return persistentHash>>([pk.left.bytes, nonce]); + } + + /** + * @description Transfers ownership to owner id `newOwnerId` without + * enforcing permission checks on the caller. + * + * @circuitInfo k=???, rows=??? + * + * Requirements: + * + * - Contract is initialized. + * + * @param {Bytes<32>} newOwnerId - The unique identifier of the new owner + * calculated by `H(pk, nonce)`. + * @returns {[]} Empty tuple. */ export circuit _transferOwnership(newOwnerId: Bytes<32>): [] { Initializable_assertInitialized(); From 8c220922384d44758694f1e8af638c7df7a9f02b Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Aug 2025 23:12:03 -0300 Subject: [PATCH 097/202] add _computeOwnerId and test --- contracts/ownable/src/test/ZOwnablePK.test.ts | 17 +++++++++++++++++ .../src/test/mocks/MockZOwnablePK.compact | 4 ++++ .../src/test/simulators/ZOwnablePKSimulator.ts | 10 ++++++++++ 3 files changed, 31 insertions(+) diff --git a/contracts/ownable/src/test/ZOwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts index 07daf67e..684f43a5 100644 --- a/contracts/ownable/src/test/ZOwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -359,6 +359,23 @@ describe('ZOwnablePK', () => { }); }); + describe('_computeOwnerId', () => { + it('should match local and contract owner id', () => { + const eitherOwner = utils.createEitherTestUser("OWNER"); + const ownerId = ownable._computeOwnerId(eitherOwner, secretNonce); + const expId = createIdHash(Z_OWNER, secretNonce); + + expect(ownerId).toEqual(expId); + }); + + it('should fail to compute ContractAddress id', () => { + const eitherContract = utils.createEitherTestContractAddress("CONTRACT"); + expect(() => { + ownable._computeOwnerId(eitherContract, secretNonce); + }).toThrow('ZOwnablePK: contract address owners are not yet supported') + }) + }); + describe('_transferOwnership', () => { it('should transfer ownership', () => { const id = createIdHash(Z_OWNER, secretNonce); diff --git a/contracts/ownable/src/test/mocks/MockZOwnablePK.compact b/contracts/ownable/src/test/mocks/MockZOwnablePK.compact index 5d64d43e..45858b8a 100644 --- a/contracts/ownable/src/test/mocks/MockZOwnablePK.compact +++ b/contracts/ownable/src/test/mocks/MockZOwnablePK.compact @@ -32,6 +32,10 @@ export circuit _computeOwnerCommitment(id: Bytes<32>, counter: Uint<64>): Bytes< return ZOwnablePK__computeOwnerCommitment(id, counter); } +export pure circuit _computeOwnerId(pk: Either, nonce: Bytes<32>): Bytes<32> { + return ZOwnablePK__computeOwnerId(pk, nonce); +} + export circuit _transferOwnership(newOwnerCommitment: Bytes<32>): [] { return ZOwnablePK__transferOwnership(newOwnerCommitment); } diff --git a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts index c4e58c25..710b53fb 100644 --- a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts @@ -8,6 +8,9 @@ import { type Ledger, ledger, Contract as MockOwnable, + Either, + ZswapCoinPublicKey, + ContractAddress } from '../../artifacts/MockZOwnablePK/contract/index.cjs'; import { ZOwnablePKPrivateState, @@ -237,6 +240,13 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< return this.circuits.impure._computeOwnerCommitment(id, counter); } + /** + * @description + */ + public _computeOwnerId(pk: Either, nonce: Uint8Array): Uint8Array { + return this.circuits.pure._computeOwnerId(pk, nonce); + } + /** * @description Internal circuit that transfers ownership of the contract to `newOwner`. */ From a3ff1ad17a0a5cbe38856221040ee381784550e5 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Aug 2025 23:13:26 -0300 Subject: [PATCH 098/202] add reqs to _computeOwnerId --- contracts/ownable/src/ZOwnablePK.compact | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/ownable/src/ZOwnablePK.compact b/contracts/ownable/src/ZOwnablePK.compact index 7470abee..91aa3771 100644 --- a/contracts/ownable/src/ZOwnablePK.compact +++ b/contracts/ownable/src/ZOwnablePK.compact @@ -233,6 +233,10 @@ module ZOwnablePK { * * @circuitInfo k=???, rows=??? * + * Requirements: + * + * - `pk` is not a ContractAddress. + * * @param {Bytes<32>} pk - The public key of the identity being committed. * @param {Bytes<32>} nonce - A private nonce to scope the commitment. * @returns {Bytes<32>} The computed owner ID. From 6325d0bc2318ab321443ab565b531f9d38a2d918 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Aug 2025 23:43:43 -0300 Subject: [PATCH 099/202] add generatePubKeyPair util --- contracts/ownable/src/test/Ownable.test.ts | 14 ++++++-------- contracts/ownable/src/test/ZOwnablePK.test.ts | 12 ++++-------- contracts/ownable/src/test/utils/address.ts | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/contracts/ownable/src/test/Ownable.test.ts b/contracts/ownable/src/test/Ownable.test.ts index bde39230..948201f4 100644 --- a/contracts/ownable/src/test/Ownable.test.ts +++ b/contracts/ownable/src/test/Ownable.test.ts @@ -3,14 +3,12 @@ import { beforeEach, describe, expect, it } from 'vitest'; import { OwnableSimulator } from './simulators/OwnableSimulator.js'; import * as utils from './utils/address.js'; -// Callers -const OWNER = utils.toHexPadded('OWNER'); -const NEW_OWNER = utils.toHexPadded('NEW_OWNER'); -const UNAUTHORIZED = utils.toHexPadded('UNAUTHORIZED'); - -// Encoded PK/Addresses -const Z_OWNER = utils.createEitherTestUser('OWNER'); -const Z_NEW_OWNER = utils.createEitherTestUser('NEW_OWNER'); +// PKs +const [OWNER, Z_OWNER] = utils.generateEitherPubKeyPair("OWNER"); +const [NEW_OWNER, Z_NEW_OWNER] = utils.generateEitherPubKeyPair("NEW_OWNER"); +const [UNAUTHORIZED, _] = utils.generateEitherPubKeyPair('UNAUTHORIZED'); + +// Encoded contract addresses const Z_OWNER_CONTRACT = utils.createEitherTestContractAddress('OWNER_CONTRACT'); const Z_RECIPIENT_CONTRACT = diff --git a/contracts/ownable/src/test/ZOwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts index 684f43a5..998735fc 100644 --- a/contracts/ownable/src/test/ZOwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -10,14 +10,10 @@ import { ZOwnablePKPrivateState } from '../witnesses/ZOwnablePKWitnesses.js'; import { ZOwnablePKSimulator } from './simulators/ZOwnablePKSimulator.js'; import * as utils from './utils/address.js'; -// Callers -const OWNER = utils.toHexPadded('OWNER'); -const NEW_OWNER = utils.toHexPadded('NEW_OWNER'); -const UNAUTHORIZED = utils.toHexPadded('UNAUTHORIZED'); - -// ZPKs -const Z_OWNER = utils.encodeToPK('OWNER'); -const Z_NEW_OWNER = utils.encodeToPK('NEW_OWNER'); +// PKs +const [OWNER, Z_OWNER] = utils.generatePubKeyPair("OWNER"); +const [NEW_OWNER, Z_NEW_OWNER] = utils.generatePubKeyPair("NEW_OWNER"); +const [UNAUTHORIZED, _] = utils.generatePubKeyPair('UNAUTHORIZED'); const INSTANCE_SALT = new Uint8Array(32).fill(8675309); const BAD_NONCE = Buffer.from(Buffer.alloc(32, 'BAD_NONCE')); diff --git a/contracts/ownable/src/test/utils/address.ts b/contracts/ownable/src/test/utils/address.ts index 82e0fe3e..086b323c 100644 --- a/contracts/ownable/src/test/utils/address.ts +++ b/contracts/ownable/src/test/utils/address.ts @@ -61,6 +61,22 @@ export const createEitherTestContractAddress = (str: string) => ({ right: encodeToAddress(str), }); +const baseGeneratePubKeyPair = ( + str: string, + asEither: boolean, +): [string, Compact.ZswapCoinPublicKey | Compact.Either] => { + const pk = toHexPadded(str); + const zpk = asEither ? createEitherTestUser(str) : encodeToPK(str); + return [pk, zpk]; +}; + +export const generatePubKeyPair = (str: string) => + baseGeneratePubKeyPair(str, false) as [string, Compact.ZswapCoinPublicKey]; + +export const generateEitherPubKeyPair = (str: string) => + baseGeneratePubKeyPair(str, true) as [string, Compact.Either]; + + export const zeroUint8Array = (length = 32) => convert_bigint_to_Uint8Array(length, 0n); From a1507ef24aacd400c350bfa2f8f201e79259e504 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 12 Aug 2025 23:46:47 -0300 Subject: [PATCH 100/202] remove line --- contracts/ownable/src/test/utils/address.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/ownable/src/test/utils/address.ts b/contracts/ownable/src/test/utils/address.ts index 086b323c..de4dcf86 100644 --- a/contracts/ownable/src/test/utils/address.ts +++ b/contracts/ownable/src/test/utils/address.ts @@ -76,7 +76,6 @@ export const generatePubKeyPair = (str: string) => export const generateEitherPubKeyPair = (str: string) => baseGeneratePubKeyPair(str, true) as [string, Compact.Either]; - export const zeroUint8Array = (length = 32) => convert_bigint_to_Uint8Array(length, 0n); From 771bfc0e7b0ff2031146a14caf3cd6520e86995b Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 13 Aug 2025 00:18:47 -0300 Subject: [PATCH 101/202] add init option to mock, test when not initialized --- contracts/ownable/src/test/ZOwnablePK.test.ts | 38 +++++++++++++++++-- .../src/test/mocks/MockZOwnablePK.compact | 13 ++++++- .../test/simulators/ZOwnablePKSimulator.ts | 3 +- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/contracts/ownable/src/test/ZOwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts index 998735fc..feaaab4d 100644 --- a/contracts/ownable/src/test/ZOwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -20,6 +20,7 @@ const BAD_NONCE = Buffer.from(Buffer.alloc(32, 'BAD_NONCE')); const DOMAIN = 'ZOwnablePK:shield:'; const INIT_COUNTER = 1n; +let isInit = true; let secretNonce: Uint8Array; let ownable: ZOwnablePKSimulator; @@ -79,7 +80,7 @@ describe('ZOwnablePK', () => { it('should fail when setting owner commitment as 0', () => { expect(() => { const badId = new Uint8Array(32).fill(0); - new ZOwnablePKSimulator(badId, INSTANCE_SALT); + new ZOwnablePKSimulator(badId, INSTANCE_SALT, isInit); }).toThrow('ZOwnablePK: invalid id'); }); @@ -87,7 +88,7 @@ describe('ZOwnablePK', () => { const notZeroPK = utils.encodeToPK('NOT_ZERO'); const notZeroNonce = new Uint8Array(32).fill(1); const nonZeroId = createIdHash(notZeroPK, notZeroNonce); - ownable = new ZOwnablePKSimulator(nonZeroId, INSTANCE_SALT); + ownable = new ZOwnablePKSimulator(nonZeroId, INSTANCE_SALT, isInit); const nonZeroCommitment = buildCommitmentFromId( nonZeroId, @@ -98,6 +99,37 @@ describe('ZOwnablePK', () => { }); }); + describe('when not initialized correctly', () => { + beforeEach(() => { + ownable = new ZOwnablePKSimulator(randomByteArray, INSTANCE_SALT, false); + }); + type FailingCircuits = [method: keyof ZOwnablePKSimulator, args: unknown[]]; + const randomByteArray = new Uint8Array(32).fill(123); + const randomCounter = 321n; + // Circuit calls should fail before the args are used + const circuitsToFail: FailingCircuits[] = [ + ['owner', []], + ['assertOnlyOwner', []], + ['transferOwnership', [randomByteArray]], + ['renounceOwnership', []], + ['_computeOwnerCommitment', [randomByteArray, randomCounter]], + ['_transferOwnership', [randomByteArray]], + ]; + it.each(circuitsToFail)('%s should fail', (circuitName, args) => { + expect(() => { + (ownable[circuitName] as (...args: unknown[]) => unknown)(...args); + }).toThrow('Initializable: contract not initialized'); + }); + + it('should allow pure computeOwnerId', () => { + const eitherOwner = utils.createEitherTestUser("OWNER"); + + expect(() => { + ownable._computeOwnerId(eitherOwner, randomByteArray); + }).not.toThrow(); + }); + }); + describe('after initialization', () => { beforeEach(() => { // Create private state object and generate nonce @@ -107,7 +139,7 @@ describe('ZOwnablePK', () => { // Prepare owner ID with gen nonce const ownerId = createIdHash(Z_OWNER, secretNonce); // Deploy contract with derived owner commitment and PS - ownable = new ZOwnablePKSimulator(ownerId, INSTANCE_SALT, { + ownable = new ZOwnablePKSimulator(ownerId, INSTANCE_SALT, isInit, { privateState: PS, }); }); diff --git a/contracts/ownable/src/test/mocks/MockZOwnablePK.compact b/contracts/ownable/src/test/mocks/MockZOwnablePK.compact index 45858b8a..ce9f7e0c 100644 --- a/contracts/ownable/src/test/mocks/MockZOwnablePK.compact +++ b/contracts/ownable/src/test/mocks/MockZOwnablePK.compact @@ -8,8 +8,17 @@ import "../../ZOwnablePK" prefix ZOwnablePK_; export { ZswapCoinPublicKey, ContractAddress, Either, Maybe }; export { ZOwnablePK__ownerCommitment, ZOwnablePK__counter }; -constructor(initOwnerCommitment: Bytes<32>, instanceSalt: Bytes<32>) { - ZOwnablePK_initialize(initOwnerCommitment, instanceSalt); +/** + * @description `isInit` is a param for testing. + * + * If `isInit` is false, the constructor will not initialize the contract. + * This behavior is to test that circuits are not callable unless the + * contract is initialized. +*/ +constructor(initOwnerCommitment: Bytes<32>, instanceSalt: Bytes<32>, isInit: Boolean) { + if (disclose(isInit)) { + ZOwnablePK_initialize(initOwnerCommitment, instanceSalt); + } } export circuit owner(): Bytes<32> { diff --git a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts index 710b53fb..bd359375 100644 --- a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts @@ -58,6 +58,7 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< constructor( initOwner: Uint8Array, instanceSalt: Uint8Array, + isInit: boolean, options: OwnableSimOptions = {}, ) { super(); @@ -69,7 +70,7 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< coinPK = '0'.repeat(64), address = sampleContractAddress(), } = options; - const constructorArgs = [initOwner, instanceSalt]; + const constructorArgs = [initOwner, instanceSalt, isInit]; this.contract = new MockOwnable(witnesses); From e9c1de5146f8242eb229f1fa2fca7826d369bbc9 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 13 Aug 2025 00:24:40 -0300 Subject: [PATCH 102/202] tidy up code --- contracts/ownable/src/test/ZOwnablePK.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/ownable/src/test/ZOwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts index feaaab4d..c6b7b634 100644 --- a/contracts/ownable/src/test/ZOwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -434,12 +434,12 @@ describe('ZOwnablePK', () => { }); it('should allow transfer to all zeroes id', () => { - const zerosId = new Uint8Array(32).fill(0); - ownable._transferOwnership(zerosId); + const zeroId = utils.zeroUint8Array(); + ownable._transferOwnership(zeroId); const nextCounter = INIT_COUNTER + 1n; const expCommitment = buildCommitmentFromId( - zerosId, + zeroId, INSTANCE_SALT, nextCounter, ); From 323b66fc524a5327915028f2b4bab17790e13322 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 13 Aug 2025 01:54:48 -0300 Subject: [PATCH 103/202] parameterize tests --- contracts/ownable/src/test/ZOwnablePK.test.ts | 119 ++++++++++++------ 1 file changed, 79 insertions(+), 40 deletions(-) diff --git a/contracts/ownable/src/test/ZOwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts index c6b7b634..cde94414 100644 --- a/contracts/ownable/src/test/ZOwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -11,8 +11,8 @@ import { ZOwnablePKSimulator } from './simulators/ZOwnablePKSimulator.js'; import * as utils from './utils/address.js'; // PKs -const [OWNER, Z_OWNER] = utils.generatePubKeyPair("OWNER"); -const [NEW_OWNER, Z_NEW_OWNER] = utils.generatePubKeyPair("NEW_OWNER"); +const [OWNER, Z_OWNER] = utils.generatePubKeyPair('OWNER'); +const [NEW_OWNER, Z_NEW_OWNER] = utils.generatePubKeyPair('NEW_OWNER'); const [UNAUTHORIZED, _] = utils.generatePubKeyPair('UNAUTHORIZED'); const INSTANCE_SALT = new Uint8Array(32).fill(8675309); @@ -100,8 +100,10 @@ describe('ZOwnablePK', () => { }); describe('when not initialized correctly', () => { + let isNotInit = false; + beforeEach(() => { - ownable = new ZOwnablePKSimulator(randomByteArray, INSTANCE_SALT, false); + ownable = new ZOwnablePKSimulator(randomByteArray, INSTANCE_SALT, isNotInit); }); type FailingCircuits = [method: keyof ZOwnablePKSimulator, args: unknown[]]; const randomByteArray = new Uint8Array(32).fill(123); @@ -122,7 +124,7 @@ describe('ZOwnablePK', () => { }); it('should allow pure computeOwnerId', () => { - const eitherOwner = utils.createEitherTestUser("OWNER"); + const eitherOwner = utils.createEitherTestUser('OWNER'); expect(() => { ownable._computeOwnerId(eitherOwner, randomByteArray); @@ -358,50 +360,87 @@ describe('ZOwnablePK', () => { }); }); - /** - * @TODO parameterize - */ describe('_computeOwnerCommitment', () => { - it('should match local and contract commitment', () => { - const id = createIdHash(Z_OWNER, secretNonce); - const counter = INIT_COUNTER; - - // Check buildCommitmentFromId - const hashFromContract = ownable._computeOwnerCommitment(id, counter); - const hashFromHelper1 = buildCommitmentFromId( - id, - INSTANCE_SALT, - counter, - ); - expect(hashFromContract).toEqual(hashFromHelper1); - - // Check buildCommitment - const hashFromHelper2 = buildCommitment( - Z_OWNER, - secretNonce, - INSTANCE_SALT, - counter, - DOMAIN, - ); - expect(hashFromHelper1).toEqual(hashFromHelper2); - }); + const MAX_U64 = 2n ** 64n - 1n; + const testCases = [ + ...Array.from({ length: 10 }, (_, i) => ({ + label: `User${i}`, + ownerPK: utils.encodeToPK(`User${i}`), + counter: BigInt(Math.floor(Math.random() * 2 ** 64 - 1)), + })), + { + label: 'ZeroCounter', + ownerPK: utils.encodeToPK('ZeroCounter'), + counter: 0n, + }, + { + label: 'MaxCounter', + ownerPK: utils.encodeToPK('MaxUser'), + counter: MAX_U64, + }, + ]; + it.each(testCases)( + 'should match commitment for $label with counter $counter', + ({ ownerPK, counter }) => { + const id = createIdHash(ownerPK, secretNonce); + + // Check buildCommitmentFromId + const hashFromContract = ownable._computeOwnerCommitment(id, counter); + const hashFromHelper1 = buildCommitmentFromId( + id, + INSTANCE_SALT, + counter, + ); + expect(hashFromContract).toEqual(hashFromHelper1); + + // Check buildCommitment + const hashFromHelper2 = buildCommitment( + ownerPK, + secretNonce, + INSTANCE_SALT, + counter, + DOMAIN, + ); + expect(hashFromHelper1).toEqual(hashFromHelper2); + }, + ); }); describe('_computeOwnerId', () => { - it('should match local and contract owner id', () => { - const eitherOwner = utils.createEitherTestUser("OWNER"); - const ownerId = ownable._computeOwnerId(eitherOwner, secretNonce); - const expId = createIdHash(Z_OWNER, secretNonce); - - expect(ownerId).toEqual(expId); - }); + const testCases = [ + ...Array.from({ length: 10 }, (_, i) => ({ + label: `User${i}`, + eitherOwner: utils.createEitherTestUser(`User${i}`), + nonce: new Uint8Array(32).fill(i), + })), + { + label: 'All-zero nonce', + eitherOwner: utils.createEitherTestUser('ZeroUser'), + nonce: new Uint8Array(32).fill(0), + }, + { + label: 'Max nonce', + eitherOwner: utils.createEitherTestUser('MaxUser'), + nonce: new Uint8Array(32).fill(255), + }, + ]; + + it.each(testCases)( + 'should match local and contract owner id for $label', + ({ eitherOwner, nonce }) => { + const ownerId = ownable._computeOwnerId(eitherOwner, nonce); + const expId = createIdHash(eitherOwner.left, nonce); + expect(ownerId).toEqual(expId); + }, + ); it('should fail to compute ContractAddress id', () => { - const eitherContract = utils.createEitherTestContractAddress("CONTRACT"); + const eitherContract = + utils.createEitherTestContractAddress('CONTRACT'); expect(() => { ownable._computeOwnerId(eitherContract, secretNonce); - }).toThrow('ZOwnablePK: contract address owners are not yet supported') - }) + }).toThrow('ZOwnablePK: contract address owners are not yet supported'); + }); }); describe('_transferOwnership', () => { From 00efb15db3eceeb4c52553ded09a6a453cffeab4 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 13 Aug 2025 01:55:27 -0300 Subject: [PATCH 104/202] fix fmt --- contracts/ownable/src/ZOwnablePK.compact | 5 ++++- contracts/ownable/src/test/Ownable.test.ts | 4 ++-- .../src/test/simulators/ZOwnablePKSimulator.ts | 11 +++++++---- contracts/ownable/src/test/utils/address.ts | 13 +++++++++++-- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/contracts/ownable/src/ZOwnablePK.compact b/contracts/ownable/src/ZOwnablePK.compact index 91aa3771..4111f415 100644 --- a/contracts/ownable/src/ZOwnablePK.compact +++ b/contracts/ownable/src/ZOwnablePK.compact @@ -241,7 +241,10 @@ module ZOwnablePK { * @param {Bytes<32>} nonce - A private nonce to scope the commitment. * @returns {Bytes<32>} The computed owner ID. */ - export pure circuit _computeOwnerId(pk: Either, nonce: Bytes<32>): Bytes<32> { + export pure circuit _computeOwnerId( + pk: Either, + nonce: Bytes<32> + ): Bytes<32> { if (!pk.is_left) { assert(false, "ZOwnablePK: contract address owners are not yet supported"); } diff --git a/contracts/ownable/src/test/Ownable.test.ts b/contracts/ownable/src/test/Ownable.test.ts index 948201f4..60053cb7 100644 --- a/contracts/ownable/src/test/Ownable.test.ts +++ b/contracts/ownable/src/test/Ownable.test.ts @@ -4,8 +4,8 @@ import { OwnableSimulator } from './simulators/OwnableSimulator.js'; import * as utils from './utils/address.js'; // PKs -const [OWNER, Z_OWNER] = utils.generateEitherPubKeyPair("OWNER"); -const [NEW_OWNER, Z_NEW_OWNER] = utils.generateEitherPubKeyPair("NEW_OWNER"); +const [OWNER, Z_OWNER] = utils.generateEitherPubKeyPair('OWNER'); +const [NEW_OWNER, Z_NEW_OWNER] = utils.generateEitherPubKeyPair('NEW_OWNER'); const [UNAUTHORIZED, _] = utils.generateEitherPubKeyPair('UNAUTHORIZED'); // Encoded contract addresses diff --git a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts index bd359375..adb7d270 100644 --- a/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts +++ b/contracts/ownable/src/test/simulators/ZOwnablePKSimulator.ts @@ -5,12 +5,12 @@ import { } from '@midnight-ntwrk/compact-runtime'; import { sampleContractAddress } from '@midnight-ntwrk/zswap'; import { + type ContractAddress, + type Either, type Ledger, ledger, Contract as MockOwnable, - Either, - ZswapCoinPublicKey, - ContractAddress + type ZswapCoinPublicKey, } from '../../artifacts/MockZOwnablePK/contract/index.cjs'; import { ZOwnablePKPrivateState, @@ -244,7 +244,10 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< /** * @description */ - public _computeOwnerId(pk: Either, nonce: Uint8Array): Uint8Array { + public _computeOwnerId( + pk: Either, + nonce: Uint8Array, + ): Uint8Array { return this.circuits.pure._computeOwnerId(pk, nonce); } diff --git a/contracts/ownable/src/test/utils/address.ts b/contracts/ownable/src/test/utils/address.ts index de4dcf86..640dbb86 100644 --- a/contracts/ownable/src/test/utils/address.ts +++ b/contracts/ownable/src/test/utils/address.ts @@ -64,7 +64,13 @@ export const createEitherTestContractAddress = (str: string) => ({ const baseGeneratePubKeyPair = ( str: string, asEither: boolean, -): [string, Compact.ZswapCoinPublicKey | Compact.Either] => { +): [ + string, + ( + | Compact.ZswapCoinPublicKey + | Compact.Either + ), +] => { const pk = toHexPadded(str); const zpk = asEither ? createEitherTestUser(str) : encodeToPK(str); return [pk, zpk]; @@ -74,7 +80,10 @@ export const generatePubKeyPair = (str: string) => baseGeneratePubKeyPair(str, false) as [string, Compact.ZswapCoinPublicKey]; export const generateEitherPubKeyPair = (str: string) => - baseGeneratePubKeyPair(str, true) as [string, Compact.Either]; + baseGeneratePubKeyPair(str, true) as [ + string, + Compact.Either, + ]; export const zeroUint8Array = (length = 32) => convert_bigint_to_Uint8Array(length, 0n); From 81ebb9f5f033de5144bfc34a67b61a8416d70b27 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 13 Aug 2025 02:06:38 -0300 Subject: [PATCH 105/202] add ledger and witness docs --- contracts/ownable/src/ZOwnablePK.compact | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/contracts/ownable/src/ZOwnablePK.compact b/contracts/ownable/src/ZOwnablePK.compact index 4111f415..99f4a998 100644 --- a/contracts/ownable/src/ZOwnablePK.compact +++ b/contracts/ownable/src/ZOwnablePK.compact @@ -45,10 +45,40 @@ module ZOwnablePK { import CompactStandardLibrary; import "../../node_modules/@openzeppelin-compact/utils/src/Initializable" prefix Initializable_; + /** + * @ledger _ownerCommitment + * @description Stores the current hashed commitment representing the owner. + * This commitment is derived from the public identifier (e.g., `H(pk, nonce)`), + * the `instanceSalt`, the transfer `counter`, and a domain separator. + * + * A commitment of `default>` (i.e., zero) indicates the contract is unowned. + */ export ledger _ownerCommitment: Bytes<32>; + /** + * @ledger _counter + * @description Internal transfer counter used to prevent commitment reuse. + * + * Increments by 1 on every successful ownership transfer. Combined with `id` and + * `instanceSalt` to compute unique owner commitments over time. + */ export ledger _counter: Counter; + /** + * @sealed @ledger _instanceSalt + * @description A per-instance value provided at initialization used to namespace + * commitments for this contract instance. + * + * This salt prevents commitment collisions across contracts that might otherwise use + * the same owner identifiers or domain parameters. It is immutable after initialization. + */ export sealed ledger _instanceSalt: Bytes<32>; + /** + * @witness secretNonce + * @description A private per-user nonce used in deriving the shielded owner identifier. + * + * Combined with the user's public key as `H(pk, nonce)` to produce an obfuscated, + * unlinkable identity commitment. Users are encouraged to rotate this value on ownership changes. + */ export witness secretNonce(): Bytes<32>; /** From c5417c1a7ec28871f4a7904e237a6729110fd881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Wed, 13 Aug 2025 13:05:34 -0400 Subject: [PATCH 106/202] Revert "Add contractAddress to nonce generation scheme" This reverts commit 54116e29d3b33bd953b9f359047458c628683b1b. --- .../ShieldedAccessControlWitnesses.ts | 44 +++++-------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts index 08260719..88e1c589 100644 --- a/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts @@ -7,6 +7,7 @@ import { type WitnessContext, } from '@midnight-ntwrk/compact-runtime'; import { encodeContractAddress } from '@midnight-ntwrk/ledger'; +import { sampleContractAddress } from '@midnight-ntwrk/zswap'; import { type ContractAddress, type Either, @@ -20,14 +21,13 @@ const { hkdfSync } = await import('node:crypto'); const KEYLEN = 32; /** - * @description The respective nonce and contract value for a given `roleId` should be at the same index + * @description The respective `nonce` value for a given `roleId` should be at the same index * for each array of `Buffer`s */ export type ShieldedAccessControlPrivateState = { secretKey: Buffer; nonces: Buffer[]; roleIds: Buffer[]; - contractAddress: Buffer; }; /** @@ -36,7 +36,6 @@ export type ShieldedAccessControlPrivateState = { * @param roleId - The role identifier. * @param salt - A salt value. * @param account - The public key of an account. - * @param contractAddress - The contract address of the contract being called. * * @returns A unique nonce value for `roleId` */ @@ -45,10 +44,9 @@ function generateNonce( roleId: Buffer, salt: Buffer, account: Buffer, - contractAddress: Buffer, ): Buffer { const domainString = Buffer.from('role-nonce'); - const info = Buffer.concat([domainString, roleId, account, contractAddress]); + const info = Buffer.concat([domainString, roleId, account]); const nonce = hkdfSync('sha512', secretKey, salt, info, KEYLEN); return Buffer.from(nonce); @@ -59,7 +57,6 @@ function generateNonce( * @param account - The public key of an account. * @param roleId - The role identifier. * @param nonce - The nonce associated with `roleId`. - * @param contractAddress - The address of the contract being called. * * @returns Whether the account was approved for a role */ @@ -67,7 +64,6 @@ function sendRoleRequestToAdmin( _account: Buffer, _roleId: Buffer, _nonce: Buffer, - _contractAddress: Buffer, ) { return true; } @@ -124,7 +120,6 @@ export const ShieldedAccessControlWitnesses = { secretKey: privateState.secretKey, roleIds: [], nonces: [], - contractAddress: privateState.contractAddress, }; const contract = @@ -137,12 +132,7 @@ export const ShieldedAccessControlWitnesses = { currentZswapLocalState, } = contract.initialState( constructorContext( - { - secretKey: privateState.secretKey, - nonces: [], - roleIds: [], - contractAddress: privateState.contractAddress, - }, + { secretKey: privateState.secretKey, nonces: [], roleIds: [] }, coinPubKey, ), ); @@ -157,18 +147,17 @@ export const ShieldedAccessControlWitnesses = { }; for (let i = 0; i < roles.length; i++) { - const role = Buffer.from(roles[i]); + const role = roles[i]; const nonce = generateNonce( privateState.secretKey, - role, + Buffer.from(role), Buffer.from(salt), Buffer.from(account), - privateState.contractAddress, ); const eitherAccount: Either = { is_left: true, left: { bytes: account }, - right: { bytes: new Uint8Array(32) }, + right: { bytes: encodeContractAddress(sampleContractAddress()) }, }; try { @@ -180,7 +169,7 @@ export const ShieldedAccessControlWitnesses = { ); if (hasRole) { newPrivateState.nonces.push(nonce); - newPrivateState.roleIds.push(role); + newPrivateState.roleIds.push(Buffer.from(role)); } } catch (err) { console.log(err); @@ -195,15 +184,11 @@ export const ShieldedAccessControlWitnesses = { * @param roleId - The role identifier. * @param account - The public key requesting a role. * @param salt - A salt value. - * @param contractAddress - The address of the contract being called. * * @returns An array of the new private state and an empty array */ requestRole: ( - { - privateState, - contractAddress, - }: WitnessContext, + { privateState }: WitnessContext, roleId: Uint8Array, account: Uint8Array, salt: Uint8Array, @@ -211,22 +196,13 @@ export const ShieldedAccessControlWitnesses = { const saltBuff = Buffer.from(salt); const roleIdBuff = Buffer.from(roleId); const accountBuff = Buffer.from(account); - const contractAddressBuff = Buffer.from( - encodeContractAddress(contractAddress), - ); const nonce = generateNonce( privateState.secretKey, roleIdBuff, saltBuff, accountBuff, - contractAddressBuff, - ); - const isApproved = sendRoleRequestToAdmin( - accountBuff, - roleIdBuff, - nonce, - contractAddressBuff, ); + const isApproved = sendRoleRequestToAdmin(accountBuff, roleIdBuff, nonce); if (isApproved) { privateState.nonces.push(nonce); From db5821f94cdf936d0f8f4fc4b73cdc4639b142fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Wed, 13 Aug 2025 14:32:10 -0400 Subject: [PATCH 107/202] Update hashing scheme --- .../src/ShieldedAccessControl.compact | 155 +++++++++--------- .../mocks/MockShieldedAccessControl.compact | 2 +- 2 files changed, 80 insertions(+), 77 deletions(-) diff --git a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact index 925c7ffb..a36cd04e 100644 --- a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact +++ b/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact @@ -8,7 +8,7 @@ pragma language_version >= 0.16.0; * This module provides a shielded role-based access control mechanism, where roles can be used to * represent a set of permissions. Roles are stored as Merkle tree commitments to avoid * disclosing information about role holder. Role commitments are created with the following - * hashing scheme SHA256( SHA256(PublicKey | roleIdentifier | nonce) | index). + * hashing scheme SHA256( SHA256(roleIdentifier | account | nonce | contractAddress) | index). * * @notice Using the SHA256 hashing function comes at a significant performace cost. In the future, we * plan on migrating to a ZK-friendly hashing function like Poseidon when an implementation is available. @@ -84,8 +84,8 @@ module ShieldedAccessControl { import "ShieldedAccessControlUtils" prefix Utils_; /** - * @description A Merkle tree of role commitments stored as SHA256( SHA256(PK | role | nonce) | index) - * @type {Bytes<32>} roleCommitment - A commitment created by the following hash: SHA256( SHA256(PK | role | nonce) | index). + * @description A Merkle tree of role commitments stored as SHA256( SHA256(roleId | account | nonce | contractAddress) | index) + * @type {Bytes<32>} roleCommitment - A commitment created by the following hash: SHA256( SHA256(roleId | account | nonce | contractAddress) | index). * @type {MerkleTree<10, roleCommitment>} * @type {MerkleTree<10, Bytes<32>>} _operatorRoles  */ @@ -102,14 +102,14 @@ module ShieldedAccessControl { /** * @description A set of nullifiers used to revoke the permissions of a role - * @type {Bytes<32> roleCommitment - A roleCommitment created by the following hash: SHA256( SHA256(PK | role | nonce) | index). + * @type {Bytes<32> roleCommitment - A roleCommitment created by the following hash: SHA256( SHA256(roleId | account | nonce | contractAddress) | index). * @type {Set} _roleCommitmentNullifiers  */ export ledger _roleCommitmentNullifiers: Set>; /** * @description Mapping from an intermediate role commitment hash to an index in the `_operatorRoles` Merkle tree. - * @type {Bytes<32>} intermediateRoleCommitment - An intermediate role commitment hash created by the following hashing scheme: SHA256(PK | role | nonce). + * @type {Bytes<32>} intermediateRoleCommitment - An intermediate role commitment hash created by the following hashing scheme: SHA256(roleId | account | nonce | contractAddress). * @type {Uint<64>} index - The index of a role commitment in the `_operatorRoles` Merkle tree. * @type {Map} * @type {Map, Uint<64>>} _roleCommitmentIndex @@ -137,7 +137,7 @@ module ShieldedAccessControl { * * @circuitInfo * - * @param {Bytes<32>} roleCommitment - A commitment created by the following hash: SHA256( SHA256(PK | role | nonce) | index). + * @param {Bytes<32>} roleCommitment - A commitment created by the following hash: SHA256( SHA256(roleId | account | nonce | contractAddress) | index). * @param {Uint<64>} index - An index in the `_operatorRoles` Merkle tree * @return {MerkleTreePath<10, Bytes<32>>} - The Merkle path of `roleCommitment` in the `_operatorRoles` Merkle tree  */ @@ -170,21 +170,21 @@ module ShieldedAccessControl { /** * @description Returns `true` if `account` has been granted `roleId`. * - * @circuitInfo k=16, rows=50605 + * @circuitInfo k=16, rows=60150 * * Requirements: * - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. @@ -205,24 +205,24 @@ module ShieldedAccessControl { } /** - * @description Reverts if `ownPublicKey()` is missing `roleId`. + * @description Reverts if caller is missing `roleId`. * - * @circuitInfo k=15, rows=25046 + * @circuitInfo k=15, rows=29780 * * Requirements: * - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must * exist at `index` in the `_operatorRoles` Merkle tree. * - The caller must not be a ContractAddress. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. @@ -242,21 +242,21 @@ module ShieldedAccessControl { /** * @description Reverts if `account` is missing `roleId`. * - * @circuitInfo k=16, rows=50584 + * @circuitInfo k=16, rows=60129 * * Requirements: * - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. @@ -273,21 +273,21 @@ module ShieldedAccessControl { /** * @description Checks if a path exists for a role commitment. * - * @circuitInfo k=15, rows=25067 + * @circuitInfo k=15, rows=29801 * * Requirements: * - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * @@ -295,10 +295,11 @@ module ShieldedAccessControl { * @param {Bytes<32>} account - The account to check represented as a Bytes<32>. * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {Boolean} - A boolean determining if a path for for the role commitment - * produced by SHA256( SHA256(roleId | account | nonce) | index) exists in the `_operatorRoles` Merkle tree + * produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) exists in the `_operatorRoles` Merkle tree */ export circuit _checkMerkleTree(roleId: Bytes<32>, account: Bytes<32>, nonce: Bytes<32>): Boolean { - const intermediateRoleCommitment = persistentHash>>([roleId, account, nonce]); + const contractAddress = kernel.self().bytes; + const intermediateRoleCommitment = persistentHash>>([roleId, account, nonce, contractAddress]); assert(_roleCommitmentIndex.member(disclose(intermediateRoleCommitment)), "ShieldedAccessControl: role commitment index not found"); const index = _roleCommitmentIndex.lookup(disclose(intermediateRoleCommitment)); @@ -331,22 +332,22 @@ module ShieldedAccessControl { /** * @description Grants `roleId` to `account`. * - * @circuitInfo k=17, rows=114944 + * @circuitInfo k=18, rows=138761 * * Requirements: * * - `account` must not be a ContractAddress. - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. @@ -364,22 +365,22 @@ module ShieldedAccessControl { /** * @description Revokes `roleId` from `account`. * - * @circuitInfo k=17, rows=114699 + * @circuitInfo k=18, rows=138517 * * Requirements: * * - `account` must not be a ContractAddress. - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. @@ -401,23 +402,23 @@ module ShieldedAccessControl { * purpose is to provide a mechanism for accounts to lose their privileges * if they are compromised (such as when a trusted device is misplaced). * - * @circuitInfo k=17, rows=89905 + * @circuitInfo k=17, rows=108992 * * Requirements: * * - The caller must be `callerConfirmation`. * - The caller must not be a `ContractAddress`. - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `callerConfirmation` - a ZswapCoinPublicKey or ContractAddress. @@ -450,22 +451,22 @@ module ShieldedAccessControl { * @description Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted. * Internal circuit without access restriction. * - * @circuitInfo k=17, rows=90077 + * @circuitInfo k=17, rows=109163 * * Requirements: * * - `account` must not be a ContractAddress. - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. @@ -484,24 +485,24 @@ module ShieldedAccessControl { * @description Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted. * Internal circuit without access restriction. It does NOT check if the role is granted to a ContractAddress. * - * @circuitInfo k=17, rows=90076 + * @circuitInfo k=17, rows=109162 * * @notice External smart contracts cannot call the token contract at this time, so granting a role to an ContractAddress may * render a circuit permanently inaccessible. * * Requirements: * - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. @@ -531,21 +532,21 @@ module ShieldedAccessControl { * @description Attempts to revoke `roleId` from `account` and returns a boolean indicating if `roleId` was revoked. * Internal circuit without access restriction. * - * @circuitInfo k=17, rows=89829 + * @circuitInfo k=17, rows=108916 * * Requirements: * - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) + * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce) | index) must + * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. @@ -576,11 +577,11 @@ module ShieldedAccessControl { * * WARNING: Exposing this circuit in the implementing contract would allow anyone to add roles. * - * @circuitInfo k=15, rows=19832 + * @circuitInfo k=15, rows=24565 * * Disclosures: * - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). * * @param {Bytes<32>} roleId - The role identifier. * @param {Bytes<32>} account - The account to add represented as a Bytes<32>. @@ -588,7 +589,8 @@ module ShieldedAccessControl { * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. */ circuit _addRoleCommitmentToLedger(roleId: Bytes<32>, account: Bytes<32>, nonce: Bytes<32>): [] { - const intermediateRoleCommitment = persistentHash>>([roleId, account, nonce]); + const contractAddress = kernel.self().bytes; + const intermediateRoleCommitment = persistentHash>>([roleId, account, nonce, contractAddress]); const index = _nextIndex.read(); const finalRoleCommitment = persistentHash>>([intermediateRoleCommitment, index as Field as Bytes<32>]); @@ -602,12 +604,12 @@ module ShieldedAccessControl { * * WARNING: Exposing this circuit in the implementing contract would allow anyone to revoke roles. * - * @circuitInfo k=15, rows=19824 + * @circuitInfo k=15, rows=24559 * * Disclosures: * - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce) | index). - * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). * * @param {Bytes<32>} roleId - The role identifier. * @param {Bytes<32>} account - The account to add represented as a Bytes<32>. @@ -615,7 +617,8 @@ module ShieldedAccessControl { * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. */ circuit _nullifyRoleCommitment(roleId: Bytes<32>, account: Bytes<32>, nonce: Bytes<32>): [] { - const intermediateRoleCommitment = persistentHash>>([roleId, account, nonce]); + const contractAddress = kernel.self().bytes; + const intermediateRoleCommitment = persistentHash>>([roleId, account, nonce, contractAddress]); const index = _roleCommitmentIndex.lookup(disclose(intermediateRoleCommitment)); const finalRoleCommitment = persistentHash>>([intermediateRoleCommitment, index as Field as Bytes<32>]); _roleCommitmentNullifiers.insert(disclose(finalRoleCommitment)); diff --git a/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact index 24f996a0..91a6e39f 100644 --- a/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact +++ b/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact @@ -70,4 +70,4 @@ export circuit _requestRole(roleId: Bytes<32>): [] { export circuit _recoverRoles(): [] { ShieldedAccessControl__recoverRoles(); -} \ No newline at end of file +} From 98c86dbe8d69d0e9bd6fd33d76be5bb53f0307c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Wed, 13 Aug 2025 15:31:57 -0400 Subject: [PATCH 108/202] Update hash function in witness --- .../src/witnesses/ShieldedAccessControlWitnesses.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts index 88e1c589..74ae49bd 100644 --- a/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts @@ -47,7 +47,7 @@ function generateNonce( ): Buffer { const domainString = Buffer.from('role-nonce'); const info = Buffer.concat([domainString, roleId, account]); - const nonce = hkdfSync('sha512', secretKey, salt, info, KEYLEN); + const nonce = hkdfSync('sha256', secretKey, salt, info, KEYLEN); return Buffer.from(nonce); } From b556fad32fddb0af65ffbea709295fb90062a02f Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 15 Aug 2025 18:14:16 -0300 Subject: [PATCH 109/202] improve computeOwnerId assertion --- contracts/ownable/src/ZOwnablePK.compact | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/ownable/src/ZOwnablePK.compact b/contracts/ownable/src/ZOwnablePK.compact index 99f4a998..99aab56c 100644 --- a/contracts/ownable/src/ZOwnablePK.compact +++ b/contracts/ownable/src/ZOwnablePK.compact @@ -275,9 +275,7 @@ module ZOwnablePK { pk: Either, nonce: Bytes<32> ): Bytes<32> { - if (!pk.is_left) { - assert(false, "ZOwnablePK: contract address owners are not yet supported"); - } + assert(pk.is_left, "ZOwnablePK: contract address owners are not yet supported"); return persistentHash>>([pk.left.bytes, nonce]); } From a880f8dcd715604884f9fa4d059a250fcb82fcac Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 15 Aug 2025 19:45:23 -0300 Subject: [PATCH 110/202] use _computeOwnerId in assertOnlyOwner --- contracts/ownable/src/ZOwnablePK.compact | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/contracts/ownable/src/ZOwnablePK.compact b/contracts/ownable/src/ZOwnablePK.compact index 99aab56c..88e43e04 100644 --- a/contracts/ownable/src/ZOwnablePK.compact +++ b/contracts/ownable/src/ZOwnablePK.compact @@ -187,9 +187,13 @@ module ZOwnablePK { export circuit assertOnlyOwner(): [] { Initializable_assertInitialized(); - const caller = ownPublicKey(); const nonce = secretNonce(); - const id = persistentHash>>([caller.bytes, nonce]); + const callerAsEither = Either { + is_left: true, + left: ownPublicKey(), + right: ContractAddress { bytes: pad(32, "") } + }; + const id = _computeOwnerId(callerAsEither, nonce); assert(_ownerCommitment == _computeOwnerCommitment(id, _counter), "ZOwnablePK: caller is not the owner"); } @@ -267,7 +271,7 @@ module ZOwnablePK { * * - `pk` is not a ContractAddress. * - * @param {Bytes<32>} pk - The public key of the identity being committed. + * @param {Either} pk - The public key of the identity being committed. * @param {Bytes<32>} nonce - A private nonce to scope the commitment. * @returns {Bytes<32>} The computed owner ID. */ From de690186949c10dce94180e5fa69e2b9fb73f459 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 15 Aug 2025 19:47:57 -0300 Subject: [PATCH 111/202] fix fmt --- contracts/ownable/src/test/ZOwnablePK.test.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/contracts/ownable/src/test/ZOwnablePK.test.ts b/contracts/ownable/src/test/ZOwnablePK.test.ts index cde94414..987a68fc 100644 --- a/contracts/ownable/src/test/ZOwnablePK.test.ts +++ b/contracts/ownable/src/test/ZOwnablePK.test.ts @@ -20,7 +20,7 @@ const BAD_NONCE = Buffer.from(Buffer.alloc(32, 'BAD_NONCE')); const DOMAIN = 'ZOwnablePK:shield:'; const INIT_COUNTER = 1n; -let isInit = true; +const isInit = true; let secretNonce: Uint8Array; let ownable: ZOwnablePKSimulator; @@ -100,10 +100,14 @@ describe('ZOwnablePK', () => { }); describe('when not initialized correctly', () => { - let isNotInit = false; + const isNotInit = false; beforeEach(() => { - ownable = new ZOwnablePKSimulator(randomByteArray, INSTANCE_SALT, isNotInit); + ownable = new ZOwnablePKSimulator( + randomByteArray, + INSTANCE_SALT, + isNotInit, + ); }); type FailingCircuits = [method: keyof ZOwnablePKSimulator, args: unknown[]]; const randomByteArray = new Uint8Array(32).fill(123); From 92b46a85182e04ba2f72b240cac7127b07c73d1e Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 16 Aug 2025 20:20:15 -0300 Subject: [PATCH 112/202] add wrapAsEither circuits --- contracts/src/utils/Utils.compact | 46 +++++++++++++++++++ .../src/utils/test/mocks/MockUtils.compact | 16 +++++++ .../utils/test/simulators/UtilsSimulator.ts | 28 +++++++++++ contracts/src/utils/test/utils.test.ts | 29 ++++++++++++ 4 files changed, 119 insertions(+) diff --git a/contracts/src/utils/Utils.compact b/contracts/src/utils/Utils.compact index 3528e10e..b7291176 100644 --- a/contracts/src/utils/Utils.compact +++ b/contracts/src/utils/Utils.compact @@ -66,6 +66,52 @@ module Utils { return !keyOrAddress.is_left; } + /** + * @description Wraps a value as the `L` variant of a disjoint union (`Either`), + * following Compact conventions. + * + * Sets `is_left` to true, assigns the provided value to `left`, and sets `right` to `default` per convention. + * + * This helper is useful when you already have a value of type `L`, but the circuit interface expects an `Either`. + * It avoids manually constructing the full struct and ensures consistent formatting. + * + * @template L - Type of the Left variant. + * @template R - Type of the Right variant. + * + * @param {L} left - The value to wrap as the Left variant. + * @returns {Either} A disjoint union (`Either`) with the value in the `L` variant. + */ + export pure circuit wrapAsEitherLeft(left: L): Either { + return Either { + is_left: true, + left: left, + right: default + }; + } + + /** + * @description Wraps a value as the `R` variant of a disjoint union (`Either`), + * following Compact conventions. + * + * Sets `is_left` to false, assigns the provided value to `right`, and sets `left` to `default` per convention. + * + * This helper is useful when you already have a value of type `R`, but the circuit interface expects an `Either`. + * It avoids manually constructing the full struct and ensures consistent formatting. + * + * @template L - Type of the Left variant. + * @template R - Type of the Right variant. + * + * @param {R} right - The value to wrap as the `R` variant. + * @returns {Either} A disjoint union (`Either`) with the value in the `R` variant. + */ + export pure circuit wrapAsEitherRight(right: R): Either { + return Either { + is_left: false, + left: default, + right: right + }; + } + /** * @description A helper function that returns the empty string: "". * diff --git a/contracts/src/utils/test/mocks/MockUtils.compact b/contracts/src/utils/test/mocks/MockUtils.compact index 54fd45f3..c3b66d64 100644 --- a/contracts/src/utils/test/mocks/MockUtils.compact +++ b/contracts/src/utils/test/mocks/MockUtils.compact @@ -25,6 +25,22 @@ export pure circuit isContractAddress(keyOrAddress: Either { + return Utils_wrapAsEitherLeft(pk); +} + +// Find a better way to test different combinations +// other than creating a circuit for each pair +export pure circuit wrapAsEitherPkOrAddressRight( + address: ContractAddress, +): Either { + return Utils_wrapAsEitherRight(address); +} + export pure circuit emptyString(): Opaque<"string"> { return Utils_emptyString(); } diff --git a/contracts/src/utils/test/simulators/UtilsSimulator.ts b/contracts/src/utils/test/simulators/UtilsSimulator.ts index 715569f8..5f46223e 100644 --- a/contracts/src/utils/test/simulators/UtilsSimulator.ts +++ b/contracts/src/utils/test/simulators/UtilsSimulator.ts @@ -139,6 +139,34 @@ export class UtilsSimulator ).result; } + /** + * @description Returns `pk` wrapped in an `Either` type. + * @param pk The target value to wrap. + * @returns `Either` with `pk` in the left position. + */ + public wrapAsEitherPkOrAddressLeft( + pk: ZswapCoinPublicKey, + ): Either { + return this.contract.circuits.wrapAsEitherPkOrAddressLeft( + this.circuitContext, + pk, + ).result; + } + + /** + * @description Returns `address` wrapped in an `Either` type. + * @param pk The target value to wrap. + * @returns `Either` with `address` in the right position. + */ + public wrapAsEitherPkOrAddressRight( + address: ContractAddress, + ): Either { + return this.contract.circuits.wrapAsEitherPkOrAddressRight( + this.circuitContext, + address, + ).result; + } + /** * @description A helper function that returns the empty string: "" * @returns The empty string: "" diff --git a/contracts/src/utils/test/utils.test.ts b/contracts/src/utils/test/utils.test.ts index 1398d4d9..52b78acc 100644 --- a/contracts/src/utils/test/utils.test.ts +++ b/contracts/src/utils/test/utils.test.ts @@ -1,4 +1,9 @@ import { describe, expect, it } from 'vitest'; +import type { + ContractAddress, + Either, + ZswapCoinPublicKey, +} from './../../../artifacts/MockUtils/contract/index.cjs'; // Combined imports import { UtilsSimulator } from './simulators/UtilsSimulator.js'; import * as contractUtils from './utils/address.js'; @@ -87,6 +92,30 @@ describe('Utils', () => { }); }); + describe('wrapAsEitherLeft', () => { + it('should wrap pk as left', () => { + const pk = contractUtils.encodeToPK('PK'); + const exp: Either = { + is_left: true, + left: pk, + right: { bytes: new Uint8Array(32).fill(0) }, + }; + expect(contract.wrapAsEitherPkOrAddressLeft(pk)).toEqual(exp); + }); + }); + + describe('wrapAsEitherRight', () => { + it('should wrap address as right', () => { + const address = contractUtils.encodeToPK('ADDRESS'); + const exp: Either = { + is_left: false, + left: { bytes: new Uint8Array(32).fill(0) }, + right: address, + }; + expect(contract.wrapAsEitherPkOrAddressRight(address)).toEqual(exp); + }); + }); + describe('emptyString', () => { it('should return the empty string', () => { expect(contract.emptyString()).toBe(EMPTY_STRING); From 9fc5bbee9cccb63e3c3e6eec6c979af959dfcec9 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 16 Aug 2025 21:23:23 -0300 Subject: [PATCH 113/202] add option to compile directory in compact --- compact/src/Compiler.ts | 39 +++++++++++++++++++++++++++++++------- compact/src/runCompiler.ts | 37 ++++++++++++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/compact/src/Compiler.ts b/compact/src/Compiler.ts index 53b292ae..e79bd48f 100755 --- a/compact/src/Compiler.ts +++ b/compact/src/Compiler.ts @@ -27,6 +27,12 @@ const COMPACTC_PATH: string = join(COMPACT_HOME, 'compactc'); * compiler.compile().catch(err => console.error(err)); * ``` * + * @example Compile specific directory + * ```typescript + * const compiler = new CompactCompiler('--skip-zk', 'security'); + * compiler.compile().catch(err => console.error(err)); + * ``` + * * @example Successful Compilation Output * ``` * ℹ [COMPILE] Found 2 .compact file(s) to compile @@ -48,19 +54,26 @@ const COMPACTC_PATH: string = join(COMPACT_HOME, 'compactc'); export class CompactCompiler { /** Stores the compiler flags passed via command-line arguments */ private readonly flags: string; + /** Optional target directory to limit compilation scope */ + private readonly targetDir?: string; /** * Constructs a new CompactCompiler instance, validating the `compactc` binary path. * * @param flags - Space-separated string of `compactc` flags (e.g., "--skip-zk --no-communications-commitment") + * @param targetDir - Optional subdirectory within src/ to limit compilation (e.g., "security", "utils") * @throws {Error} If the `compactc` binary is not found at the resolved path */ - constructor(flags: string) { + constructor(flags: string, targetDir?: string) { this.flags = flags.trim(); + this.targetDir = targetDir; const spinner = ora(); spinner.info(chalk.blue(`[COMPILE] COMPACT_HOME: ${COMPACT_HOME}`)); spinner.info(chalk.blue(`[COMPILE] COMPACTC_PATH: ${COMPACTC_PATH}`)); + if (this.targetDir) { + spinner.info(chalk.blue(`[COMPILE] TARGET_DIR: ${this.targetDir}`)); + } if (!existsSync(COMPACTC_PATH)) { spinner.fail( @@ -73,25 +86,36 @@ export class CompactCompiler { } /** - * Compiles all `.compact` files in the source directory and its subdirectories (e.g., `src/test/mock/`). - * Scans the `src` directory recursively for `.compact` files, compiles each one using `compactc`, - * and displays progress with a spinner and colored output. + * Compiles all `.compact` files in the source directory (or target subdirectory) and its subdirectories. + * Scans the `src` directory (or `src/{targetDir}`) recursively for `.compact` files, + * compiles each one using `compactc`, and displays progress with a spinner and colored output. * * @returns A promise that resolves when all files are compiled successfully * @throws {Error} If compilation fails for any file */ public async compile(): Promise { - const compactFiles: string[] = await this.getCompactFiles(SRC_DIR); + const searchDir = this.targetDir ? join(SRC_DIR, this.targetDir) : SRC_DIR; + + // Validate target directory exists + if (this.targetDir && !existsSync(searchDir)) { + const spinner = ora(); + spinner.fail(chalk.red(`[COMPILE] Error: Target directory ${searchDir} does not exist.`)); + throw new Error(`Target directory ${searchDir} does not exist`); + } + + const compactFiles: string[] = await this.getCompactFiles(searchDir); const spinner = ora(); if (compactFiles.length === 0) { - spinner.warn(chalk.yellow('[COMPILE] No .compact files found.')); + const searchLocation = this.targetDir ? `${this.targetDir}/` : ''; + spinner.warn(chalk.yellow(`[COMPILE] No .compact files found in ${searchLocation}.`)); return; } + const searchLocation = this.targetDir ? ` in ${this.targetDir}/` : ''; spinner.info( chalk.blue( - `[COMPILE] Found ${compactFiles.length} .compact file(s) to compile`, + `[COMPILE] Found ${compactFiles.length} .compact file(s) to compile${searchLocation}`, ), ); @@ -123,6 +147,7 @@ export class CompactCompiler { } if (entry.isFile() && fullPath.endsWith('.compact')) { + // Always return relative path from SRC_DIR, regardless of search directory return [relative(SRC_DIR, fullPath)]; } return []; diff --git a/compact/src/runCompiler.ts b/compact/src/runCompiler.ts index b7a84f24..9c6215ff 100644 --- a/compact/src/runCompiler.ts +++ b/compact/src/runCompiler.ts @@ -12,13 +12,20 @@ import { CompactCompiler } from './Compiler.js'; * ```bash * npx compact-compiler --skip-zk * ``` + * + * @example Compile specific directory + * ```bash + * npx compact-compiler --dir security --skip-zk + * ``` + * * Expected output: * ``` * ℹ [COMPILE] Compact compiler started * ℹ [COMPILE] COMPACT_HOME: /path/to/compactc * ℹ [COMPILE] COMPACTC_PATH: /path/to/compactc/compactc - * ℹ [COMPILE] Found 1 .compact file(s) to compile - * ✔ [COMPILE] [1/1] Compiled Foo.compact + * ℹ [COMPILE] TARGET_DIR: security + * ℹ [COMPILE] Found 1 .compact file(s) to compile in security/ + * ✔ [COMPILE] [1/1] Compiled security/AccessControl.compact * Compactc version: 0.24.0 * ``` */ @@ -26,8 +33,30 @@ async function runCompiler(): Promise { const spinner = ora(chalk.blue('[COMPILE] Compact Compiler started')).info(); try { - const compilerFlags = process.argv.slice(2).join(' '); - const compiler = new CompactCompiler(compilerFlags); + const args = process.argv.slice(2); + + // Parse arguments more robustly + let targetDir: string | undefined; + const compilerFlags: string[] = []; + + for (let i = 0; i < args.length; i++) { + if (args[i] === '--dir') { + if (i + 1 < args.length && !args[i + 1].startsWith('--')) { + targetDir = args[i + 1]; + i++; // Skip the next argument (directory name) + } else { + spinner.fail(chalk.red('[COMPILE] Error: --dir flag requires a directory name')); + console.log(chalk.yellow('Usage: compact-compiler --dir [other-flags]')); + console.log(chalk.yellow('Example: compact-compiler --dir security --skip-zk')); + process.exit(1); + } + } else { + // All other arguments are compiler flags + compilerFlags.push(args[i]); + } + } + + const compiler = new CompactCompiler(compilerFlags.join(' '), targetDir); await compiler.compile(); } catch (err) { spinner.fail( From 98b7a3210f04cb5f925fcc89b1259fda1a8c1a7f Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 16 Aug 2025 21:24:23 -0300 Subject: [PATCH 114/202] add granular compile scripts --- contracts/package.json | 5 ++++ turbo.json | 57 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/contracts/package.json b/contracts/package.json index 493433f8..fc36646d 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -15,6 +15,11 @@ }, "scripts": { "compact": "compact-compiler", + "compact:access": "compact-compiler --dir access", + "compact:archive": "compact-compiler --dir archive", + "compact:security": "compact-compiler --dir security", + "compact:token": "compact-compiler --dir token", + "compact:utils": "compact-compiler --dir utils", "build": "compact-builder && tsc", "test": "vitest run", "types": "tsc -p tsconfig.json --noEmit", diff --git a/turbo.json b/turbo.json index fc4bb12f..4b5acd9c 100644 --- a/turbo.json +++ b/turbo.json @@ -1,10 +1,63 @@ { "$schema": "https://turbo.build/schema.json", "tasks": { - "compact": { + "compact:security": { "dependsOn": ["^build"], "env": ["COMPACT_HOME"], - "inputs": ["contracts/src/**/*.compact"], + "inputs": ["src/security/**/*.compact"], + "outputLogs": "new-only", + "outputs": ["artifacts/**/"] + }, + "compact:utils": { + "dependsOn": ["^build"], + "env": ["COMPACT_HOME"], + "inputs": ["src/utils/**/*.compact"], + "outputLogs": "new-only", + "outputs": ["artifacts/**/"] + }, + "compact:access": { + "dependsOn": ["^build", "compact:security", "compact:utils"], + "env": ["COMPACT_HOME"], + "inputs": [ + "src/access/**/*.compact", + "artifacts/**" + ], + "outputLogs": "new-only", + "outputs": ["artifacts/**/"] + }, + "compact:archive": { + "dependsOn": ["^build", "compact:utils"], + "env": ["COMPACT_HOME"], + "inputs": [ + "src/archive/**/*.compact", + "artifacts/**" + ], + "outputLogs": "new-only", + "outputs": ["artifacts/**/"] + }, + "compact:token": { + "dependsOn": ["^build", "compact:security", "compact:utils"], + "env": ["COMPACT_HOME"], + "inputs": [ + "src/token/**/*.compact", + "artifacts/**" + ], + "outputLogs": "new-only", + "outputs": ["artifacts/**/"] + }, + "compact": { + "dependsOn": [ + "compact:security", + "compact:utils", + "compact:access", + "compact:archive", + "compact:token" + ], + "env": ["COMPACT_HOME"], + "inputs": [ + "src/**/*.compact", + "test/**/*.compact" + ], "outputLogs": "new-only", "outputs": ["artifacts/**"] }, From ae9e3118b8df28b02d847e4aed97d7e68ecf8bf9 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 16 Aug 2025 21:36:15 -0300 Subject: [PATCH 115/202] fix fmt --- compact/src/Compiler.ts | 10 ++++++++-- compact/src/runCompiler.ts | 14 +++++++++++--- turbo.json | 20 ++++---------------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/compact/src/Compiler.ts b/compact/src/Compiler.ts index e79bd48f..af426286 100755 --- a/compact/src/Compiler.ts +++ b/compact/src/Compiler.ts @@ -99,7 +99,11 @@ export class CompactCompiler { // Validate target directory exists if (this.targetDir && !existsSync(searchDir)) { const spinner = ora(); - spinner.fail(chalk.red(`[COMPILE] Error: Target directory ${searchDir} does not exist.`)); + spinner.fail( + chalk.red( + `[COMPILE] Error: Target directory ${searchDir} does not exist.`, + ), + ); throw new Error(`Target directory ${searchDir} does not exist`); } @@ -108,7 +112,9 @@ export class CompactCompiler { const spinner = ora(); if (compactFiles.length === 0) { const searchLocation = this.targetDir ? `${this.targetDir}/` : ''; - spinner.warn(chalk.yellow(`[COMPILE] No .compact files found in ${searchLocation}.`)); + spinner.warn( + chalk.yellow(`[COMPILE] No .compact files found in ${searchLocation}.`), + ); return; } diff --git a/compact/src/runCompiler.ts b/compact/src/runCompiler.ts index 9c6215ff..a1e9e002 100644 --- a/compact/src/runCompiler.ts +++ b/compact/src/runCompiler.ts @@ -45,9 +45,17 @@ async function runCompiler(): Promise { targetDir = args[i + 1]; i++; // Skip the next argument (directory name) } else { - spinner.fail(chalk.red('[COMPILE] Error: --dir flag requires a directory name')); - console.log(chalk.yellow('Usage: compact-compiler --dir [other-flags]')); - console.log(chalk.yellow('Example: compact-compiler --dir security --skip-zk')); + spinner.fail( + chalk.red('[COMPILE] Error: --dir flag requires a directory name'), + ); + console.log( + chalk.yellow( + 'Usage: compact-compiler --dir [other-flags]', + ), + ); + console.log( + chalk.yellow('Example: compact-compiler --dir security --skip-zk'), + ); process.exit(1); } } else { diff --git a/turbo.json b/turbo.json index 4b5acd9c..fdc0cb74 100644 --- a/turbo.json +++ b/turbo.json @@ -18,30 +18,21 @@ "compact:access": { "dependsOn": ["^build", "compact:security", "compact:utils"], "env": ["COMPACT_HOME"], - "inputs": [ - "src/access/**/*.compact", - "artifacts/**" - ], + "inputs": ["src/access/**/*.compact", "artifacts/**"], "outputLogs": "new-only", "outputs": ["artifacts/**/"] }, "compact:archive": { "dependsOn": ["^build", "compact:utils"], "env": ["COMPACT_HOME"], - "inputs": [ - "src/archive/**/*.compact", - "artifacts/**" - ], + "inputs": ["src/archive/**/*.compact", "artifacts/**"], "outputLogs": "new-only", "outputs": ["artifacts/**/"] }, "compact:token": { "dependsOn": ["^build", "compact:security", "compact:utils"], "env": ["COMPACT_HOME"], - "inputs": [ - "src/token/**/*.compact", - "artifacts/**" - ], + "inputs": ["src/token/**/*.compact", "artifacts/**"], "outputLogs": "new-only", "outputs": ["artifacts/**/"] }, @@ -54,10 +45,7 @@ "compact:token" ], "env": ["COMPACT_HOME"], - "inputs": [ - "src/**/*.compact", - "test/**/*.compact" - ], + "inputs": ["src/**/*.compact", "test/**/*.compact"], "outputLogs": "new-only", "outputs": ["artifacts/**"] }, From 4a9885ba116b7303b11a014e057e6cb3e69675d4 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 16 Aug 2025 22:53:48 -0300 Subject: [PATCH 116/202] use fast compilation prior to tests, cache tests --- contracts/package.json | 2 +- turbo.json | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/contracts/package.json b/contracts/package.json index fc36646d..acd41414 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -21,7 +21,7 @@ "compact:token": "compact-compiler --dir token", "compact:utils": "compact-compiler --dir utils", "build": "compact-builder && tsc", - "test": "vitest run", + "test": "compact-compiler --skip-zk && vitest run", "types": "tsc -p tsconfig.json --noEmit", "clean": "git clean -fXd" }, diff --git a/turbo.json b/turbo.json index fdc0cb74..86592d3e 100644 --- a/turbo.json +++ b/turbo.json @@ -50,9 +50,16 @@ "outputs": ["artifacts/**"] }, "test": { - "dependsOn": ["^build", "compact"], + "dependsOn": ["^build"], + "env": ["COMPACT_HOME"], + "inputs": [ + "src/**/*.ts", + "src/**/*.compact", + "vitest.config.ts", + "package.json" + ], "outputs": [], - "cache": false + "cache": true }, "build": { "dependsOn": ["^build"], From 6fb3f74f54a29bc988aa86175009ce2f1b7b4e67 Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 17 Aug 2025 01:53:05 -0300 Subject: [PATCH 117/202] add circuit tag --- contracts/ownable/src/ZOwnablePK.compact | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/ownable/src/ZOwnablePK.compact b/contracts/ownable/src/ZOwnablePK.compact index 88e43e04..bf06a4a3 100644 --- a/contracts/ownable/src/ZOwnablePK.compact +++ b/contracts/ownable/src/ZOwnablePK.compact @@ -217,6 +217,8 @@ module ZOwnablePK { * even with repeated `id` values. * - `domain`: A domain separator to prevent hash collisions when extending the module. * + * @circuitInfo k=???, rows=??? + * * Requirements: * * - Contract is initialized. From 2ac92b54d556bc9c9caaba1cb00dad663ae25f08 Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 17 Aug 2025 16:30:20 -0300 Subject: [PATCH 118/202] move ZOwnablePK witness interface --- .../src/access/witnesses/ZOwnablePKWitnesses.ts | 14 +++++++++++++- contracts/src/access/witnesses/interface.ts | 15 --------------- 2 files changed, 13 insertions(+), 16 deletions(-) delete mode 100644 contracts/src/access/witnesses/interface.ts diff --git a/contracts/src/access/witnesses/ZOwnablePKWitnesses.ts b/contracts/src/access/witnesses/ZOwnablePKWitnesses.ts index ce1e0aec..f034951b 100644 --- a/contracts/src/access/witnesses/ZOwnablePKWitnesses.ts +++ b/contracts/src/access/witnesses/ZOwnablePKWitnesses.ts @@ -1,7 +1,19 @@ import { getRandomValues } from 'node:crypto'; import type { WitnessContext } from '@midnight-ntwrk/compact-runtime'; import type { Ledger } from '../../../artifacts/MockZOwnablePK/contract/index.cjs'; -import type { IZOwnablePKWitnesses } from './interface.js'; + +/** + * @description Interface defining the witness methods for ZOwnablePK operations. + * @template P - The private state type. + */ +export interface IZOwnablePKWitnesses

{ + /** + * Retrieves the secret nonce from the private state. + * @param context - The witness context containing the private state. + * @returns A tuple of the private state and the secret nonce as a Uint8Array. + */ + secretNonce(context: WitnessContext): [P, Uint8Array]; +} /** * @description Represents the private state of an ownable contract, storing a secret nonce. diff --git a/contracts/src/access/witnesses/interface.ts b/contracts/src/access/witnesses/interface.ts deleted file mode 100644 index fb84e59d..00000000 --- a/contracts/src/access/witnesses/interface.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { WitnessContext } from '@midnight-ntwrk/compact-runtime'; -import type { Ledger } from '../../../artifacts/MockZOwnablePK/contract/index.cjs'; // Combined imports - -/** - * @description Interface defining the witness methods for Ownable operations. - * @template P - The private state type. - */ -export interface IZOwnablePKWitnesses

{ - /** - * Retrieves the secret nonce from the private state. - * @param context - The witness context containing the private state. - * @returns A tuple of the private state and the secret nonce as a Uint8Array. - */ - secretNonce(context: WitnessContext): [P, Uint8Array]; -} From db87258e357f3855b64405261392418d2c656f52 Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 17 Aug 2025 21:45:02 -0300 Subject: [PATCH 119/202] improve in-code docs --- contracts/src/access/ZOwnablePK.compact | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/contracts/src/access/ZOwnablePK.compact b/contracts/src/access/ZOwnablePK.compact index 564e8093..65225663 100644 --- a/contracts/src/access/ZOwnablePK.compact +++ b/contracts/src/access/ZOwnablePK.compact @@ -93,7 +93,7 @@ module ZOwnablePK { * Requirements: * * - Contract is not initialized. - * - `ownerId` is not all zeroes. + * - `ownerId` is not an empty array. * * @param {Bytes<32>} ownerId - The owner's unique identifier H(pk, nonce). * @param {Bytes<32>} instanceSalt - Contract salt to prevent duplicate commitments if @@ -134,8 +134,8 @@ module ZOwnablePK { * Requirements: * * - Contract is initialized. - * - Caller must be the current owner. - * - `newOwnerId` must not be all zeroes. + * - Caller is the the current owner. + * - `newOwnerId` is not an empty array. * * @param {Bytes<32>} newOwnerId - The new owner's unique identifier (`H(pk, nonce)`). * @returns {[]} Empty tuple. @@ -158,7 +158,7 @@ module ZOwnablePK { * Requirements: * * - Contract is initialized. - * - Caller must be the current owner. + * - Caller is the the current owner. * * @returns {[]} Empty tuple. */ @@ -172,14 +172,14 @@ module ZOwnablePK { /** * @description Throws if called by any account whose id hash `H(pk, nonce)` does not match * the stored owner commitment. - * Use this to restrict access of specific circuits to the owner. + * Use this to only allow the owner to call specific circuits. * * @circuitInfo k=???, rows=??? * * Requirements: * * - Contract is initialized. - * - Caller's id (`H(pk, nonce)`) when used in `_computeOwnerCommitment` must equal + * - Caller's id (`H(pk, nonce)`) when used in `_computeOwnerCommitment` equals * the stored `_ownerCommitment`, thus verifying themselves as the owner. * * @returns {[]} Empty tuple. @@ -212,10 +212,11 @@ module ZOwnablePK { * * - `id`: See above. * - `instanceSalt`: A unique per-deployment salt, stored during initialization. - * This prevents commitment collisions across deployments. + * This prevents commitment collisions across deployments. * - `counter`: Incremented with each ownership transfer, ensuring uniqueness - * even with repeated `id` values. - * - `domain`: A domain separator to prevent hash collisions when extending the module. + * even with repeated `id` values. Cast to `Field` then `Bytes<32>` for hashing. + * - `domain`: Domain separator `"ZOwnablePK:shield:"` (padded to 32 bytes) to prevent + * hash collisions when extending the module or using similar commitment schemes. * * @circuitInfo k=???, rows=??? * @@ -253,13 +254,12 @@ module ZOwnablePK { * - `pk`: The public key of the caller. This is passed explicitly to allow * for off-chain derivation, testing, or scenarios where the caller is * different from the subject of the computation. - * - `nonce`: A secret nonce tied to the identity. This value should be - * randomly generated and kept private. It may be rotated periodically - * for enhanced unlinkability. + * - `nonce`: A secret nonce tied to the identity. The generation strategy is + * left to the user, offering different security/convenience trade-offs. * * The result is a 32-byte commitment that uniquely identifies the owner. - * This value is later used in owner commitment hashing, and acts as a privacy-preserving - * alternative to a raw public key. + * This value is later used in owner commitment hashing, + * and acts as a privacy-preserving alternative to a raw public key. * * @notice This module allows ownership to be tied to an identity commitment derived * from a public key and secret nonce. From 9d78542fb483729570ac95d43bb26e5ee929ec41 Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 17 Aug 2025 21:45:22 -0300 Subject: [PATCH 120/202] add ZOwnablePK api --- docs/modules/ROOT/pages/api/access.adoc | 200 ++++++++++++++++++++++++ 1 file changed, 200 insertions(+) diff --git a/docs/modules/ROOT/pages/api/access.adoc b/docs/modules/ROOT/pages/api/access.adoc index 7237e42b..7a8c1e39 100644 --- a/docs/modules/ROOT/pages/api/access.adoc +++ b/docs/modules/ROOT/pages/api/access.adoc @@ -399,3 +399,203 @@ Requirements: Constraints: - k=10, rows=216 + +[.contract] +[[ZOwnablePK]] +=== `++ZOwnablePK++` link:https://github.com/OpenZeppelin/compact-contracts/blob/main/contracts/ownable/src/ZOwnablePK.compact[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```ts +import "./node_modules/@openzeppelin-compact/contracts/src/access/ZOwnablePK"; +``` + +`ZOwnablePK` provides a privacy-preserving access control mechanism for contracts with a single administrative user. Unlike traditional `Ownable` implementations that store or expose the owner's public key on-chain, +this module stores only a commitment to a hashed identifier derived from the owner's public key and a secret nonce. + +Ownable provides a basic access control mechanism where an account (an owner) can be granted exclusive access to specific circuits. + +This module includes <> to restrict a circuit to be used only by the owner. + +TIP: For an overview of the module, read the {ownable-guide}. + +[.contract-index] +.Circuits +-- + +[.sub-index#ZOwnablePKModule] +* xref:#ZOwnablePK-initialize[`++initialize(ownerId, instanceSalt)++`] +* xref:#ZOwnablePK-owner[`++owner()++`] +* xref:#ZOwnablePK-transferOwnership[`++transferOwnership(newOwnerId)++`] +* xref:#ZOwnablePK-renounceOwnership[`++renounceOwnership()++`] +* xref:#ZOwnablePK-assertOnlyOwner[`++assertOnlyOwner(operator, approved)++`] +* xref:#ZOwnablePK-_computeOwnerCommitment[`++_computeOwnerCommitment(id, counter)++`] +* xref:#ZOwnablePK-_computeOwnerId[`++_computeOwnerId(pk, nonce)++`] +* xref:#ZOwnablePK-_transferOwnership[`++_transferOwnership(newOwnerId)++`] +-- + +[.contract-item] +[[ZOwnablePK-initialize]] +==== `[.contract-item-name]#++initialize++#++(initialOwner: Either) → []++` [.item-kind]#circuit# + +Initializes the contract by setting the initial owner via `ownerId` +and storing the `instanceSalt` that acts as a privacy additive +for preventing duplicate commitments among other contracts implementing ZOwnablePK. + +NOTE: The `ownerId` must be calculated prior to contract deployment. +See <> + +Requirements: + +- Contract is not already initialized. +- `ownerId` is not an empty array. + +Constraints: + +- k=???, rows=??? + +[.contract-item] +[[ZOwnablePK-owner]] +==== `[.contract-item-name]#++owner++#++() → Bytes<32>++` [.item-kind]#circuit# + +Returns the current commitment representing the contract owner. +The full commitment is: `H(H(pk, nonce), instanceSalt, counter, domain)`. + +Requirements: + +- Contract is initialized. + +Constraints: + +- k=???, rows=??? + +[.contract-item] +[[ZOwnablePK-transferOwnership]] +==== `[.contract-item-name]#++transferOwnership++#++(newOwnerId: Bytes<32>) → []++` [.item-kind]#circuit# + +Transfers ownership of the contract to `newOwnerId`. +`newOwnerId` must be precalculated and given to the current owner off chain. + +Requirements: + +- Contract is initialized. +- Caller is the current contract owner. +- `newOwnerId` is not an empty array. + +Constraints: + +- k=???, rows=??? + +[.contract-item] +[[ZOwnablePK-renounceOwnership]] +==== `[.contract-item-name]#++renounceOwnership++#++() → []++` [.item-kind]#circuit# + +Leaves the contract without an owner. +It will not be possible to call <> circuits anymore. +Can only be called by the current owner. + +Requirements: + +- Contract is initialized. +- Caller is the current owner. + +Constraints: + +- k=???, rows=??? + +[.contract-item] +[[ZOwnablePK-assertOnlyOwner]] +==== `[.contract-item-name]#++assertOnlyOwner++#++() → []++` [.item-kind]#circuit# + +Throws if called by any account whose id hash `H(pk, nonce)` does not match the stored owner commitment. +Use this to only allow the owner to call specific circuits. + +Requirements: + +- Contract is initialized. +- Caller's id (`H(pk, nonce)`) when used in <> equals the stored `_ownerCommitment`, +thus verifying themselves as the owner. + +Constraints: + +- k=???, rows=??? + +[.contract-item] +[[ZOwnablePK-_computeOwnerCommitment]] +==== `[.contract-item-name]#++_computeOwnerCommitment++#++(id: Bytes<32>, counter: Uint<64>) → Bytes<32>++` [.item-kind]#circuit# + +Computes the owner commitment from the given `id` and `counter`. + +**Owner ID (`id`)** + +The `id` is expected to be computed off-chain as: `id = H(pk, nonce)` + +- `pk`: The owner's public key. +- `nonce`: A secret nonce scoped to the instance, ideally rotated with each transfer. + +**Commitment Derivation** + +`commitment = H(id, instanceSalt, counter, domain)` + +- `id`: See above. +- `instanceSalt`: A unique per-deployment salt, stored during initialization. +This prevents commitment collisions across deployments. +- `counter`: Incremented with each ownership transfer, ensuring uniqueness even with repeated `id` values. +Cast to `Field` then `Bytes<32>` for hashing. +- `domain`: Domain separator `"ZOwnablePK:shield:"` (padded to 32 bytes) to prevent hash collisions +when extending the module or using similar commitment schemes. + +Requirements: + +- Contract is initialized. + +Constraints: + +- k=???, rows=??? + +[.contract-item] +[[ZOwnablePK-_computeOwnerId]] +==== `[.contract-item-name]#++_computeOwnerId++#++(pk: Either, nonce: Bytes<32>) → Bytes<32>++` [.item-kind]#circuit# + +Computes the unique identifier (`id`) of the owner from their public key and a secret nonce. + +**ID Derivation** +`id = H(pk, nonce)` + +- `pk`: The public key of the caller. +This is passed explicitly to allow for off-chain derivation, testing, or scenarios +where the caller is different from the subject of the computation. +- `nonce`: A secret nonce tied to the identity. +This value should be randomly generated and kept private. +It may be rotated periodically for enhanced unlinkability. + +The result is a 32-byte commitment that uniquely identifies the owner. +This value is later used in owner commitment hashing, +and acts as a privacy-preserving alternative to a raw public key. + +NOTE: This module allows ownership to be tied to an identity commitment derived from a public key and secret nonce. +While typically used with user public keys, +this mechanism may also support contract addresses as identifiers in future contract-to-contract interactions. +Both are treated as 32-byte values (`Bytes<32>`). + +Requirements: + +- Contract is initialized. +- `newOwner` is not a ContractAddress. + +Constraints: + +- k=???, rows=??? + +[.contract-item] +[[ZOwnablePK-_transferOwnership]] +==== `[.contract-item-name]#++_transferOwnership++#++(newOwnerId: Bytes<32>) → []++` [.item-kind]#circuit# + +Transfers ownership to owner id `newOwnerId` without enforcing permission checks on the caller. + +Requirements: + +- Contract is initialized. + +Constraints: + +- k=???, rows=??? From 96f95c920f1c24f0d38a9f0139f7f963d209eaa2 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 18 Aug 2025 01:10:16 -0300 Subject: [PATCH 121/202] add withNonce to PS --- .../access/witnesses/ZOwnablePKWitnesses.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/contracts/src/access/witnesses/ZOwnablePKWitnesses.ts b/contracts/src/access/witnesses/ZOwnablePKWitnesses.ts index f034951b..58098d02 100644 --- a/contracts/src/access/witnesses/ZOwnablePKWitnesses.ts +++ b/contracts/src/access/witnesses/ZOwnablePKWitnesses.ts @@ -34,6 +34,24 @@ export const ZOwnablePKPrivateState = { generate: (): ZOwnablePKPrivateState => { return { secretNonce: getRandomValues(Buffer.alloc(32)) }; }, + + /** + * @description Generates a new private state with a user-defined secret nonce. + * Useful for deterministic nonce generation or advanced use cases. + * + * @param nonce - The 32-byte secret nonce to use. + * @returns A fresh ZOwnablePKPrivateState instance with the provided nonce. + * + * @example + * ```typescript + * // For deterministic nonces (user-defined scheme) + * const deterministicNonce = myDeterministicScheme(...); + * const privateState = ZOwnablePKPrivateState.withNonce(deterministicNonce); + * ``` + */ + withNonce: (nonce: Buffer): ZOwnablePKPrivateState => { + return { secretNonce: nonce }; + }, }; /** From c16cd7db0e0ab57d943120eb9077c59d3d5da69f Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 18 Aug 2025 01:10:57 -0300 Subject: [PATCH 122/202] add ZOwnablePK docs (less setup) --- docs/modules/ROOT/pages/access.adoc | 125 ++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/docs/modules/ROOT/pages/access.adoc b/docs/modules/ROOT/pages/access.adoc index f39581d5..a104d887 100644 --- a/docs/modules/ROOT/pages/access.adoc +++ b/docs/modules/ROOT/pages/access.adoc @@ -133,6 +133,131 @@ there is no direct way for a contract to call circuits of other contracts or tra NOTE: The unsafe circuits are planned to become deprecated once contract-to-contract calls become available. +== Shielded Ownership and `ZOwnablePK` + +Privacy-preserving access control is a fundamental building block for confidential smart contracts on Midnight. +While traditional ownership patterns expose the owner's identity on-chain, +many applications require administrative control without revealing who holds that authority. + +=== Privacy-First Ownership + +The most common approach to access control in traditional smart contracts is ownership: +there's an account that is the owner of a contract and can perform administrative tasks. +However, this approach reveals the owner's identity to all observers, creating privacy and security risks. +In privacy-sensitive applications—such as confidential voting systems, private treasuries, or anonymous governance—revealing the administrator's identity may compromise the entire system's confidentiality. +This library provides the `ZOwnablePK` module that implements shielded ownership—administrative control without identity disclosure. +The owner's public key is never revealed on-chain; instead, +the contract stores only a cryptographic commitment that proves ownership without exposing the underlying identity. + +=== Commitment Scheme + +The `ZOwnablePK` module employs a two-layer cryptographic commitment scheme designed to provide privacy, +unlinkability, and collision resistance across deployments and ownership transfers. + +==== Owner ID Computation + +The foundation of the system is the owner identifier, computed as: + +```ts +id = H(pk, nonce) +``` + +Where `pk` is the owner's public key and `nonce` is a secret value that may be either randomly generated +for maximum privacy or deterministically derived for recoverability. +This identifier serves as a privacy-preserving alternative to exposing the raw public key, +ensuring the owner's identity remains confidential. + +==== Owner Commitment Computation + +The final ownership commitment stored on-chain is computed as: + +```ts +commitment = H(id, instanceSalt, counter, pad(32, "ZOwnablePK:shield:")) +``` + +This multi-element hash provides several security properties: + +- `id`: The privacy-preserving owner identifier described above. +- `instanceSalt`: A unique per-deployment salt that prevents commitment collisions across different contract instances, even when the same owner and nonce are used. +- `counter`: Incremented with each ownership transfer to ensure unlinkability—each transfer produces a completely different commitment even with the same underlying owner. +- `pad(32, "ZOwnablePK:shield:")`: A domain separator padded to 32 bytes that prevents hash collisions with other commitment schemes and enables safe protocol extensions. + +==== Security Properties + +This commitment scheme ensures that: + +- Owner identities remain completely private—public keys are never revealed on-chain. +- Ownership transfers are unlinkable—observers cannot correlate past and future ownership. +- Cross-contract attacks are prevented through instance-specific salting. + +=== Nonce Generation Strategies + +The choice of nonce generation strategy represents a fundamental trade-off between simplicity/security and recoverability. +Both approaches are valid, and the best choice depends on your specific threat model and operational requirements. + +==== Random Nonce + +Generating a cryptographically random nonce provides the strongest privacy guarantees: + +```typescript +const randomNonce = crypto.getRandomValues(new Uint8Array(32)); +const ownerId = ZOwnablePK._computeOwnerId(publicKey, randomNonce); +``` + +This approach is easy to generate and ensures maximum unlinkability—even with sophisticated analysis, +observers cannot correlate ownership across different contracts or time periods. +However, it requires secure backup of both the private key and the nonce. +*Loss of either component results in permanent, irrecoverable loss of ownership.* + +==== Deterministic Nonce + +Deriving the nonce deterministically enables recovery through derivation schemes. +Some examples: + +- `H(passphrase + context)` - recoverable from passphrase only, but passphrase becomes critical single point of failure. +- `H(publicKey + userPassphrase + context)` - requires both public key and passphrase. +- `H(signature + context)` where `signature = sign(context)` - leverages wallet without exposing private key. + +*Context-Dependent Derivations:* + +- Include contract address, deployment timestamp, user ID, etc. +- Trade-off: more context is more unique but harder to recreate. + +WARNING: Approaches that avoid private key exposure (public key + passphrase, signature-based) +are generally recommended for operational security. + +Deriving the nonce deterministically from the public key and user passphrase provides a balance of security and recoverability: + +```typescript +// Example: Scrypt-based derivation +import { scryptSync } from 'node:crypto'; + +const deterministicNonce = scryptSync( + userPassphrase + publicKey + ":ZOwnablePK:nonce:v1", + 32, + { N: 16384, r: 8, p: 1 } // Standard scrypt parameters +); +const recoverableOwnerId = ZOwnablePK._computeOwnerId(publicKey, deterministicNonce); +``` + +**Security Considerations** + +The `ZOwnablePK` module remains agnostic to nonce generation methods, placing the security/convenience decision entirely with the user. Key considerations include: + +- **Backup requirements**: Random nonces require additional secure storage. +- **Recovery scenarios**: Deterministic nonces enable recovery. +- **Cross-contract correlation**: Reusing nonce strategies may reduce privacy across deployments. +- **Rotation costs**: Changing nonces requires ownership transfer transactions with associated DUST costs. + +Users should carefully evaluate their threat model, operational requirements, +and privacy needs when selecting a nonce generation strategy, +as this choice cannot be easily changed without transferring ownership. + +=== Setup + +Add meee! + == Role-Based Access Control While the simplicity of _ownership_ can be useful for simple systems or quick prototyping, different levels of authorization are often needed. From 3586158a01a2212a45d534c93a51e6336e6773d9 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 18 Aug 2025 01:14:38 -0300 Subject: [PATCH 123/202] improve sec prop section --- docs/modules/ROOT/pages/access.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/modules/ROOT/pages/access.adoc b/docs/modules/ROOT/pages/access.adoc index a104d887..d7f180f1 100644 --- a/docs/modules/ROOT/pages/access.adoc +++ b/docs/modules/ROOT/pages/access.adoc @@ -186,9 +186,9 @@ This multi-element hash provides several security properties: This commitment scheme ensures that: -- Owner identities remain completely private—public keys are never revealed on-chain. -- Ownership transfers are unlinkable—observers cannot correlate past and future ownership. -- Cross-contract attacks are prevented through instance-specific salting. +- Public keys are never revealed on-chain. +- Observers cannot correlate past and future ownership. +- Cross-contract collisions are prevented through instance-specific salting. === Nonce Generation Strategies From 33148b06ba7766e7e86ab9f164983e657402ded0 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 18 Aug 2025 01:17:40 -0300 Subject: [PATCH 124/202] fix fmt --- contracts/src/access/ZOwnablePK.compact | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/src/access/ZOwnablePK.compact b/contracts/src/access/ZOwnablePK.compact index 65225663..3de3e4f1 100644 --- a/contracts/src/access/ZOwnablePK.compact +++ b/contracts/src/access/ZOwnablePK.compact @@ -252,10 +252,10 @@ module ZOwnablePK { * `id = H(pk, nonce)` * * - `pk`: The public key of the caller. This is passed explicitly to allow - * for off-chain derivation, testing, or scenarios where the caller is - * different from the subject of the computation. + * for off-chain derivation, testing, or scenarios where the caller is + * different from the subject of the computation. * - `nonce`: A secret nonce tied to the identity. The generation strategy is - * left to the user, offering different security/convenience trade-offs. + * left to the user, offering different security/convenience trade-offs. * * The result is a 32-byte commitment that uniquely identifies the owner. * This value is later used in owner commitment hashing, From 71ad93fba0d089c6eb7ebd3ec45498073a3e3b58 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 18 Aug 2025 02:47:31 -0300 Subject: [PATCH 125/202] add usage section --- docs/modules/ROOT/pages/access.adoc | 81 ++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/access.adoc b/docs/modules/ROOT/pages/access.adoc index d7f180f1..7ebb095c 100644 --- a/docs/modules/ROOT/pages/access.adoc +++ b/docs/modules/ROOT/pages/access.adoc @@ -254,9 +254,86 @@ Users should carefully evaluate their threat model, operational requirements, and privacy needs when selecting a nonce generation strategy, as this choice cannot be easily changed without transferring ownership. -=== Setup +=== Usage + +Import the `ZOwnablePK` module into the implementing contract and expose the ownership-handling circuits. +It’s recommended to prefix the module with `ZOwnablePK_` to avoid circuit signature clashes. + +```typescript +// MyZOwnablePKContract.compact + +pragma language_version >= 0.16.0; + +import CompactStandardLibrary; +import "./node_modules/@openzeppelin-compact/contracts/src/access/ZOwnablePK" + prefix ZOwnablePK_; + +constructor( + initOwnerCommitment: Bytes<32>, + instanceSalt: Bytes<32>, +) { + ZOwnablePK_initialize(initOwnerCommitment, instanceSalt); +} + +export circuit owner(): Bytes<32> { + return ZOwnablePK_owner(); +} + +export circuit transferOwnership(newOwnerCommitment: Bytes<32>): [] { + return ZOwnablePK_transferOwnership(disclose(newOwnerCommitment)); +} + +export circuit renounceOwnership(): [] { + return ZOwnablePK_renounceOwnership(); +} +``` + +Similar to the Ownable module, +circuits can be protected so that only the contract owner may them by adding `assertOnlyOwner` +as the first line in the circuit body like this: + +```typescript +export circuit mySensitiveCircuit(): [] { + ZOwnablePK_assertOnlyOwner(); + + // Do something +} +``` + +This covers the basic for creating a contract, but before deploying the contract, +the owner's id must be derived for the commitment scheme because it's required to deploy the contract. + +First, the owner needs to generate a secret nonce that's stored in the owner's private state. +See <>. + +Once the owner has the secret nonce generated, they can insert their public key and nonce into the following: + +```typescript +import { + CompactTypeBytes, + CompactTypeVector, + persistentHash, +} from '@midnight-ntwrk/compact-runtime'; +import { getRandomValues } from 'node:crypto'; + +// Owner ID +const generateId = ( + pk: Uint8Array, + nonce: Uint8Array, +): Uint8Array => { + const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32)); + return persistentHash(rt_type, [pk, nonce]); +}; + +// Instance salt for the constructor +const generateInstanceSalt = (): Uint8Array => { + return getRandomValues(new Uint8Array(32)); +} +``` -Add meee! +TIP: Another way to get the user ID is to expose `_computeOwnerId` in the contract +and call this circuit off chain through a contract simulator. +Be on the lookout for future tooling that makes this process easier. == Role-Based Access Control From d707d2837d2ad247495bc68d5f00d0308170870e Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 18 Aug 2025 02:54:47 -0300 Subject: [PATCH 126/202] revert changes --- contracts/src/utils/Utils.compact | 46 ------------------- .../src/utils/test/mocks/MockUtils.compact | 16 ------- .../utils/test/simulators/UtilsSimulator.ts | 28 ----------- contracts/src/utils/test/utils.test.ts | 29 ------------ 4 files changed, 119 deletions(-) diff --git a/contracts/src/utils/Utils.compact b/contracts/src/utils/Utils.compact index b7291176..3528e10e 100644 --- a/contracts/src/utils/Utils.compact +++ b/contracts/src/utils/Utils.compact @@ -66,52 +66,6 @@ module Utils { return !keyOrAddress.is_left; } - /** - * @description Wraps a value as the `L` variant of a disjoint union (`Either`), - * following Compact conventions. - * - * Sets `is_left` to true, assigns the provided value to `left`, and sets `right` to `default` per convention. - * - * This helper is useful when you already have a value of type `L`, but the circuit interface expects an `Either`. - * It avoids manually constructing the full struct and ensures consistent formatting. - * - * @template L - Type of the Left variant. - * @template R - Type of the Right variant. - * - * @param {L} left - The value to wrap as the Left variant. - * @returns {Either} A disjoint union (`Either`) with the value in the `L` variant. - */ - export pure circuit wrapAsEitherLeft(left: L): Either { - return Either { - is_left: true, - left: left, - right: default - }; - } - - /** - * @description Wraps a value as the `R` variant of a disjoint union (`Either`), - * following Compact conventions. - * - * Sets `is_left` to false, assigns the provided value to `right`, and sets `left` to `default` per convention. - * - * This helper is useful when you already have a value of type `R`, but the circuit interface expects an `Either`. - * It avoids manually constructing the full struct and ensures consistent formatting. - * - * @template L - Type of the Left variant. - * @template R - Type of the Right variant. - * - * @param {R} right - The value to wrap as the `R` variant. - * @returns {Either} A disjoint union (`Either`) with the value in the `R` variant. - */ - export pure circuit wrapAsEitherRight(right: R): Either { - return Either { - is_left: false, - left: default, - right: right - }; - } - /** * @description A helper function that returns the empty string: "". * diff --git a/contracts/src/utils/test/mocks/MockUtils.compact b/contracts/src/utils/test/mocks/MockUtils.compact index c3b66d64..54fd45f3 100644 --- a/contracts/src/utils/test/mocks/MockUtils.compact +++ b/contracts/src/utils/test/mocks/MockUtils.compact @@ -25,22 +25,6 @@ export pure circuit isContractAddress(keyOrAddress: Either { - return Utils_wrapAsEitherLeft(pk); -} - -// Find a better way to test different combinations -// other than creating a circuit for each pair -export pure circuit wrapAsEitherPkOrAddressRight( - address: ContractAddress, -): Either { - return Utils_wrapAsEitherRight(address); -} - export pure circuit emptyString(): Opaque<"string"> { return Utils_emptyString(); } diff --git a/contracts/src/utils/test/simulators/UtilsSimulator.ts b/contracts/src/utils/test/simulators/UtilsSimulator.ts index 5f46223e..715569f8 100644 --- a/contracts/src/utils/test/simulators/UtilsSimulator.ts +++ b/contracts/src/utils/test/simulators/UtilsSimulator.ts @@ -139,34 +139,6 @@ export class UtilsSimulator ).result; } - /** - * @description Returns `pk` wrapped in an `Either` type. - * @param pk The target value to wrap. - * @returns `Either` with `pk` in the left position. - */ - public wrapAsEitherPkOrAddressLeft( - pk: ZswapCoinPublicKey, - ): Either { - return this.contract.circuits.wrapAsEitherPkOrAddressLeft( - this.circuitContext, - pk, - ).result; - } - - /** - * @description Returns `address` wrapped in an `Either` type. - * @param pk The target value to wrap. - * @returns `Either` with `address` in the right position. - */ - public wrapAsEitherPkOrAddressRight( - address: ContractAddress, - ): Either { - return this.contract.circuits.wrapAsEitherPkOrAddressRight( - this.circuitContext, - address, - ).result; - } - /** * @description A helper function that returns the empty string: "" * @returns The empty string: "" diff --git a/contracts/src/utils/test/utils.test.ts b/contracts/src/utils/test/utils.test.ts index 52b78acc..1398d4d9 100644 --- a/contracts/src/utils/test/utils.test.ts +++ b/contracts/src/utils/test/utils.test.ts @@ -1,9 +1,4 @@ import { describe, expect, it } from 'vitest'; -import type { - ContractAddress, - Either, - ZswapCoinPublicKey, -} from './../../../artifacts/MockUtils/contract/index.cjs'; // Combined imports import { UtilsSimulator } from './simulators/UtilsSimulator.js'; import * as contractUtils from './utils/address.js'; @@ -92,30 +87,6 @@ describe('Utils', () => { }); }); - describe('wrapAsEitherLeft', () => { - it('should wrap pk as left', () => { - const pk = contractUtils.encodeToPK('PK'); - const exp: Either = { - is_left: true, - left: pk, - right: { bytes: new Uint8Array(32).fill(0) }, - }; - expect(contract.wrapAsEitherPkOrAddressLeft(pk)).toEqual(exp); - }); - }); - - describe('wrapAsEitherRight', () => { - it('should wrap address as right', () => { - const address = contractUtils.encodeToPK('ADDRESS'); - const exp: Either = { - is_left: false, - left: { bytes: new Uint8Array(32).fill(0) }, - right: address, - }; - expect(contract.wrapAsEitherPkOrAddressRight(address)).toEqual(exp); - }); - }); - describe('emptyString', () => { it('should return the empty string', () => { expect(contract.emptyString()).toBe(EMPTY_STRING); From e018e039b7e4b4a1f192f7cd5eaacc776f2a2933 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 18 Aug 2025 03:00:13 -0300 Subject: [PATCH 127/202] fix typo --- contracts/src/access/ZOwnablePK.compact | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/access/ZOwnablePK.compact b/contracts/src/access/ZOwnablePK.compact index 3de3e4f1..38f1e233 100644 --- a/contracts/src/access/ZOwnablePK.compact +++ b/contracts/src/access/ZOwnablePK.compact @@ -14,7 +14,7 @@ pragma language_version >= 0.16.0; * * @notice This module explicitly supports commitments derived from public keys; * however, it may be possible to use contract addresses when contract-to-contract - * calls become available. This will be revisited when it is know if/how witnesses + * calls become available. This will be revisited when it's known if/how witnesses * are used from a contract address context. * * @dev Features: From d11380220b2e38b1266629bc7a3e7c35e0194dba Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 18 Aug 2025 03:05:44 -0300 Subject: [PATCH 128/202] update readme with targeted compilation --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0e730124..bc8d31ab 100644 --- a/README.md +++ b/README.md @@ -88,10 +88,10 @@ Cached: 0 cached, 2 total Time: 7.178s ``` -**Note:** Speed up the development process by skipping the prover and verifier key file generation: +**Note:** Speed up the development process by targeting a single directory and skipping the prover and verifier key file generation: ```bash -turbo compact -- --skip-zk +turbo compact:token -- --skip-zk ``` ### Run tests From b3a0bd4cb713a81f4181da6405fc01e6b90819e1 Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 24 Aug 2025 15:29:30 -0300 Subject: [PATCH 129/202] add k and rows --- contracts/src/access/ZOwnablePK.compact | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/contracts/src/access/ZOwnablePK.compact b/contracts/src/access/ZOwnablePK.compact index 38f1e233..f4c302c1 100644 --- a/contracts/src/access/ZOwnablePK.compact +++ b/contracts/src/access/ZOwnablePK.compact @@ -88,7 +88,7 @@ module ZOwnablePK { * * @dev The `ownerId` must be calculated prior to contract deployment. * - * @circuitInfo k=???, rows=??? + * @circuitInfo k=14, rows=14933 * * Requirements: * @@ -112,7 +112,7 @@ module ZOwnablePK { * @description Returns the current commitment representing the contract owner. * The full commitment is: `H(H(pk, nonce), instanceSalt, counter, domain)`. * - * @circuitInfo k=???, rows=??? + * @circuitInfo k=10, rows=57 * * Requirements: * @@ -129,7 +129,7 @@ module ZOwnablePK { * @description Transfers ownership to `newOwnerId`. * `newOwnerId` must be precalculated and given to the current owner off chain. * - * @circuitInfo k=???, rows=??? + * @circuitInfo k=16, rows=39240 * * Requirements: * @@ -153,7 +153,7 @@ module ZOwnablePK { * It will not be possible to call `assertOnlyOnwer` circuits anymore. * Can only be called by the current owner. * - * @circuitInfo k=???, rows=??? + * @circuitInfo k=24442, rows=24442 * * Requirements: * @@ -174,7 +174,7 @@ module ZOwnablePK { * the stored owner commitment. * Use this to only allow the owner to call specific circuits. * - * @circuitInfo k=???, rows=??? + * @circuitInfo k=15, rows=24437 * * Requirements: * @@ -218,7 +218,7 @@ module ZOwnablePK { * - `domain`: Domain separator `"ZOwnablePK:shield:"` (padded to 32 bytes) to prevent * hash collisions when extending the module or using similar commitment schemes. * - * @circuitInfo k=???, rows=??? + * @circuitInfo k=14, rows=14853 * * Requirements: * @@ -267,8 +267,6 @@ module ZOwnablePK { * support contract addresses as identifiers in future contract-to-contract * interactions. Both are treated as 32-byte values (`Bytes<32>`). * - * @circuitInfo k=???, rows=??? - * * Requirements: * * - `pk` is not a ContractAddress. @@ -290,7 +288,7 @@ module ZOwnablePK { * @description Transfers ownership to owner id `newOwnerId` without * enforcing permission checks on the caller. * - * @circuitInfo k=???, rows=??? + * @circuitInfo k=14, rows=14823 * * Requirements: * From f3e2872fdd040af0ca0802dd72d2a51b9640bfc5 Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 24 Aug 2025 15:32:36 -0300 Subject: [PATCH 130/202] add constraints to docs --- contracts/src/access/ZOwnablePK.compact | 2 +- docs/modules/ROOT/pages/api/access.adoc | 18 +++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/contracts/src/access/ZOwnablePK.compact b/contracts/src/access/ZOwnablePK.compact index f4c302c1..4ec23a05 100644 --- a/contracts/src/access/ZOwnablePK.compact +++ b/contracts/src/access/ZOwnablePK.compact @@ -153,7 +153,7 @@ module ZOwnablePK { * It will not be possible to call `assertOnlyOnwer` circuits anymore. * Can only be called by the current owner. * - * @circuitInfo k=24442, rows=24442 + * @circuitInfo k=15, rows=24442 * * Requirements: * diff --git a/docs/modules/ROOT/pages/api/access.adoc b/docs/modules/ROOT/pages/api/access.adoc index 7a8c1e39..8ed48e6e 100644 --- a/docs/modules/ROOT/pages/api/access.adoc +++ b/docs/modules/ROOT/pages/api/access.adoc @@ -451,7 +451,7 @@ Requirements: Constraints: -- k=???, rows=??? +- k=14, rows=14933 [.contract-item] [[ZOwnablePK-owner]] @@ -466,7 +466,7 @@ Requirements: Constraints: -- k=???, rows=??? +- k=10, rows=57 [.contract-item] [[ZOwnablePK-transferOwnership]] @@ -483,7 +483,7 @@ Requirements: Constraints: -- k=???, rows=??? +- k=16, rows=39240 [.contract-item] [[ZOwnablePK-renounceOwnership]] @@ -500,7 +500,7 @@ Requirements: Constraints: -- k=???, rows=??? +- k=15, rows=24442 [.contract-item] [[ZOwnablePK-assertOnlyOwner]] @@ -517,7 +517,7 @@ thus verifying themselves as the owner. Constraints: -- k=???, rows=??? +- k=15, rows=24437 [.contract-item] [[ZOwnablePK-_computeOwnerCommitment]] @@ -550,7 +550,7 @@ Requirements: Constraints: -- k=???, rows=??? +- k=14, rows=14853 [.contract-item] [[ZOwnablePK-_computeOwnerId]] @@ -582,10 +582,6 @@ Requirements: - Contract is initialized. - `newOwner` is not a ContractAddress. -Constraints: - -- k=???, rows=??? - [.contract-item] [[ZOwnablePK-_transferOwnership]] ==== `[.contract-item-name]#++_transferOwnership++#++(newOwnerId: Bytes<32>) → []++` [.item-kind]#circuit# @@ -598,4 +594,4 @@ Requirements: Constraints: -- k=???, rows=??? +- k=14, rows=14823 From 7b57e9330905fc676b487a046808d4410207e827 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 25 Aug 2025 16:49:09 -0300 Subject: [PATCH 131/202] add SKIP_ZK env var to compile --- compact/src/runCompiler.ts | 49 ++++++++++++++++++++++++++++++++------ turbo.json | 12 +++++----- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/compact/src/runCompiler.ts b/compact/src/runCompiler.ts index a1e9e002..79fef57b 100644 --- a/compact/src/runCompiler.ts +++ b/compact/src/runCompiler.ts @@ -8,24 +8,50 @@ import { CompactCompiler } from './Compiler.js'; * Executes the Compact compiler CLI. * Compiles `.compact` files using the `CompactCompiler` class with provided flags. * - * @example + * Supports both CLI flags and environment variables for common development flags. + * Environment variables take precedence and are useful when using with Turbo monorepo tasks. + * + * @example CLI usage with flags * ```bash * npx compact-compiler --skip-zk * ``` * - * @example Compile specific directory + * @example Compile specific directory with CLI flags + * ```bash + * npx compact-compiler --dir access --skip-zk + * ``` + * + * @example Environment variable usage (recommended with Turbo) + * ```bash + * SKIP_ZK=true npx compact-compiler --dir access + * ``` + * + * @example Turbo monorepo usage * ```bash - * npx compact-compiler --dir security --skip-zk + * # Compile specific module with skip-zk for development + * SKIP_ZK=true turbo compact:access + * + * # Full build with skip-zk + * SKIP_ZK=true turbo compact + * + * # Normal compilation without flags + * turbo compact:access * ``` * + * Environment Variables: + * - `SKIP_ZK=true`: Adds --skip-zk flag to compilation (skips zero-knowledge proof generation for faster development builds) + * * Expected output: * ``` * ℹ [COMPILE] Compact compiler started * ℹ [COMPILE] COMPACT_HOME: /path/to/compactc * ℹ [COMPILE] COMPACTC_PATH: /path/to/compactc/compactc - * ℹ [COMPILE] TARGET_DIR: security - * ℹ [COMPILE] Found 1 .compact file(s) to compile in security/ - * ✔ [COMPILE] [1/1] Compiled security/AccessControl.compact + * ℹ [COMPILE] TARGET_DIR: accesss:compact:access: + * ℹ [COMPILE] Found 4 .compact file(s) to compile in access/ + * ✔ [COMPILE] [1/4] Compiled access/AccessControl.compact + * ✔ [COMPILE] [2/4] Compiled access/Ownable.compact + * ✔ [COMPILE] [3/4] Compiled access/test/mocks/MockAccessControl.compact + * ✔ [COMPILE] [4/4] Compiled access/test/mocks/MockOwnable.compact * Compactc version: 0.24.0 * ``` */ @@ -39,6 +65,12 @@ async function runCompiler(): Promise { let targetDir: string | undefined; const compilerFlags: string[] = []; + // Handle common development flags via environment variables + // This is especially useful when using with Turbo monorepo tasks + if (process.env.SKIP_ZK === 'true') { + compilerFlags.push('--skip-zk'); + } + for (let i = 0; i < args.length; i++) { if (args[i] === '--dir') { if (i + 1 < args.length && !args[i + 1].startsWith('--')) { @@ -54,7 +86,10 @@ async function runCompiler(): Promise { ), ); console.log( - chalk.yellow('Example: compact-compiler --dir security --skip-zk'), + chalk.yellow('Example: compact-compiler --dir access --skip-zk'), + ); + console.log( + chalk.yellow('Example: SKIP_ZK=true compact-compiler --dir access'), ); process.exit(1); } diff --git a/turbo.json b/turbo.json index 86592d3e..57e1bbb5 100644 --- a/turbo.json +++ b/turbo.json @@ -3,35 +3,35 @@ "tasks": { "compact:security": { "dependsOn": ["^build"], - "env": ["COMPACT_HOME"], + "env": ["COMPACT_HOME", "SKIP_ZK"], "inputs": ["src/security/**/*.compact"], "outputLogs": "new-only", "outputs": ["artifacts/**/"] }, "compact:utils": { "dependsOn": ["^build"], - "env": ["COMPACT_HOME"], + "env": ["COMPACT_HOME", "SKIP_ZK"], "inputs": ["src/utils/**/*.compact"], "outputLogs": "new-only", "outputs": ["artifacts/**/"] }, "compact:access": { "dependsOn": ["^build", "compact:security", "compact:utils"], - "env": ["COMPACT_HOME"], + "env": ["COMPACT_HOME", "SKIP_ZK"], "inputs": ["src/access/**/*.compact", "artifacts/**"], "outputLogs": "new-only", "outputs": ["artifacts/**/"] }, "compact:archive": { "dependsOn": ["^build", "compact:utils"], - "env": ["COMPACT_HOME"], + "env": ["COMPACT_HOME", "SKIP_ZK"], "inputs": ["src/archive/**/*.compact", "artifacts/**"], "outputLogs": "new-only", "outputs": ["artifacts/**/"] }, "compact:token": { "dependsOn": ["^build", "compact:security", "compact:utils"], - "env": ["COMPACT_HOME"], + "env": ["COMPACT_HOME", "SKIP_ZK"], "inputs": ["src/token/**/*.compact", "artifacts/**"], "outputLogs": "new-only", "outputs": ["artifacts/**/"] @@ -44,7 +44,7 @@ "compact:archive", "compact:token" ], - "env": ["COMPACT_HOME"], + "env": ["COMPACT_HOME", "SKIP_ZK"], "inputs": ["src/**/*.compact", "test/**/*.compact"], "outputLogs": "new-only", "outputs": ["artifacts/**"] From 822fcd6cbd47def8e2b09bde9dbe695be9f864f2 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 25 Aug 2025 16:54:52 -0300 Subject: [PATCH 132/202] improve docs --- compact/src/runCompiler.ts | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/compact/src/runCompiler.ts b/compact/src/runCompiler.ts index 79fef57b..e5a0ecf6 100644 --- a/compact/src/runCompiler.ts +++ b/compact/src/runCompiler.ts @@ -8,38 +8,33 @@ import { CompactCompiler } from './Compiler.js'; * Executes the Compact compiler CLI. * Compiles `.compact` files using the `CompactCompiler` class with provided flags. * - * Supports both CLI flags and environment variables for common development flags. - * Environment variables take precedence and are useful when using with Turbo monorepo tasks. + * For individual module compilation, CLI flags work directly. + * For full compilation with dependencies, use environment variables due to Turbo task orchestration. * - * @example CLI usage with flags + * @example Individual module compilation (CLI flags work directly) * ```bash - * npx compact-compiler --skip-zk + * npx compact-compiler --dir security --skip-zk + * turbo compact:access -- --skip-zk + * turbo compact:security -- --skip-zk --other-flag * ``` * - * @example Compile specific directory with CLI flags + * @example Full compilation (environment variables required) * ```bash - * npx compact-compiler --dir access --skip-zk - * ``` + * # Use environment variables for full builds due to task dependencies + * SKIP_ZK=true turbo compact * - * @example Environment variable usage (recommended with Turbo) - * ```bash - * SKIP_ZK=true npx compact-compiler --dir access + * # Normal full build + * turbo compact * ``` * - * @example Turbo monorepo usage + * @example Direct CLI usage * ```bash - * # Compile specific module with skip-zk for development - * SKIP_ZK=true turbo compact:access - * - * # Full build with skip-zk - * SKIP_ZK=true turbo compact - * - * # Normal compilation without flags - * turbo compact:access + * npx compact-compiler --skip-zk + * npx compact-compiler --dir security --skip-zk * ``` * - * Environment Variables: - * - `SKIP_ZK=true`: Adds --skip-zk flag to compilation (skips zero-knowledge proof generation for faster development builds) + * Environment Variables (only needed for full builds): + * - `SKIP_ZK=true`: Adds --skip-zk flag when running full compilation via `turbo compact` * * Expected output: * ``` From 1c36f67e7e20560f244258d081e78e43aa19b856 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 25 Aug 2025 17:04:17 -0300 Subject: [PATCH 133/202] update README --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bc8d31ab..71c1cec8 100644 --- a/README.md +++ b/README.md @@ -88,10 +88,15 @@ Cached: 0 cached, 2 total Time: 7.178s ``` -**Note:** Speed up the development process by targeting a single directory and skipping the prover and verifier key file generation: +Speed up the development process by targeting a single directory +and skipping the prover and verifier key file generation: ```bash +# Individual module compilation (recommended for development) turbo compact:token -- --skip-zk + +# Full compilation with skip-zk (use environment variable) +SKIP_ZK=true turbo compact ``` ### Run tests From c676fd02cf59f67c91a7aaeaaffc8bac96ae7301 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Mon, 25 Aug 2025 16:27:09 -0500 Subject: [PATCH 134/202] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ⟣ €₥ℵ∪ℓ ⟢ <34749913+emnul@users.noreply.github.com> Signed-off-by: Andrew Fleming --- contracts/src/access/ZOwnablePK.compact | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/contracts/src/access/ZOwnablePK.compact b/contracts/src/access/ZOwnablePK.compact index 4ec23a05..6bf43273 100644 --- a/contracts/src/access/ZOwnablePK.compact +++ b/contracts/src/access/ZOwnablePK.compact @@ -27,10 +27,11 @@ pragma language_version >= 0.16.0; * @dev Commitment structure: * ``` * id = H(pk, secretNonce) - * commitment = H(id, instanceSalt, counter, "ZOwnablePK:shield:") + * commitment = H256(id, instanceSalt, counter, "ZOwnablePK:shield:") * ``` - * The commitment changes on each transfer due to the incrementing `counter`, - * providing unlinkability across ownership changes. + * Where `H()` is the SHA256 hash algorithm. The commitment changes + * on each transfer due to the incrementing `counter`, providing + * unlinkability across ownership changes. * * @dev Security Considerations: * - The `secretNonce` must be kept private. Loss of the nonce prevents the @@ -86,14 +87,15 @@ module ZOwnablePK { * and storing the `instanceSalt` that acts as a privacy additive for preventing * duplicate commitments among other contracts implementing ZOwnablePK. * - * @dev The `ownerId` must be calculated prior to contract deployment. + * @warning The `ownerId` must be calculated prior to contract deployment using the SHA256 hashing algorithm. + * Using any other algorithm will result in a permanent loss of contract access. * * @circuitInfo k=14, rows=14933 * * Requirements: * * - Contract is not initialized. - * - `ownerId` is not an empty array. + * - `ownerId` is not zero. * * @param {Bytes<32>} ownerId - The owner's unique identifier H(pk, nonce). * @param {Bytes<32>} instanceSalt - Contract salt to prevent duplicate commitments if From 0cd3c594646971d3f32b97077fb6603160583fbc Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 25 Aug 2025 18:43:49 -0300 Subject: [PATCH 135/202] change generic H to SHA256 in docs --- contracts/src/access/ZOwnablePK.compact | 35 ++++++++++++------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/contracts/src/access/ZOwnablePK.compact b/contracts/src/access/ZOwnablePK.compact index 6bf43273..9f750dff 100644 --- a/contracts/src/access/ZOwnablePK.compact +++ b/contracts/src/access/ZOwnablePK.compact @@ -26,12 +26,11 @@ pragma language_version >= 0.16.0; * * @dev Commitment structure: * ``` - * id = H(pk, secretNonce) - * commitment = H256(id, instanceSalt, counter, "ZOwnablePK:shield:") + * id = SHA256(pk, secretNonce) + * commitment = SHA256(id, instanceSalt, counter, "ZOwnablePK:shield:") * ``` - * Where `H()` is the SHA256 hash algorithm. The commitment changes - * on each transfer due to the incrementing `counter`, providing - * unlinkability across ownership changes. + * The commitment changes on each transfer due to the incrementing `counter`, + * providing unlinkability across ownership changes. * * @dev Security Considerations: * - The `secretNonce` must be kept private. Loss of the nonce prevents the @@ -49,7 +48,7 @@ module ZOwnablePK { /** * @ledger _ownerCommitment * @description Stores the current hashed commitment representing the owner. - * This commitment is derived from the public identifier (e.g., `H(pk, nonce)`), + * This commitment is derived from the public identifier (e.g., `SHA256(pk, nonce)`), * the `instanceSalt`, the transfer `counter`, and a domain separator. * * A commitment of `default>` (i.e., zero) indicates the contract is unowned. @@ -77,7 +76,7 @@ module ZOwnablePK { * @witness secretNonce * @description A private per-user nonce used in deriving the shielded owner identifier. * - * Combined with the user's public key as `H(pk, nonce)` to produce an obfuscated, + * Combined with the user's public key as `SHA256(pk, nonce)` to produce an obfuscated, * unlinkable identity commitment. Users are encouraged to rotate this value on ownership changes. */ export witness secretNonce(): Bytes<32>; @@ -88,7 +87,7 @@ module ZOwnablePK { * duplicate commitments among other contracts implementing ZOwnablePK. * * @warning The `ownerId` must be calculated prior to contract deployment using the SHA256 hashing algorithm. - * Using any other algorithm will result in a permanent loss of contract access. + * Using any other algorithm will result in a permanent loss of contract access. * * @circuitInfo k=14, rows=14933 * @@ -97,7 +96,7 @@ module ZOwnablePK { * - Contract is not initialized. * - `ownerId` is not zero. * - * @param {Bytes<32>} ownerId - The owner's unique identifier H(pk, nonce). + * @param {Bytes<32>} ownerId - The owner's unique identifier SHA256(pk, nonce). * @param {Bytes<32>} instanceSalt - Contract salt to prevent duplicate commitments if * users reuse their PK and secretNonce witness (not recommended). * @returns {[]} Empty tuple. @@ -112,7 +111,7 @@ module ZOwnablePK { /** * @description Returns the current commitment representing the contract owner. - * The full commitment is: `H(H(pk, nonce), instanceSalt, counter, domain)`. + * The full commitment is: `SHA256(SHA256(pk, nonce), instanceSalt, counter, domain)`. * * @circuitInfo k=10, rows=57 * @@ -139,7 +138,7 @@ module ZOwnablePK { * - Caller is the the current owner. * - `newOwnerId` is not an empty array. * - * @param {Bytes<32>} newOwnerId - The new owner's unique identifier (`H(pk, nonce)`). + * @param {Bytes<32>} newOwnerId - The new owner's unique identifier (`SHA256(pk, nonce)`). * @returns {[]} Empty tuple. */ export circuit transferOwnership(newOwnerId: Bytes<32>): [] { @@ -172,7 +171,7 @@ module ZOwnablePK { } /** - * @description Throws if called by any account whose id hash `H(pk, nonce)` does not match + * @description Throws if called by any account whose id hash `SHA256(pk, nonce)` does not match * the stored owner commitment. * Use this to only allow the owner to call specific circuits. * @@ -181,7 +180,7 @@ module ZOwnablePK { * Requirements: * * - Contract is initialized. - * - Caller's id (`H(pk, nonce)`) when used in `_computeOwnerCommitment` equals + * - Caller's id (`SHA256(pk, nonce)`) when used in `_computeOwnerCommitment` equals * the stored `_ownerCommitment`, thus verifying themselves as the owner. * * @returns {[]} Empty tuple. @@ -204,13 +203,13 @@ module ZOwnablePK { * * ## Owner ID (`id`) * The `id` is expected to be computed off-chain as: - * `id = H(pk, nonce)` + * `id = SHA256(pk, nonce)` * * - `pk`: The owner's public key. * - `nonce`: A secret nonce scoped to the instance, ideally rotated with each transfer. * * ## Commitment Derivation - * `commitment = H(id, instanceSalt, counter, domain)` + * `commitment = SHA256(id, instanceSalt, counter, domain)` * * - `id`: See above. * - `instanceSalt`: A unique per-deployment salt, stored during initialization. @@ -226,7 +225,7 @@ module ZOwnablePK { * * - Contract is initialized. * - * @param {Bytes<32>} id - The unique identifier of the owner calculated by `H(pk, nonce)`. + * @param {Bytes<32>} id - The unique identifier of the owner calculated by `SHA256(pk, nonce)`. * @param {Uint<64>} counter - The current counter or round. This increments by `1` * after every transfer to prevent duplicate commitments given the same `id`. * @returns {Bytes<32>} The commitment derived from `id` and `counter`. @@ -251,7 +250,7 @@ module ZOwnablePK { * public key and a secret nonce. * * ## ID Derivation - * `id = H(pk, nonce)` + * `id = SHA256(pk, nonce)` * * - `pk`: The public key of the caller. This is passed explicitly to allow * for off-chain derivation, testing, or scenarios where the caller is @@ -297,7 +296,7 @@ module ZOwnablePK { * - Contract is initialized. * * @param {Bytes<32>} newOwnerId - The unique identifier of the new owner - * calculated by `H(pk, nonce)`. + * calculated by `SHA256(pk, nonce)`. * @returns {[]} Empty tuple. */ export circuit _transferOwnership(newOwnerId: Bytes<32>): [] { From 06f3c83e218737f512573d2e7be89410bd947be5 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 25 Aug 2025 18:46:18 -0300 Subject: [PATCH 136/202] change generic H to SHA256 --- docs/modules/ROOT/pages/access.adoc | 4 ++-- docs/modules/ROOT/pages/api/access.adoc | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/modules/ROOT/pages/access.adoc b/docs/modules/ROOT/pages/access.adoc index 7ebb095c..44c1deeb 100644 --- a/docs/modules/ROOT/pages/access.adoc +++ b/docs/modules/ROOT/pages/access.adoc @@ -159,7 +159,7 @@ unlinkability, and collision resistance across deployments and ownership transfe The foundation of the system is the owner identifier, computed as: ```ts -id = H(pk, nonce) +id = SHA256(pk, nonce) ``` Where `pk` is the owner's public key and `nonce` is a secret value that may be either randomly generated @@ -172,7 +172,7 @@ ensuring the owner's identity remains confidential. The final ownership commitment stored on-chain is computed as: ```ts -commitment = H(id, instanceSalt, counter, pad(32, "ZOwnablePK:shield:")) +commitment = SHA256(id, instanceSalt, counter, pad(32, "ZOwnablePK:shield:")) ``` This multi-element hash provides several security properties: diff --git a/docs/modules/ROOT/pages/api/access.adoc b/docs/modules/ROOT/pages/api/access.adoc index 8ed48e6e..acc88d0b 100644 --- a/docs/modules/ROOT/pages/api/access.adoc +++ b/docs/modules/ROOT/pages/api/access.adoc @@ -458,7 +458,7 @@ Constraints: ==== `[.contract-item-name]#++owner++#++() → Bytes<32>++` [.item-kind]#circuit# Returns the current commitment representing the contract owner. -The full commitment is: `H(H(pk, nonce), instanceSalt, counter, domain)`. +The full commitment is: `SHA256(SHA256(pk, nonce), instanceSalt, counter, domain)`. Requirements: @@ -506,13 +506,13 @@ Constraints: [[ZOwnablePK-assertOnlyOwner]] ==== `[.contract-item-name]#++assertOnlyOwner++#++() → []++` [.item-kind]#circuit# -Throws if called by any account whose id hash `H(pk, nonce)` does not match the stored owner commitment. +Throws if called by any account whose id hash `SHA256(pk, nonce)` does not match the stored owner commitment. Use this to only allow the owner to call specific circuits. Requirements: - Contract is initialized. -- Caller's id (`H(pk, nonce)`) when used in <> equals the stored `_ownerCommitment`, +- Caller's id (`SHA256(pk, nonce)`) when used in <> equals the stored `_ownerCommitment`, thus verifying themselves as the owner. Constraints: @@ -527,14 +527,14 @@ Computes the owner commitment from the given `id` and `counter`. **Owner ID (`id`)** -The `id` is expected to be computed off-chain as: `id = H(pk, nonce)` +The `id` is expected to be computed off-chain as: `id = SHA256(pk, nonce)` - `pk`: The owner's public key. - `nonce`: A secret nonce scoped to the instance, ideally rotated with each transfer. **Commitment Derivation** -`commitment = H(id, instanceSalt, counter, domain)` +`commitment = SHA256(id, instanceSalt, counter, domain)` - `id`: See above. - `instanceSalt`: A unique per-deployment salt, stored during initialization. @@ -559,7 +559,7 @@ Constraints: Computes the unique identifier (`id`) of the owner from their public key and a secret nonce. **ID Derivation** -`id = H(pk, nonce)` +`id = SHA256(pk, nonce)` - `pk`: The public key of the caller. This is passed explicitly to allow for off-chain derivation, testing, or scenarios From 8e28bdae68d77838f1dfaaa67f5ed19bc84bb5ed Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 25 Aug 2025 18:47:01 -0300 Subject: [PATCH 137/202] fix lang version in mock --- contracts/src/access/test/mocks/MockZOwnablePK.compact | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/access/test/mocks/MockZOwnablePK.compact b/contracts/src/access/test/mocks/MockZOwnablePK.compact index ce9f7e0c..d769f30e 100644 --- a/contracts/src/access/test/mocks/MockZOwnablePK.compact +++ b/contracts/src/access/test/mocks/MockZOwnablePK.compact @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma language_version >= 0.15.0; +pragma language_version >= 0.16.0; import CompactStandardLibrary; import "../../ZOwnablePK" prefix ZOwnablePK_; From 9b3e97c844c74d5746e0c5c9e51814faa1c43c14 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 26 Aug 2025 01:37:48 -0300 Subject: [PATCH 138/202] add bad owner id hash scenario --- contracts/src/access/test/ZOwnablePK.test.ts | 34 +++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/contracts/src/access/test/ZOwnablePK.test.ts b/contracts/src/access/test/ZOwnablePK.test.ts index b8103142..0369705a 100644 --- a/contracts/src/access/test/ZOwnablePK.test.ts +++ b/contracts/src/access/test/ZOwnablePK.test.ts @@ -3,6 +3,8 @@ import { CompactTypeVector, convert_bigint_to_Uint8Array, persistentHash, + transientHash, + upgradeFromTransient, } from '@midnight-ntwrk/compact-runtime'; import { beforeEach, describe, expect, it } from 'vitest'; import type { ZswapCoinPublicKey } from '../../../artifacts/MockOwnable/contract/index.cjs'; @@ -99,7 +101,7 @@ describe('ZOwnablePK', () => { }); }); - describe('when not initialized correctly', () => { + describe('when not deployed and not initialized', () => { const isNotInit = false; beforeEach(() => { @@ -136,6 +138,36 @@ describe('ZOwnablePK', () => { }); }); + describe('when incorrect hashing algo (not SHA256) is used to generate initial owner id', () => { + // ZOwnablePK only supports sha256 for owner id calculation + // Obviously, using any other algo for the id will not work + const badHashAlgo = (pk: ZswapCoinPublicKey, nonce: Uint8Array) => { + const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32)); + return upgradeFromTransient(transientHash(rt_type, [pk.bytes, nonce])); + }; + const secretNonce = ZOwnablePKPrivateState.generate().secretNonce; + const badOwnerId = badHashAlgo(Z_OWNER, secretNonce); + + beforeEach(() => { + ownable = new ZOwnablePKSimulator(badOwnerId, INSTANCE_SALT, isInit); + }); + // + type FailingCircuits = [method: keyof ZOwnablePKSimulator, args: unknown[]]; + const protectedCircuits: FailingCircuits[] = [ + ['assertOnlyOwner', []], + ['transferOwnership', [badOwnerId]], + ['renounceOwnership', []], + ]; + + it.each(protectedCircuits)('%s should fail', (circuitName, args) => { + ownable.callerCtx.setCaller(OWNER); + + expect(() => { + (ownable[circuitName] as (...args: unknown[]) => unknown)(...args); + }).toThrow('ZOwnablePK: caller is not the owner'); + }); + }); + describe('after initialization', () => { beforeEach(() => { // Create private state object and generate nonce From 490af83ab4d4117dd716c7ed20528d0c961b91c0 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Tue, 26 Aug 2025 13:42:02 -0500 Subject: [PATCH 139/202] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ⟣ €₥ℵ∪ℓ ⟢ <34749913+emnul@users.noreply.github.com> Signed-off-by: Andrew Fleming --- contracts/src/access/test/types/test.ts | 4 ++-- contracts/src/access/test/utils/createCircuitProxies.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/src/access/test/types/test.ts b/contracts/src/access/test/types/test.ts index 48da9c81..643def10 100644 --- a/contracts/src/access/test/types/test.ts +++ b/contracts/src/access/test/types/test.ts @@ -50,8 +50,8 @@ export interface IContractSimulator { * @template TContract - Contract type with `circuits` and `impureCircuits`. */ export type ExtractPureCircuits = TContract extends { - circuits: infer TCircuits; - impureCircuits: infer TImpureCircuits; + circuits: infer TCircuits extends Record; + impureCircuits: infer TImpureCircuits extends Record; } ? Omit : never; diff --git a/contracts/src/access/test/utils/createCircuitProxies.ts b/contracts/src/access/test/utils/createCircuitProxies.ts index ad4211b6..334181d4 100644 --- a/contracts/src/access/test/utils/createCircuitProxies.ts +++ b/contracts/src/access/test/utils/createCircuitProxies.ts @@ -10,7 +10,7 @@ import type { */ export function createCircuitProxies< P, - ContractType extends { circuits: any; impureCircuits: any }, + ContractType extends { circuits: Record; impureCircuits: Record;}, >( contract: ContractType, getContext: () => CircuitContext

, From 018ba5ce34a46a1bfc9a4af7c34149698988044d Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 26 Aug 2025 15:48:24 -0300 Subject: [PATCH 140/202] improve create proxy constraints, cast circuits to the extracted type, fix fmt --- .../access/test/utils/createCircuitProxies.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/contracts/src/access/test/utils/createCircuitProxies.ts b/contracts/src/access/test/utils/createCircuitProxies.ts index 334181d4..8aa500da 100644 --- a/contracts/src/access/test/utils/createCircuitProxies.ts +++ b/contracts/src/access/test/utils/createCircuitProxies.ts @@ -10,17 +10,20 @@ import type { */ export function createCircuitProxies< P, - ContractType extends { circuits: Record; impureCircuits: Record;}, + ContractType extends { + circuits: Record; + impureCircuits: Record; + }, >( contract: ContractType, getContext: () => CircuitContext

, getCallerContext: () => CircuitContext

, updateContext: (ctx: CircuitContext

) => void, - createPureProxy: ( + createPureProxy: >( circuits: C, context: () => CircuitContext

, ) => ContextlessCircuits, - createImpureProxy: ( + createImpureProxy: >( circuits: C, context: () => CircuitContext

, updateContext: (ctx: CircuitContext

) => void, @@ -36,11 +39,14 @@ export function createCircuitProxies< return { get circuits() { if (!pureProxy) { - pureProxy = createPureProxy(contract.circuits, getContext); + pureProxy = createPureProxy( + contract.circuits as ExtractPureCircuits, + getContext, + ); } if (!impureProxy) { impureProxy = createImpureProxy( - contract.impureCircuits, + contract.impureCircuits as ExtractImpureCircuits, getCallerContext, updateContext, ); From 12a06c7f7075791f4b0e9b695c7c843df5a6a6d3 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Tue, 26 Aug 2025 14:14:41 -0500 Subject: [PATCH 141/202] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ⟣ €₥ℵ∪ℓ ⟢ <34749913+emnul@users.noreply.github.com> Signed-off-by: Andrew Fleming --- docs/modules/ROOT/pages/access.adoc | 4 ++-- docs/modules/ROOT/pages/api/access.adoc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/modules/ROOT/pages/access.adoc b/docs/modules/ROOT/pages/access.adoc index 44c1deeb..e8c1cf47 100644 --- a/docs/modules/ROOT/pages/access.adoc +++ b/docs/modules/ROOT/pages/access.adoc @@ -197,7 +197,7 @@ Both approaches are valid, and the best choice depends on your specific threat m ==== Random Nonce -Generating a cryptographically random nonce provides the strongest privacy guarantees: +Generating a cryptographically strong random nonce provides the strongest privacy guarantees: ```typescript const randomNonce = crypto.getRandomValues(new Uint8Array(32)); @@ -300,7 +300,7 @@ export circuit mySensitiveCircuit(): [] { } ``` -This covers the basic for creating a contract, but before deploying the contract, +This covers the basics for creating a contract, but before deploying the contract, the owner's id must be derived for the commitment scheme because it's required to deploy the contract. First, the owner needs to generate a secret nonce that's stored in the owner's private state. diff --git a/docs/modules/ROOT/pages/api/access.adoc b/docs/modules/ROOT/pages/api/access.adoc index acc88d0b..5b3b95f6 100644 --- a/docs/modules/ROOT/pages/api/access.adoc +++ b/docs/modules/ROOT/pages/api/access.adoc @@ -427,7 +427,7 @@ TIP: For an overview of the module, read the {ownable-guide}. * xref:#ZOwnablePK-owner[`++owner()++`] * xref:#ZOwnablePK-transferOwnership[`++transferOwnership(newOwnerId)++`] * xref:#ZOwnablePK-renounceOwnership[`++renounceOwnership()++`] -* xref:#ZOwnablePK-assertOnlyOwner[`++assertOnlyOwner(operator, approved)++`] +* xref:#ZOwnablePK-assertOnlyOwner[`++assertOnlyOwner()++`] * xref:#ZOwnablePK-_computeOwnerCommitment[`++_computeOwnerCommitment(id, counter)++`] * xref:#ZOwnablePK-_computeOwnerId[`++_computeOwnerId(pk, nonce)++`] * xref:#ZOwnablePK-_transferOwnership[`++_transferOwnership(newOwnerId)++`] @@ -580,7 +580,7 @@ Both are treated as 32-byte values (`Bytes<32>`). Requirements: - Contract is initialized. -- `newOwner` is not a ContractAddress. +- `pk` is not a ContractAddress. [.contract-item] [[ZOwnablePK-_transferOwnership]] From 1b55a09829bdf08eda697fbf7f4740854f004260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 26 Aug 2025 16:59:17 -0400 Subject: [PATCH 142/202] Remove old dir, rename files --- contracts/shieldedAccessControl/package.json | 32 -- .../mocks/MockShieldedAccessControl.compact | 73 ----- .../shieldedAccessControl/tsconfig.build.json | 5 - contracts/shieldedAccessControl/tsconfig.json | 25 -- .../shieldedAccessControl/vitest.config.ts | 10 - .../access}/ShieldedAccessControl.compact | 305 ++++++------------ .../ShieldedAccessControlUtils.compact | 0 .../mocks/MockShieldedAccessControl.compact | 64 ++++ .../ShieldedAccessControlSimulator.ts | 297 +++++++++++++++++ .../ShieldedAccessControlWitnesses.ts | 2 +- 10 files changed, 469 insertions(+), 344 deletions(-) delete mode 100644 contracts/shieldedAccessControl/package.json delete mode 100644 contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact delete mode 100644 contracts/shieldedAccessControl/tsconfig.build.json delete mode 100644 contracts/shieldedAccessControl/tsconfig.json delete mode 100644 contracts/shieldedAccessControl/vitest.config.ts rename contracts/{shieldedAccessControl/src => src/access}/ShieldedAccessControl.compact (60%) rename contracts/{shieldedAccessControl/src => src/access}/ShieldedAccessControlUtils.compact (100%) create mode 100644 contracts/src/access/test/mocks/MockShieldedAccessControl.compact create mode 100644 contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts rename contracts/{shieldedAccessControl/src => src/access}/witnesses/ShieldedAccessControlWitnesses.ts (98%) diff --git a/contracts/shieldedAccessControl/package.json b/contracts/shieldedAccessControl/package.json deleted file mode 100644 index 65238178..00000000 --- a/contracts/shieldedAccessControl/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "@openzeppelin-compact/shielded-access-control", - "private": true, - "type": "module", - "main": "dist/index.js", - "module": "dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "require": "./dist/index.js", - "import": "./dist/index.js", - "default": "./dist/index.js" - } - }, - "scripts": { - "compact": "compact-compiler", - "build": "compact-builder && tsc", - "test": "vitest run", - "types": "tsc -p tsconfig.json --noEmit", - "clean": "git clean -fXd" - }, - "dependencies": { - "@openzeppelin-compact/compact": "workspace:^" - }, - "devDependencies": { - "@types/node": "22.14.0", - "ts-node": "^10.9.2", - "typescript": "^5.2.2", - "vitest": "^3.1.3" - } -} \ No newline at end of file diff --git a/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact b/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact deleted file mode 100644 index 91a6e39f..00000000 --- a/contracts/shieldedAccessControl/src/test/mocks/MockShieldedAccessControl.compact +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma language_version >= 0.16.0; - -import CompactStandardLibrary; - -import "../../ShieldedAccessControl" prefix ShieldedAccessControl_; - -export { - ZswapCoinPublicKey, - ContractAddress, - Either, - Maybe, - ShieldedAccessControl_DEFAULT_ADMIN_ROLE, - ShieldedAccessControl__salt, - ShieldedAccessControl__operatorRoles -}; - -export circuit hasRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { - return ShieldedAccessControl_hasRole(roleId, account, nonce); -} - -export circuit assertOnlyRole(roleId: Bytes<32>, nonce: Bytes<32>): [] { - ShieldedAccessControl_assertOnlyRole(roleId, nonce); -} - -export circuit _checkRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): [] { - ShieldedAccessControl__checkRole(roleId, account, nonce); -} - -export circuit _checkMerkleTree(roleId: Bytes<32>, account: Bytes<32>, nonce: Bytes<32>): Boolean { - return ShieldedAccessControl__checkMerkleTree(roleId, account, nonce); -} - -export circuit getRoleAdmin(roleId: Bytes<32>): Bytes<32> { - return ShieldedAccessControl_getRoleAdmin(roleId); -} - -export circuit grantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): [] { - ShieldedAccessControl_grantRole(roleId, account, nonce); -} - -export circuit revokeRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): [] { - ShieldedAccessControl_revokeRole(roleId, account, nonce); -} - -export circuit renounceRole(roleId: Bytes<32>, callerConfirmation: Either, nonce: Bytes<32>): [] { - ShieldedAccessControl_renounceRole(roleId, callerConfirmation, nonce); -} - -export circuit _setRoleAdmin(roleId: Bytes<32>, adminRole: Bytes<32>): [] { - ShieldedAccessControl__setRoleAdmin(roleId, adminRole); -} - -export circuit _grantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { - return ShieldedAccessControl__grantRole(roleId, account, nonce); -} - -export circuit _unsafeGrantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { - return ShieldedAccessControl__unsafeGrantRole(roleId, account, nonce); -} - -export circuit _revokeRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { - return ShieldedAccessControl__revokeRole(roleId, account, nonce); -} - -export circuit _requestRole(roleId: Bytes<32>): [] { - ShieldedAccessControl__requestRole(roleId); -} - -export circuit _recoverRoles(): [] { - ShieldedAccessControl__recoverRoles(); -} diff --git a/contracts/shieldedAccessControl/tsconfig.build.json b/contracts/shieldedAccessControl/tsconfig.build.json deleted file mode 100644 index f1132509..00000000 --- a/contracts/shieldedAccessControl/tsconfig.build.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "./tsconfig.json", - "exclude": ["src/test/**/*.ts"], - "compilerOptions": {} -} diff --git a/contracts/shieldedAccessControl/tsconfig.json b/contracts/shieldedAccessControl/tsconfig.json deleted file mode 100644 index 4ae082c4..00000000 --- a/contracts/shieldedAccessControl/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "include": [ - "src/**/*.ts" - ], - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "declaration": true, - "lib": [ - "ES2022" - ], - "target": "ES2022", - "module": "nodenext", - "moduleResolution": "nodenext", - "allowJs": true, - "forceConsistentCasingInFileNames": true, - "noImplicitAny": true, - "strict": true, - "isolatedModules": true, - "sourceMap": true, - "resolveJsonModule": true, - "esModuleInterop": true, - "skipLibCheck": true - } -} diff --git a/contracts/shieldedAccessControl/vitest.config.ts b/contracts/shieldedAccessControl/vitest.config.ts deleted file mode 100644 index 785b792e..00000000 --- a/contracts/shieldedAccessControl/vitest.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - globals: true, - environment: 'node', - include: ['src/test/**/*.test.ts'], - reporters: 'verbose', - }, -}); diff --git a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact b/contracts/src/access/ShieldedAccessControl.compact similarity index 60% rename from contracts/shieldedAccessControl/src/ShieldedAccessControl.compact rename to contracts/src/access/ShieldedAccessControl.compact index a36cd04e..fd21521c 100644 --- a/contracts/shieldedAccessControl/src/ShieldedAccessControl.compact +++ b/contracts/src/access/ShieldedAccessControl.compact @@ -8,10 +8,10 @@ pragma language_version >= 0.16.0; * This module provides a shielded role-based access control mechanism, where roles can be used to * represent a set of permissions. Roles are stored as Merkle tree commitments to avoid * disclosing information about role holder. Role commitments are created with the following - * hashing scheme SHA256( SHA256(roleIdentifier | account | nonce | contractAddress) | index). + * hashing scheme SHA256(roleId | account | nonce | merkleTreeIndex). * * @notice Using the SHA256 hashing function comes at a significant performace cost. In the future, we - * plan on migrating to a ZK-friendly hashing function like Poseidon when an implementation is available. + * plan on migrating to a ZK-friendly hashing function when an implementation is available. * * Roles are referred to by their `Bytes<32>` identifier. These should be exposed * in the top-level contract and be unique. One way to achieve this is by @@ -51,12 +51,6 @@ pragma language_version >= 0.16.0; * grant and revoke this role. Extra precautions should be taken to secure * accounts that have been granted it. * - * By default, the salt value used to generate nonce values in the `requestRole` witness - * is set to 0. The use of a random salt value adds significantly to the strength of the - * underlying HKDF function and is highly encouraged. A random salt value can be set - * by implementing the `Initializable` module and setting `_salt` in the `initialize() - * circuit. - * * @notice Roles can only be granted to ZswapCoinPublicKeys * through the main role approval circuits (`grantRole` and `_grantRole`). * In other words, role approvals to contract addresses are disallowed through these @@ -84,8 +78,8 @@ module ShieldedAccessControl { import "ShieldedAccessControlUtils" prefix Utils_; /** - * @description A Merkle tree of role commitments stored as SHA256( SHA256(roleId | account | nonce | contractAddress) | index) - * @type {Bytes<32>} roleCommitment - A commitment created by the following hash: SHA256( SHA256(roleId | account | nonce | contractAddress) | index). + * @description A Merkle tree of role commitments stored as SHA256(roleId | account | nonce | merkleTreeIndex) + * @type {Bytes<32>} roleCommitment - A role commitment created by the following hash: SHA256(roleId | account | nonce | merkleTreeIndex). * @type {MerkleTree<10, roleCommitment>} * @type {MerkleTree<10, Bytes<32>>} _operatorRoles  */ @@ -102,70 +96,27 @@ module ShieldedAccessControl { /** * @description A set of nullifiers used to revoke the permissions of a role - * @type {Bytes<32> roleCommitment - A roleCommitment created by the following hash: SHA256( SHA256(roleId | account | nonce | contractAddress) | index). + * @type {Bytes<32> roleCommitment - A role commitment created by the following hash: SHA256(roleId | account | nonce | merkleTreeIndex). * @type {Set} _roleCommitmentNullifiers  */ export ledger _roleCommitmentNullifiers: Set>; - /** - * @description Mapping from an intermediate role commitment hash to an index in the `_operatorRoles` Merkle tree. - * @type {Bytes<32>} intermediateRoleCommitment - An intermediate role commitment hash created by the following hashing scheme: SHA256(roleId | account | nonce | contractAddress). - * @type {Uint<64>} index - The index of a role commitment in the `_operatorRoles` Merkle tree. - * @type {Map} - * @type {Map, Uint<64>>} _roleCommitmentIndex -  */ - export ledger _roleCommitmentIndex: Map, Uint<64>>; - - /** - * @description A counter tracking the next available index in the `_operatorRoles` MerkleTree - */ - export ledger _nextIndex: Counter; - - /** - * @description A random salt value used to strengthen the HKDF function used in the `requestRole` witness function. - */ - export ledger _salt: Bytes<32>; - export ledger DEFAULT_ADMIN_ROLE: Bytes<32>; /** * @description Returns a Merkle path in the `_operatorRoles` Merkle tree, given the knowledge that a `roleCommitment` is at the given index. * - * Requirements: - * - * - It is an error to call this if `roleCommitment` is not contained at the given index. - * - * @circuitInfo - * - * @param {Bytes<32>} roleCommitment - A commitment created by the following hash: SHA256( SHA256(roleId | account | nonce | contractAddress) | index). + * @param {Bytes<32>} roleCommitment - A commitment created by the following hash: SHA256(roleId | account | nonce | merkleTreeIndex). * @param {Uint<64>} index - An index in the `_operatorRoles` Merkle tree * @return {MerkleTreePath<10, Bytes<32>>} - The Merkle path of `roleCommitment` in the `_operatorRoles` Merkle tree  */ - witness getRoleCommitmentPath(roleCommitment: Bytes<32>, index: Uint<64>): MerkleTreePath<10, Bytes<32>>; + witness getRoleCommitmentPath(roleCommitment: Bytes<32>): MerkleTreePath<10, Bytes<32>>; - /** - * @description Locally creates and stores a nonce value using the HKDF function and the associated role identifier. - * - * @dev Developers must provide an implementation to privately send the account's public key, roleId, and nonce to an admin. One - * possible solution is by using an HTTP API. - * - * @param {Bytes<32>} roleId - A hash representing a role identifier. - * @param {Bytes<32>} account - The account requesting a role. - * @param {Bytes<32>} salt - A salt value for the underlying HKDF function. - * @return {[]} - Empty tuple. -  */ - witness requestRole(roleId: Bytes<32>, account: Bytes<32>, salt: Bytes<32>): []; + witness secretNonce(roleId: Bytes<32>): Bytes<32>; - /** - * @description Used to recover roles in the event of data loss. - * - * @dev Developers must export publicly declared roles from the top-level contract to generate possible roles for each. - * - * @param {Bytes<32>} account - The account requesting a role. - * @param {Bytes<32>} salt - A salt value for the underlying HKDF function. - * @return {[]} - Empty tuple. -  */ - witness recoverRoles(account: Bytes<32>, salt: Bytes<32>): []; + witness merkleTreeIndex(roleId: Bytes<32>): Uint<64>; + + witness getFirstFreeMerkleTreeIndex(): Uint<64>; /** * @description Returns `true` if `account` has been granted `roleId`. @@ -174,17 +125,17 @@ module ShieldedAccessControl { * * Requirements: * - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must + * - A path for the role commitment produced by SHA256(roleId | account | nonce) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. @@ -194,14 +145,14 @@ module ShieldedAccessControl { * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK). * @return {Boolean} - A boolean determining if the account has the specified role.  */ - export circuit hasRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { + export circuit hasRole(roleId: Bytes<32>, account: Either): Boolean { if (!Utils_isContractAddress(account)) { const zswapPubKey = account.left.bytes; - return _checkMerkleTree(roleId, zswapPubKey, nonce); + return _checkMerkleTree(roleId, zswapPubKey); } const contractAddress = account.right.bytes; - return _checkMerkleTree(roleId, contractAddress, nonce); + return _checkMerkleTree(roleId, contractAddress); } /** @@ -211,18 +162,18 @@ module ShieldedAccessControl { * * Requirements: * - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must + * - A path for the role commitment produced by SHA256(roleId | account | nonce) must * exist at `index` in the `_operatorRoles` Merkle tree. * - The caller must not be a ContractAddress. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. @@ -231,11 +182,10 @@ module ShieldedAccessControl { * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {[]} - Empty tuple. */ - export circuit assertOnlyRole(roleId: Bytes<32>, nonce: Bytes<32>): [] { + export circuit assertOnlyRole(roleId: Bytes<32>): [] { _checkRole( roleId, - left(ownPublicKey()), - nonce + left(ownPublicKey()) ); } @@ -246,28 +196,27 @@ module ShieldedAccessControl { * * Requirements: * - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must + * - A path for the role commitment produced by SHA256(roleId | account | nonce) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. * * @param {Bytes<32>} roleId - The role identifier. * @param {Either} account - The account to check. - * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {[]} - Empty tuple. */ - export circuit _checkRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): [] { - assert(hasRole(roleId, account, nonce), "ShieldedAccessControl: unauthorized account"); + export circuit _checkRole(roleId: Bytes<32>, account: Either): [] { + assert(hasRole(roleId, account), "ShieldedAccessControl: unauthorized account"); } /** @@ -277,17 +226,17 @@ module ShieldedAccessControl { * * Requirements: * - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must + * - A path for the role commitment produced by SHA256(roleId | account | nonce) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * @@ -295,18 +244,15 @@ module ShieldedAccessControl { * @param {Bytes<32>} account - The account to check represented as a Bytes<32>. * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {Boolean} - A boolean determining if a path for for the role commitment - * produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) exists in the `_operatorRoles` Merkle tree + * produced by SHA256(roleId | account | nonce) exists in the `_operatorRoles` Merkle tree */ - export circuit _checkMerkleTree(roleId: Bytes<32>, account: Bytes<32>, nonce: Bytes<32>): Boolean { - const contractAddress = kernel.self().bytes; - const intermediateRoleCommitment = persistentHash>>([roleId, account, nonce, contractAddress]); - assert(_roleCommitmentIndex.member(disclose(intermediateRoleCommitment)), "ShieldedAccessControl: role commitment index not found"); - - const index = _roleCommitmentIndex.lookup(disclose(intermediateRoleCommitment)); - const finalRoleCommitment = persistentHash>>([intermediateRoleCommitment, index as Field as Bytes<32>]); - assert(!_roleCommitmentNullifiers.member(disclose(finalRoleCommitment)), "ShieldedAccessControl: role commitment access revoked"); + export circuit _checkMerkleTree(roleId: Bytes<32>, account: Bytes<32>): Boolean { + const nonce = secretNonce(roleId); + const index = merkleTreeIndex(roleId); + const roleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Bytes<32>]); + assert(!_roleCommitmentNullifiers.member(disclose(roleCommitment)), "ShieldedAccessControl: role commitment access revoked"); - const authPath = getRoleCommitmentPath(finalRoleCommitment, index); + const authPath = getRoleCommitmentPath(roleCommitment); return _operatorRoles .checkRoot(merkleTreePathRoot<10, Bytes<32>>(disclose(authPath))); } @@ -337,17 +283,17 @@ module ShieldedAccessControl { * Requirements: * * - `account` must not be a ContractAddress. - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must + * - A path for the role commitment produced by SHA256(roleId | account | nonce) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. @@ -357,9 +303,9 @@ module ShieldedAccessControl { * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {[]} - Empty tuple. */ - export circuit grantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): [] { - assertOnlyRole(getRoleAdmin(roleId), nonce); - _grantRole(roleId, account, nonce); + export circuit grantRole(roleId: Bytes<32>, account: Either): [] { + assertOnlyRole(getRoleAdmin(roleId)); + _grantRole(roleId, account); } /** @@ -370,17 +316,17 @@ module ShieldedAccessControl { * Requirements: * * - `account` must not be a ContractAddress. - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must + * - A path for the role commitment produced by SHA256(roleId | account | nonce) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. @@ -390,9 +336,9 @@ module ShieldedAccessControl { * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {[]} - Empty tuple. */ - export circuit revokeRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): [] { - assertOnlyRole(getRoleAdmin(roleId), nonce); - _revokeRole(roleId, account, nonce); + export circuit revokeRole(roleId: Bytes<32>, account: Either): [] { + assertOnlyRole(getRoleAdmin(roleId)); + _revokeRole(roleId, account); } /** @@ -408,17 +354,17 @@ module ShieldedAccessControl { * * - The caller must be `callerConfirmation`. * - The caller must not be a `ContractAddress`. - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must + * - A path for the role commitment produced by SHA256(roleId | account | nonce) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `callerConfirmation` - a ZswapCoinPublicKey or ContractAddress. @@ -428,10 +374,10 @@ module ShieldedAccessControl { * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {[]} - Empty tuple. */ - export circuit renounceRole(roleId: Bytes<32>, callerConfirmation: Either, nonce: Bytes<32>): [] { + export circuit renounceRole(roleId: Bytes<32>, callerConfirmation: Either): [] { assert(callerConfirmation == left(ownPublicKey()), "ShieldedAccessControl: bad confirmation"); - _revokeRole(roleId, callerConfirmation, nonce); + _revokeRole(roleId, callerConfirmation); } /** @@ -456,17 +402,17 @@ module ShieldedAccessControl { * Requirements: * * - `account` must not be a ContractAddress. - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must + * - A path for the role commitment produced by SHA256(roleId | account | nonce) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. @@ -476,9 +422,9 @@ module ShieldedAccessControl { * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {Boolean} roleGranted - A boolean indicating if `roleId` was granted. */ - export circuit _grantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { + export circuit _grantRole(roleId: Bytes<32>, account: Either): Boolean { assert(!Utils_isContractAddress(account), "ShieldedAccessControl: unsafe role approval"); - return _unsafeGrantRole(roleId, account, nonce); + return _unsafeGrantRole(roleId, account); } /** @@ -492,17 +438,17 @@ module ShieldedAccessControl { * * Requirements: * - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must + * - A path for the role commitment produced by SHA256(roleId | account | nonce) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. @@ -512,19 +458,19 @@ module ShieldedAccessControl { * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {Boolean} roleGranted - A boolean indicating if `role` was granted. */ - export circuit _unsafeGrantRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { - if (hasRole(roleId, account, nonce)) { + export circuit _unsafeGrantRole(roleId: Bytes<32>, account: Either): Boolean { + if (hasRole(roleId, account)) { return false; } if (!Utils_isContractAddress(account)) { const zswapPubKey = account.left.bytes; - _addRoleCommitmentToLedger(roleId, zswapPubKey, nonce); + _addRoleCommitmentToLedger(roleId, zswapPubKey); return true; } const contractAddress = account.right.bytes; - _addRoleCommitmentToLedger(roleId, contractAddress, nonce); + _addRoleCommitmentToLedger(roleId, contractAddress); return true; } @@ -536,17 +482,17 @@ module ShieldedAccessControl { * * Requirements: * - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress) + * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) + * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce) * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index) must + * - A path for the role commitment produced by SHA256(roleId | account | nonce) must * exist at `index` in the `_operatorRoles` Merkle tree. * * Disclosures: * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). + * - The role commitment produced by SHA256(roleId | account | nonce). * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` * Merkle tree. * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. @@ -556,19 +502,19 @@ module ShieldedAccessControl { * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. */ - export circuit _revokeRole(roleId: Bytes<32>, account: Either, nonce: Bytes<32>): Boolean { - if (!hasRole(roleId, account, nonce)) { + export circuit _revokeRole(roleId: Bytes<32>, account: Either): Boolean { + if (!hasRole(roleId, account)) { return false; } if(!Utils_isContractAddress(account)) { const zswapPubKey = account.left.bytes; - _nullifyRoleCommitment(roleId, zswapPubKey, nonce); + _nullifyRoleCommitment(roleId, zswapPubKey); return true; } const contractAddress = account.right.bytes; - _nullifyRoleCommitment(roleId, contractAddress, nonce); + _nullifyRoleCommitment(roleId, contractAddress); return true; } @@ -581,22 +527,19 @@ module ShieldedAccessControl { * * Disclosures: * - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). + * - The role commitment produced by SHA256(roleId | account | nonce). * * @param {Bytes<32>} roleId - The role identifier. * @param {Bytes<32>} account - The account to add represented as a Bytes<32>. * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. */ - circuit _addRoleCommitmentToLedger(roleId: Bytes<32>, account: Bytes<32>, nonce: Bytes<32>): [] { - const contractAddress = kernel.self().bytes; - const intermediateRoleCommitment = persistentHash>>([roleId, account, nonce, contractAddress]); - const index = _nextIndex.read(); - const finalRoleCommitment = persistentHash>>([intermediateRoleCommitment, index as Field as Bytes<32>]); - - _operatorRoles.insertHashIndex(disclose(finalRoleCommitment), index); - _roleCommitmentIndex.insert(disclose(finalRoleCommitment), index); - _nextIndex.increment(1); + circuit _addRoleCommitmentToLedger(roleId: Bytes<32>, account: Bytes<32>): [] { + const nonce = secretNonce(roleId); + const index = getFirstFreeMerkleTreeIndex(); + const roleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Byes<32>]); + + _operatorRoles.insertHashIndex(disclose(roleCommitment), index); } /** @@ -608,52 +551,18 @@ module ShieldedAccessControl { * * Disclosures: * - * - The role commitment produced by SHA256( SHA256(roleId | account | nonce | contractAddress) | index). - * - The intermediate role commitment produced by SHA256(roleId | account | nonce | contractAddress). + * - The role commitment produced by SHA256(roleId | account | nonce). + * - The intermediate role commitment produced by SHA256(roleId | account | nonce). * * @param {Bytes<32>} roleId - The role identifier. * @param {Bytes<32>} account - The account to add represented as a Bytes<32>. * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. */ - circuit _nullifyRoleCommitment(roleId: Bytes<32>, account: Bytes<32>, nonce: Bytes<32>): [] { - const contractAddress = kernel.self().bytes; - const intermediateRoleCommitment = persistentHash>>([roleId, account, nonce, contractAddress]); - const index = _roleCommitmentIndex.lookup(disclose(intermediateRoleCommitment)); - const finalRoleCommitment = persistentHash>>([intermediateRoleCommitment, index as Field as Bytes<32>]); - _roleCommitmentNullifiers.insert(disclose(finalRoleCommitment)); - } - - /** - * @description A wrapper circuit for the `requestRole` witness. - * - * @circuitInfo k=10, rows=188 - * - * Requirements: - * - * - The caller must not be a ContractAddress. - * - * @param {Bytes<32>} roleId - A hash representing a role identifier. - * @return {[]} - Empty tuple. -  */ - export circuit _requestRole(roleId: Bytes<32>): [] { - const publicKey = left(ownPublicKey()); - requestRole(roleId, publicKey.left.bytes, _salt); - } - - /** - * @description A wrapper circuit for the `recoverRoles` witness. - * - * @circuitInfo k=10, rows=99 - * - * Requirements: - * - * - The caller must not be a ContractAddress. - * - * @return {[]} - Empty tuple. -  */ - export circuit _recoverRoles(): [] { - const publicKey = left(ownPublicKey()); - recoverRoles(publicKey.left.bytes, _salt); + circuit _nullifyRoleCommitment(roleId: Bytes<32>, account: Bytes<32>): [] { + const nonce = secretNonce(roleId); + const index = merkleTreeIndex(roleId); + const roleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Bytes<32>]); + _roleCommitmentNullifiers.insert(disclose(roleCommitment)); } } diff --git a/contracts/shieldedAccessControl/src/ShieldedAccessControlUtils.compact b/contracts/src/access/ShieldedAccessControlUtils.compact similarity index 100% rename from contracts/shieldedAccessControl/src/ShieldedAccessControlUtils.compact rename to contracts/src/access/ShieldedAccessControlUtils.compact diff --git a/contracts/src/access/test/mocks/MockShieldedAccessControl.compact b/contracts/src/access/test/mocks/MockShieldedAccessControl.compact new file mode 100644 index 00000000..ac009ca3 --- /dev/null +++ b/contracts/src/access/test/mocks/MockShieldedAccessControl.compact @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT + +pragma language_version >= 0.16.0; + +import CompactStandardLibrary; + +import "../../ShieldedAccessControl" prefix ShieldedAccessControl_; + +export { + ZswapCoinPublicKey, + ContractAddress, + Either, + Maybe, + ShieldedAccessControl_DEFAULT_ADMIN_ROLE, + ShieldedAccessControl__operatorRoles +}; + +export circuit hasRole(roleId: Bytes<32>, account: Either): Boolean { + return ShieldedAccessControl_hasRole(roleId, account); +} + +export circuit assertOnlyRole(roleId: Bytes<32>): [] { + ShieldedAccessControl_assertOnlyRole(roleId); +} + +export circuit _checkRole(roleId: Bytes<32>, account: Either): [] { + ShieldedAccessControl__checkRole(roleId, account); +} + +export circuit _checkMerkleTree(roleId: Bytes<32>, account: Bytes<32>): Boolean { + return ShieldedAccessControl__checkMerkleTree(roleId, account); +} + +export circuit getRoleAdmin(roleId: Bytes<32>): Bytes<32> { + return ShieldedAccessControl_getRoleAdmin(roleId); +} + +export circuit grantRole(roleId: Bytes<32>, account: Either): [] { + ShieldedAccessControl_grantRole(roleId, account); +} + +export circuit revokeRole(roleId: Bytes<32>, account: Either): [] { + ShieldedAccessControl_revokeRole(roleId, account); +} + +export circuit renounceRole(roleId: Bytes<32>, callerConfirmation: Either): [] { + ShieldedAccessControl_renounceRole(roleId, callerConfirmation); +} + +export circuit _setRoleAdmin(roleId: Bytes<32>, adminRole: Bytes<32>): [] { + ShieldedAccessControl__setRoleAdmin(roleId, adminRole); +} + +export circuit _grantRole(roleId: Bytes<32>, account: Either): Boolean { + return ShieldedAccessControl__grantRole(roleId, account); +} + +export circuit _unsafeGrantRole(roleId: Bytes<32>, account: Either): Boolean { + return ShieldedAccessControl__unsafeGrantRole(roleId, account); +} + +export circuit _revokeRole(roleId: Bytes<32>, account: Either): Boolean { + return ShieldedAccessControl__revokeRole(roleId, account); +} \ No newline at end of file diff --git a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts new file mode 100644 index 00000000..b71d4cf4 --- /dev/null +++ b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts @@ -0,0 +1,297 @@ +import { + type CircuitContext, + type CoinPublicKey, + type ContractState, + QueryContext, + constructorContext, + emptyZswapLocalState, +} from '@midnight-ntwrk/compact-runtime'; +import { sampleContractAddress } from '@midnight-ntwrk/zswap'; +import { + type ContractAddress, + type Either, + type Ledger, + Contract as MockShieldedAccessControl, + type ZswapCoinPublicKey, + ledger, +} from '../../shieldedAccessControl/src/artifacts/MockShieldedAccessControl/contract/index.cjs'; // Combined imports +import { + type ShieldedAccessControlPrivateState, + ShieldedAccessControlWitnesses, +} from '../../witnesses/ShieldedAccessControlWitnesses.js'; +import type { IContractSimulator } from '../../shieldedAccessControl/src/test/types/test.js'; + +/** + * @description A simulator implementation of a AccessControl contract for testing purposes. + * @template P - The private state type, fixed to ShieldedAccessControlPrivateState. + * @template L - The ledger type, fixed to Contract.Ledger. + */ +export class AccessControlSimulator + implements IContractSimulator { + /** @description The underlying contract instance managing contract logic. */ + readonly contract: MockShieldedAccessControl; + + /** @description The deployed address of the contract. */ + readonly contractAddress: string; + + /** @description The current circuit context, updated by contract operations. */ + circuitContext: CircuitContext; + + /** + * @description Initializes the mock contract. + */ + constructor() { + this.contract = new MockShieldedAccessControl( + ShieldedAccessControlWitnesses, + ); + const { + currentPrivateState, + currentContractState, + currentZswapLocalState, + } = this.contract.initialState(constructorContext({}, '0'.repeat(64))); + this.circuitContext = { + currentPrivateState, + currentZswapLocalState, + originalState: currentContractState, + transactionContext: new QueryContext( + currentContractState.data, + sampleContractAddress(), + ), + }; + this.contractAddress = this.circuitContext.transactionContext.address; + } + + /** + * @description Retrieves the current public ledger state of the contract. + * @returns The ledger state as defined by the contract. + */ + public getCurrentPublicState(): Ledger { + return ledger(this.circuitContext.transactionContext.state); + } + + /** + * @description Retrieves the current private state of the contract. + * @returns The private state of type ShieldedAccessControlPrivateState. + */ + public getCurrentPrivateState(): ShieldedAccessControlPrivateState { + return this.circuitContext.currentPrivateState; + } + + /** + * @description Retrieves the current contract state. + * @returns The contract state object. + */ + public getCurrentContractState(): ContractState { + return this.circuitContext.originalState; + } + + /** + * @description Retrieves an account's permission for `roleId`. + * @param roleId - The role identifier. + * @param account - A ZswapCoinPublicKey or a ContractAddress. + * @returns Whether an account has a specified role. + */ + public hasRole( + roleId: Uint8Array, + account: Either, + ): boolean { + return this.contract.impureCircuits.hasRole( + this.circuitContext, + roleId, + account, + ).result; + } + + /** + * @description Retrieves an account's permission for `roleId`. + * @param caller - Optional. Sets the caller context if provided. + * @param roleId - The role identifier. + */ + public assertOnlyRole(roleId: Uint8Array, caller?: CoinPublicKey) { + const res = this.contract.impureCircuits.assertOnlyRole( + { + ...this.circuitContext, + currentZswapLocalState: caller + ? emptyZswapLocalState(caller) + : this.circuitContext.currentZswapLocalState, + }, + roleId, + ); + + this.circuitContext = res.context; + } + + /** + * @description Retrieves an account's permission for `roleId`. + * @param roleId - The role identifier. + * @param account - A ZswapCoinPublicKey or a ContractAddress. + */ + public _checkRole( + roleId: Uint8Array, + account: Either, + ) { + this.circuitContext = this.contract.impureCircuits._checkRole( + this.circuitContext, + roleId, + account, + ).context; + } + + /** + * @description Retrieves `roleId`'s admin identifier. + * @param roleId - The role identifier. + * @returns The admin identifier for `roleId`. + */ + public getRoleAdmin(roleId: Uint8Array): Uint8Array { + return this.contract.impureCircuits.getRoleAdmin( + this.circuitContext, + roleId, + ).result; + } + + /** + * @description Grants an account permissions to use `roleId`. + * @param caller - Optional. Sets the caller context if provided. + * @param roleId - The role identifier. + * @param account - A ZswapCoinPublicKey or a ContractAddress. + */ + public grantRole( + roleId: Uint8Array, + account: Either, + caller?: CoinPublicKey, + ) { + const res = this.contract.impureCircuits.grantRole( + { + ...this.circuitContext, + currentZswapLocalState: caller + ? emptyZswapLocalState(caller) + : this.circuitContext.currentZswapLocalState, + }, + roleId, + account, + ); + + this.circuitContext = res.context; + } + + /** + * @description Revokes an account's permission to use `roleId`. + * @param caller - Optional. Sets the caller context if provided. + * @param roleId - The role identifier. + * @param account - A ZswapCoinPublicKey or a ContractAddress. + */ + public revokeRole( + roleId: Uint8Array, + account: Either, + caller?: CoinPublicKey, + ) { + const res = this.contract.impureCircuits.revokeRole( + { + ...this.circuitContext, + currentZswapLocalState: caller + ? emptyZswapLocalState(caller) + : this.circuitContext.currentZswapLocalState, + }, + roleId, + account, + ); + + this.circuitContext = res.context; + } + + /** + * @description Revokes `roleId` from the calling account. + * @param caller - Optional. Sets the caller context if provided. + * @param roleId - The role identifier. + * @param account - A ZswapCoinPublicKey or a ContractAddress. + */ + public renounceRole( + roleId: Uint8Array, + account: Either, + caller?: CoinPublicKey, + ) { + const res = this.contract.impureCircuits.renounceRole( + { + ...this.circuitContext, + currentZswapLocalState: caller + ? emptyZswapLocalState(caller) + : this.circuitContext.currentZswapLocalState, + }, + roleId, + account, + ); + + this.circuitContext = res.context; + } + + /** + * @description Sets the admin identifier for `roleId`. + * @param roleId - The role identifier. + * @param adminId - The admin role identifier. + */ + public _setRoleAdmin(roleId: Uint8Array, adminId: Uint8Array) { + this.circuitContext = this.contract.impureCircuits._setRoleAdmin( + this.circuitContext, + roleId, + adminId, + ).context; + } + + /** + * @description Grants an account permissions to use `roleId`. Internal function without access restriction. + * @param roleId - The role identifier. + * @param account - A ZswapCoinPublicKey or a ContractAddress. + */ + public _grantRole( + roleId: Uint8Array, + account: Either, + ): boolean { + const res = this.contract.impureCircuits._grantRole( + this.circuitContext, + roleId, + account, + ); + + this.circuitContext = res.context; + return res.result; + } + + /** + * @description Grants an account permissions to use `roleId`. Internal function without access restriction. + * DOES NOT restrict sending to a ContractAddress. + * @param roleId - The role identifier. + * @param account - A ZswapCoinPublicKey or a ContractAddress. + */ + public _unsafeGrantRole( + roleId: Uint8Array, + account: Either, + ): boolean { + const res = this.contract.impureCircuits._unsafeGrantRole( + this.circuitContext, + roleId, + account, + ); + + this.circuitContext = res.context; + return res.result; + } + + /** + * @description Revokes an account's permission to use `roleId`. Internal function without access restriction. + * @param roleId - The role identifier. + * @param account - A ZswapCoinPublicKey or a ContractAddress. + */ + public _revokeRole( + roleId: Uint8Array, + account: Either, + ): boolean { + const res = this.contract.impureCircuits._revokeRole( + this.circuitContext, + roleId, + account, + ); + + this.circuitContext = res.context; + return res.result; + } +} diff --git a/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts similarity index 98% rename from contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts rename to contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts index 74ae49bd..4ad00147 100644 --- a/contracts/shieldedAccessControl/src/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts @@ -14,7 +14,7 @@ import { type Ledger, Contract as MockShieldedAccessControl, type ZswapCoinPublicKey, -} from '../artifacts/MockShieldedAccessControl/contract/index.cjs'; // Combined imports +} from '../shieldedAccessControl/src/artifacts/MockShieldedAccessControl/contract/index.cjs'; // Combined imports const { hkdfSync } = await import('node:crypto'); From 8e5dd6bdd2862da583258a7ba0be720562b9f6d9 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Tue, 26 Aug 2025 22:42:14 -0500 Subject: [PATCH 143/202] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ⟣ €₥ℵ∪ℓ ⟢ <34749913+emnul@users.noreply.github.com> Signed-off-by: Andrew Fleming --- compact/src/runCompiler.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compact/src/runCompiler.ts b/compact/src/runCompiler.ts index e5a0ecf6..31d69cea 100644 --- a/compact/src/runCompiler.ts +++ b/compact/src/runCompiler.ts @@ -41,7 +41,7 @@ import { CompactCompiler } from './Compiler.js'; * ℹ [COMPILE] Compact compiler started * ℹ [COMPILE] COMPACT_HOME: /path/to/compactc * ℹ [COMPILE] COMPACTC_PATH: /path/to/compactc/compactc - * ℹ [COMPILE] TARGET_DIR: accesss:compact:access: + * ℹ [COMPILE] TARGET_DIR: access:compact:access: * ℹ [COMPILE] Found 4 .compact file(s) to compile in access/ * ✔ [COMPILE] [1/4] Compiled access/AccessControl.compact * ✔ [COMPILE] [2/4] Compiled access/Ownable.compact @@ -68,7 +68,8 @@ async function runCompiler(): Promise { for (let i = 0; i < args.length; i++) { if (args[i] === '--dir') { - if (i + 1 < args.length && !args[i + 1].startsWith('--')) { + const dirNameExists = i + 1 < args.length && !args[i + 1].startsWith('--'); + if (dirNameExists) { targetDir = args[i + 1]; i++; // Skip the next argument (directory name) } else { From b100b9e506c6635aaf6684c8abbcad94b5a77823 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 27 Aug 2025 12:57:30 -0300 Subject: [PATCH 144/202] add AGPK section --- docs/modules/ROOT/pages/access.adoc | 33 ++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/access.adoc b/docs/modules/ROOT/pages/access.adoc index e8c1cf47..0347b1f0 100644 --- a/docs/modules/ROOT/pages/access.adoc +++ b/docs/modules/ROOT/pages/access.adoc @@ -226,7 +226,7 @@ Some examples: WARNING: Approaches that avoid private key exposure (public key + passphrase, signature-based) are generally recommended for operational security. -Deriving the nonce deterministically from the public key and user passphrase provides a balance of security and recoverability: +Deriving the nonce deterministically from an <> and user passphrase provides a balance of security and recoverability: ```typescript // Example: Scrypt-based derivation @@ -254,6 +254,37 @@ Users should carefully evaluate their threat model, operational requirements, and privacy needs when selecting a nonce generation strategy, as this choice cannot be easily changed without transferring ownership. +=== Air-Gapped Public Key (AGPK) + +For maximum privacy guarantees, +users should employ an Air-Gapped Public Key (AGPK) exclusively for contract ownership and administrative circuits. +An AGPK is a public key that maintains complete isolation from all other on-chain activities, +similar to how air-gapped systems are isolated from networks to prevent data leakage. + +==== The Privacy Enhancement + +While `ZOwnablePK` provides cryptographic privacy through its commitment scheme, +operational security practices like using an AGPK provide an additional layer of protection against correlation attacks. Even with the strongest cryptographic commitments, +reusing a public key across different on-chain activities can potentially compromise privacy +through transaction pattern analysis. + +==== AGPK Principles + +An Air-Gapped Public Key must adhere to strict isolation principles: + +- *Never used before:* The public key has no prior transaction history on any blockchain network. +- *Never used elsewhere:* The key is exclusively reserved for the specific contract's administrative circuits i.e. `assertOnlyOwner`. +- *Never used again:* The private key is destroyed once ownership is renounced or transferred to another account. + +==== Best Practices Recommendation + +While neither required nor enforced by the `ZOwnablePK` module, +an Air-Gapped Public Key provides strong operational privacy hygiene for shielded contract administration. +Users should evaluate their threat model and privacy requirements when deciding whether to implement AGPK practices. + +WARNING: The effectiveness of an AGPK depends entirely on abiding by the AGPK principles. +A single transaction using the key outside the administrative context compromises all privacy benefits. + === Usage Import the `ZOwnablePK` module into the implementing contract and expose the ownership-handling circuits. From 15aff68740757c21c7b6fe0657c67f1a56d16783 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 27 Aug 2025 13:16:05 -0300 Subject: [PATCH 145/202] fix guide links in access api, add agpk ref --- docs/modules/ROOT/pages/api/access.adoc | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/modules/ROOT/pages/api/access.adoc b/docs/modules/ROOT/pages/api/access.adoc index 5b3b95f6..adc19ac6 100644 --- a/docs/modules/ROOT/pages/api/access.adoc +++ b/docs/modules/ROOT/pages/api/access.adoc @@ -1,6 +1,8 @@ :github-icon: pass:[] -:accessControl-guide: xref:accessControl.adoc[AccessControl guide] -:ownable-guide: xref:ownable.adoc[Ownable guide] +:accessControl-guide: xref:access.adoc#role_based_access_control[AccessControl guide] +:ownable-guide: xref:access.adoc#ownership_and_ownable[Ownable guide] +:zownablepk-guide: xref:access.adoc#shielded_ownership_and_zownablepk[ZOwnablePK guide] +:agpk: xref:access.adoc#air_gapped_public_key_agpk[Air-Gapped Public Key] :grantRole: <> :revokeRole: <> @@ -12,6 +14,8 @@ This directory provides ways to restrict who can access the circuits of a contra - `<>` is a simpler mechanism with a single owner "role" that can be assigned to a single account. This simpler mechanism can be useful for quick tests but projects with production concerns are likely to outgrow it. +- `<>` provides a privacy-preserving single owner access control mechanism using cryptographic commitments. The owner's public key is never revealed on-chain, instead storing only a commitment that proves ownership without exposing identity, suitable for applications requiring administrative control with strong privacy guarantees. + == Core [.contract] @@ -411,12 +415,13 @@ import "./node_modules/@openzeppelin-compact/contracts/src/access/ZOwnablePK"; `ZOwnablePK` provides a privacy-preserving access control mechanism for contracts with a single administrative user. Unlike traditional `Ownable` implementations that store or expose the owner's public key on-chain, this module stores only a commitment to a hashed identifier derived from the owner's public key and a secret nonce. +For the strongest security guarantees, use an {agpk}. Ownable provides a basic access control mechanism where an account (an owner) can be granted exclusive access to specific circuits. This module includes <> to restrict a circuit to be used only by the owner. -TIP: For an overview of the module, read the {ownable-guide}. +TIP: For an overview of the module, read the {zownablepk-guide}. [.contract-index] .Circuits From 524dab91958be425310994ba21f59f0546f54867 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 27 Aug 2025 13:31:56 -0300 Subject: [PATCH 146/202] add agpk recommendation --- contracts/src/access/ZOwnablePK.compact | 2 ++ docs/modules/ROOT/pages/api/access.adoc | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/src/access/ZOwnablePK.compact b/contracts/src/access/ZOwnablePK.compact index 9f750dff..c4704a9c 100644 --- a/contracts/src/access/ZOwnablePK.compact +++ b/contracts/src/access/ZOwnablePK.compact @@ -11,6 +11,7 @@ pragma language_version >= 0.16.0; * `Ownable` implementations that store or expose the owner's public key * on-chain, this module stores only a commitment to a hashed identifier * derived from the owner's public key and a secret nonce. + * For the strongest security guarantees, use an Air-Gapped Public Key. * * @notice This module explicitly supports commitments derived from public keys; * however, it may be possible to use contract addresses when contract-to-contract @@ -255,6 +256,7 @@ module ZOwnablePK { * - `pk`: The public key of the caller. This is passed explicitly to allow * for off-chain derivation, testing, or scenarios where the caller is * different from the subject of the computation. + * We recommend using an Air-Gapped Public Key. * - `nonce`: A secret nonce tied to the identity. The generation strategy is * left to the user, offering different security/convenience trade-offs. * diff --git a/docs/modules/ROOT/pages/api/access.adoc b/docs/modules/ROOT/pages/api/access.adoc index adc19ac6..dd900b8a 100644 --- a/docs/modules/ROOT/pages/api/access.adoc +++ b/docs/modules/ROOT/pages/api/access.adoc @@ -444,7 +444,7 @@ TIP: For an overview of the module, read the {zownablepk-guide}. Initializes the contract by setting the initial owner via `ownerId` and storing the `instanceSalt` that acts as a privacy additive -for preventing duplicate commitments among other contracts implementing ZOwnablePK. +for preventing duplicate commitments among other contracts implementing `ZOwnablePK`. NOTE: The `ownerId` must be calculated prior to contract deployment. See <> @@ -569,6 +569,7 @@ Computes the unique identifier (`id`) of the owner from their public key and a s - `pk`: The public key of the caller. This is passed explicitly to allow for off-chain derivation, testing, or scenarios where the caller is different from the subject of the computation. +We recommend using an {agpk}. - `nonce`: A secret nonce tied to the identity. This value should be randomly generated and kept private. It may be rotated periodically for enhanced unlinkability. From 7ba770dfcca6649c48bcb38abb867ce0413a6ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Wed, 27 Aug 2025 16:31:29 -0400 Subject: [PATCH 147/202] WIP Experimental re-design --- .../src/access/ShieldedAccessControl.compact | 86 ++++++------------- 1 file changed, 25 insertions(+), 61 deletions(-) diff --git a/contracts/src/access/ShieldedAccessControl.compact b/contracts/src/access/ShieldedAccessControl.compact index fd21521c..d9870c24 100644 --- a/contracts/src/access/ShieldedAccessControl.compact +++ b/contracts/src/access/ShieldedAccessControl.compact @@ -101,6 +101,8 @@ module ShieldedAccessControl {  */ export ledger _roleCommitmentNullifiers: Set>; + export ledger _currentMerkleTreeIndex: Counter; + export ledger DEFAULT_ADMIN_ROLE: Bytes<32>; /** @@ -114,9 +116,12 @@ module ShieldedAccessControl { witness secretNonce(roleId: Bytes<32>): Bytes<32>; - witness merkleTreeIndex(roleId: Bytes<32>): Uint<64>; + witness getRoleCommitmentIndex(roleId: Bytes<32>): Uint<64>; - witness getFirstFreeMerkleTreeIndex(): Uint<64>; + struct Role { + hasRole: Boolean; + roleCommitment: Bytes<32>; + } /** * @description Returns `true` if `account` has been granted `roleId`. @@ -145,7 +150,7 @@ module ShieldedAccessControl { * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK). * @return {Boolean} - A boolean determining if the account has the specified role.  */ - export circuit hasRole(roleId: Bytes<32>, account: Either): Boolean { + export circuit hasRole(roleId: Bytes<32>, account: Either): Role { if (!Utils_isContractAddress(account)) { const zswapPubKey = account.left.bytes; return _checkMerkleTree(roleId, zswapPubKey); @@ -216,7 +221,8 @@ module ShieldedAccessControl { * @return {[]} - Empty tuple. */ export circuit _checkRole(roleId: Bytes<32>, account: Either): [] { - assert(hasRole(roleId, account), "ShieldedAccessControl: unauthorized account"); + const role = hasRole(roleId, account); + assert(role.hasRole, "ShieldedAccessControl: unauthorized account"); } /** @@ -246,15 +252,16 @@ module ShieldedAccessControl { * @return {Boolean} - A boolean determining if a path for for the role commitment * produced by SHA256(roleId | account | nonce) exists in the `_operatorRoles` Merkle tree */ - export circuit _checkMerkleTree(roleId: Bytes<32>, account: Bytes<32>): Boolean { + export circuit _checkMerkleTree(roleId: Bytes<32>, account: Bytes<32>): Role { const nonce = secretNonce(roleId); - const index = merkleTreeIndex(roleId); + const index = getMerkleTreeIndex(roleId); const roleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Bytes<32>]); assert(!_roleCommitmentNullifiers.member(disclose(roleCommitment)), "ShieldedAccessControl: role commitment access revoked"); const authPath = getRoleCommitmentPath(roleCommitment); - return _operatorRoles + const hasRole = _operatorRoles .checkRoot(merkleTreePathRoot<10, Bytes<32>>(disclose(authPath))); + return Role {hasRole, roleCommitment}; } /** @@ -459,18 +466,22 @@ module ShieldedAccessControl { * @return {Boolean} roleGranted - A boolean indicating if `role` was granted. */ export circuit _unsafeGrantRole(roleId: Bytes<32>, account: Either): Boolean { - if (hasRole(roleId, account)) { + const role = hasRole(roleId, account); + if (role.hasRole) { return false; } if (!Utils_isContractAddress(account)) { const zswapPubKey = account.left.bytes; - _addRoleCommitmentToLedger(roleId, zswapPubKey); + _operatorRoles.insertHashIndex(disclose(role.roleCommitment), _currentMerkleTreeIndex); + _currentMerkleTreeIndex.increment(1); return true; } const contractAddress = account.right.bytes; - _addRoleCommitmentToLedger(roleId, contractAddress); + // Use ledger index as source of truth + _operatorRoles.insertHashIndex(disclose(role.roleCommitment), _currentMerkleTreeIndex); + _currentMerkleTreeIndex.increment(1); return true; } @@ -503,66 +514,19 @@ module ShieldedAccessControl { * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. */ export circuit _revokeRole(roleId: Bytes<32>, account: Either): Boolean { - if (!hasRole(roleId, account)) { + const role = hasRole(roleId, account); + if (!role.hasRole) { return false; } if(!Utils_isContractAddress(account)) { const zswapPubKey = account.left.bytes; - _nullifyRoleCommitment(roleId, zswapPubKey); + _roleCommitmentNullifiers.insert(disclose(role.roleCommitment)); return true; } const contractAddress = account.right.bytes; - _nullifyRoleCommitment(roleId, contractAddress); + _roleCommitmentNullifiers.insert(disclose(role.roleCommitment)); return true; } - - /** - * @description Adds a role commitment to the `_operatorRoles` Merkle tree. - * - * WARNING: Exposing this circuit in the implementing contract would allow anyone to add roles. - * - * @circuitInfo k=15, rows=24565 - * - * Disclosures: - * - * - The role commitment produced by SHA256(roleId | account | nonce). - * - * @param {Bytes<32>} roleId - The role identifier. - * @param {Bytes<32>} account - The account to add represented as a Bytes<32>. - * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) - * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. - */ - circuit _addRoleCommitmentToLedger(roleId: Bytes<32>, account: Bytes<32>): [] { - const nonce = secretNonce(roleId); - const index = getFirstFreeMerkleTreeIndex(); - const roleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Byes<32>]); - - _operatorRoles.insertHashIndex(disclose(roleCommitment), index); - } - - /** - * @description Adds a role commitment to the `_roleNullifiers` nullifer set. - * - * WARNING: Exposing this circuit in the implementing contract would allow anyone to revoke roles. - * - * @circuitInfo k=15, rows=24559 - * - * Disclosures: - * - * - The role commitment produced by SHA256(roleId | account | nonce). - * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - * @param {Bytes<32>} roleId - The role identifier. - * @param {Bytes<32>} account - The account to add represented as a Bytes<32>. - * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) - * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. - */ - circuit _nullifyRoleCommitment(roleId: Bytes<32>, account: Bytes<32>): [] { - const nonce = secretNonce(roleId); - const index = merkleTreeIndex(roleId); - const roleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Bytes<32>]); - _roleCommitmentNullifiers.insert(disclose(roleCommitment)); - } } From a3e6a3ad01e893ff189a7a2fdc260e7bc9077a11 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 27 Aug 2025 17:57:16 -0300 Subject: [PATCH 148/202] add descriptions to circuits in sim --- .../test/simulators/ZOwnablePKSimulator.ts | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/contracts/src/access/test/simulators/ZOwnablePKSimulator.ts b/contracts/src/access/test/simulators/ZOwnablePKSimulator.ts index 730ae0d4..e6e3f62b 100644 --- a/contracts/src/access/test/simulators/ZOwnablePKSimulator.ts +++ b/contracts/src/access/test/simulators/ZOwnablePKSimulator.ts @@ -203,46 +203,57 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< } /** - * @description Returns the shielded owner. - * @returns The shielded owner. + * @description Returns the current commitment representing the contract owner. + * The full commitment is: `SHA256(SHA256(pk, nonce), instanceSalt, counter, domain)`. + * @returns The current owner's commitment. */ public owner(): Uint8Array { return this.circuits.impure.owner(); } /** - * @description + * @description Transfers ownership to `newOwnerId`. + * `newOwnerId` must be precalculated and given to the current owner off chain. + * @param newOwnerId The new owner's unique identifier (`SHA256(pk, nonce)`). */ public transferOwnership(newOwnerId: Uint8Array) { this.circuits.impure.transferOwnership(newOwnerId); } /** - * @description Leaves the contract without an owner. It will not be - * possible to call `assertOnlyOnwer` circuits anymore. Can only be - * called by the current owner. + * @description Leaves the contract without an owner. + * It will not be possible to call `assertOnlyOnwer` circuits anymore. + * Can only be called by the current owner. */ public renounceOwnership() { this.circuits.impure.renounceOwnership(); } /** - * @description Throws if called by any account other than the owner. - * Use this to restrict access to sensitive circuits. + * @description Throws if called by any account whose id hash `SHA256(pk, nonce)` does not match + * the stored owner commitment. Use this to only allow the owner to call specific circuits. */ public assertOnlyOwner() { this.circuits.impure.assertOnlyOwner(); } /** - * @description + * @description Computes the owner commitment from the given `id` and `counter`. + * @param id - The unique identifier of the owner calculated by `SHA256(pk, nonce)`. + * @param counter - The current counter or round. This increments by `1` + * after every transfer to prevent duplicate commitments given the same `id`. + * @returns The commitment derived from `id` and `counter`. */ public _computeOwnerCommitment(id: Uint8Array, counter: bigint): Uint8Array { return this.circuits.impure._computeOwnerCommitment(id, counter); } /** - * @description + * @description Computes the unique identifier (`id`) of the owner from their + * public key and a secret nonce. + * @param pk - The public key of the identity being committed. + * @param nonce - A private nonce to scope the commitment. + * @returns The computed owner ID. */ public _computeOwnerId( pk: Either, @@ -252,7 +263,9 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< } /** - * @description Internal circuit that transfers ownership of the contract to `newOwner`. + * @description Transfers ownership to owner id `newOwnerId` without + * enforcing permission checks on the caller. + * @param newOwnerId - The unique identifier of the new owner calculated by `SHA256(pk, nonce)`. */ public _transferOwnership(newOwnerId: Uint8Array) { this.circuits.impure._transferOwnership(newOwnerId); @@ -260,7 +273,9 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< public readonly privateState = { /** - * @description Stubs a new nonce into the private state. + * @description Contextually sets a new nonce into the private state. + * @param newNonce The secret nonce. + * @returns The ZOwnablePK private state after setting the new nonce. */ injectSecretNonce: ( newNonce: Buffer, @@ -271,6 +286,10 @@ export class ZOwnablePKSimulator extends AbstractContractSimulator< return updatedState; }, + /** + * @description Returns the secret nonce given the context. + * @returns The secret nonce. + */ getCurrentSecretNonce: (): Uint8Array => { return this.stateManager.getContext().currentPrivateState.secretNonce; }, From a22ba6c68e722fb0fe6379fc2b7d0a1433309c62 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 27 Aug 2025 17:57:51 -0300 Subject: [PATCH 149/202] fix sim state mngr, improve docs --- .../src/access/test/utils/SimualatorStateManager.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/contracts/src/access/test/utils/SimualatorStateManager.ts b/contracts/src/access/test/utils/SimualatorStateManager.ts index 943ecf35..def6174a 100644 --- a/contracts/src/access/test/utils/SimualatorStateManager.ts +++ b/contracts/src/access/test/utils/SimualatorStateManager.ts @@ -4,6 +4,7 @@ import { type ContractState, constructorContext, QueryContext, + sampleContractAddress } from '@midnight-ntwrk/compact-runtime'; /** @@ -28,9 +29,9 @@ import { * const contract = new MyContract(witnesses); * const manager = new SimulatorStateManager( * contract, - * { foo: 1n }, // initial private state + * { foo: 1n }, // initial private state * '0'.repeat(64), // coin public key - * undefined, // optional contract address + * sampleContractAddress(), // optional contract address * arg1, arg2 // additional constructor args * ); * @@ -45,8 +46,8 @@ export class SimulatorStateManager

{ * * @param contract - A compiled Compact contract instance (from artifacts), exposing `initialState()`. * @param privateState - The initial private state to inject into the contract. - * @param coinPK - The caller's coin public key (used to create the constructor context and as default address). - * @param contractAddress - Optional override for the contract's address. Defaults to `coinPK` if not provided. + * @param coinPK - The caller's coin public key (used to create the constructor context). + * @param contractAddress - Optional override for the contract's address. Defaults to `sampleContractAddress` if not provided. * @param contractArgs - Additional arguments to pass to the contract constructor (e.g., circuit params). */ constructor( @@ -79,7 +80,7 @@ export class SimulatorStateManager

{ originalState: currentContractState, transactionContext: new QueryContext( currentContractState.data, - contractAddress ?? coinPK, + contractAddress ?? sampleContractAddress(), ), }; } From 37bb7aa85efbf27078361e4bca4840c50e4090e2 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 27 Aug 2025 17:58:37 -0300 Subject: [PATCH 150/202] fix fmt --- contracts/src/access/test/utils/SimualatorStateManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/access/test/utils/SimualatorStateManager.ts b/contracts/src/access/test/utils/SimualatorStateManager.ts index def6174a..ac06fdba 100644 --- a/contracts/src/access/test/utils/SimualatorStateManager.ts +++ b/contracts/src/access/test/utils/SimualatorStateManager.ts @@ -4,7 +4,7 @@ import { type ContractState, constructorContext, QueryContext, - sampleContractAddress + sampleContractAddress, } from '@midnight-ntwrk/compact-runtime'; /** From 02227ab2cfa624606d33227df575206008feea88 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 27 Aug 2025 18:35:01 -0300 Subject: [PATCH 151/202] add non-deterministic sig warning --- docs/modules/ROOT/pages/access.adoc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/access.adoc b/docs/modules/ROOT/pages/access.adoc index 0347b1f0..2f3facbb 100644 --- a/docs/modules/ROOT/pages/access.adoc +++ b/docs/modules/ROOT/pages/access.adoc @@ -211,12 +211,20 @@ However, it requires secure backup of both the private key and the nonce. ==== Deterministic Nonce +:rfc6979: https://datatracker.ietf.org/doc/html/rfc6979[RFC 6979] +:ed25519: https://ed25519.cr.yp.to/[Ed25519] + Deriving the nonce deterministically enables recovery through derivation schemes. Some examples: - `H(passphrase + context)` - recoverable from passphrase only, but passphrase becomes critical single point of failure. - `H(publicKey + userPassphrase + context)` - requires both public key and passphrase. -- `H(signature + context)` where `signature = sign(context)` - leverages wallet without exposing private key. +- `H(signature + context)` where `signature = sign(context)` - leverages wallet without exposing private key + +WARNING: When using signature-based nonce derivation, +ensure the wallet/library uses deterministic signatures ({ed25519} or {rfc6979} for ECDSA). +Non-deterministic signatures will generate different nonces on each signing, making recovery impossible. +Test the implementation by signing the same message twice then verify that the signatures match. *Context-Dependent Derivations:* From b584c533cfc4cdcfbbddb44e0846143f270d3b52 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 27 Aug 2025 18:35:50 -0300 Subject: [PATCH 152/202] add period --- docs/modules/ROOT/pages/access.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/access.adoc b/docs/modules/ROOT/pages/access.adoc index 2f3facbb..ecb1852a 100644 --- a/docs/modules/ROOT/pages/access.adoc +++ b/docs/modules/ROOT/pages/access.adoc @@ -219,7 +219,7 @@ Some examples: - `H(passphrase + context)` - recoverable from passphrase only, but passphrase becomes critical single point of failure. - `H(publicKey + userPassphrase + context)` - requires both public key and passphrase. -- `H(signature + context)` where `signature = sign(context)` - leverages wallet without exposing private key +- `H(signature + context)` where `signature = sign(context)` - leverages wallet without exposing private key. WARNING: When using signature-based nonce derivation, ensure the wallet/library uses deterministic signatures ({ed25519} or {rfc6979} for ECDSA). From 418a7b564ae5276aef53beeb1a2dc452b0bd55b3 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 27 Aug 2025 19:21:56 -0300 Subject: [PATCH 153/202] improve agpk principles --- docs/modules/ROOT/pages/access.adoc | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/modules/ROOT/pages/access.adoc b/docs/modules/ROOT/pages/access.adoc index ecb1852a..556a5379 100644 --- a/docs/modules/ROOT/pages/access.adoc +++ b/docs/modules/ROOT/pages/access.adoc @@ -280,9 +280,16 @@ through transaction pattern analysis. An Air-Gapped Public Key must adhere to strict isolation principles: -- *Never used before:* The public key has no prior transaction history on any blockchain network. -- *Never used elsewhere:* The key is exclusively reserved for the specific contract's administrative circuits i.e. `assertOnlyOwner`. -- *Never used again:* The private key is destroyed once ownership is renounced or transferred to another account. +- *Never used before:* The private key material +(including any seed, parent key, or entropy source from which this key is derived) +has never generated any public key that appears in any on-chain transaction, across any blockchain network. +The key material must be cryptographically virgin. +- *Never used elsewhere:* From the moment of AGPK generation until its destruction, +the private key material is used exclusively for this contract's administrative functions (i.e. `assertOnlyOwner`). +No other public keys may be derived from or generated with the same key material during this period. +- *Never used again:* Users commit to destroying all copies of the private key material +upon ownership renunciation or transfer. +This relies entirely on user discipline and cannot be externally verified or enforced. ==== Best Practices Recommendation From bd18c19094c59ed12748700d206f2a011539689a Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 27 Aug 2025 19:28:07 -0300 Subject: [PATCH 154/202] improve clarity on 'never used elsewhere' --- docs/modules/ROOT/pages/access.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/access.adoc b/docs/modules/ROOT/pages/access.adoc index 556a5379..f9c2d52c 100644 --- a/docs/modules/ROOT/pages/access.adoc +++ b/docs/modules/ROOT/pages/access.adoc @@ -286,7 +286,7 @@ has never generated any public key that appears in any on-chain transaction, acr The key material must be cryptographically virgin. - *Never used elsewhere:* From the moment of AGPK generation until its destruction, the private key material is used exclusively for this contract's administrative functions (i.e. `assertOnlyOwner`). -No other public keys may be derived from or generated with the same key material during this period. +No other public keys may ever be derived from or generated with the same key material. - *Never used again:* Users commit to destroying all copies of the private key material upon ownership renunciation or transfer. This relies entirely on user discipline and cannot be externally verified or enforced. From acf33979a8413ee13a9ca4c941a2ff7f08465fa9 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 27 Aug 2025 22:15:22 -0300 Subject: [PATCH 155/202] add wit_ prefix to witnesses --- contracts/src/access/ZOwnablePK.compact | 6 +++--- contracts/src/access/witnesses/ZOwnablePKWitnesses.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/src/access/ZOwnablePK.compact b/contracts/src/access/ZOwnablePK.compact index c4704a9c..37e4616f 100644 --- a/contracts/src/access/ZOwnablePK.compact +++ b/contracts/src/access/ZOwnablePK.compact @@ -74,13 +74,13 @@ module ZOwnablePK { export sealed ledger _instanceSalt: Bytes<32>; /** - * @witness secretNonce + * @witness wit_secretNonce * @description A private per-user nonce used in deriving the shielded owner identifier. * * Combined with the user's public key as `SHA256(pk, nonce)` to produce an obfuscated, * unlinkable identity commitment. Users are encouraged to rotate this value on ownership changes. */ - export witness secretNonce(): Bytes<32>; + export witness wit_secretNonce(): Bytes<32>; /** * @description Initializes the contract by setting the initial owner via `ownerId` @@ -189,7 +189,7 @@ module ZOwnablePK { export circuit assertOnlyOwner(): [] { Initializable_assertInitialized(); - const nonce = secretNonce(); + const nonce = wit_secretNonce(); const callerAsEither = Either { is_left: true, left: ownPublicKey(), diff --git a/contracts/src/access/witnesses/ZOwnablePKWitnesses.ts b/contracts/src/access/witnesses/ZOwnablePKWitnesses.ts index 58098d02..62cc3bba 100644 --- a/contracts/src/access/witnesses/ZOwnablePKWitnesses.ts +++ b/contracts/src/access/witnesses/ZOwnablePKWitnesses.ts @@ -12,7 +12,7 @@ export interface IZOwnablePKWitnesses

{ * @param context - The witness context containing the private state. * @returns A tuple of the private state and the secret nonce as a Uint8Array. */ - secretNonce(context: WitnessContext): [P, Uint8Array]; + wit_secretNonce(context: WitnessContext): [P, Uint8Array]; } /** @@ -60,7 +60,7 @@ export const ZOwnablePKPrivateState = { */ export const ZOwnablePKWitnesses = (): IZOwnablePKWitnesses => ({ - secretNonce( + wit_secretNonce( context: WitnessContext, ): [ZOwnablePKPrivateState, Uint8Array] { return [context.privateState, context.privateState.secretNonce]; From 84899249c29e4bf87aa0d05a1b6bc55079af6f27 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Wed, 27 Aug 2025 20:23:06 -0500 Subject: [PATCH 156/202] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ⟣ €₥ℵ∪ℓ ⟢ <34749913+emnul@users.noreply.github.com> Signed-off-by: Andrew Fleming --- turbo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/turbo.json b/turbo.json index 57e1bbb5..9d1bac99 100644 --- a/turbo.json +++ b/turbo.json @@ -18,7 +18,7 @@ "compact:access": { "dependsOn": ["^build", "compact:security", "compact:utils"], "env": ["COMPACT_HOME", "SKIP_ZK"], - "inputs": ["src/access/**/*.compact", "artifacts/**"], + "inputs": ["src/access/**/*.compact"], "outputLogs": "new-only", "outputs": ["artifacts/**/"] }, From 67972b935727d3b4fe3e71d585a28dc479b9f71c Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 27 Aug 2025 22:24:00 -0300 Subject: [PATCH 157/202] remove artifacts from inputs --- turbo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/turbo.json b/turbo.json index 9d1bac99..a293cf52 100644 --- a/turbo.json +++ b/turbo.json @@ -25,14 +25,14 @@ "compact:archive": { "dependsOn": ["^build", "compact:utils"], "env": ["COMPACT_HOME", "SKIP_ZK"], - "inputs": ["src/archive/**/*.compact", "artifacts/**"], + "inputs": ["src/archive/**/*.compact"], "outputLogs": "new-only", "outputs": ["artifacts/**/"] }, "compact:token": { "dependsOn": ["^build", "compact:security", "compact:utils"], "env": ["COMPACT_HOME", "SKIP_ZK"], - "inputs": ["src/token/**/*.compact", "artifacts/**"], + "inputs": ["src/token/**/*.compact"], "outputLogs": "new-only", "outputs": ["artifacts/**/"] }, From f23e56af213be809d4e8a65e4f8107df7e22f5a7 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 27 Aug 2025 22:24:49 -0300 Subject: [PATCH 158/202] fix fmt --- compact/src/runCompiler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compact/src/runCompiler.ts b/compact/src/runCompiler.ts index 31d69cea..8d5b98aa 100644 --- a/compact/src/runCompiler.ts +++ b/compact/src/runCompiler.ts @@ -68,7 +68,8 @@ async function runCompiler(): Promise { for (let i = 0; i < args.length; i++) { if (args[i] === '--dir') { - const dirNameExists = i + 1 < args.length && !args[i + 1].startsWith('--'); + const dirNameExists = + i + 1 < args.length && !args[i + 1].startsWith('--'); if (dirNameExists) { targetDir = args[i + 1]; i++; // Skip the next argument (directory name) From efaa2a0d34f5784adc65991fdcce3994ec125dde Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Wed, 27 Aug 2025 22:47:13 -0500 Subject: [PATCH 159/202] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ⟣ €₥ℵ∪ℓ ⟢ <34749913+emnul@users.noreply.github.com> Signed-off-by: Andrew Fleming --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71c1cec8..6b688a27 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ and skipping the prover and verifier key file generation: ```bash # Individual module compilation (recommended for development) -turbo compact:token -- --skip-zk +turbo compact:token --filter=@openzeppelin-compact/contracts -- --skip-zk # Full compilation with skip-zk (use environment variable) SKIP_ZK=true turbo compact From eb3d4bc4b9618fac2fa234190b1aa4f1eafd4aaa Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 28 Aug 2025 15:35:29 -0300 Subject: [PATCH 160/202] rebase --- contracts/src/access/ZOwnablePK.compact | 310 ++++++++++ contracts/src/access/test/Ownable.test.ts | 14 +- contracts/src/access/test/ZOwnablePK.test.ts | 534 ++++++++++++++++++ .../access/test/mocks/MockZOwnablePK.compact | 50 ++ .../test/simulators/AccessControlSimulator.ts | 6 +- .../test/simulators/OwnableSimulator.ts | 6 +- .../test/simulators/ZOwnablePKSimulator.ts | 307 ++++++++++ contracts/src/access/test/types/test.ts | 98 +++- .../test/utils/AbstractContractSimulator.ts | 131 +++++ .../test/utils/SimualatorStateManager.ts | 115 ++++ contracts/src/access/test/utils/address.ts | 24 + .../access/test/utils/createCircuitProxies.ts | 64 +++ contracts/src/access/test/utils/test.ts | 4 +- .../access/witnesses/ZOwnablePKWitnesses.ts | 68 +++ docs/modules/ROOT/pages/access.adoc | 248 ++++++++ docs/modules/ROOT/pages/api/access.adoc | 206 ++++++- 16 files changed, 2156 insertions(+), 29 deletions(-) create mode 100644 contracts/src/access/ZOwnablePK.compact create mode 100644 contracts/src/access/test/ZOwnablePK.test.ts create mode 100644 contracts/src/access/test/mocks/MockZOwnablePK.compact create mode 100644 contracts/src/access/test/simulators/ZOwnablePKSimulator.ts create mode 100644 contracts/src/access/test/utils/AbstractContractSimulator.ts create mode 100644 contracts/src/access/test/utils/SimualatorStateManager.ts create mode 100644 contracts/src/access/test/utils/createCircuitProxies.ts create mode 100644 contracts/src/access/witnesses/ZOwnablePKWitnesses.ts diff --git a/contracts/src/access/ZOwnablePK.compact b/contracts/src/access/ZOwnablePK.compact new file mode 100644 index 00000000..37e4616f --- /dev/null +++ b/contracts/src/access/ZOwnablePK.compact @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: MIT + +pragma language_version >= 0.16.0; + +/** + * @module ZOwnablePK + * @description A shielded, public key-derived Ownable module. + * + * `ZOwnablePK` provides a privacy-preserving access control mechanism + * for contracts with a single administrative user. Unlike traditional + * `Ownable` implementations that store or expose the owner's public key + * on-chain, this module stores only a commitment to a hashed identifier + * derived from the owner's public key and a secret nonce. + * For the strongest security guarantees, use an Air-Gapped Public Key. + * + * @notice This module explicitly supports commitments derived from public keys; + * however, it may be possible to use contract addresses when contract-to-contract + * calls become available. This will be revisited when it's known if/how witnesses + * are used from a contract address context. + * + * @dev Features: + * - Obfuscated owner identity: The owner's public key is never revealed on-chain. + * - Stateless verification: The contract never needs access to the full public key. + * - Built-in support for transfer and renounce functionality. + * - Instance-specific salts to prevent cross-contract correlation. + * - Deterministic hashing with `persistentHash` to support zero-knowledge verification. + * + * @dev Commitment structure: + * ``` + * id = SHA256(pk, secretNonce) + * commitment = SHA256(id, instanceSalt, counter, "ZOwnablePK:shield:") + * ``` + * The commitment changes on each transfer due to the incrementing `counter`, + * providing unlinkability across ownership changes. + * + * @dev Security Considerations: + * - The `secretNonce` must be kept private. Loss of the nonce prevents the + * owner from proving ownership or transferring it. + * - Ownership validation is entirely circuit-based using witness-provided values. + * - The `_instanceSalt` is immutable and used to differentiate deployments. + * + * @notice Best used for single-admin contracts with privacy requirements. + * It is not designed for multi-owner or role-based access control. + */ +module ZOwnablePK { + import CompactStandardLibrary; + import "../security/Initializable" prefix Initializable_; + + /** + * @ledger _ownerCommitment + * @description Stores the current hashed commitment representing the owner. + * This commitment is derived from the public identifier (e.g., `SHA256(pk, nonce)`), + * the `instanceSalt`, the transfer `counter`, and a domain separator. + * + * A commitment of `default>` (i.e., zero) indicates the contract is unowned. + */ + export ledger _ownerCommitment: Bytes<32>; + /** + * @ledger _counter + * @description Internal transfer counter used to prevent commitment reuse. + * + * Increments by 1 on every successful ownership transfer. Combined with `id` and + * `instanceSalt` to compute unique owner commitments over time. + */ + export ledger _counter: Counter; + /** + * @sealed @ledger _instanceSalt + * @description A per-instance value provided at initialization used to namespace + * commitments for this contract instance. + * + * This salt prevents commitment collisions across contracts that might otherwise use + * the same owner identifiers or domain parameters. It is immutable after initialization. + */ + export sealed ledger _instanceSalt: Bytes<32>; + + /** + * @witness wit_secretNonce + * @description A private per-user nonce used in deriving the shielded owner identifier. + * + * Combined with the user's public key as `SHA256(pk, nonce)` to produce an obfuscated, + * unlinkable identity commitment. Users are encouraged to rotate this value on ownership changes. + */ + export witness wit_secretNonce(): Bytes<32>; + + /** + * @description Initializes the contract by setting the initial owner via `ownerId` + * and storing the `instanceSalt` that acts as a privacy additive for preventing + * duplicate commitments among other contracts implementing ZOwnablePK. + * + * @warning The `ownerId` must be calculated prior to contract deployment using the SHA256 hashing algorithm. + * Using any other algorithm will result in a permanent loss of contract access. + * + * @circuitInfo k=14, rows=14933 + * + * Requirements: + * + * - Contract is not initialized. + * - `ownerId` is not zero. + * + * @param {Bytes<32>} ownerId - The owner's unique identifier SHA256(pk, nonce). + * @param {Bytes<32>} instanceSalt - Contract salt to prevent duplicate commitments if + * users reuse their PK and secretNonce witness (not recommended). + * @returns {[]} Empty tuple. + */ + export circuit initialize(ownerId: Bytes<32>, instanceSalt: Bytes<32>): [] { + Initializable_initialize(); + + assert(ownerId != default>, "ZOwnablePK: invalid id"); + _instanceSalt = disclose(instanceSalt); + _transferOwnership(ownerId); + } + + /** + * @description Returns the current commitment representing the contract owner. + * The full commitment is: `SHA256(SHA256(pk, nonce), instanceSalt, counter, domain)`. + * + * @circuitInfo k=10, rows=57 + * + * Requirements: + * + * - Contract is initialized. + * + * @returns {Bytes<32>} The current owner's commitment. + */ + export circuit owner(): Bytes<32> { + Initializable_assertInitialized(); + return _ownerCommitment; + } + + /** + * @description Transfers ownership to `newOwnerId`. + * `newOwnerId` must be precalculated and given to the current owner off chain. + * + * @circuitInfo k=16, rows=39240 + * + * Requirements: + * + * - Contract is initialized. + * - Caller is the the current owner. + * - `newOwnerId` is not an empty array. + * + * @param {Bytes<32>} newOwnerId - The new owner's unique identifier (`SHA256(pk, nonce)`). + * @returns {[]} Empty tuple. + */ + export circuit transferOwnership(newOwnerId: Bytes<32>): [] { + Initializable_assertInitialized(); + + assertOnlyOwner(); + assert(newOwnerId != default>, "ZOwnablePK: invalid id"); + _transferOwnership(newOwnerId); + } + + /** + * @description Leaves the contract without an owner. + * It will not be possible to call `assertOnlyOnwer` circuits anymore. + * Can only be called by the current owner. + * + * @circuitInfo k=15, rows=24442 + * + * Requirements: + * + * - Contract is initialized. + * - Caller is the the current owner. + * + * @returns {[]} Empty tuple. + */ + export circuit renounceOwnership(): [] { + Initializable_assertInitialized(); + + assertOnlyOwner(); + _ownerCommitment.resetToDefault(); + } + + /** + * @description Throws if called by any account whose id hash `SHA256(pk, nonce)` does not match + * the stored owner commitment. + * Use this to only allow the owner to call specific circuits. + * + * @circuitInfo k=15, rows=24437 + * + * Requirements: + * + * - Contract is initialized. + * - Caller's id (`SHA256(pk, nonce)`) when used in `_computeOwnerCommitment` equals + * the stored `_ownerCommitment`, thus verifying themselves as the owner. + * + * @returns {[]} Empty tuple. + */ + export circuit assertOnlyOwner(): [] { + Initializable_assertInitialized(); + + const nonce = wit_secretNonce(); + const callerAsEither = Either { + is_left: true, + left: ownPublicKey(), + right: ContractAddress { bytes: pad(32, "") } + }; + const id = _computeOwnerId(callerAsEither, nonce); + assert(_ownerCommitment == _computeOwnerCommitment(id, _counter), "ZOwnablePK: caller is not the owner"); + } + + /** + * @description Computes the owner commitment from the given `id` and `counter`. + * + * ## Owner ID (`id`) + * The `id` is expected to be computed off-chain as: + * `id = SHA256(pk, nonce)` + * + * - `pk`: The owner's public key. + * - `nonce`: A secret nonce scoped to the instance, ideally rotated with each transfer. + * + * ## Commitment Derivation + * `commitment = SHA256(id, instanceSalt, counter, domain)` + * + * - `id`: See above. + * - `instanceSalt`: A unique per-deployment salt, stored during initialization. + * This prevents commitment collisions across deployments. + * - `counter`: Incremented with each ownership transfer, ensuring uniqueness + * even with repeated `id` values. Cast to `Field` then `Bytes<32>` for hashing. + * - `domain`: Domain separator `"ZOwnablePK:shield:"` (padded to 32 bytes) to prevent + * hash collisions when extending the module or using similar commitment schemes. + * + * @circuitInfo k=14, rows=14853 + * + * Requirements: + * + * - Contract is initialized. + * + * @param {Bytes<32>} id - The unique identifier of the owner calculated by `SHA256(pk, nonce)`. + * @param {Uint<64>} counter - The current counter or round. This increments by `1` + * after every transfer to prevent duplicate commitments given the same `id`. + * @returns {Bytes<32>} The commitment derived from `id` and `counter`. + */ + export circuit _computeOwnerCommitment( + id: Bytes<32>, + counter: Uint<64>, + ): Bytes<32> { + Initializable_assertInitialized(); + return persistentHash>>( + [ + id, + _instanceSalt, + counter as Field as Bytes<32>, + pad(32, "ZOwnablePK:shield:") + ] + ); + } + + /** + * @description Computes the unique identifier (`id`) of the owner from their + * public key and a secret nonce. + * + * ## ID Derivation + * `id = SHA256(pk, nonce)` + * + * - `pk`: The public key of the caller. This is passed explicitly to allow + * for off-chain derivation, testing, or scenarios where the caller is + * different from the subject of the computation. + * We recommend using an Air-Gapped Public Key. + * - `nonce`: A secret nonce tied to the identity. The generation strategy is + * left to the user, offering different security/convenience trade-offs. + * + * The result is a 32-byte commitment that uniquely identifies the owner. + * This value is later used in owner commitment hashing, + * and acts as a privacy-preserving alternative to a raw public key. + * + * @notice This module allows ownership to be tied to an identity commitment derived + * from a public key and secret nonce. + * While typically used with user public keys, this mechanism may also + * support contract addresses as identifiers in future contract-to-contract + * interactions. Both are treated as 32-byte values (`Bytes<32>`). + * + * Requirements: + * + * - `pk` is not a ContractAddress. + * + * @param {Either} pk - The public key of the identity being committed. + * @param {Bytes<32>} nonce - A private nonce to scope the commitment. + * @returns {Bytes<32>} The computed owner ID. + */ + export pure circuit _computeOwnerId( + pk: Either, + nonce: Bytes<32> + ): Bytes<32> { + assert(pk.is_left, "ZOwnablePK: contract address owners are not yet supported"); + + return persistentHash>>([pk.left.bytes, nonce]); + } + + /** + * @description Transfers ownership to owner id `newOwnerId` without + * enforcing permission checks on the caller. + * + * @circuitInfo k=14, rows=14823 + * + * Requirements: + * + * - Contract is initialized. + * + * @param {Bytes<32>} newOwnerId - The unique identifier of the new owner + * calculated by `SHA256(pk, nonce)`. + * @returns {[]} Empty tuple. + */ + export circuit _transferOwnership(newOwnerId: Bytes<32>): [] { + Initializable_assertInitialized(); + + _counter.increment(1); + _ownerCommitment = _computeOwnerCommitment(disclose(newOwnerId), _counter); + } +} diff --git a/contracts/src/access/test/Ownable.test.ts b/contracts/src/access/test/Ownable.test.ts index bde39230..60053cb7 100644 --- a/contracts/src/access/test/Ownable.test.ts +++ b/contracts/src/access/test/Ownable.test.ts @@ -3,14 +3,12 @@ import { beforeEach, describe, expect, it } from 'vitest'; import { OwnableSimulator } from './simulators/OwnableSimulator.js'; import * as utils from './utils/address.js'; -// Callers -const OWNER = utils.toHexPadded('OWNER'); -const NEW_OWNER = utils.toHexPadded('NEW_OWNER'); -const UNAUTHORIZED = utils.toHexPadded('UNAUTHORIZED'); - -// Encoded PK/Addresses -const Z_OWNER = utils.createEitherTestUser('OWNER'); -const Z_NEW_OWNER = utils.createEitherTestUser('NEW_OWNER'); +// PKs +const [OWNER, Z_OWNER] = utils.generateEitherPubKeyPair('OWNER'); +const [NEW_OWNER, Z_NEW_OWNER] = utils.generateEitherPubKeyPair('NEW_OWNER'); +const [UNAUTHORIZED, _] = utils.generateEitherPubKeyPair('UNAUTHORIZED'); + +// Encoded contract addresses const Z_OWNER_CONTRACT = utils.createEitherTestContractAddress('OWNER_CONTRACT'); const Z_RECIPIENT_CONTRACT = diff --git a/contracts/src/access/test/ZOwnablePK.test.ts b/contracts/src/access/test/ZOwnablePK.test.ts new file mode 100644 index 00000000..0369705a --- /dev/null +++ b/contracts/src/access/test/ZOwnablePK.test.ts @@ -0,0 +1,534 @@ +import { + CompactTypeBytes, + CompactTypeVector, + convert_bigint_to_Uint8Array, + persistentHash, + transientHash, + upgradeFromTransient, +} from '@midnight-ntwrk/compact-runtime'; +import { beforeEach, describe, expect, it } from 'vitest'; +import type { ZswapCoinPublicKey } from '../../../artifacts/MockOwnable/contract/index.cjs'; +import { ZOwnablePKPrivateState } from '../witnesses/ZOwnablePKWitnesses.js'; +import { ZOwnablePKSimulator } from './simulators/ZOwnablePKSimulator.js'; +import * as utils from './utils/address.js'; + +// PKs +const [OWNER, Z_OWNER] = utils.generatePubKeyPair('OWNER'); +const [NEW_OWNER, Z_NEW_OWNER] = utils.generatePubKeyPair('NEW_OWNER'); +const [UNAUTHORIZED, _] = utils.generatePubKeyPair('UNAUTHORIZED'); + +const INSTANCE_SALT = new Uint8Array(32).fill(8675309); +const BAD_NONCE = Buffer.from(Buffer.alloc(32, 'BAD_NONCE')); +const DOMAIN = 'ZOwnablePK:shield:'; +const INIT_COUNTER = 1n; + +const isInit = true; +let secretNonce: Uint8Array; +let ownable: ZOwnablePKSimulator; + +// Helpers +const createIdHash = ( + pk: ZswapCoinPublicKey, + nonce: Uint8Array, +): Uint8Array => { + const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32)); + + const bPK = pk.bytes; + return persistentHash(rt_type, [bPK, nonce]); +}; + +const buildCommitmentFromId = ( + id: Uint8Array, + instanceSalt: Uint8Array, + counter: bigint, +): Uint8Array => { + const rt_type = new CompactTypeVector(4, new CompactTypeBytes(32)); + const bCounter = convert_bigint_to_Uint8Array(32, counter); + const bDomain = new TextEncoder().encode(DOMAIN); + + const commitment = persistentHash(rt_type, [ + id, + instanceSalt, + bCounter, + bDomain, + ]); + return commitment; +}; + +const buildCommitment = ( + pk: ZswapCoinPublicKey, + nonce: Uint8Array, + instanceSalt: Uint8Array, + counter: bigint, + domain: string, +): Uint8Array => { + const id = createIdHash(pk, nonce); + + const rt_type = new CompactTypeVector(4, new CompactTypeBytes(32)); + const bCounter = convert_bigint_to_Uint8Array(32, counter); + const bDomain = new TextEncoder().encode(domain); + + const commitment = persistentHash(rt_type, [ + id, + instanceSalt, + bCounter, + bDomain, + ]); + return commitment; +}; + +describe('ZOwnablePK', () => { + describe('before initialize', () => { + it('should fail when setting owner commitment as 0', () => { + expect(() => { + const badId = new Uint8Array(32).fill(0); + new ZOwnablePKSimulator(badId, INSTANCE_SALT, isInit); + }).toThrow('ZOwnablePK: invalid id'); + }); + + it('should initialize with non-zero commitment', () => { + const notZeroPK = utils.encodeToPK('NOT_ZERO'); + const notZeroNonce = new Uint8Array(32).fill(1); + const nonZeroId = createIdHash(notZeroPK, notZeroNonce); + ownable = new ZOwnablePKSimulator(nonZeroId, INSTANCE_SALT, isInit); + + const nonZeroCommitment = buildCommitmentFromId( + nonZeroId, + INSTANCE_SALT, + INIT_COUNTER, + ); + expect(ownable.owner()).toEqual(nonZeroCommitment); + }); + }); + + describe('when not deployed and not initialized', () => { + const isNotInit = false; + + beforeEach(() => { + ownable = new ZOwnablePKSimulator( + randomByteArray, + INSTANCE_SALT, + isNotInit, + ); + }); + type FailingCircuits = [method: keyof ZOwnablePKSimulator, args: unknown[]]; + const randomByteArray = new Uint8Array(32).fill(123); + const randomCounter = 321n; + // Circuit calls should fail before the args are used + const circuitsToFail: FailingCircuits[] = [ + ['owner', []], + ['assertOnlyOwner', []], + ['transferOwnership', [randomByteArray]], + ['renounceOwnership', []], + ['_computeOwnerCommitment', [randomByteArray, randomCounter]], + ['_transferOwnership', [randomByteArray]], + ]; + it.each(circuitsToFail)('%s should fail', (circuitName, args) => { + expect(() => { + (ownable[circuitName] as (...args: unknown[]) => unknown)(...args); + }).toThrow('Initializable: contract not initialized'); + }); + + it('should allow pure computeOwnerId', () => { + const eitherOwner = utils.createEitherTestUser('OWNER'); + + expect(() => { + ownable._computeOwnerId(eitherOwner, randomByteArray); + }).not.toThrow(); + }); + }); + + describe('when incorrect hashing algo (not SHA256) is used to generate initial owner id', () => { + // ZOwnablePK only supports sha256 for owner id calculation + // Obviously, using any other algo for the id will not work + const badHashAlgo = (pk: ZswapCoinPublicKey, nonce: Uint8Array) => { + const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32)); + return upgradeFromTransient(transientHash(rt_type, [pk.bytes, nonce])); + }; + const secretNonce = ZOwnablePKPrivateState.generate().secretNonce; + const badOwnerId = badHashAlgo(Z_OWNER, secretNonce); + + beforeEach(() => { + ownable = new ZOwnablePKSimulator(badOwnerId, INSTANCE_SALT, isInit); + }); + // + type FailingCircuits = [method: keyof ZOwnablePKSimulator, args: unknown[]]; + const protectedCircuits: FailingCircuits[] = [ + ['assertOnlyOwner', []], + ['transferOwnership', [badOwnerId]], + ['renounceOwnership', []], + ]; + + it.each(protectedCircuits)('%s should fail', (circuitName, args) => { + ownable.callerCtx.setCaller(OWNER); + + expect(() => { + (ownable[circuitName] as (...args: unknown[]) => unknown)(...args); + }).toThrow('ZOwnablePK: caller is not the owner'); + }); + }); + + describe('after initialization', () => { + beforeEach(() => { + // Create private state object and generate nonce + const PS = ZOwnablePKPrivateState.generate(); + // Bind nonce for convenience + secretNonce = PS.secretNonce; + // Prepare owner ID with gen nonce + const ownerId = createIdHash(Z_OWNER, secretNonce); + // Deploy contract with derived owner commitment and PS + ownable = new ZOwnablePKSimulator(ownerId, INSTANCE_SALT, isInit, { + privateState: PS, + }); + }); + + describe('owner', () => { + it('should return the correct owner commitment', () => { + const expCommitment = buildCommitment( + Z_OWNER, + secretNonce, + INSTANCE_SALT, + INIT_COUNTER, + DOMAIN, + ); + expect(ownable.owner()).toEqual(expCommitment); + }); + }); + + describe('transferOwnership', () => { + let newOwnerCommitment: Uint8Array; + let newOwnerNonce: Uint8Array; + let newIdHash: Uint8Array; + let newCounter: bigint; + + beforeEach(() => { + // Prepare new owner commitment + newOwnerNonce = ZOwnablePKPrivateState.generate().secretNonce; + newCounter = INIT_COUNTER + 1n; + newIdHash = createIdHash(Z_NEW_OWNER, newOwnerNonce); + newOwnerCommitment = buildCommitment( + Z_NEW_OWNER, + newOwnerNonce, + INSTANCE_SALT, + newCounter, + DOMAIN, + ); + }); + + it('should transfer ownership', () => { + ownable.callerCtx.setCaller(OWNER); + ownable.transferOwnership(newIdHash); + expect(ownable.owner()).toEqual(newOwnerCommitment); + + // Old owner + ownable.callerCtx.setCaller(OWNER); + expect(() => { + ownable.assertOnlyOwner(); + }).toThrow('ZOwnablePK: caller is not the owner'); + + // Unauthorized + ownable.callerCtx.setCaller(UNAUTHORIZED); + expect(() => { + ownable.assertOnlyOwner(); + }).toThrow('ZOwnablePK: caller is not the owner'); + + // New owner + ownable.callerCtx.setCaller(NEW_OWNER); + ownable.privateState.injectSecretNonce(Buffer.from(newOwnerNonce)); + expect(ownable.assertOnlyOwner()).not.to.throw; + }); + + it('should fail when transferring to id zero', () => { + ownable.callerCtx.setCaller(OWNER); + const badId = new Uint8Array(32).fill(0); + expect(() => { + ownable.transferOwnership(badId); + }).toThrow('ZOwnablePK: invalid id'); + }); + + it('should fail when unauthorized transfers ownership', () => { + ownable.callerCtx.setCaller(UNAUTHORIZED); + expect(() => { + ownable.transferOwnership(newOwnerCommitment); + }).toThrow('ZOwnablePK: caller is not the owner'); + }); + + /** + * @description More thoroughly tested in `_transferOwnership` + * */ + it('should bump instance after transfer', () => { + const beforeInstance = ownable.getPublicState().ZOwnablePK__counter; + + // Transfer + ownable.callerCtx.setCaller(OWNER); + ownable.transferOwnership(newOwnerCommitment); + + // Check counter + const afterInstance = ownable.getPublicState().ZOwnablePK__counter; + expect(afterInstance).toEqual(beforeInstance + 1n); + }); + + it('should change commitment when transferring ownership to self with same pk + nonce)', () => { + // Confirm current commitment + const repeatedId = createIdHash(Z_OWNER, secretNonce); + const initCommitment = ownable.owner(); + const expInitCommitment = buildCommitmentFromId( + repeatedId, + INSTANCE_SALT, + INIT_COUNTER, + ); + expect(initCommitment).toEqual(expInitCommitment); + + // Transfer ownership to self with the same id -> `H(pk, nonce)` + ownable.callerCtx.setCaller(OWNER); + ownable.transferOwnership(repeatedId); + + // Check commitments don't match + const newCommitment = ownable.owner(); + expect(initCommitment).not.toEqual(newCommitment); + + // Build commitment locally and validate new commitment == expected + const bumpedCounter = INIT_COUNTER + 1n; + const expNewCommitment = buildCommitmentFromId( + repeatedId, + INSTANCE_SALT, + bumpedCounter, + ); + expect(newCommitment).toEqual(expNewCommitment); + + // Check same owner maintains permissions after transfer + ownable.callerCtx.setCaller(OWNER); + expect(ownable.assertOnlyOwner()).not.to.throw; + }); + }); + + describe('renounceOwnership', () => { + it('should renounce ownership', () => { + ownable.callerCtx.setCaller(OWNER); + ownable.renounceOwnership(); + + // Check owner is reset + expect(ownable.owner()).toEqual(new Uint8Array(32).fill(0)); + + // Check revoked permissions + expect(() => { + ownable.assertOnlyOwner(); + }).toThrow('ZOwnablePK: caller is not the owner'); + }); + + it('should fail when renouncing from unauthorized', () => { + ownable.callerCtx.setCaller(UNAUTHORIZED); + expect(() => { + ownable.renounceOwnership(); + }); + }); + + it('should fail when renouncing from authorized with bad nonce', () => { + ownable.callerCtx.setCaller(OWNER); + ownable.privateState.injectSecretNonce(BAD_NONCE); + expect(() => { + ownable.renounceOwnership(); + }); + }); + + it('should fail when renouncing from unauthorized with bad nonce', () => { + ownable.callerCtx.setCaller(UNAUTHORIZED); + ownable.privateState.injectSecretNonce(BAD_NONCE); + expect(() => { + ownable.renounceOwnership(); + }); + }); + }); + + describe('assertOnlyOwner', () => { + it('should allow authorized caller with correct nonce to call', () => { + // Check nonce is correct + expect(ownable.privateState.getCurrentSecretNonce()).toEqual( + secretNonce, + ); + + ownable.callerCtx.setCaller(OWNER); + expect(ownable.assertOnlyOwner()).to.not.throw; + }); + + it('should fail when the authorized caller has the wrong nonce', () => { + // Inject bad nonce + ownable.privateState.injectSecretNonce(BAD_NONCE); + + // Check nonce does not match + expect(ownable.privateState.getCurrentSecretNonce()).not.toEqual( + secretNonce, + ); + + // Set caller and call circuit + ownable.callerCtx.setCaller(OWNER); + expect(() => { + ownable.assertOnlyOwner(); + }).toThrow('ZOwnablePK: caller is not the owner'); + }); + + it('should fail when unauthorized caller has the correct nonce', () => { + // Check nonce is correct + expect(ownable.privateState.getCurrentSecretNonce()).toEqual( + secretNonce, + ); + + ownable.callerCtx.setCaller(UNAUTHORIZED); + expect(() => { + ownable.assertOnlyOwner(); + }).toThrow('ZOwnablePK: caller is not the owner'); + }); + + it('should fail when unauthorized caller has the wrong nonce', () => { + // Inject bad nonce + ownable.privateState.injectSecretNonce(BAD_NONCE); + + // Check nonce does not match + expect(ownable.privateState.getCurrentSecretNonce()).not.toEqual( + secretNonce, + ); + + // Set unauthorized caller and call circuit + ownable.callerCtx.setCaller(UNAUTHORIZED); + expect(() => { + ownable.assertOnlyOwner(); + }).toThrow('ZOwnablePK: caller is not the owner'); + }); + }); + + describe('_computeOwnerCommitment', () => { + const MAX_U64 = 2n ** 64n - 1n; + const testCases = [ + ...Array.from({ length: 10 }, (_, i) => ({ + label: `User${i}`, + ownerPK: utils.encodeToPK(`User${i}`), + counter: BigInt(Math.floor(Math.random() * 2 ** 64 - 1)), + })), + { + label: 'ZeroCounter', + ownerPK: utils.encodeToPK('ZeroCounter'), + counter: 0n, + }, + { + label: 'MaxCounter', + ownerPK: utils.encodeToPK('MaxUser'), + counter: MAX_U64, + }, + ]; + it.each(testCases)( + 'should match commitment for $label with counter $counter', + ({ ownerPK, counter }) => { + const id = createIdHash(ownerPK, secretNonce); + + // Check buildCommitmentFromId + const hashFromContract = ownable._computeOwnerCommitment(id, counter); + const hashFromHelper1 = buildCommitmentFromId( + id, + INSTANCE_SALT, + counter, + ); + expect(hashFromContract).toEqual(hashFromHelper1); + + // Check buildCommitment + const hashFromHelper2 = buildCommitment( + ownerPK, + secretNonce, + INSTANCE_SALT, + counter, + DOMAIN, + ); + expect(hashFromHelper1).toEqual(hashFromHelper2); + }, + ); + }); + + describe('_computeOwnerId', () => { + const testCases = [ + ...Array.from({ length: 10 }, (_, i) => ({ + label: `User${i}`, + eitherOwner: utils.createEitherTestUser(`User${i}`), + nonce: new Uint8Array(32).fill(i), + })), + { + label: 'All-zero nonce', + eitherOwner: utils.createEitherTestUser('ZeroUser'), + nonce: new Uint8Array(32).fill(0), + }, + { + label: 'Max nonce', + eitherOwner: utils.createEitherTestUser('MaxUser'), + nonce: new Uint8Array(32).fill(255), + }, + ]; + + it.each(testCases)( + 'should match local and contract owner id for $label', + ({ eitherOwner, nonce }) => { + const ownerId = ownable._computeOwnerId(eitherOwner, nonce); + const expId = createIdHash(eitherOwner.left, nonce); + expect(ownerId).toEqual(expId); + }, + ); + + it('should fail to compute ContractAddress id', () => { + const eitherContract = + utils.createEitherTestContractAddress('CONTRACT'); + expect(() => { + ownable._computeOwnerId(eitherContract, secretNonce); + }).toThrow('ZOwnablePK: contract address owners are not yet supported'); + }); + }); + + describe('_transferOwnership', () => { + it('should transfer ownership', () => { + const id = createIdHash(Z_OWNER, secretNonce); + ownable._transferOwnership(id); + + const nextCounter = INIT_COUNTER + 1n; + const expCommitment = buildCommitmentFromId( + id, + INSTANCE_SALT, + nextCounter, + ); + expect(ownable.owner()).toEqual(expCommitment); + }); + + it('should bump the counter with each transfer', () => { + const nTransfers = 10; + const counterStart = 2; // count starts at 2 bc the constructor bumps the count to 1 + for (let i = counterStart; i <= nTransfers; i++) { + const pk = utils.encodeToPK(`Id${i}`); + const nonce = new Uint8Array(32).fill(i); + const id = createIdHash(pk, nonce); + ownable._transferOwnership(id); + + expect(ownable.getPublicState().ZOwnablePK__counter).toEqual( + BigInt(i), + ); + } + }); + + it('should allow transfer to all zeroes id', () => { + const zeroId = utils.zeroUint8Array(); + ownable._transferOwnership(zeroId); + + const nextCounter = INIT_COUNTER + 1n; + const expCommitment = buildCommitmentFromId( + zeroId, + INSTANCE_SALT, + nextCounter, + ); + expect(ownable.owner()).toEqual(expCommitment); + }); + + it('should allow anyone to transfer', () => { + ownable.callerCtx.setCaller(OWNER); + const id = createIdHash(Z_OWNER, secretNonce); + expect(ownable._transferOwnership(id)).not.to.throw; + + ownable.callerCtx.setCaller(UNAUTHORIZED); + expect(ownable._transferOwnership(id)).not.to.throw; + }); + }); + }); +}); diff --git a/contracts/src/access/test/mocks/MockZOwnablePK.compact b/contracts/src/access/test/mocks/MockZOwnablePK.compact new file mode 100644 index 00000000..d769f30e --- /dev/null +++ b/contracts/src/access/test/mocks/MockZOwnablePK.compact @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT + +pragma language_version >= 0.16.0; + +import CompactStandardLibrary; +import "../../ZOwnablePK" prefix ZOwnablePK_; + +export { ZswapCoinPublicKey, ContractAddress, Either, Maybe }; +export { ZOwnablePK__ownerCommitment, ZOwnablePK__counter }; + +/** + * @description `isInit` is a param for testing. + * + * If `isInit` is false, the constructor will not initialize the contract. + * This behavior is to test that circuits are not callable unless the + * contract is initialized. +*/ +constructor(initOwnerCommitment: Bytes<32>, instanceSalt: Bytes<32>, isInit: Boolean) { + if (disclose(isInit)) { + ZOwnablePK_initialize(initOwnerCommitment, instanceSalt); + } +} + +export circuit owner(): Bytes<32> { + return ZOwnablePK_owner(); +} + +export circuit transferOwnership(newOwnerCommitment: Bytes<32>): [] { + return ZOwnablePK_transferOwnership(disclose(newOwnerCommitment)); +} + +export circuit renounceOwnership(): [] { + return ZOwnablePK_renounceOwnership(); +} + +export circuit assertOnlyOwner(): [] { + return ZOwnablePK_assertOnlyOwner(); +} + +export circuit _computeOwnerCommitment(id: Bytes<32>, counter: Uint<64>): Bytes<32> { + return ZOwnablePK__computeOwnerCommitment(id, counter); +} + +export pure circuit _computeOwnerId(pk: Either, nonce: Bytes<32>): Bytes<32> { + return ZOwnablePK__computeOwnerId(pk, nonce); +} + +export circuit _transferOwnership(newOwnerCommitment: Bytes<32>): [] { + return ZOwnablePK__transferOwnership(newOwnerCommitment); +} diff --git a/contracts/src/access/test/simulators/AccessControlSimulator.ts b/contracts/src/access/test/simulators/AccessControlSimulator.ts index b0309133..b9d0701c 100644 --- a/contracts/src/access/test/simulators/AccessControlSimulator.ts +++ b/contracts/src/access/test/simulators/AccessControlSimulator.ts @@ -66,7 +66,7 @@ export class AccessControlSimulator * @description Retrieves the current public ledger state of the contract. * @returns The ledger state as defined by the contract. */ - public getCurrentPublicState(): Ledger { + public getPublicState(): Ledger { return ledger(this.circuitContext.transactionContext.state); } @@ -74,7 +74,7 @@ export class AccessControlSimulator * @description Retrieves the current private state of the contract. * @returns The private state of type AccessControlPrivateState. */ - public getCurrentPrivateState(): AccessControlPrivateState { + public getPrivateState(): AccessControlPrivateState { return this.circuitContext.currentPrivateState; } @@ -82,7 +82,7 @@ export class AccessControlSimulator * @description Retrieves the current contract state. * @returns The contract state object. */ - public getCurrentContractState(): ContractState { + public getContractState(): ContractState { return this.circuitContext.originalState; } diff --git a/contracts/src/access/test/simulators/OwnableSimulator.ts b/contracts/src/access/test/simulators/OwnableSimulator.ts index de90adb4..f89c310a 100644 --- a/contracts/src/access/test/simulators/OwnableSimulator.ts +++ b/contracts/src/access/test/simulators/OwnableSimulator.ts @@ -71,7 +71,7 @@ export class OwnableSimulator * @description Retrieves the current public ledger state of the contract. * @returns The ledger state as defined by the contract. */ - public getCurrentPublicState(): Ledger { + public getPublicState(): Ledger { return ledger(this.circuitContext.transactionContext.state); } @@ -79,7 +79,7 @@ export class OwnableSimulator * @description Retrieves the current private state of the contract. * @returns The private state of type OwnablePrivateState. */ - public getCurrentPrivateState(): OwnablePrivateState { + public getPrivateState(): OwnablePrivateState { return this.circuitContext.currentPrivateState; } @@ -87,7 +87,7 @@ export class OwnableSimulator * @description Retrieves the current contract state. * @returns The contract state object. */ - public getCurrentContractState(): ContractState { + public getContractState(): ContractState { return this.circuitContext.originalState; } diff --git a/contracts/src/access/test/simulators/ZOwnablePKSimulator.ts b/contracts/src/access/test/simulators/ZOwnablePKSimulator.ts new file mode 100644 index 00000000..e6e3f62b --- /dev/null +++ b/contracts/src/access/test/simulators/ZOwnablePKSimulator.ts @@ -0,0 +1,307 @@ +import { + type CircuitContext, + type CoinPublicKey, + emptyZswapLocalState, +} from '@midnight-ntwrk/compact-runtime'; +import { sampleContractAddress } from '@midnight-ntwrk/zswap'; +import { + type ContractAddress, + type Either, + type Ledger, + ledger, + Contract as MockOwnable, + type ZswapCoinPublicKey, +} from '../../../../artifacts/MockZOwnablePK/contract/index.cjs'; +import { + ZOwnablePKPrivateState, + ZOwnablePKWitnesses, +} from '../../witnesses/ZOwnablePKWitnesses.js'; +import type { + ContextlessCircuits, + ExtractImpureCircuits, + ExtractPureCircuits, + SimulatorOptions, +} from '../types/test.js'; +import { AbstractContractSimulator } from '../utils/AbstractContractSimulator.js'; +import { SimulatorStateManager } from '../utils/SimualatorStateManager.js'; + +type OwnableSimOptions = SimulatorOptions< + ZOwnablePKPrivateState, + typeof ZOwnablePKWitnesses +>; + +/** + * @description A simulator implementation of a contract for testing purposes. + * @template P - The private state type, fixed to ZOwnablePKPrivateState. + * @template L - The ledger type, fixed to Contract.Ledger. + */ +export class ZOwnablePKSimulator extends AbstractContractSimulator< + ZOwnablePKPrivateState, + Ledger +> { + contract: MockOwnable; + readonly contractAddress: string; + private stateManager: SimulatorStateManager; + private callerOverride: CoinPublicKey | null = null; + private _witnesses: ReturnType; + + private _pureCircuitProxy?: ContextlessCircuits< + ExtractPureCircuits>, + ZOwnablePKPrivateState + >; + + private _impureCircuitProxy?: ContextlessCircuits< + ExtractImpureCircuits>, + ZOwnablePKPrivateState + >; + + constructor( + initOwner: Uint8Array, + instanceSalt: Uint8Array, + isInit: boolean, + options: OwnableSimOptions = {}, + ) { + super(); + + // Setup initial state + const { + privateState = ZOwnablePKPrivateState.generate(), + witnesses = ZOwnablePKWitnesses(), + coinPK = '0'.repeat(64), + address = sampleContractAddress(), + } = options; + const constructorArgs = [initOwner, instanceSalt, isInit]; + + this.contract = new MockOwnable(witnesses); + + this.stateManager = new SimulatorStateManager( + this.contract, + privateState, + coinPK, + address, + ...constructorArgs, + ); + this.contractAddress = this.circuitContext.transactionContext.address; + this._witnesses = witnesses; + this.contract = new MockOwnable(this._witnesses); + } + + get circuitContext() { + return this.stateManager.getContext(); + } + + set circuitContext(ctx) { + this.stateManager.setContext(ctx); + } + + getPublicState(): Ledger { + return ledger(this.circuitContext.transactionContext.state); + } + + /** + * @description Constructs a caller-specific circuit context. + * If a caller override is present, it replaces the current Zswap local state with an empty one + * scoped to the overridden caller. Otherwise, the existing context is reused as-is. + * @returns A circuit context adjusted for the current simulated caller. + */ + protected getCallerContext(): CircuitContext { + return { + ...this.circuitContext, + currentZswapLocalState: this.callerOverride + ? emptyZswapLocalState(this.callerOverride) + : this.circuitContext.currentZswapLocalState, + }; + } + + /** + * @description Initializes and returns a proxy to pure contract circuits. + * The proxy automatically injects the current circuit context into each call, + * and returns only the result portion of each circuit's output. + * @notice The proxy is created only when first accessed a.k.a lazy initialization. + * This approach is efficient in cases where only pure or only impure circuits are used, + * avoiding unnecessary proxy creation. + * @returns A proxy object exposing pure circuit functions without requiring explicit context. + */ + protected get pureCircuit(): ContextlessCircuits< + ExtractPureCircuits>, + ZOwnablePKPrivateState + > { + if (!this._pureCircuitProxy) { + this._pureCircuitProxy = this.createPureCircuitProxy< + MockOwnable['circuits'] + >(this.contract.circuits, () => this.circuitContext); + } + return this._pureCircuitProxy; + } + + /** + * @description Initializes and returns a proxy to impure contract circuits. + * The proxy automatically injects the current (possibly caller-modified) context into each call, + * and updates the circuit context with the one returned by the circuit after execution. + * @notice The proxy is created only when first accessed a.k.a. lazy initialization. + * This approach is efficient in cases where only pure or only impure circuits are used, + * avoiding unnecessary proxy creation. + * @returns A proxy object exposing impure circuit functions without requiring explicit context management. + */ + protected get impureCircuit(): ContextlessCircuits< + ExtractImpureCircuits>, + ZOwnablePKPrivateState + > { + if (!this._impureCircuitProxy) { + this._impureCircuitProxy = this.createImpureCircuitProxy< + MockOwnable['impureCircuits'] + >( + this.contract.impureCircuits, + () => this.getCallerContext(), + (ctx: any) => { + this.circuitContext = ctx; + }, + ); + } + return this._impureCircuitProxy; + } + + /** + * @description Resets the cached circuit proxy instances. + * This is useful if the underlying contract state or circuit context has changed, + * and you want to ensure the proxies are recreated with updated context on next access. + */ + public resetCircuitProxies(): void { + this._pureCircuitProxy = undefined; + this._impureCircuitProxy = undefined; + } + + /** + * @description Helper method that provides access to both pure and impure circuit proxies. + * These proxies automatically inject the appropriate circuit context when invoked. + * @returns An object containing `pure` and `impure` circuit proxy interfaces. + */ + public get circuits() { + return { + pure: this.pureCircuit, + impure: this.impureCircuit, + }; + } + + public get witnesses(): ReturnType { + return this._witnesses; + } + + public set witnesses(newWitnesses: ReturnType) { + this._witnesses = newWitnesses; + this.contract = new MockOwnable(this._witnesses); + } + + public overrideWitness( + key: K, + fn: (typeof this._witnesses)[K], + ) { + this.witnesses = { + ...this._witnesses, + [key]: fn, + }; + } + + /** + * @description Returns the current commitment representing the contract owner. + * The full commitment is: `SHA256(SHA256(pk, nonce), instanceSalt, counter, domain)`. + * @returns The current owner's commitment. + */ + public owner(): Uint8Array { + return this.circuits.impure.owner(); + } + + /** + * @description Transfers ownership to `newOwnerId`. + * `newOwnerId` must be precalculated and given to the current owner off chain. + * @param newOwnerId The new owner's unique identifier (`SHA256(pk, nonce)`). + */ + public transferOwnership(newOwnerId: Uint8Array) { + this.circuits.impure.transferOwnership(newOwnerId); + } + + /** + * @description Leaves the contract without an owner. + * It will not be possible to call `assertOnlyOnwer` circuits anymore. + * Can only be called by the current owner. + */ + public renounceOwnership() { + this.circuits.impure.renounceOwnership(); + } + + /** + * @description Throws if called by any account whose id hash `SHA256(pk, nonce)` does not match + * the stored owner commitment. Use this to only allow the owner to call specific circuits. + */ + public assertOnlyOwner() { + this.circuits.impure.assertOnlyOwner(); + } + + /** + * @description Computes the owner commitment from the given `id` and `counter`. + * @param id - The unique identifier of the owner calculated by `SHA256(pk, nonce)`. + * @param counter - The current counter or round. This increments by `1` + * after every transfer to prevent duplicate commitments given the same `id`. + * @returns The commitment derived from `id` and `counter`. + */ + public _computeOwnerCommitment(id: Uint8Array, counter: bigint): Uint8Array { + return this.circuits.impure._computeOwnerCommitment(id, counter); + } + + /** + * @description Computes the unique identifier (`id`) of the owner from their + * public key and a secret nonce. + * @param pk - The public key of the identity being committed. + * @param nonce - A private nonce to scope the commitment. + * @returns The computed owner ID. + */ + public _computeOwnerId( + pk: Either, + nonce: Uint8Array, + ): Uint8Array { + return this.circuits.pure._computeOwnerId(pk, nonce); + } + + /** + * @description Transfers ownership to owner id `newOwnerId` without + * enforcing permission checks on the caller. + * @param newOwnerId - The unique identifier of the new owner calculated by `SHA256(pk, nonce)`. + */ + public _transferOwnership(newOwnerId: Uint8Array) { + this.circuits.impure._transferOwnership(newOwnerId); + } + + public readonly privateState = { + /** + * @description Contextually sets a new nonce into the private state. + * @param newNonce The secret nonce. + * @returns The ZOwnablePK private state after setting the new nonce. + */ + injectSecretNonce: ( + newNonce: Buffer, + ): ZOwnablePKPrivateState => { + const currentState = this.stateManager.getContext().currentPrivateState; + const updatedState = { ...currentState, secretNonce: newNonce }; + this.stateManager.updatePrivateState(updatedState); + return updatedState; + }, + + /** + * @description Returns the secret nonce given the context. + * @returns The secret nonce. + */ + getCurrentSecretNonce: (): Uint8Array => { + return this.stateManager.getContext().currentPrivateState.secretNonce; + }, + }; + + public callerCtx = { + /** + * @description Sets the caller context. + * @param caller The caller in context of the proceeding circuit calls. + */ + setCaller: (caller: CoinPublicKey) => { + this.callerOverride = caller; + }, + }; +} diff --git a/contracts/src/access/test/types/test.ts b/contracts/src/access/test/types/test.ts index 7a909543..643def10 100644 --- a/contracts/src/access/test/types/test.ts +++ b/contracts/src/access/test/types/test.ts @@ -4,23 +4,99 @@ import type { } from '@midnight-ntwrk/compact-runtime'; /** - * Generic interface for mock contract implementations. - * @template P - The type of the contract's private state. - * @template L - The type of the contract's ledger (public state). + * Interface defining a generic contract simulator. + * + * @template P - Type representing the private contract state. + * @template L - Type representing the public ledger state. */ export interface IContractSimulator { - /** The contract's deployed address. */ + /** + * The deployed contract's address. + */ readonly contractAddress: string; - /** The current circuit context. */ + /** + * The current circuit context holding the contract state. + */ circuitContext: CircuitContext

; - /** Retrieves the current ledger state. */ - getCurrentPublicState(): L; + /** + * Returns the current public ledger state. + * + * @returns The current ledger state of type L. + */ + getPublicState(): L; - /** Retrieves the current private state. */ - getCurrentPrivateState(): P; + /** + * Returns the current private contract state. + * + * @returns The current private state of type P. + */ + getPrivateState(): P; - /** Retrieves the current contract state. */ - getCurrentContractState(): ContractState; + /** + * Returns the original contract state. + * + * @returns The current contract state. + */ + getContractState(): ContractState; } + +/** + * Extracts pure circuits from a contract type. + * + * Pure circuits are those in `circuits` but not in `impureCircuits`. + * + * @template TContract - Contract type with `circuits` and `impureCircuits`. + */ +export type ExtractPureCircuits = TContract extends { + circuits: infer TCircuits extends Record; + impureCircuits: infer TImpureCircuits extends Record; +} + ? Omit + : never; + +/** + * Extracts impure circuits from a contract type. + * + * Impure circuits are those in `impureCircuits`. + * + * @template TContract - Contract type with `circuits` and `impureCircuits`. + */ +export type ExtractImpureCircuits = TContract extends { + impureCircuits: infer TImpureCircuits; +} + ? TImpureCircuits + : never; + +/** + * Transforms a collection of circuit functions by removing the explicit `CircuitContext` parameter, + * producing a version of each function that can be called without passing the context explicitly. + * + * Each original circuit function is expected to have the signature: + * `(ctx: CircuitContext, ...args) => { result: R; context: CircuitContext }` + * or a compatible shape. + * + * The transformed type maps each key `K` of the input `Circuits` type to a new function + * that takes the same parameters as the original, *except* the first `CircuitContext` argument, + * and returns the `result` part `R` directly. + * + * @template Circuits - An object type whose values are circuit functions accepting a `CircuitContext` + * and returning an object with `result` and optionally `context`. + * @template TState - The type representing the private or contract state passed inside `CircuitContext`. + */ +export type ContextlessCircuits = { + [K in keyof Circuits]: Circuits[K] extends ( + ctx: CircuitContext, + ...args: infer P + ) => { result: infer R; context: CircuitContext } + ? (...args: P) => R + : never; +}; + +export type SimulatorOptions any> = { + address?: string; + coinPK?: string; + privateState?: PS; + witnesses?: ReturnType; +}; diff --git a/contracts/src/access/test/utils/AbstractContractSimulator.ts b/contracts/src/access/test/utils/AbstractContractSimulator.ts new file mode 100644 index 00000000..36ac5d73 --- /dev/null +++ b/contracts/src/access/test/utils/AbstractContractSimulator.ts @@ -0,0 +1,131 @@ +import type { + CircuitContext, + ContractState, +} from '@midnight-ntwrk/compact-runtime'; +import type { ContextlessCircuits, IContractSimulator } from '../types/test.js'; + +/** + * Abstract base class for simulating contract behavior. + * Provides common functionality for managing circuit contexts and creating proxies + * for pure and impure circuit functions. + * + * @template P - The type representing the private state of the contract. + * @template L - The type representing the public ledger (contract) state. + */ +export abstract class AbstractContractSimulator + implements IContractSimulator +{ + /** + * The deployed contract's address. + * Must be implemented by concrete subclasses. + */ + abstract readonly contractAddress: string; + + /** + * The current circuit context containing private state, contract state, and transaction context. + * Must be implemented by concrete subclasses. + */ + abstract circuitContext: CircuitContext

; + + /** + * Retrieves the current public ledger state of the contract. + * Must be implemented by concrete subclasses. + * + * @returns The current public ledger state. + */ + abstract getPublicState(): L; + + /** + * Retrieves the current private state from the circuit context. + * + * @returns The current private state of the contract. + */ + public getPrivateState(): P { + return this.circuitContext.currentPrivateState; + } + + /** + * Retrieves the original contract state from the circuit context. + * + * @returns The original contract state. + */ + public getContractState(): ContractState { + return this.circuitContext.originalState; + } + + /** + * Creates a proxy wrapper around pure circuits. + * Pure circuits do not modify contract state, so only the result is returned. + * + * @template Circuits - The type of the circuits object to proxy. + * @param circuits - The original circuits object containing functions accepting a CircuitContext. + * @param context - A function returning the current CircuitContext to pass to circuit functions. + * @returns A proxy with contextless circuits that accept the original arguments and return only results. + */ + protected createPureCircuitProxy( + circuits: Circuits, + context: () => CircuitContext

, + ): ContextlessCircuits { + return new Proxy(circuits, { + get(target, prop, receiver) { + const original = Reflect.get(target, prop, receiver); + + if (typeof original !== 'function') return original; + + return (...args: any[]) => { + const ctx = context(); + + const fn = original as ( + ctx: CircuitContext

, + ...args: any[] + ) => { result: any }; + + return fn(ctx, ...args).result; + }; + }, + }) as ContextlessCircuits; + } + + /** + * Creates a proxy wrapper around impure circuits. + * Impure circuits can modify contract state, so the circuit context is updated accordingly. + * + * @template Circuits - The type of the circuits object to proxy. + * @param circuits - The original circuits object containing functions accepting a CircuitContext. + * @param context - A function returning the current CircuitContext to pass to circuit functions. + * @param updateContext - A callback to update the circuit context with the new context returned by the circuit. + * @returns A proxy with contextless circuits that accept the original arguments, update context, and return results. + */ + protected createImpureCircuitProxy( + circuits: Circuits, + context: () => CircuitContext

, + updateContext: (ctx: CircuitContext

) => void, + ): ContextlessCircuits { + return new Proxy(circuits, { + get(target, prop, receiver) { + const original = Reflect.get(target, prop, receiver); + + if (typeof original !== 'function') return original; + + return (...args: any[]) => { + const ctx = context(); + + const fn = original as ( + ctx: CircuitContext

, + ...args: any[] + ) => { result: any; context: CircuitContext

}; + + const { result, context: newCtx } = fn(ctx, ...args); + updateContext(newCtx); + return result; + }; + }, + }) as ContextlessCircuits; + } + + /** + * Optional method to reset any cached circuit proxies. + * Concrete subclasses can override this to clear proxies if needed. + */ + public resetCircuitProxies?(): void {} +} diff --git a/contracts/src/access/test/utils/SimualatorStateManager.ts b/contracts/src/access/test/utils/SimualatorStateManager.ts new file mode 100644 index 00000000..ac06fdba --- /dev/null +++ b/contracts/src/access/test/utils/SimualatorStateManager.ts @@ -0,0 +1,115 @@ +import { + type CircuitContext, + type ConstructorContext, + type ContractState, + constructorContext, + QueryContext, + sampleContractAddress, +} from '@midnight-ntwrk/compact-runtime'; + +/** + * A composable utility class for managing Compact contract state in simulations. + * + * This class handles initialization and lifecycle management of the `CircuitContext`, + * which includes private state, public (ledger) state, zswap local state, and transaction context. + * + * It is designed to be embedded compositionally inside contract simulator classes + * (e.g., `FooSimulator`), enabling better separation of concerns and easier test setup. + * + * @template P - The type of the contract's private state. + * + * ### Responsibilities + * - Initializes the contract state using the compiled contract's `.initialState` method. + * - Stores and exposes the `CircuitContext` via getters/setters. + * - Supports injection of private state and contract constructor arguments. + * - Allows the owning simulator to update private state manually during testing. + * + * ### Example Usage: + * ```ts + * const contract = new MyContract(witnesses); + * const manager = new SimulatorStateManager( + * contract, + * { foo: 1n }, // initial private state + * '0'.repeat(64), // coin public key + * sampleContractAddress(), // optional contract address + * arg1, arg2 // additional constructor args + * ); + * + * const context = manager.getContext(); + * ``` + */ +export class SimulatorStateManager

{ + private context: CircuitContext

; + + /** + * Creates an instance of `SimulatorStateManager`. + * + * @param contract - A compiled Compact contract instance (from artifacts), exposing `initialState()`. + * @param privateState - The initial private state to inject into the contract. + * @param coinPK - The caller's coin public key (used to create the constructor context). + * @param contractAddress - Optional override for the contract's address. Defaults to `sampleContractAddress` if not provided. + * @param contractArgs - Additional arguments to pass to the contract constructor (e.g., circuit params). + */ + constructor( + contract: { + initialState: ( + ctx: ConstructorContext

, + ...args: any[] + ) => { + currentPrivateState: P; + currentContractState: ContractState; + currentZswapLocalState: any; + }; + }, + privateState: P, + coinPK: string, + contractAddress?: string, + ...contractArgs: any[] + ) { + const initCtx = constructorContext(privateState, coinPK); + + const { + currentPrivateState, + currentContractState, + currentZswapLocalState, + } = contract.initialState(initCtx, ...contractArgs); + + this.context = { + currentPrivateState, + currentZswapLocalState, + originalState: currentContractState, + transactionContext: new QueryContext( + currentContractState.data, + contractAddress ?? sampleContractAddress(), + ), + }; + } + + /** + * Retrieves the current `CircuitContext`, including private state, + * zswap state, contract state, and transaction context. + */ + getContext(): CircuitContext

{ + return this.context; + } + + /** + * Replaces the internal `CircuitContext` with a new one. + * + * Useful when circuits mutate state and return an updated context. + */ + setContext(newContext: CircuitContext

) { + this.context = newContext; + } + + /** + * Updates just the private state inside the existing context. + * + * This is a lightweight way to simulate local state changes without reconstructing the full context. + * + * @param newPrivateState - The new private state object to apply. + */ + updatePrivateState(newPrivateState: P) { + this.context.currentPrivateState = newPrivateState; + } +} diff --git a/contracts/src/access/test/utils/address.ts b/contracts/src/access/test/utils/address.ts index f89eab11..fb22e4be 100644 --- a/contracts/src/access/test/utils/address.ts +++ b/contracts/src/access/test/utils/address.ts @@ -61,6 +61,30 @@ export const createEitherTestContractAddress = (str: string) => ({ right: encodeToAddress(str), }); +const baseGeneratePubKeyPair = ( + str: string, + asEither: boolean, +): [ + string, + ( + | Compact.ZswapCoinPublicKey + | Compact.Either + ), +] => { + const pk = toHexPadded(str); + const zpk = asEither ? createEitherTestUser(str) : encodeToPK(str); + return [pk, zpk]; +}; + +export const generatePubKeyPair = (str: string) => + baseGeneratePubKeyPair(str, false) as [string, Compact.ZswapCoinPublicKey]; + +export const generateEitherPubKeyPair = (str: string) => + baseGeneratePubKeyPair(str, true) as [ + string, + Compact.Either, + ]; + export const zeroUint8Array = (length = 32) => convert_bigint_to_Uint8Array(length, 0n); diff --git a/contracts/src/access/test/utils/createCircuitProxies.ts b/contracts/src/access/test/utils/createCircuitProxies.ts new file mode 100644 index 00000000..8aa500da --- /dev/null +++ b/contracts/src/access/test/utils/createCircuitProxies.ts @@ -0,0 +1,64 @@ +import type { CircuitContext } from '@midnight-ntwrk/compact-runtime'; +import type { + ContextlessCircuits, + ExtractImpureCircuits, + ExtractPureCircuits, +} from '../types/test.js'; + +/** + * Creates lazily-initialized circuit proxies for pure and impure contract functions. + */ +export function createCircuitProxies< + P, + ContractType extends { + circuits: Record; + impureCircuits: Record; + }, +>( + contract: ContractType, + getContext: () => CircuitContext

, + getCallerContext: () => CircuitContext

, + updateContext: (ctx: CircuitContext

) => void, + createPureProxy: >( + circuits: C, + context: () => CircuitContext

, + ) => ContextlessCircuits, + createImpureProxy: >( + circuits: C, + context: () => CircuitContext

, + updateContext: (ctx: CircuitContext

) => void, + ) => ContextlessCircuits, +) { + let pureProxy: + | ContextlessCircuits, P> + | undefined; + let impureProxy: + | ContextlessCircuits, P> + | undefined; + + return { + get circuits() { + if (!pureProxy) { + pureProxy = createPureProxy( + contract.circuits as ExtractPureCircuits, + getContext, + ); + } + if (!impureProxy) { + impureProxy = createImpureProxy( + contract.impureCircuits as ExtractImpureCircuits, + getCallerContext, + updateContext, + ); + } + return { + pure: pureProxy, + impure: impureProxy, + }; + }, + resetProxies() { + pureProxy = undefined; + impureProxy = undefined; + }, + }; +} diff --git a/contracts/src/access/test/utils/test.ts b/contracts/src/access/test/utils/test.ts index 2fd5a504..52e92528 100644 --- a/contracts/src/access/test/utils/test.ts +++ b/contracts/src/access/test/utils/test.ts @@ -55,8 +55,8 @@ export function useCircuitContextSender< L, C extends IContractSimulator, >(contract: C, sender: CoinPublicKey): CircuitContext

{ - const currentPrivateState = contract.getCurrentPrivateState(); - const originalState = contract.getCurrentContractState(); + const currentPrivateState = contract.getPrivateState(); + const originalState = contract.getContractState(); const contractAddress = contract.contractAddress; return { diff --git a/contracts/src/access/witnesses/ZOwnablePKWitnesses.ts b/contracts/src/access/witnesses/ZOwnablePKWitnesses.ts new file mode 100644 index 00000000..62cc3bba --- /dev/null +++ b/contracts/src/access/witnesses/ZOwnablePKWitnesses.ts @@ -0,0 +1,68 @@ +import { getRandomValues } from 'node:crypto'; +import type { WitnessContext } from '@midnight-ntwrk/compact-runtime'; +import type { Ledger } from '../../../artifacts/MockZOwnablePK/contract/index.cjs'; + +/** + * @description Interface defining the witness methods for ZOwnablePK operations. + * @template P - The private state type. + */ +export interface IZOwnablePKWitnesses

{ + /** + * Retrieves the secret nonce from the private state. + * @param context - The witness context containing the private state. + * @returns A tuple of the private state and the secret nonce as a Uint8Array. + */ + wit_secretNonce(context: WitnessContext): [P, Uint8Array]; +} + +/** + * @description Represents the private state of an ownable contract, storing a secret nonce. + */ +export type ZOwnablePKPrivateState = { + /** @description A 32-byte secret nonce used as a privacy additive. */ + secretNonce: Buffer; +}; + +/** + * @description Utility object for managing the private state of an Ownable contract. + */ +export const ZOwnablePKPrivateState = { + /** + * @description Generates a new private state with a random secret nonce. + * @returns A fresh ZOwnablePKPrivateState instance. + */ + generate: (): ZOwnablePKPrivateState => { + return { secretNonce: getRandomValues(Buffer.alloc(32)) }; + }, + + /** + * @description Generates a new private state with a user-defined secret nonce. + * Useful for deterministic nonce generation or advanced use cases. + * + * @param nonce - The 32-byte secret nonce to use. + * @returns A fresh ZOwnablePKPrivateState instance with the provided nonce. + * + * @example + * ```typescript + * // For deterministic nonces (user-defined scheme) + * const deterministicNonce = myDeterministicScheme(...); + * const privateState = ZOwnablePKPrivateState.withNonce(deterministicNonce); + * ``` + */ + withNonce: (nonce: Buffer): ZOwnablePKPrivateState => { + return { secretNonce: nonce }; + }, +}; + +/** + * @description Factory function creating witness implementations for Ownable operations. + * @returns An object implementing the Witnesses interface for ZOwnablePKPrivateState. + */ +export const ZOwnablePKWitnesses = + (): IZOwnablePKWitnesses => ({ + wit_secretNonce( + context: WitnessContext, + ): [ZOwnablePKPrivateState, Uint8Array] { + return [context.privateState, context.privateState.secretNonce]; + }, + }); diff --git a/docs/modules/ROOT/pages/access.adoc b/docs/modules/ROOT/pages/access.adoc index f39581d5..7978c1fa 100644 --- a/docs/modules/ROOT/pages/access.adoc +++ b/docs/modules/ROOT/pages/access.adoc @@ -133,6 +133,254 @@ there is no direct way for a contract to call circuits of other contracts or tra NOTE: The unsafe circuits are planned to become deprecated once contract-to-contract calls become available. +== Shielded Ownership and `ZOwnablePK` + +Privacy-preserving access control is a fundamental building block for confidential smart contracts on Midnight. +While traditional ownership patterns expose the owner's identity on-chain, +many applications require administrative control without revealing who holds that authority. + +=== Privacy-First Ownership + +The most common approach to access control in traditional smart contracts is ownership: +there's an account that is the owner of a contract and can perform administrative tasks. +However, this approach reveals the owner's identity to all observers, creating privacy and security risks. +In privacy-sensitive applications—such as confidential voting systems, private treasuries, or anonymous governance—revealing the administrator's identity may compromise the entire system's confidentiality. +This library provides the `ZOwnablePK` module that implements shielded ownership—administrative control without identity disclosure. +The owner's public key is never revealed on-chain; instead, +the contract stores only a cryptographic commitment that proves ownership without exposing the underlying identity. + +=== Commitment Scheme + +The `ZOwnablePK` module employs a two-layer cryptographic commitment scheme designed to provide privacy, +unlinkability, and collision resistance across deployments and ownership transfers. + +==== Owner ID Computation + +The foundation of the system is the owner identifier, computed as: + +```ts +id = SHA256(pk, nonce) +``` + +Where `pk` is the owner's public key and `nonce` is a secret value that may be either randomly generated +for maximum privacy or deterministically derived for recoverability. +This identifier serves as a privacy-preserving alternative to exposing the raw public key, +ensuring the owner's identity remains confidential. + +==== Owner Commitment Computation + +The final ownership commitment stored on-chain is computed as: + +```ts +commitment = SHA256(id, instanceSalt, counter, pad(32, "ZOwnablePK:shield:")) +``` + +This multi-element hash provides several security properties: + +- `id`: The privacy-preserving owner identifier described above. +- `instanceSalt`: A unique per-deployment salt that prevents commitment collisions across different contract instances, even when the same owner and nonce are used. +- `counter`: Incremented with each ownership transfer to ensure unlinkability—each transfer produces a completely different commitment even with the same underlying owner. +- `pad(32, "ZOwnablePK:shield:")`: A domain separator padded to 32 bytes that prevents hash collisions with other commitment schemes and enables safe protocol extensions. + +==== Security Properties + +This commitment scheme ensures that: + +- Public keys are never revealed on-chain. +- Observers cannot correlate past and future ownership. +- Cross-contract collisions are prevented through instance-specific salting. + +=== Nonce Generation Strategies + +The choice of nonce generation strategy represents a fundamental trade-off between simplicity/security and recoverability. +Both approaches are valid, and the best choice depends on your specific threat model and operational requirements. + +==== Random Nonce + +Generating a cryptographically strong random nonce provides the strongest privacy guarantees: + +```typescript +const randomNonce = crypto.getRandomValues(new Uint8Array(32)); +const ownerId = ZOwnablePK._computeOwnerId(publicKey, randomNonce); +``` + +This approach is easy to generate and ensures maximum unlinkability—even with sophisticated analysis, +observers cannot correlate ownership across different contracts or time periods. +However, it requires secure backup of both the private key and the nonce. +*Loss of either component results in permanent, irrecoverable loss of ownership.* + +==== Deterministic Nonce + +:rfc6979: https://datatracker.ietf.org/doc/html/rfc6979[RFC 6979] +:ed25519: https://ed25519.cr.yp.to/[Ed25519] + +Deriving the nonce deterministically enables recovery through derivation schemes. +Some examples: + +- `H(passphrase + context)` - recoverable from passphrase only, but passphrase becomes critical single point of failure. +- `H(publicKey + userPassphrase + context)` - requires both public key and passphrase. +- `H(signature + context)` where `signature = sign(context)` - leverages wallet without exposing private key. + +WARNING: When using signature-based nonce derivation, +ensure the wallet/library uses deterministic signatures ({ed25519} or {rfc6979} for ECDSA). +Non-deterministic signatures will generate different nonces on each signing, making recovery impossible. +Test the implementation by signing the same message twice then verify that the signatures match. + +*Context-Dependent Derivations:* + +- Include contract address, deployment timestamp, user ID, etc. +- Trade-off: more context is more unique but harder to recreate. + +WARNING: Approaches that avoid private key exposure (public key + passphrase, signature-based) +are generally recommended for operational security. + +Deriving the nonce deterministically from an <> and user passphrase provides a balance of security and recoverability: + +```typescript +// Example: Scrypt-based derivation +import { scryptSync } from 'node:crypto'; + +const deterministicNonce = scryptSync( + userPassphrase + publicKey + ":ZOwnablePK:nonce:v1", + 32, + { N: 16384, r: 8, p: 1 } // Standard scrypt parameters +); +const recoverableOwnerId = ZOwnablePK._computeOwnerId(publicKey, deterministicNonce); +``` + +**Security Considerations** + +The `ZOwnablePK` module remains agnostic to nonce generation methods, placing the security/convenience decision entirely with the user. Key considerations include: + +- **Backup requirements**: Random nonces require additional secure storage. +- **Recovery scenarios**: Deterministic nonces enable recovery. +- **Cross-contract correlation**: Reusing nonce strategies may reduce privacy across deployments. +- **Rotation costs**: Changing nonces requires ownership transfer transactions with associated DUST costs. + +Users should carefully evaluate their threat model, operational requirements, +and privacy needs when selecting a nonce generation strategy, +as this choice cannot be easily changed without transferring ownership. + +=== Air-Gapped Public Key (AGPK) + +For maximum privacy guarantees, +users should employ an Air-Gapped Public Key (AGPK) exclusively for contract ownership and administrative circuits. +An AGPK is a public key that maintains complete isolation from all other on-chain activities, +similar to how air-gapped systems are isolated from networks to prevent data leakage. + +==== The Privacy Enhancement + +While `ZOwnablePK` provides cryptographic privacy through its commitment scheme, +operational security practices like using an AGPK provide an additional layer of protection against correlation attacks. Even with the strongest cryptographic commitments, +reusing a public key across different on-chain activities can potentially compromise privacy +through transaction pattern analysis. + +==== AGPK Principles + +An Air-Gapped Public Key must adhere to strict isolation principles: + +- *Never used before:* The private key material +(including any seed, parent key, or entropy source from which this key is derived) +has never generated any public key that appears in any on-chain transaction, across any blockchain network. +The key material must be cryptographically pure. +- *Never used elsewhere:* From the moment of AGPK generation until its destruction, +the private key material is used exclusively for this contract's administrative functions (i.e. `assertOnlyOwner`). +No other public keys may ever be derived from or generated with the same key material. +- *Never used again:* Users commit to destroying all copies of the private key material +upon ownership renunciation or transfer. +This relies entirely on user discipline and cannot be externally verified or enforced. + +==== Best Practices Recommendation + +While neither required nor enforced by the `ZOwnablePK` module, +an Air-Gapped Public Key provides strong operational privacy hygiene for shielded contract administration. +Users should evaluate their threat model and privacy requirements when deciding whether to implement AGPK practices. + +WARNING: The effectiveness of an AGPK depends entirely on abiding by the AGPK principles. +A single transaction using the key outside the administrative context compromises all privacy benefits. + +=== Usage + +Import the `ZOwnablePK` module into the implementing contract and expose the ownership-handling circuits. +It’s recommended to prefix the module with `ZOwnablePK_` to avoid circuit signature clashes. + +```typescript +// MyZOwnablePKContract.compact + +pragma language_version >= 0.16.0; + +import CompactStandardLibrary; +import "./node_modules/@openzeppelin-compact/contracts/src/access/ZOwnablePK" + prefix ZOwnablePK_; + +constructor( + initOwnerCommitment: Bytes<32>, + instanceSalt: Bytes<32>, +) { + ZOwnablePK_initialize(initOwnerCommitment, instanceSalt); +} + +export circuit owner(): Bytes<32> { + return ZOwnablePK_owner(); +} + +export circuit transferOwnership(newOwnerCommitment: Bytes<32>): [] { + return ZOwnablePK_transferOwnership(disclose(newOwnerCommitment)); +} + +export circuit renounceOwnership(): [] { + return ZOwnablePK_renounceOwnership(); +} +``` + +Similar to the Ownable module, +circuits can be protected so that only the contract owner may them by adding `assertOnlyOwner` +as the first line in the circuit body like this: + +```typescript +export circuit mySensitiveCircuit(): [] { + ZOwnablePK_assertOnlyOwner(); + + // Do something +} +``` + +This covers the basics for creating a contract, but before deploying the contract, +the owner's id must be derived for the commitment scheme because it's required to deploy the contract. + +First, the owner needs to generate a secret nonce that's stored in the owner's private state. +See <>. + +Once the owner has the secret nonce generated, they can insert their public key and nonce into the following: + +```typescript +import { + CompactTypeBytes, + CompactTypeVector, + persistentHash, +} from '@midnight-ntwrk/compact-runtime'; +import { getRandomValues } from 'node:crypto'; + +// Owner ID +const generateId = ( + pk: Uint8Array, + nonce: Uint8Array, +): Uint8Array => { + const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32)); + return persistentHash(rt_type, [pk, nonce]); +}; + +// Instance salt for the constructor +const generateInstanceSalt = (): Uint8Array => { + return getRandomValues(new Uint8Array(32)); +} +``` + +TIP: Another way to get the user ID is to expose `_computeOwnerId` in the contract +and call this circuit off chain through a contract simulator. +Be on the lookout for future tooling that makes this process easier. + == Role-Based Access Control While the simplicity of _ownership_ can be useful for simple systems or quick prototyping, different levels of authorization are often needed. diff --git a/docs/modules/ROOT/pages/api/access.adoc b/docs/modules/ROOT/pages/api/access.adoc index 7237e42b..dd900b8a 100644 --- a/docs/modules/ROOT/pages/api/access.adoc +++ b/docs/modules/ROOT/pages/api/access.adoc @@ -1,6 +1,8 @@ :github-icon: pass:[] -:accessControl-guide: xref:accessControl.adoc[AccessControl guide] -:ownable-guide: xref:ownable.adoc[Ownable guide] +:accessControl-guide: xref:access.adoc#role_based_access_control[AccessControl guide] +:ownable-guide: xref:access.adoc#ownership_and_ownable[Ownable guide] +:zownablepk-guide: xref:access.adoc#shielded_ownership_and_zownablepk[ZOwnablePK guide] +:agpk: xref:access.adoc#air_gapped_public_key_agpk[Air-Gapped Public Key] :grantRole: <> :revokeRole: <> @@ -12,6 +14,8 @@ This directory provides ways to restrict who can access the circuits of a contra - `<>` is a simpler mechanism with a single owner "role" that can be assigned to a single account. This simpler mechanism can be useful for quick tests but projects with production concerns are likely to outgrow it. +- `<>` provides a privacy-preserving single owner access control mechanism using cryptographic commitments. The owner's public key is never revealed on-chain, instead storing only a commitment that proves ownership without exposing identity, suitable for applications requiring administrative control with strong privacy guarantees. + == Core [.contract] @@ -399,3 +403,201 @@ Requirements: Constraints: - k=10, rows=216 + +[.contract] +[[ZOwnablePK]] +=== `++ZOwnablePK++` link:https://github.com/OpenZeppelin/compact-contracts/blob/main/contracts/ownable/src/ZOwnablePK.compact[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```ts +import "./node_modules/@openzeppelin-compact/contracts/src/access/ZOwnablePK"; +``` + +`ZOwnablePK` provides a privacy-preserving access control mechanism for contracts with a single administrative user. Unlike traditional `Ownable` implementations that store or expose the owner's public key on-chain, +this module stores only a commitment to a hashed identifier derived from the owner's public key and a secret nonce. +For the strongest security guarantees, use an {agpk}. + +Ownable provides a basic access control mechanism where an account (an owner) can be granted exclusive access to specific circuits. + +This module includes <> to restrict a circuit to be used only by the owner. + +TIP: For an overview of the module, read the {zownablepk-guide}. + +[.contract-index] +.Circuits +-- + +[.sub-index#ZOwnablePKModule] +* xref:#ZOwnablePK-initialize[`++initialize(ownerId, instanceSalt)++`] +* xref:#ZOwnablePK-owner[`++owner()++`] +* xref:#ZOwnablePK-transferOwnership[`++transferOwnership(newOwnerId)++`] +* xref:#ZOwnablePK-renounceOwnership[`++renounceOwnership()++`] +* xref:#ZOwnablePK-assertOnlyOwner[`++assertOnlyOwner()++`] +* xref:#ZOwnablePK-_computeOwnerCommitment[`++_computeOwnerCommitment(id, counter)++`] +* xref:#ZOwnablePK-_computeOwnerId[`++_computeOwnerId(pk, nonce)++`] +* xref:#ZOwnablePK-_transferOwnership[`++_transferOwnership(newOwnerId)++`] +-- + +[.contract-item] +[[ZOwnablePK-initialize]] +==== `[.contract-item-name]#++initialize++#++(initialOwner: Either) → []++` [.item-kind]#circuit# + +Initializes the contract by setting the initial owner via `ownerId` +and storing the `instanceSalt` that acts as a privacy additive +for preventing duplicate commitments among other contracts implementing `ZOwnablePK`. + +NOTE: The `ownerId` must be calculated prior to contract deployment. +See <> + +Requirements: + +- Contract is not already initialized. +- `ownerId` is not an empty array. + +Constraints: + +- k=14, rows=14933 + +[.contract-item] +[[ZOwnablePK-owner]] +==== `[.contract-item-name]#++owner++#++() → Bytes<32>++` [.item-kind]#circuit# + +Returns the current commitment representing the contract owner. +The full commitment is: `SHA256(SHA256(pk, nonce), instanceSalt, counter, domain)`. + +Requirements: + +- Contract is initialized. + +Constraints: + +- k=10, rows=57 + +[.contract-item] +[[ZOwnablePK-transferOwnership]] +==== `[.contract-item-name]#++transferOwnership++#++(newOwnerId: Bytes<32>) → []++` [.item-kind]#circuit# + +Transfers ownership of the contract to `newOwnerId`. +`newOwnerId` must be precalculated and given to the current owner off chain. + +Requirements: + +- Contract is initialized. +- Caller is the current contract owner. +- `newOwnerId` is not an empty array. + +Constraints: + +- k=16, rows=39240 + +[.contract-item] +[[ZOwnablePK-renounceOwnership]] +==== `[.contract-item-name]#++renounceOwnership++#++() → []++` [.item-kind]#circuit# + +Leaves the contract without an owner. +It will not be possible to call <> circuits anymore. +Can only be called by the current owner. + +Requirements: + +- Contract is initialized. +- Caller is the current owner. + +Constraints: + +- k=15, rows=24442 + +[.contract-item] +[[ZOwnablePK-assertOnlyOwner]] +==== `[.contract-item-name]#++assertOnlyOwner++#++() → []++` [.item-kind]#circuit# + +Throws if called by any account whose id hash `SHA256(pk, nonce)` does not match the stored owner commitment. +Use this to only allow the owner to call specific circuits. + +Requirements: + +- Contract is initialized. +- Caller's id (`SHA256(pk, nonce)`) when used in <> equals the stored `_ownerCommitment`, +thus verifying themselves as the owner. + +Constraints: + +- k=15, rows=24437 + +[.contract-item] +[[ZOwnablePK-_computeOwnerCommitment]] +==== `[.contract-item-name]#++_computeOwnerCommitment++#++(id: Bytes<32>, counter: Uint<64>) → Bytes<32>++` [.item-kind]#circuit# + +Computes the owner commitment from the given `id` and `counter`. + +**Owner ID (`id`)** + +The `id` is expected to be computed off-chain as: `id = SHA256(pk, nonce)` + +- `pk`: The owner's public key. +- `nonce`: A secret nonce scoped to the instance, ideally rotated with each transfer. + +**Commitment Derivation** + +`commitment = SHA256(id, instanceSalt, counter, domain)` + +- `id`: See above. +- `instanceSalt`: A unique per-deployment salt, stored during initialization. +This prevents commitment collisions across deployments. +- `counter`: Incremented with each ownership transfer, ensuring uniqueness even with repeated `id` values. +Cast to `Field` then `Bytes<32>` for hashing. +- `domain`: Domain separator `"ZOwnablePK:shield:"` (padded to 32 bytes) to prevent hash collisions +when extending the module or using similar commitment schemes. + +Requirements: + +- Contract is initialized. + +Constraints: + +- k=14, rows=14853 + +[.contract-item] +[[ZOwnablePK-_computeOwnerId]] +==== `[.contract-item-name]#++_computeOwnerId++#++(pk: Either, nonce: Bytes<32>) → Bytes<32>++` [.item-kind]#circuit# + +Computes the unique identifier (`id`) of the owner from their public key and a secret nonce. + +**ID Derivation** +`id = SHA256(pk, nonce)` + +- `pk`: The public key of the caller. +This is passed explicitly to allow for off-chain derivation, testing, or scenarios +where the caller is different from the subject of the computation. +We recommend using an {agpk}. +- `nonce`: A secret nonce tied to the identity. +This value should be randomly generated and kept private. +It may be rotated periodically for enhanced unlinkability. + +The result is a 32-byte commitment that uniquely identifies the owner. +This value is later used in owner commitment hashing, +and acts as a privacy-preserving alternative to a raw public key. + +NOTE: This module allows ownership to be tied to an identity commitment derived from a public key and secret nonce. +While typically used with user public keys, +this mechanism may also support contract addresses as identifiers in future contract-to-contract interactions. +Both are treated as 32-byte values (`Bytes<32>`). + +Requirements: + +- Contract is initialized. +- `pk` is not a ContractAddress. + +[.contract-item] +[[ZOwnablePK-_transferOwnership]] +==== `[.contract-item-name]#++_transferOwnership++#++(newOwnerId: Bytes<32>) → []++` [.item-kind]#circuit# + +Transfers ownership to owner id `newOwnerId` without enforcing permission checks on the caller. + +Requirements: + +- Contract is initialized. + +Constraints: + +- k=14, rows=14823 From c16a6beb813f2701e58bf2bdb12d150bb5fccecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Fri, 29 Aug 2025 17:32:24 -0400 Subject: [PATCH 161/202] Construct simulator and witnesses for impl --- .../src/access/ShieldedAccessControl.compact | 17 +- .../mocks/MockShieldedAccessControl.compact | 9 +- .../ShieldedAccessControlSimulator.ts | 474 ++++++++++-------- .../ShieldedAccessControlWitnesses.ts | 296 ++++------- 4 files changed, 386 insertions(+), 410 deletions(-) diff --git a/contracts/src/access/ShieldedAccessControl.compact b/contracts/src/access/ShieldedAccessControl.compact index d9870c24..79598fe6 100644 --- a/contracts/src/access/ShieldedAccessControl.compact +++ b/contracts/src/access/ShieldedAccessControl.compact @@ -112,13 +112,13 @@ module ShieldedAccessControl { * @param {Uint<64>} index - An index in the `_operatorRoles` Merkle tree * @return {MerkleTreePath<10, Bytes<32>>} - The Merkle path of `roleCommitment` in the `_operatorRoles` Merkle tree  */ - witness getRoleCommitmentPath(roleCommitment: Bytes<32>): MerkleTreePath<10, Bytes<32>>; + witness wit_getRoleCommitmentPath(roleCommitment: Bytes<32>): MerkleTreePath<10, Bytes<32>>; - witness secretNonce(roleId: Bytes<32>): Bytes<32>; + witness wit_secretNonce(roleId: Bytes<32>): Bytes<32>; - witness getRoleCommitmentIndex(roleId: Bytes<32>): Uint<64>; + witness wit_getRoleIndex(roleId: Bytes<32>): Uint<64>; - struct Role { + export struct Role { hasRole: Boolean; roleCommitment: Bytes<32>; } @@ -253,15 +253,15 @@ module ShieldedAccessControl { * produced by SHA256(roleId | account | nonce) exists in the `_operatorRoles` Merkle tree */ export circuit _checkMerkleTree(roleId: Bytes<32>, account: Bytes<32>): Role { - const nonce = secretNonce(roleId); - const index = getMerkleTreeIndex(roleId); + const nonce = wit_secretNonce(roleId); + const index = wit_getRoleIndex(roleId); const roleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Bytes<32>]); assert(!_roleCommitmentNullifiers.member(disclose(roleCommitment)), "ShieldedAccessControl: role commitment access revoked"); - const authPath = getRoleCommitmentPath(roleCommitment); + const authPath = wit_getRoleCommitmentPath(roleCommitment); const hasRole = _operatorRoles .checkRoot(merkleTreePathRoot<10, Bytes<32>>(disclose(authPath))); - return Role {hasRole, roleCommitment}; + return Role {hasRole, disclose(roleCommitment)}; } /** @@ -473,6 +473,7 @@ module ShieldedAccessControl { if (!Utils_isContractAddress(account)) { const zswapPubKey = account.left.bytes; + // Use ledger index as source of truth _operatorRoles.insertHashIndex(disclose(role.roleCommitment), _currentMerkleTreeIndex); _currentMerkleTreeIndex.increment(1); return true; diff --git a/contracts/src/access/test/mocks/MockShieldedAccessControl.compact b/contracts/src/access/test/mocks/MockShieldedAccessControl.compact index ac009ca3..78c36a96 100644 --- a/contracts/src/access/test/mocks/MockShieldedAccessControl.compact +++ b/contracts/src/access/test/mocks/MockShieldedAccessControl.compact @@ -11,11 +11,14 @@ export { ContractAddress, Either, Maybe, + MerkleTreePath, ShieldedAccessControl_DEFAULT_ADMIN_ROLE, - ShieldedAccessControl__operatorRoles + ShieldedAccessControl__operatorRoles, + ShieldedAccessControl__currentMerkleTreeIndex, + ShieldedAccessControl_Role }; -export circuit hasRole(roleId: Bytes<32>, account: Either): Boolean { +export circuit hasRole(roleId: Bytes<32>, account: Either): ShieldedAccessControl_Role { return ShieldedAccessControl_hasRole(roleId, account); } @@ -27,7 +30,7 @@ export circuit _checkRole(roleId: Bytes<32>, account: Either, account: Bytes<32>): Boolean { +export circuit _checkMerkleTree(roleId: Bytes<32>, account: Bytes<32>): ShieldedAccessControl_Role { return ShieldedAccessControl__checkMerkleTree(roleId, account); } diff --git a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts index b71d4cf4..d194a5a3 100644 --- a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts +++ b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts @@ -1,9 +1,6 @@ import { type CircuitContext, type CoinPublicKey, - type ContractState, - QueryContext, - constructorContext, emptyZswapLocalState, } from '@midnight-ntwrk/compact-runtime'; import { sampleContractAddress } from '@midnight-ntwrk/zswap'; @@ -11,287 +8,344 @@ import { type ContractAddress, type Either, type Ledger, + ledger, Contract as MockShieldedAccessControl, type ZswapCoinPublicKey, - ledger, -} from '../../shieldedAccessControl/src/artifacts/MockShieldedAccessControl/contract/index.cjs'; // Combined imports + type ShieldedAccessControl_Role as Role +} from '../../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; import { - type ShieldedAccessControlPrivateState, + ShieldedAccessControlPrivateState, ShieldedAccessControlWitnesses, } from '../../witnesses/ShieldedAccessControlWitnesses.js'; -import type { IContractSimulator } from '../../shieldedAccessControl/src/test/types/test.js'; +import type { + ContextlessCircuits, + ExtractImpureCircuits, + ExtractPureCircuits, + SimulatorOptions, +} from '../types/test.js'; +import { AbstractContractSimulator } from '../utils/AbstractContractSimulator.js'; +import { SimulatorStateManager } from '../utils/SimualatorStateManager.js'; + +type ShieldedAccessControlSimOptions = SimulatorOptions< + ShieldedAccessControlPrivateState, + typeof ShieldedAccessControlWitnesses +>; /** - * @description A simulator implementation of a AccessControl contract for testing purposes. + * @description A simulator implementation of a contract for testing purposes. * @template P - The private state type, fixed to ShieldedAccessControlPrivateState. * @template L - The ledger type, fixed to Contract.Ledger. */ -export class AccessControlSimulator - implements IContractSimulator { - /** @description The underlying contract instance managing contract logic. */ - readonly contract: MockShieldedAccessControl; - - /** @description The deployed address of the contract. */ +export class ShieldedAccessControlSimulator extends AbstractContractSimulator< + ShieldedAccessControlPrivateState, + Ledger +> { + contract: MockShieldedAccessControl; readonly contractAddress: string; + private stateManager: SimulatorStateManager; + private callerOverride: CoinPublicKey | null = null; + private _witnesses: ReturnType; - /** @description The current circuit context, updated by contract operations. */ - circuitContext: CircuitContext; + private _pureCircuitProxy?: ContextlessCircuits< + ExtractPureCircuits>, + ShieldedAccessControlPrivateState + >; - /** - * @description Initializes the mock contract. - */ - constructor() { - this.contract = new MockShieldedAccessControl( - ShieldedAccessControlWitnesses, - ); + private _impureCircuitProxy?: ContextlessCircuits< + ExtractImpureCircuits>, + ShieldedAccessControlPrivateState + >; + + constructor( + initUser: Uint8Array, + options: ShieldedAccessControlSimOptions = {}, + ) { + super(); + + // Setup initial state const { - currentPrivateState, - currentContractState, - currentZswapLocalState, - } = this.contract.initialState(constructorContext({}, '0'.repeat(64))); - this.circuitContext = { - currentPrivateState, - currentZswapLocalState, - originalState: currentContractState, - transactionContext: new QueryContext( - currentContractState.data, - sampleContractAddress(), - ), - }; + privateState = ShieldedAccessControlPrivateState.generate(initUser), + witnesses = ShieldedAccessControlWitnesses(), + coinPK = '0'.repeat(64), + address = sampleContractAddress(), + } = options; + + this.contract = new MockShieldedAccessControl(witnesses); + + this.stateManager = new SimulatorStateManager( + this.contract, + privateState, + coinPK, + address, + [], + ); this.contractAddress = this.circuitContext.transactionContext.address; + this._witnesses = witnesses; + this.contract = new MockShieldedAccessControl(this._witnesses); } - /** - * @description Retrieves the current public ledger state of the contract. - * @returns The ledger state as defined by the contract. - */ - public getCurrentPublicState(): Ledger { + get circuitContext() { + return this.stateManager.getContext(); + } + + set circuitContext(ctx) { + this.stateManager.setContext(ctx); + } + + getPublicState(): Ledger { return ledger(this.circuitContext.transactionContext.state); } /** - * @description Retrieves the current private state of the contract. - * @returns The private state of type ShieldedAccessControlPrivateState. + * @description Constructs a caller-specific circuit context. + * If a caller override is present, it replaces the current Zswap local state with an empty one + * scoped to the overridden caller. Otherwise, the existing context is reused as-is. + * @returns A circuit context adjusted for the current simulated caller. */ - public getCurrentPrivateState(): ShieldedAccessControlPrivateState { - return this.circuitContext.currentPrivateState; + protected getCallerContext(): CircuitContext { + return { + ...this.circuitContext, + currentZswapLocalState: this.callerOverride + ? emptyZswapLocalState(this.callerOverride) + : this.circuitContext.currentZswapLocalState, + }; } /** - * @description Retrieves the current contract state. - * @returns The contract state object. + * @description Initializes and returns a proxy to pure contract circuits. + * The proxy automatically injects the current circuit context into each call, + * and returns only the result portion of each circuit's output. + * @notice The proxy is created only when first accessed a.k.a lazy initialization. + * This approach is efficient in cases where only pure or only impure circuits are used, + * avoiding unnecessary proxy creation. + * @returns A proxy object exposing pure circuit functions without requiring explicit context. */ - public getCurrentContractState(): ContractState { - return this.circuitContext.originalState; + protected get pureCircuit(): ContextlessCircuits< + ExtractPureCircuits>, + ShieldedAccessControlPrivateState + > { + if (!this._pureCircuitProxy) { + this._pureCircuitProxy = this.createPureCircuitProxy< + MockShieldedAccessControl['circuits'] + >(this.contract.circuits, () => this.circuitContext); + } + return this._pureCircuitProxy; } /** - * @description Retrieves an account's permission for `roleId`. - * @param roleId - The role identifier. - * @param account - A ZswapCoinPublicKey or a ContractAddress. - * @returns Whether an account has a specified role. + * @description Initializes and returns a proxy to impure contract circuits. + * The proxy automatically injects the current (possibly caller-modified) context into each call, + * and updates the circuit context with the one returned by the circuit after execution. + * @notice The proxy is created only when first accessed a.k.a. lazy initialization. + * This approach is efficient in cases where only pure or only impure circuits are used, + * avoiding unnecessary proxy creation. + * @returns A proxy object exposing impure circuit functions without requiring explicit context management. */ - public hasRole( - roleId: Uint8Array, - account: Either, - ): boolean { - return this.contract.impureCircuits.hasRole( - this.circuitContext, - roleId, - account, - ).result; + protected get impureCircuit(): ContextlessCircuits< + ExtractImpureCircuits>, + ShieldedAccessControlPrivateState + > { + if (!this._impureCircuitProxy) { + this._impureCircuitProxy = this.createImpureCircuitProxy< + MockShieldedAccessControl['impureCircuits'] + >( + this.contract.impureCircuits, + () => this.getCallerContext(), + (ctx: any) => { + this.circuitContext = ctx; + }, + ); + } + return this._impureCircuitProxy; } /** - * @description Retrieves an account's permission for `roleId`. - * @param caller - Optional. Sets the caller context if provided. - * @param roleId - The role identifier. + * @description Resets the cached circuit proxy instances. + * This is useful if the underlying contract state or circuit context has changed, + * and you want to ensure the proxies are recreated with updated context on next access. */ - public assertOnlyRole(roleId: Uint8Array, caller?: CoinPublicKey) { - const res = this.contract.impureCircuits.assertOnlyRole( - { - ...this.circuitContext, - currentZswapLocalState: caller - ? emptyZswapLocalState(caller) - : this.circuitContext.currentZswapLocalState, - }, - roleId, - ); - - this.circuitContext = res.context; + public resetCircuitProxies(): void { + this._pureCircuitProxy = undefined; + this._impureCircuitProxy = undefined; } /** - * @description Retrieves an account's permission for `roleId`. - * @param roleId - The role identifier. - * @param account - A ZswapCoinPublicKey or a ContractAddress. + * @description Helper method that provides access to both pure and impure circuit proxies. + * These proxies automatically inject the appropriate circuit context when invoked. + * @returns An object containing `pure` and `impure` circuit proxy interfaces. */ - public _checkRole( - roleId: Uint8Array, - account: Either, + public get circuits() { + return { + pure: this.pureCircuit, + impure: this.impureCircuit, + }; + } + + public get witnesses(): ReturnType { + return this._witnesses; + } + + public set witnesses(newWitnesses: ReturnType) { + this._witnesses = newWitnesses; + this.contract = new MockShieldedAccessControl(this._witnesses); + } + + public overrideWitness( + key: K, + fn: (typeof this._witnesses)[K], ) { - this.circuitContext = this.contract.impureCircuits._checkRole( - this.circuitContext, - roleId, - account, - ).context; + this.witnesses = { + ...this._witnesses, + [key]: fn, + }; } /** - * @description Retrieves `roleId`'s admin identifier. - * @param roleId - The role identifier. - * @returns The admin identifier for `roleId`. + * @description Returns the current commitment representing the contract owner. + * The full commitment is: `SHA256(SHA256(pk, nonce), instanceSalt, counter, domain)`. + * @returns The current owner's commitment. */ - public getRoleAdmin(roleId: Uint8Array): Uint8Array { - return this.contract.impureCircuits.getRoleAdmin( - this.circuitContext, - roleId, - ).result; + public hasRole(roleId: Uint8Array, account: Either): Role { + return this.circuits.impure.hasRole(roleId, account); } /** - * @description Grants an account permissions to use `roleId`. - * @param caller - Optional. Sets the caller context if provided. - * @param roleId - The role identifier. - * @param account - A ZswapCoinPublicKey or a ContractAddress. + * @description Transfers ownership to `newOwnerId`. + * `newOwnerId` must be precalculated and given to the current owner off chain. + * @param newOwnerId The new owner's unique identifier (`SHA256(pk, nonce)`). */ - public grantRole( - roleId: Uint8Array, - account: Either, - caller?: CoinPublicKey, - ) { - const res = this.contract.impureCircuits.grantRole( - { - ...this.circuitContext, - currentZswapLocalState: caller - ? emptyZswapLocalState(caller) - : this.circuitContext.currentZswapLocalState, - }, - roleId, - account, - ); - - this.circuitContext = res.context; + public assertOnlyRole(roleId: Uint8Array) { + this.circuits.impure.assertOnlyRole(roleId); } /** - * @description Revokes an account's permission to use `roleId`. - * @param caller - Optional. Sets the caller context if provided. - * @param roleId - The role identifier. - * @param account - A ZswapCoinPublicKey or a ContractAddress. + * @description Leaves the contract without an owner. + * It will not be possible to call `assertOnlyOnwer` circuits anymore. + * Can only be called by the current owner. */ - public revokeRole( - roleId: Uint8Array, - account: Either, - caller?: CoinPublicKey, - ) { - const res = this.contract.impureCircuits.revokeRole( - { - ...this.circuitContext, - currentZswapLocalState: caller - ? emptyZswapLocalState(caller) - : this.circuitContext.currentZswapLocalState, - }, - roleId, - account, - ); - - this.circuitContext = res.context; + public _checkRole(roleId: Uint8Array, account: Either) { + this.circuits.impure._checkRole(roleId, account); } /** - * @description Revokes `roleId` from the calling account. - * @param caller - Optional. Sets the caller context if provided. - * @param roleId - The role identifier. - * @param account - A ZswapCoinPublicKey or a ContractAddress. + * @description Throws if called by any account whose id hash `SHA256(pk, nonce)` does not match + * the stored owner commitment. Use this to only allow the owner to call specific circuits. */ - public renounceRole( - roleId: Uint8Array, - account: Either, - caller?: CoinPublicKey, - ) { - const res = this.contract.impureCircuits.renounceRole( - { - ...this.circuitContext, - currentZswapLocalState: caller - ? emptyZswapLocalState(caller) - : this.circuitContext.currentZswapLocalState, - }, - roleId, - account, - ); + public _checkMerkleTree(roleId: Uint8Array, account: Uint8Array): Role { + return this.circuits.impure._checkMerkleTree(roleId, account); + } - this.circuitContext = res.context; + /** + * @description Computes the owner commitment from the given `id` and `counter`. + * @param id - The unique identifier of the owner calculated by `SHA256(pk, nonce)`. + * @param counter - The current counter or round. This increments by `1` + * after every transfer to prevent duplicate commitments given the same `id`. + * @returns The commitment derived from `id` and `counter`. + */ + public getRoleAdmin(roleId: Uint8Array): Uint8Array { + return this.circuits.impure.getRoleAdmin(roleId); } /** - * @description Sets the admin identifier for `roleId`. - * @param roleId - The role identifier. - * @param adminId - The admin role identifier. + * @description Computes the unique identifier (`id`) of the owner from their + * public key and a secret nonce. + * @param pk - The public key of the identity being committed. + * @param nonce - A private nonce to scope the commitment. + * @returns The computed owner ID. */ - public _setRoleAdmin(roleId: Uint8Array, adminId: Uint8Array) { - this.circuitContext = this.contract.impureCircuits._setRoleAdmin( - this.circuitContext, - roleId, - adminId, - ).context; + public grantRole(roleId: Uint8Array, account: Either) { + this.circuits.impure.grantRole(roleId, account); } /** - * @description Grants an account permissions to use `roleId`. Internal function without access restriction. - * @param roleId - The role identifier. - * @param account - A ZswapCoinPublicKey or a ContractAddress. + * @description Transfers ownership to owner id `newOwnerId` without + * enforcing permission checks on the caller. + * @param newOwnerId - The unique identifier of the new owner calculated by `SHA256(pk, nonce)`. */ - public _grantRole( - roleId: Uint8Array, - account: Either, - ): boolean { - const res = this.contract.impureCircuits._grantRole( - this.circuitContext, - roleId, - account, - ); + public revokeRole(roleId: Uint8Array, account: Either) { + this.circuits.impure.revokeRole(roleId, account); + } - this.circuitContext = res.context; - return res.result; + /** + * @description Transfers ownership to owner id `newOwnerId` without + * enforcing permission checks on the caller. + * @param newOwnerId - The unique identifier of the new owner calculated by `SHA256(pk, nonce)`. + */ + public renounceRole(roleId: Uint8Array, callerConfirmation: Either) { + this.circuits.impure.renounceRole(roleId, callerConfirmation); } /** - * @description Grants an account permissions to use `roleId`. Internal function without access restriction. - * DOES NOT restrict sending to a ContractAddress. - * @param roleId - The role identifier. - * @param account - A ZswapCoinPublicKey or a ContractAddress. + * @description Transfers ownership to owner id `newOwnerId` without + * enforcing permission checks on the caller. + * @param newOwnerId - The unique identifier of the new owner calculated by `SHA256(pk, nonce)`. */ - public _unsafeGrantRole( - roleId: Uint8Array, - account: Either, - ): boolean { - const res = this.contract.impureCircuits._unsafeGrantRole( - this.circuitContext, - roleId, - account, - ); + public _setRoleAdmin(roleId: Uint8Array, adminRole: Uint8Array) { + this.circuits.impure._setRoleAdmin(roleId, adminRole); + } - this.circuitContext = res.context; - return res.result; + /** + * @description Transfers ownership to owner id `newOwnerId` without + * enforcing permission checks on the caller. + * @param newOwnerId - The unique identifier of the new owner calculated by `SHA256(pk, nonce)`. + */ + public _grantRole(roleId: Uint8Array, account: Either): Boolean { + return this.circuits.impure._grantRole(roleId, account); } /** - * @description Revokes an account's permission to use `roleId`. Internal function without access restriction. - * @param roleId - The role identifier. - * @param account - A ZswapCoinPublicKey or a ContractAddress. + * @description Transfers ownership to owner id `newOwnerId` without + * enforcing permission checks on the caller. + * @param newOwnerId - The unique identifier of the new owner calculated by `SHA256(pk, nonce)`. */ - public _revokeRole( - roleId: Uint8Array, - account: Either, - ): boolean { - const res = this.contract.impureCircuits._revokeRole( - this.circuitContext, - roleId, - account, - ); + public _unsafeGrantRole(roleId: Uint8Array, account: Either): Boolean { + return this.circuits.impure._unsafeGrantRole(roleId, account); + } - this.circuitContext = res.context; - return res.result; + /** + * @description Transfers ownership to owner id `newOwnerId` without + * enforcing permission checks on the caller. + * @param newOwnerId - The unique identifier of the new owner calculated by `SHA256(pk, nonce)`. + */ + public _revokeRole(roleId: Uint8Array, account: Either): Boolean { + return this.circuits.impure._revokeRole(roleId, account); } + + public readonly privateState = { + /** + * @description Contextually sets a new nonce into the private state. + * @param newNonce The secret nonce. + * @returns The ZOwnablePK private state after setting the new nonce. + */ + injectSecretNonce: ( + roleId: Buffer, + newNonce: Buffer, + ): ShieldedAccessControlPrivateState => { + const currentState = this.stateManager.getContext().currentPrivateState; + const updatedState = { ...currentState, roles: { ...currentState.roles } } + const roleString = roleId.toString('hex'); + updatedState.roles[roleString] = newNonce; + this.stateManager.updatePrivateState(updatedState); + return updatedState; + }, + + /** + * @description Returns the secret nonce given the context. + * @returns The secret nonce. + */ + getCurrentSecretNonce: (roleId: Buffer): Uint8Array => { + const roleString = roleId.toString('hex'); + return this.stateManager.getContext().currentPrivateState.roles[roleString]; + }, + }; + + public callerCtx = { + /** + * @description Sets the caller context. + * @param caller The caller in context of the proceeding circuit calls. + */ + setCaller: (caller: CoinPublicKey) => { + this.callerOverride = caller; + }, + }; } diff --git a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts index 4ad00147..586dcb2e 100644 --- a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts @@ -1,214 +1,132 @@ -import { Buffer } from 'node:buffer'; -import { - constructorContext, - decodeCoinPublicKey, - type MerkleTreePath, - QueryContext, - type WitnessContext, -} from '@midnight-ntwrk/compact-runtime'; -import { encodeContractAddress } from '@midnight-ntwrk/ledger'; -import { sampleContractAddress } from '@midnight-ntwrk/zswap'; -import { - type ContractAddress, - type Either, - type Ledger, - Contract as MockShieldedAccessControl, - type ZswapCoinPublicKey, -} from '../shieldedAccessControl/src/artifacts/MockShieldedAccessControl/contract/index.cjs'; // Combined imports +import { getRandomValues } from 'node:crypto'; +import { CompactTypeVector, CompactTypeBytes, persistentHash, type WitnessContext, convert_bigint_to_Uint8Array } from '@midnight-ntwrk/compact-runtime'; +import type { Ledger, MerkleTreePath } from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; -const { hkdfSync } = await import('node:crypto'); - -const KEYLEN = 32; +const MERKLE_TREE_DEPTH = 2 ** 10; /** - * @description The respective `nonce` value for a given `roleId` should be at the same index - * for each array of `Buffer`s + * @description Interface defining the witness methods for ShieldedAccessControl operations. + * @template P - The private state type. */ -export type ShieldedAccessControlPrivateState = { - secretKey: Buffer; - nonces: Buffer[]; - roleIds: Buffer[]; -}; +export interface IShieldedAccessControlWitnesses

{ + /** + * Retrieves the secret nonce from the private state. + * @param context - The witness context containing the private state. + * @returns A tuple of the private state and the secret nonce as a Uint8Array. + */ + wit_secretNonce(context: WitnessContext, roleId: Uint8Array): [P, Uint8Array]; + wit_getRoleCommitmentPath(context: WitnessContext, roleCommitment: Uint8Array): [P, MerkleTreePath]; + wit_getRoleIndex(context: WitnessContext, roleId: Uint8Array): [P, bigint]; +} + +type RoleId = string; +type SecretNonce = Uint8Array; /** - * @description Generates a nonce value using the following scheme: HKDF-SHA256(SK, "role-nonce" | roleId | PK) - * @param secretKey - The secret key associated with the contract. - * @param roleId - The role identifier. - * @param salt - A salt value. - * @param account - The public key of an account. - * - * @returns A unique nonce value for `roleId` + * @description Represents the private state of an ownable contract, storing a secret nonce. */ -function generateNonce( - secretKey: Buffer, - roleId: Buffer, - salt: Buffer, - account: Buffer, -): Buffer { - const domainString = Buffer.from('role-nonce'); - const info = Buffer.concat([domainString, roleId, account]); - const nonce = hkdfSync('sha256', secretKey, salt, info, KEYLEN); - - return Buffer.from(nonce); -} +export type ShieldedAccessControlPrivateState = { + /** @description A 32-byte secret nonce used as a privacy additive. */ + roles: Record, + account: Uint8Array +}; /** - * @description A stub function that simulates a successful role approval - * @param account - The public key of an account. - * @param roleId - The role identifier. - * @param nonce - The nonce associated with `roleId`. - * - * @returns Whether the account was approved for a role + * @description Utility object for managing the private state of a Shielded AccessControl contract. */ -function sendRoleRequestToAdmin( - _account: Buffer, - _roleId: Buffer, - _nonce: Buffer, -) { - return true; -} - -export const ShieldedAccessControlWitnesses = { +export const ShieldedAccessControlPrivateState = { /** - * @description Typescript implementation of the `getRoleCommitmentPath` witness function. - * @param privateState - The current private state. - * @param ledger - A snapshot of the current ledger state. - * @param roleCommitment - The role commitment to query. - * @param index - The index of `roleCommitment`in the Merkle tree. - * - * @returns An array of the private state and the Merkle tree path of `roleCommitment` - * in the `_operatorRoles` Merkle tree. + * @description Generates a new private state with a random secret nonce. + * @returns A fresh ShieldedAccessControlPrivateState instance. */ - getRoleCommitmentPath: ( - { - ledger, - privateState, - }: WitnessContext, - roleCommitment: Uint8Array, - index: bigint, - ): [ShieldedAccessControlPrivateState, MerkleTreePath] => { - const merkleTreePath = - ledger.ShieldedAccessControl__operatorRoles.pathForLeaf( - index, - roleCommitment, - ); - return [privateState, merkleTreePath]; + generate: (account: Uint8Array): ShieldedAccessControlPrivateState => { + const defaultRoleId: string = Buffer.alloc(32).toString('hex'); + const privateState: ShieldedAccessControlPrivateState = { roles: {}, account }; + privateState.roles[defaultRoleId] = getRandomValues(Buffer.alloc(32)); + return privateState; }, + /** - * @description Typescript implementation of the `recoverNonce` witness function. Simulates calls to the `hasRole` circuit - * to determine if the account has the specified role. Updates the private state with any found roles. - * @param privateState - The current private state. - * @param ledger - A snapshot of the current ledger state. - * @param contractAddress - The address of the contract. - * @param account - The public key associated with a role. - * @param salt - A salt value. + * @description Generates a new private state with a user-defined secret nonce. + * Useful for deterministic nonce generation or advanced use cases. * - * @returns An array of the new private state and the empty tuple + * @param nonce - The 32-byte secret nonce to use. + * @returns A fresh ShieldedAccessControlPrivateState instance with the provided nonce. + * + * @example + * ```typescript + * // For deterministic nonces (user-defined scheme) + * const deterministicNonce = myDeterministicScheme(...); + * const privateState = ShieldedAccessControlPrivateState.withNonce(deterministicNonce); + * ``` */ - recoverRoles: ( - { - ledger, - privateState, - contractAddress, - }: WitnessContext, - account: Uint8Array, - salt: Uint8Array, - ): [ShieldedAccessControlPrivateState, []] => { - const roles = [ledger.ShieldedAccessControl_DEFAULT_ADMIN_ROLE]; - const coinPubKey = decodeCoinPublicKey(account); - const newPrivateState: ShieldedAccessControlPrivateState = { - secretKey: privateState.secretKey, - roleIds: [], - nonces: [], - }; - - const contract = - new MockShieldedAccessControl( - ShieldedAccessControlWitnesses, - ); - const { - currentPrivateState, - currentContractState, - currentZswapLocalState, - } = contract.initialState( - constructorContext( - { secretKey: privateState.secretKey, nonces: [], roleIds: [] }, - coinPubKey, - ), - ); - const circuitContext = { - currentPrivateState, - currentZswapLocalState, - originalState: currentContractState, - transactionContext: new QueryContext( - currentContractState.data, - contractAddress, - ), - }; + withRoleAndNonce: (account: Uint8Array, roleId: Buffer, nonce: Buffer): ShieldedAccessControlPrivateState => { + const roleString = roleId.toString('hex'); + const privateState: ShieldedAccessControlPrivateState = { roles: {}, account }; + privateState.roles[roleString] = nonce; + return privateState; + }, - for (let i = 0; i < roles.length; i++) { - const role = roles[i]; - const nonce = generateNonce( - privateState.secretKey, - Buffer.from(role), - Buffer.from(salt), - Buffer.from(account), - ); - const eitherAccount: Either = { - is_left: true, - left: { bytes: account }, - right: { bytes: encodeContractAddress(sampleContractAddress()) }, - }; + setRole: (privateState: ShieldedAccessControlPrivateState, roleId: Buffer, nonce: Buffer): ShieldedAccessControlPrivateState => { + const roleString = roleId.toString('hex'); + privateState.roles[roleString] = nonce; + return privateState; + }, - try { - const hasRole = contract.impureCircuits.hasRole( - circuitContext, - role, - eitherAccount, - nonce, - ); - if (hasRole) { - newPrivateState.nonces.push(nonce); - newPrivateState.roleIds.push(Buffer.from(role)); - } - } catch (err) { - console.log(err); - } + getRoleCommitmentPath: (ledger: Ledger, roleCommitment: Uint8Array): MerkleTreePath => { + const path = ledger.ShieldedAccessControl__operatorRoles.findPathForLeaf(roleCommitment); + const defaultPath: MerkleTreePath = { + leaf: Buffer.alloc(32), + path: [] } - - return [newPrivateState, []]; + return path ? path : defaultPath; }, - /** - * @description Typescript implementation of the `requestRole` witness function. - * @param privateState - The current private state. - * @param roleId - The role identifier. - * @param account - The public key requesting a role. - * @param salt - A salt value. - * - * @returns An array of the new private state and an empty array - */ - requestRole: ( - { privateState }: WitnessContext, - roleId: Uint8Array, - account: Uint8Array, - salt: Uint8Array, - ): [ShieldedAccessControlPrivateState, []] => { - const saltBuff = Buffer.from(salt); - const roleIdBuff = Buffer.from(roleId); - const accountBuff = Buffer.from(account); - const nonce = generateNonce( - privateState.secretKey, - roleIdBuff, - saltBuff, - accountBuff, - ); - const isApproved = sendRoleRequestToAdmin(accountBuff, roleIdBuff, nonce); - if (isApproved) { - privateState.nonces.push(nonce); - privateState.roleIds.push(roleIdBuff); + // If index cannot be found in MT return _currentMTIndex + getRoleIndex: ({ ledger, privateState }: WitnessContext, roleId: Uint8Array): bigint => { + const roleIdString = Buffer.from(roleId).toString('hex'); + // Iterate over each MT to determine if commitment exists + for (let i = 0; i < MERKLE_TREE_DEPTH; i++) { + const rt_type = new CompactTypeVector(4, new CompactTypeBytes(32)); + const bIndex = convert_bigint_to_Uint8Array(32, BigInt(i)); + const commitment = persistentHash(rt_type, [roleId, privateState.account, privateState.roles[roleIdString], bIndex]); + try { + ledger.ShieldedAccessControl__operatorRoles.pathForLeaf(BigInt(i), commitment); + return BigInt(i); + } catch (e) { + console.error(e); + } } - return [privateState, []]; + // If commitment doesn't exist return currentMTIndex + // Used for adding roles or as a standard default + return ledger.ShieldedAccessControl__currentMerkleTreeIndex; }, }; + +/** + * @description Factory function creating witness implementations for Shielded AccessControl operations. + * @returns An object implementing the Witnesses interface for ShieldedAccessControlPrivateState. + */ +export const ShieldedAccessControlWitnesses = + (): IShieldedAccessControlWitnesses => ({ + wit_secretNonce( + context: WitnessContext, + roleId: Uint8Array + ): [ShieldedAccessControlPrivateState, Uint8Array] { + const roleString = Buffer.from(roleId).toString('hex'); + return [context.privateState, context.privateState.roles[roleString]]; + }, + wit_getRoleCommitmentPath( + context: WitnessContext, + roleCommitment: Uint8Array + ): [ShieldedAccessControlPrivateState, MerkleTreePath] { + return [context.privateState, ShieldedAccessControlPrivateState.getRoleCommitmentPath(context.ledger, roleCommitment)]; + }, + wit_getRoleIndex( + context: WitnessContext, + roleId: Uint8Array + ): [ShieldedAccessControlPrivateState, bigint] { + return [context.privateState, ShieldedAccessControlPrivateState.getRoleIndex(context, roleId)]; + }, + }); \ No newline at end of file From 9117fc698ba1abcddc4c43e68bc07e3984711ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:52:48 -0400 Subject: [PATCH 162/202] Restrict usage to ZSwapCoinPubKeys, simplify logic --- .../src/access/ShieldedAccessControl.compact | 72 +++---------------- 1 file changed, 10 insertions(+), 62 deletions(-) diff --git a/contracts/src/access/ShieldedAccessControl.compact b/contracts/src/access/ShieldedAccessControl.compact index 79598fe6..0d2c266b 100644 --- a/contracts/src/access/ShieldedAccessControl.compact +++ b/contracts/src/access/ShieldedAccessControl.compact @@ -151,13 +151,17 @@ module ShieldedAccessControl { * @return {Boolean} - A boolean determining if the account has the specified role.  */ export circuit hasRole(roleId: Bytes<32>, account: Either): Role { - if (!Utils_isContractAddress(account)) { - const zswapPubKey = account.left.bytes; - return _checkMerkleTree(roleId, zswapPubKey); - } + assert(!Utils_isContractAddress(account), "ShieldedAccessControl: contract address roles are not yet supported"); + + const nonce = wit_secretNonce(roleId); + const index = wit_getRoleIndex(roleId); + const roleCommitment = persistentHash>>([roleId, account.left.bytes, nonce, index as Field as Bytes<32>, pad(32, "ShieldedAccessControl:shield:")]); + assert(!_roleCommitmentNullifiers.member(disclose(roleCommitment)), "ShieldedAccessControl: role commitment access revoked"); - const contractAddress = account.right.bytes; - return _checkMerkleTree(roleId, contractAddress); + const authPath = wit_getRoleCommitmentPath(roleCommitment); + const hasRole = _operatorRoles + .checkRoot(merkleTreePathRoot<10, Bytes<32>>(disclose(authPath))); + return Role {hasRole, disclose(roleCommitment)}; } /** @@ -225,45 +229,6 @@ module ShieldedAccessControl { assert(role.hasRole, "ShieldedAccessControl: unauthorized account"); } - /** - * @description Checks if a path exists for a role commitment. - * - * @circuitInfo k=15, rows=29801 - * - * Requirements: - * - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) - * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce) - * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256(roleId | account | nonce) must - * exist at `index` in the `_operatorRoles` Merkle tree. - * - * Disclosures: - * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256(roleId | account | nonce). - * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` - * Merkle tree. - * - * @param {Bytes<32>} roleId - The role identifier. - * @param {Bytes<32>} account - The account to check represented as a Bytes<32>. - * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) - * @return {Boolean} - A boolean determining if a path for for the role commitment - * produced by SHA256(roleId | account | nonce) exists in the `_operatorRoles` Merkle tree - */ - export circuit _checkMerkleTree(roleId: Bytes<32>, account: Bytes<32>): Role { - const nonce = wit_secretNonce(roleId); - const index = wit_getRoleIndex(roleId); - const roleCommitment = persistentHash>>([roleId, account, nonce, index as Field as Bytes<32>]); - assert(!_roleCommitmentNullifiers.member(disclose(roleCommitment)), "ShieldedAccessControl: role commitment access revoked"); - - const authPath = wit_getRoleCommitmentPath(roleCommitment); - const hasRole = _operatorRoles - .checkRoot(merkleTreePathRoot<10, Bytes<32>>(disclose(authPath))); - return Role {hasRole, disclose(roleCommitment)}; - } - /** * @description Returns the admin role that controls `roleId` or * a byte array with all zero bytes if `roleId` doesn't exist. See {grantRole} and {revokeRole}. @@ -430,7 +395,6 @@ module ShieldedAccessControl { * @return {Boolean} roleGranted - A boolean indicating if `roleId` was granted. */ export circuit _grantRole(roleId: Bytes<32>, account: Either): Boolean { - assert(!Utils_isContractAddress(account), "ShieldedAccessControl: unsafe role approval"); return _unsafeGrantRole(roleId, account); } @@ -471,15 +435,6 @@ module ShieldedAccessControl { return false; } - if (!Utils_isContractAddress(account)) { - const zswapPubKey = account.left.bytes; - // Use ledger index as source of truth - _operatorRoles.insertHashIndex(disclose(role.roleCommitment), _currentMerkleTreeIndex); - _currentMerkleTreeIndex.increment(1); - return true; - } - - const contractAddress = account.right.bytes; // Use ledger index as source of truth _operatorRoles.insertHashIndex(disclose(role.roleCommitment), _currentMerkleTreeIndex); _currentMerkleTreeIndex.increment(1); @@ -520,13 +475,6 @@ module ShieldedAccessControl { return false; } - if(!Utils_isContractAddress(account)) { - const zswapPubKey = account.left.bytes; - _roleCommitmentNullifiers.insert(disclose(role.roleCommitment)); - return true; - } - - const contractAddress = account.right.bytes; _roleCommitmentNullifiers.insert(disclose(role.roleCommitment)); return true; } From e77d127e31e35a3369fc40b3872a9efb03b6d7ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:53:25 -0400 Subject: [PATCH 163/202] Update *.compact testing dependencies --- .../test/mocks/MockShieldedAccessControl.compact | 4 ---- .../test/simulators/ShieldedAccessControlSimulator.ts | 10 +--------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/contracts/src/access/test/mocks/MockShieldedAccessControl.compact b/contracts/src/access/test/mocks/MockShieldedAccessControl.compact index 78c36a96..361bab4a 100644 --- a/contracts/src/access/test/mocks/MockShieldedAccessControl.compact +++ b/contracts/src/access/test/mocks/MockShieldedAccessControl.compact @@ -30,10 +30,6 @@ export circuit _checkRole(roleId: Bytes<32>, account: Either, account: Bytes<32>): ShieldedAccessControl_Role { - return ShieldedAccessControl__checkMerkleTree(roleId, account); -} - export circuit getRoleAdmin(roleId: Bytes<32>): Bytes<32> { return ShieldedAccessControl_getRoleAdmin(roleId); } diff --git a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts index d194a5a3..38845ff1 100644 --- a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts +++ b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts @@ -57,7 +57,7 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< >; constructor( - initUser: Uint8Array, + initUser: Either, options: ShieldedAccessControlSimOptions = {}, ) { super(); @@ -227,14 +227,6 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< this.circuits.impure._checkRole(roleId, account); } - /** - * @description Throws if called by any account whose id hash `SHA256(pk, nonce)` does not match - * the stored owner commitment. Use this to only allow the owner to call specific circuits. - */ - public _checkMerkleTree(roleId: Uint8Array, account: Uint8Array): Role { - return this.circuits.impure._checkMerkleTree(roleId, account); - } - /** * @description Computes the owner commitment from the given `id` and `counter`. * @param id - The unique identifier of the owner calculated by `SHA256(pk, nonce)`. From 0c3361175f4ce402be39219aab94ae6ab6475d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:54:03 -0400 Subject: [PATCH 164/202] Add helper fn, update API for improved flexibility --- contracts/src/access/test/utils/address.ts | 37 +++++++++++++++------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/contracts/src/access/test/utils/address.ts b/contracts/src/access/test/utils/address.ts index fb22e4be..0cd6f6a2 100644 --- a/contracts/src/access/test/utils/address.ts +++ b/contracts/src/access/test/utils/address.ts @@ -64,23 +64,30 @@ export const createEitherTestContractAddress = (str: string) => ({ const baseGeneratePubKeyPair = ( str: string, asEither: boolean, + asPK: boolean ): [ - string, - ( - | Compact.ZswapCoinPublicKey - | Compact.Either - ), -] => { + string, + ( + | Compact.ZswapCoinPublicKey + | Compact.Either + ), + ] => { const pk = toHexPadded(str); - const zpk = asEither ? createEitherTestUser(str) : encodeToPK(str); - return [pk, zpk]; + + if (asEither && asPK) { + return [pk, createEitherTestUser(str)] + } else if (asEither && !asPK) { + return [pk, createEitherTestContractAddress(str)] + } + + return [pk, encodeToPK(str)]; }; export const generatePubKeyPair = (str: string) => - baseGeneratePubKeyPair(str, false) as [string, Compact.ZswapCoinPublicKey]; + baseGeneratePubKeyPair(str, false, false) as [string, Compact.ZswapCoinPublicKey]; -export const generateEitherPubKeyPair = (str: string) => - baseGeneratePubKeyPair(str, true) as [ +export const generateEitherPubKeyPair = (str: string, asPK = true) => + baseGeneratePubKeyPair(str, true, asPK) as [ string, Compact.Either, ]; @@ -99,3 +106,11 @@ export const ZERO_ADDRESS = { left: encodeToPK(''), right: { bytes: zeroUint8Array() }, }; + +export const eitherToBytes = (account: Compact.Either) => { + if (account.is_left) { + return account.left.bytes; + } + + return account.right.bytes; +} \ No newline at end of file From e3cb30db9827737f44c46259b3cb9e163339808d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:54:30 -0400 Subject: [PATCH 165/202] Use helper in witness impl --- .../access/witnesses/ShieldedAccessControlWitnesses.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts index 586dcb2e..fc7fb5ee 100644 --- a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts @@ -1,6 +1,7 @@ import { getRandomValues } from 'node:crypto'; import { CompactTypeVector, CompactTypeBytes, persistentHash, type WitnessContext, convert_bigint_to_Uint8Array } from '@midnight-ntwrk/compact-runtime'; -import type { Ledger, MerkleTreePath } from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; +import type { Ledger, MerkleTreePath, Either, ZswapCoinPublicKey, ContractAddress } from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; +import { eitherToBytes } from '../test/utils/address'; const MERKLE_TREE_DEPTH = 2 ** 10; @@ -36,12 +37,13 @@ export type ShieldedAccessControlPrivateState = { */ export const ShieldedAccessControlPrivateState = { /** - * @description Generates a new private state with a random secret nonce. + * @description Generates a new private state with a random secret nonce and a default roleId of 0. * @returns A fresh ShieldedAccessControlPrivateState instance. */ - generate: (account: Uint8Array): ShieldedAccessControlPrivateState => { + generate: (account: Either): ShieldedAccessControlPrivateState => { const defaultRoleId: string = Buffer.alloc(32).toString('hex'); - const privateState: ShieldedAccessControlPrivateState = { roles: {}, account }; + const bAccount = eitherToBytes(account); + const privateState: ShieldedAccessControlPrivateState = { roles: {}, account: bAccount }; privateState.roles[defaultRoleId] = getRandomValues(Buffer.alloc(32)); return privateState; }, From 95821e05340f7c451b4ab95cefa22f6e32fe449f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:54:42 -0400 Subject: [PATCH 166/202] Init tests --- .../access/test/ShieldedAccessControl.test.ts | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 contracts/src/access/test/ShieldedAccessControl.test.ts diff --git a/contracts/src/access/test/ShieldedAccessControl.test.ts b/contracts/src/access/test/ShieldedAccessControl.test.ts new file mode 100644 index 00000000..8a7eb8e0 --- /dev/null +++ b/contracts/src/access/test/ShieldedAccessControl.test.ts @@ -0,0 +1,90 @@ +import { + CompactTypeBytes, + CompactTypeVector, + convert_bigint_to_Uint8Array, + persistentHash, + transientHash, + upgradeFromTransient, +} from '@midnight-ntwrk/compact-runtime'; +import { beforeEach, describe, expect, it } from 'vitest'; +import type { ZswapCoinPublicKey, Either, ContractAddress } from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; +import { ShieldedAccessControlPrivateState } from '../witnesses/ShieldedAccessControlWitnesses.js'; +import { ShieldedAccessControlSimulator } from './simulators/ShieldedAccessControlSimulator.js'; +import * as utils from './utils/address.js'; + +// PKs +const [ADMIN, Z_ADMIN] = utils.generateEitherPubKeyPair('ADMIN'); +const [CUSTOM_ADMIN, Z_CUSTOM_ADMIN] = utils.generateEitherPubKeyPair('CUSTOM_ADMIN'); +const [OPERATOR_1, Z_OPERATOR_1] = utils.generateEitherPubKeyPair('OPERATOR_1'); +const [OPERATOR_CONTRACT, Z_OPERATOR_CONTRACT] = utils.generateEitherPubKeyPair('OPERATOR_CONTRACT', false); + +// Constants +const INSTANCE_SALT = new Uint8Array(32).fill(8675309); +const BAD_NONCE = Buffer.from(Buffer.alloc(32, 'BAD_NONCE')); +const DOMAIN = 'ShieldedAccessControl:shield:'; +const INIT_COUNTER = 0n; + +// Roles +const DEFAULT_ADMIN_ROLE = utils.zeroUint8Array(); +const OPERATOR_ROLE_1 = convert_bigint_to_Uint8Array(32, 1n); +const OPERATOR_ROLE_2 = convert_bigint_to_Uint8Array(32, 2n); +const OPERATOR_ROLE_3 = convert_bigint_to_Uint8Array(32, 3n); +const CUSTOM_ADMIN_ROLE = convert_bigint_to_Uint8Array(32, 4n); +const UNINITIALIZED_ROLE = convert_bigint_to_Uint8Array(32, 5n); + +const operatorTypes = [ + ['contract', Z_OPERATOR_CONTRACT], + ['pubkey', Z_OPERATOR_1], +] as const; + +// Role to string +const DEFAULT_ADMIN_ROLE_TO_STRING = Buffer.from(DEFAULT_ADMIN_ROLE).toString('hex'); + +let secretNonce: Uint8Array; +let shieldedAccessControl: ShieldedAccessControlSimulator; + +// Helpers +const buildCommitment = ( + roleId: Uint8Array, + account: Either, + nonce: Uint8Array, + index: bigint, +): Uint8Array => { + const rt_type = new CompactTypeVector(5, new CompactTypeBytes(32)); + const bAccount = utils.eitherToBytes(account); + const bIndex = convert_bigint_to_Uint8Array(32, index); + const bDomain = new TextEncoder().encode(DOMAIN); + + const commitment = persistentHash(rt_type, [ + roleId, + bAccount, + nonce, + bIndex, + bDomain, + ]); + + return commitment; +}; + +describe('ShieldedAccessControl', () => { + beforeEach(() => { + // Create private state object and generate nonce + const PS = ShieldedAccessControlPrivateState.generate(Z_ADMIN); + // Bind nonce for convenience + secretNonce = PS.roles[DEFAULT_ADMIN_ROLE_TO_STRING]; + // Prepare owner ID with gen nonce + // Deploy contract with derived owner commitment and PS + shieldedAccessControl = new ShieldedAccessControlSimulator(Z_ADMIN, { + privateState: PS, + }); + + describe('hasRole', () => { + it('should throw if caller is contract address', () => { + shieldedAccessControl.callerCtx.setCaller(OPERATOR_CONTRACT); + expect(() => { + shieldedAccessControl.hasRole(UNINITIALIZED_ROLE, Z_OPERATOR_CONTRACT) + }).toThrow('ShieldedAccessControl: contract address roles are not yet supported'); + }) + }) + }); +}); From 52000a2026328834b9a2c8290da7f25082be6650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 2 Sep 2025 15:44:03 -0400 Subject: [PATCH 167/202] Update simulator initialization --- .../src/access/test/simulators/ShieldedAccessControlSimulator.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts index 38845ff1..048bd107 100644 --- a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts +++ b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts @@ -77,7 +77,6 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< privateState, coinPK, address, - [], ); this.contractAddress = this.circuitContext.transactionContext.address; this._witnesses = witnesses; From 3af7b9a5bec6de807d62aa7d2780e9f7fcf6fe80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:44:28 -0400 Subject: [PATCH 168/202] Update hashing scheme --- .../src/access/witnesses/ShieldedAccessControlWitnesses.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts index fc7fb5ee..e13f6472 100644 --- a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts @@ -4,6 +4,7 @@ import type { Ledger, MerkleTreePath, Either, ZswapCoinPublicKey, ContractAddres import { eitherToBytes } from '../test/utils/address'; const MERKLE_TREE_DEPTH = 2 ** 10; +const DOMAIN = new TextEncoder().encode("ShieldedAccessControl:shield:"); /** * @description Interface defining the witness methods for ShieldedAccessControl operations. @@ -89,9 +90,9 @@ export const ShieldedAccessControlPrivateState = { const roleIdString = Buffer.from(roleId).toString('hex'); // Iterate over each MT to determine if commitment exists for (let i = 0; i < MERKLE_TREE_DEPTH; i++) { - const rt_type = new CompactTypeVector(4, new CompactTypeBytes(32)); + const rt_type = new CompactTypeVector(5, new CompactTypeBytes(32)); const bIndex = convert_bigint_to_Uint8Array(32, BigInt(i)); - const commitment = persistentHash(rt_type, [roleId, privateState.account, privateState.roles[roleIdString], bIndex]); + const commitment = persistentHash(rt_type, [roleId, privateState.account, privateState.roles[roleIdString], bIndex, DOMAIN]); try { ledger.ShieldedAccessControl__operatorRoles.pathForLeaf(BigInt(i), commitment); return BigInt(i); From 8d2306192f627ecd1dcc8cd3eac32a6b61061d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:45:06 -0400 Subject: [PATCH 169/202] Fix incorrect default MerkleTreePath construction --- .../src/access/witnesses/ShieldedAccessControlWitnesses.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts index e13f6472..302be0a4 100644 --- a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts @@ -79,8 +79,11 @@ export const ShieldedAccessControlPrivateState = { getRoleCommitmentPath: (ledger: Ledger, roleCommitment: Uint8Array): MerkleTreePath => { const path = ledger.ShieldedAccessControl__operatorRoles.findPathForLeaf(roleCommitment); const defaultPath: MerkleTreePath = { - leaf: Buffer.alloc(32), - path: [] + leaf: new Uint8Array(32), + path: Array.from({ length: 10 }, () => ({ + sibling: { field: 0n }, + goes_left: false, + })) } return path ? path : defaultPath; }, From 47f5d3ea31a40e4ec1af6a3dbb1e0aee556715a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:45:45 -0400 Subject: [PATCH 170/202] Improve typesafety of try catch block, add debugging logic --- .../witnesses/ShieldedAccessControlWitnesses.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts index 302be0a4..b4f1d7aa 100644 --- a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts @@ -99,13 +99,20 @@ export const ShieldedAccessControlPrivateState = { try { ledger.ShieldedAccessControl__operatorRoles.pathForLeaf(BigInt(i), commitment); return BigInt(i); - } catch (e) { - console.error(e); + } catch (e: unknown) { + if (e instanceof Error) { + const [msg, index] = e.message.split(":"); + if (msg === "invalid index into sparse merkle tree") { + // console.log(`${roleIdString} not found at index ${index}`); + } else { + throw e; + } + } } } // If commitment doesn't exist return currentMTIndex - // Used for adding roles or as a standard default + // Used for adding roles return ledger.ShieldedAccessControl__currentMerkleTreeIndex; }, }; From c607923fe33b9079ecf7bf1f309881f2a6b8fded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:46:21 -0400 Subject: [PATCH 171/202] Add initialization checks, correct role commitment checks --- .../access/test/ShieldedAccessControl.test.ts | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/contracts/src/access/test/ShieldedAccessControl.test.ts b/contracts/src/access/test/ShieldedAccessControl.test.ts index 8a7eb8e0..33839b50 100644 --- a/contracts/src/access/test/ShieldedAccessControl.test.ts +++ b/contracts/src/access/test/ShieldedAccessControl.test.ts @@ -23,6 +23,7 @@ const INSTANCE_SALT = new Uint8Array(32).fill(8675309); const BAD_NONCE = Buffer.from(Buffer.alloc(32, 'BAD_NONCE')); const DOMAIN = 'ShieldedAccessControl:shield:'; const INIT_COUNTER = 0n; +const EMPTY_ROOT = { field: 0n }; // Roles const DEFAULT_ADMIN_ROLE = utils.zeroUint8Array(); @@ -77,14 +78,36 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl = new ShieldedAccessControlSimulator(Z_ADMIN, { privateState: PS, }); + }); + + describe('initialization checks', () => { + it('DEFAULT_ADMIN_ROLE should be 0', () => { + expect(shieldedAccessControl.getPublicState().ShieldedAccessControl_DEFAULT_ADMIN_ROLE).toEqual(DEFAULT_ADMIN_ROLE); + }); - describe('hasRole', () => { - it('should throw if caller is contract address', () => { - shieldedAccessControl.callerCtx.setCaller(OPERATOR_CONTRACT); - expect(() => { - shieldedAccessControl.hasRole(UNINITIALIZED_ROLE, Z_OPERATOR_CONTRACT) - }).toThrow('ShieldedAccessControl: contract address roles are not yet supported'); - }) - }) + it('Merkle tree root should be 0', () => { + expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.root()).toEqual(EMPTY_ROOT); + }); }); + + describe('hasRole', () => { + it('should throw if caller is contract address', () => { + shieldedAccessControl.callerCtx.setCaller(OPERATOR_CONTRACT); + expect(() => { + shieldedAccessControl.hasRole(UNINITIALIZED_ROLE, Z_OPERATOR_CONTRACT) + }).toThrow('ShieldedAccessControl: contract address roles are not yet supported'); + }); + + it('should return correct role commitment', () => { + const expCommitment = buildCommitment( + DEFAULT_ADMIN_ROLE, + Z_ADMIN, + secretNonce, + INIT_COUNTER, + ); + + const role = shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + expect(role.roleCommitment).toEqual(expCommitment); + }); + }) }); From 4140f3b249634e487bd8754218e290c90bb064ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Thu, 4 Sep 2025 18:23:29 -0400 Subject: [PATCH 172/202] Use correct MT API --- contracts/src/access/ShieldedAccessControl.compact | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/access/ShieldedAccessControl.compact b/contracts/src/access/ShieldedAccessControl.compact index 0d2c266b..01151441 100644 --- a/contracts/src/access/ShieldedAccessControl.compact +++ b/contracts/src/access/ShieldedAccessControl.compact @@ -436,7 +436,7 @@ module ShieldedAccessControl { } // Use ledger index as source of truth - _operatorRoles.insertHashIndex(disclose(role.roleCommitment), _currentMerkleTreeIndex); + _operatorRoles.insertIndex(disclose(role.roleCommitment), _currentMerkleTreeIndex); _currentMerkleTreeIndex.increment(1); return true; } From 67f36aa660a3f7a1f8f17c1407d484a34aed3334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Thu, 4 Sep 2025 19:38:05 -0400 Subject: [PATCH 173/202] Add utility fn and improve logging --- .../witnesses/ShieldedAccessControlWitnesses.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts index b4f1d7aa..bda581a0 100644 --- a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts @@ -6,6 +6,15 @@ import { eitherToBytes } from '../test/utils/address'; const MERKLE_TREE_DEPTH = 2 ** 10; const DOMAIN = new TextEncoder().encode("ShieldedAccessControl:shield:"); +function fmtHexString(bytes: String | Uint8Array): string { + if (bytes instanceof String) { + return `${bytes.slice(0, 4)}...${bytes.slice(-4)}` + } else { + const buffStr = Buffer.from(bytes).toString('hex'); + return `${buffStr.slice(0, 4)}...${buffStr.slice(-4)}`; + } +} + /** * @description Interface defining the witness methods for ShieldedAccessControl operations. * @template P - The private state type. @@ -103,7 +112,7 @@ export const ShieldedAccessControlPrivateState = { if (e instanceof Error) { const [msg, index] = e.message.split(":"); if (msg === "invalid index into sparse merkle tree") { - // console.log(`${roleIdString} not found at index ${index}`); + // console.log(`role ${fmtHexString(roleIdString)} with commitment ${fmtHexString(commitment)} not found at index ${index}`); } else { throw e; } From 99fa0be551da2b80d2e27f77c3654ea93558777f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Thu, 4 Sep 2025 19:39:04 -0400 Subject: [PATCH 174/202] add test --- .../access/test/ShieldedAccessControl.test.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/contracts/src/access/test/ShieldedAccessControl.test.ts b/contracts/src/access/test/ShieldedAccessControl.test.ts index 33839b50..d4103fcc 100644 --- a/contracts/src/access/test/ShieldedAccessControl.test.ts +++ b/contracts/src/access/test/ShieldedAccessControl.test.ts @@ -80,17 +80,11 @@ describe('ShieldedAccessControl', () => { }); }); - describe('initialization checks', () => { - it('DEFAULT_ADMIN_ROLE should be 0', () => { - expect(shieldedAccessControl.getPublicState().ShieldedAccessControl_DEFAULT_ADMIN_ROLE).toEqual(DEFAULT_ADMIN_ROLE); - }); - - it('Merkle tree root should be 0', () => { - expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.root()).toEqual(EMPTY_ROOT); + describe('hasRole', () => { + beforeEach(() => { + shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); }); - }); - describe('hasRole', () => { it('should throw if caller is contract address', () => { shieldedAccessControl.callerCtx.setCaller(OPERATOR_CONTRACT); expect(() => { @@ -109,5 +103,10 @@ describe('ShieldedAccessControl', () => { const role = shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); expect(role.roleCommitment).toEqual(expCommitment); }); + + it('should return true when admin has role', () => { + const role = shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + expect(role.hasRole).toEqual(true); + }); }) }); From 4b1ff86846898daf4f846cf3f5362681ac422164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:53:51 -0400 Subject: [PATCH 175/202] Fix typo in filename --- .../utils/{SimualatorStateManager.ts => SimulatorStateManager.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename contracts/src/access/test/utils/{SimualatorStateManager.ts => SimulatorStateManager.ts} (100%) diff --git a/contracts/src/access/test/utils/SimualatorStateManager.ts b/contracts/src/access/test/utils/SimulatorStateManager.ts similarity index 100% rename from contracts/src/access/test/utils/SimualatorStateManager.ts rename to contracts/src/access/test/utils/SimulatorStateManager.ts From 9a735b273ec91801682a61368cb144e4faadca3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:54:08 -0400 Subject: [PATCH 176/202] Update imports --- contracts/src/access/test/simulators/ZOwnablePKSimulator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/access/test/simulators/ZOwnablePKSimulator.ts b/contracts/src/access/test/simulators/ZOwnablePKSimulator.ts index e6e3f62b..adfb67ce 100644 --- a/contracts/src/access/test/simulators/ZOwnablePKSimulator.ts +++ b/contracts/src/access/test/simulators/ZOwnablePKSimulator.ts @@ -23,7 +23,7 @@ import type { SimulatorOptions, } from '../types/test.js'; import { AbstractContractSimulator } from '../utils/AbstractContractSimulator.js'; -import { SimulatorStateManager } from '../utils/SimualatorStateManager.js'; +import { SimulatorStateManager } from '../utils/SimulatorStateManager.js'; type OwnableSimOptions = SimulatorOptions< ZOwnablePKPrivateState, From 2f375bac513b12ef5bb926b23a6288fecd7c5e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:54:51 -0400 Subject: [PATCH 177/202] Update witness fn signatures --- .../witnesses/ShieldedAccessControlWitnesses.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts index bda581a0..7de12adb 100644 --- a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts @@ -6,7 +6,7 @@ import { eitherToBytes } from '../test/utils/address'; const MERKLE_TREE_DEPTH = 2 ** 10; const DOMAIN = new TextEncoder().encode("ShieldedAccessControl:shield:"); -function fmtHexString(bytes: String | Uint8Array): string { +export function fmtHexString(bytes: String | Uint8Array): string { if (bytes instanceof String) { return `${bytes.slice(0, 4)}...${bytes.slice(-4)}` } else { @@ -39,7 +39,7 @@ type SecretNonce = Uint8Array; export type ShieldedAccessControlPrivateState = { /** @description A 32-byte secret nonce used as a privacy additive. */ roles: Record, - account: Uint8Array + account: Either }; /** @@ -52,8 +52,7 @@ export const ShieldedAccessControlPrivateState = { */ generate: (account: Either): ShieldedAccessControlPrivateState => { const defaultRoleId: string = Buffer.alloc(32).toString('hex'); - const bAccount = eitherToBytes(account); - const privateState: ShieldedAccessControlPrivateState = { roles: {}, account: bAccount }; + const privateState: ShieldedAccessControlPrivateState = { roles: {}, account }; privateState.roles[defaultRoleId] = getRandomValues(Buffer.alloc(32)); return privateState; }, @@ -72,7 +71,7 @@ export const ShieldedAccessControlPrivateState = { * const privateState = ShieldedAccessControlPrivateState.withNonce(deterministicNonce); * ``` */ - withRoleAndNonce: (account: Uint8Array, roleId: Buffer, nonce: Buffer): ShieldedAccessControlPrivateState => { + withRoleAndNonce: (account: Either, roleId: Buffer, nonce: Buffer): ShieldedAccessControlPrivateState => { const roleString = roleId.toString('hex'); const privateState: ShieldedAccessControlPrivateState = { roles: {}, account }; privateState.roles[roleString] = nonce; @@ -104,7 +103,9 @@ export const ShieldedAccessControlPrivateState = { for (let i = 0; i < MERKLE_TREE_DEPTH; i++) { const rt_type = new CompactTypeVector(5, new CompactTypeBytes(32)); const bIndex = convert_bigint_to_Uint8Array(32, BigInt(i)); - const commitment = persistentHash(rt_type, [roleId, privateState.account, privateState.roles[roleIdString], bIndex, DOMAIN]); + const bAccount = eitherToBytes(privateState.account); + const bNonce = privateState.roles[roleIdString]; + const commitment = persistentHash(rt_type, [roleId, bAccount, bNonce, bIndex, DOMAIN]); try { ledger.ShieldedAccessControl__operatorRoles.pathForLeaf(BigInt(i), commitment); return BigInt(i); From a5a176309aa0460af1685374318673dacf2bfc73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:57:11 -0400 Subject: [PATCH 178/202] Update constructor, witnesses setter --- .../access/test/simulators/ShieldedAccessControlSimulator.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts index 048bd107..6666df91 100644 --- a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts +++ b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts @@ -24,7 +24,7 @@ import type { SimulatorOptions, } from '../types/test.js'; import { AbstractContractSimulator } from '../utils/AbstractContractSimulator.js'; -import { SimulatorStateManager } from '../utils/SimualatorStateManager.js'; +import { SimulatorStateManager } from '../utils/SimulatorStateManager.js'; type ShieldedAccessControlSimOptions = SimulatorOptions< ShieldedAccessControlPrivateState, @@ -64,7 +64,7 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< // Setup initial state const { - privateState = ShieldedAccessControlPrivateState.generate(initUser), + privateState = options.privateState ? options.privateState : ShieldedAccessControlPrivateState.generate(initUser), witnesses = ShieldedAccessControlWitnesses(), coinPK = '0'.repeat(64), address = sampleContractAddress(), @@ -185,6 +185,7 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< } public set witnesses(newWitnesses: ReturnType) { + this.resetCircuitProxies(); this._witnesses = newWitnesses; this.contract = new MockShieldedAccessControl(this._witnesses); } From 353b37939c421392adbd1c4b36724b7c8593277e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:57:41 -0400 Subject: [PATCH 179/202] Add bad index tests --- .../access/test/ShieldedAccessControl.test.ts | 52 +++++++++++++++---- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/contracts/src/access/test/ShieldedAccessControl.test.ts b/contracts/src/access/test/ShieldedAccessControl.test.ts index d4103fcc..34051905 100644 --- a/contracts/src/access/test/ShieldedAccessControl.test.ts +++ b/contracts/src/access/test/ShieldedAccessControl.test.ts @@ -3,11 +3,10 @@ import { CompactTypeVector, convert_bigint_to_Uint8Array, persistentHash, - transientHash, - upgradeFromTransient, + WitnessContext, } from '@midnight-ntwrk/compact-runtime'; import { beforeEach, describe, expect, it } from 'vitest'; -import type { ZswapCoinPublicKey, Either, ContractAddress } from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; +import type { ZswapCoinPublicKey, Either, ContractAddress, Ledger } from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; import { ShieldedAccessControlPrivateState } from '../witnesses/ShieldedAccessControlWitnesses.js'; import { ShieldedAccessControlSimulator } from './simulators/ShieldedAccessControlSimulator.js'; import * as utils from './utils/address.js'; @@ -20,7 +19,7 @@ const [OPERATOR_CONTRACT, Z_OPERATOR_CONTRACT] = utils.generateEitherPubKeyPair( // Constants const INSTANCE_SALT = new Uint8Array(32).fill(8675309); -const BAD_NONCE = Buffer.from(Buffer.alloc(32, 'BAD_NONCE')); +const BAD_NONCE = Buffer.alloc(32, 'BAD_NONCE'); const DOMAIN = 'ShieldedAccessControl:shield:'; const INIT_COUNTER = 0n; const EMPTY_ROOT = { field: 0n }; @@ -41,7 +40,7 @@ const operatorTypes = [ // Role to string const DEFAULT_ADMIN_ROLE_TO_STRING = Buffer.from(DEFAULT_ADMIN_ROLE).toString('hex'); -let secretNonce: Uint8Array; +const secretNonce = Buffer.alloc(32, "secretNonce"); let shieldedAccessControl: ShieldedAccessControlSimulator; // Helpers @@ -67,19 +66,50 @@ const buildCommitment = ( return commitment; }; +function RETURN_BAD_INDEX(context: WitnessContext, roleId: Uint8Array): [ShieldedAccessControlPrivateState, bigint] { + console.log("WIT RETURN BAD INDEX"); + return [context.privateState, 1023n] +} + describe('ShieldedAccessControl', () => { beforeEach(() => { // Create private state object and generate nonce - const PS = ShieldedAccessControlPrivateState.generate(Z_ADMIN); - // Bind nonce for convenience - secretNonce = PS.roles[DEFAULT_ADMIN_ROLE_TO_STRING]; - // Prepare owner ID with gen nonce - // Deploy contract with derived owner commitment and PS + const PS = ShieldedAccessControlPrivateState.withRoleAndNonce(Z_ADMIN, Buffer.from(DEFAULT_ADMIN_ROLE), secretNonce); + // Init contract for user with PS shieldedAccessControl = new ShieldedAccessControlSimulator(Z_ADMIN, { privateState: PS, }); }); + describe.only('should fail when incorrect witness values provided', () => { + beforeEach(() => { + shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + }); + + type FailingCircuits = [method: keyof ShieldedAccessControlSimulator, args: unknown[]]; + const protectedCircuits: FailingCircuits[] = [ + ['assertOnlyRole', [DEFAULT_ADMIN_ROLE]], + ['_checkRole', [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ]; + + it.each(protectedCircuits)('%s should fail with bad nonce', (circuitName, args) => { + shieldedAccessControl.privateState.injectSecretNonce(Buffer.from(DEFAULT_ADMIN_ROLE), BAD_NONCE); + + expect(() => { + (shieldedAccessControl[circuitName] as (...args: unknown[]) => unknown)(...args); + }).toThrow('ShieldedAccessControl: unauthorized account'); + }); + + it.each(protectedCircuits)('%s should fail with bad index', (circuitName, args) => { + shieldedAccessControl.overrideWitness("wit_getRoleIndex", RETURN_BAD_INDEX); + expect(() => { + (shieldedAccessControl[circuitName] as (...args: unknown[]) => unknown)(...args); + }).toThrow('ShieldedAccessControl: unauthorized account'); + }); + }); + describe('hasRole', () => { beforeEach(() => { shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); @@ -108,5 +138,7 @@ describe('ShieldedAccessControl', () => { const role = shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); expect(role.hasRole).toEqual(true); }); + + }) }); From 26d357634da5a188b5c203c4717dd624c95b156b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Mon, 8 Sep 2025 18:22:10 -0400 Subject: [PATCH 180/202] Update Role field name --- contracts/src/access/ShieldedAccessControl.compact | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/src/access/ShieldedAccessControl.compact b/contracts/src/access/ShieldedAccessControl.compact index 01151441..116af121 100644 --- a/contracts/src/access/ShieldedAccessControl.compact +++ b/contracts/src/access/ShieldedAccessControl.compact @@ -119,7 +119,7 @@ module ShieldedAccessControl { witness wit_getRoleIndex(roleId: Bytes<32>): Uint<64>; export struct Role { - hasRole: Boolean; + isApproved: Boolean; roleCommitment: Bytes<32>; } @@ -156,12 +156,12 @@ module ShieldedAccessControl { const nonce = wit_secretNonce(roleId); const index = wit_getRoleIndex(roleId); const roleCommitment = persistentHash>>([roleId, account.left.bytes, nonce, index as Field as Bytes<32>, pad(32, "ShieldedAccessControl:shield:")]); - assert(!_roleCommitmentNullifiers.member(disclose(roleCommitment)), "ShieldedAccessControl: role commitment access revoked"); + assert(!_roleCommitmentNullifiers.member(disclose(roleCommitment)), "ShieldedAccessControl: role access has been revoked"); const authPath = wit_getRoleCommitmentPath(roleCommitment); - const hasRole = _operatorRoles + const isApproved = _operatorRoles .checkRoot(merkleTreePathRoot<10, Bytes<32>>(disclose(authPath))); - return Role {hasRole, disclose(roleCommitment)}; + return Role {isApproved, disclose(roleCommitment)}; } /** @@ -226,7 +226,7 @@ module ShieldedAccessControl { */ export circuit _checkRole(roleId: Bytes<32>, account: Either): [] { const role = hasRole(roleId, account); - assert(role.hasRole, "ShieldedAccessControl: unauthorized account"); + assert(role.isApproved, "ShieldedAccessControl: unauthorized account"); } /** @@ -431,7 +431,7 @@ module ShieldedAccessControl { */ export circuit _unsafeGrantRole(roleId: Bytes<32>, account: Either): Boolean { const role = hasRole(roleId, account); - if (role.hasRole) { + if (role.isApproved) { return false; } @@ -471,7 +471,7 @@ module ShieldedAccessControl { */ export circuit _revokeRole(roleId: Bytes<32>, account: Either): Boolean { const role = hasRole(roleId, account); - if (!role.hasRole) { + if (!role.isApproved) { return false; } From da274c429ac1c88eb8c096c77775a6a32727b07a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Mon, 8 Sep 2025 18:22:43 -0400 Subject: [PATCH 181/202] Add tests --- .../access/test/ShieldedAccessControl.test.ts | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/contracts/src/access/test/ShieldedAccessControl.test.ts b/contracts/src/access/test/ShieldedAccessControl.test.ts index 34051905..f95381a9 100644 --- a/contracts/src/access/test/ShieldedAccessControl.test.ts +++ b/contracts/src/access/test/ShieldedAccessControl.test.ts @@ -6,19 +6,19 @@ import { WitnessContext, } from '@midnight-ntwrk/compact-runtime'; import { beforeEach, describe, expect, it } from 'vitest'; -import type { ZswapCoinPublicKey, Either, ContractAddress, Ledger } from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; +import type { ZswapCoinPublicKey, Either, ContractAddress, Ledger, MerkleTreePath } from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; import { ShieldedAccessControlPrivateState } from '../witnesses/ShieldedAccessControlWitnesses.js'; import { ShieldedAccessControlSimulator } from './simulators/ShieldedAccessControlSimulator.js'; import * as utils from './utils/address.js'; // PKs const [ADMIN, Z_ADMIN] = utils.generateEitherPubKeyPair('ADMIN'); +const [UNAUTHORIZED, Z_UNAUTHORIZED] = utils.generateEitherPubKeyPair('UNAUTHORIZED'); const [CUSTOM_ADMIN, Z_CUSTOM_ADMIN] = utils.generateEitherPubKeyPair('CUSTOM_ADMIN'); const [OPERATOR_1, Z_OPERATOR_1] = utils.generateEitherPubKeyPair('OPERATOR_1'); const [OPERATOR_CONTRACT, Z_OPERATOR_CONTRACT] = utils.generateEitherPubKeyPair('OPERATOR_CONTRACT', false); // Constants -const INSTANCE_SALT = new Uint8Array(32).fill(8675309); const BAD_NONCE = Buffer.alloc(32, 'BAD_NONCE'); const DOMAIN = 'ShieldedAccessControl:shield:'; const INIT_COUNTER = 0n; @@ -67,10 +67,20 @@ const buildCommitment = ( }; function RETURN_BAD_INDEX(context: WitnessContext, roleId: Uint8Array): [ShieldedAccessControlPrivateState, bigint] { - console.log("WIT RETURN BAD INDEX"); return [context.privateState, 1023n] } +function RETURN_BAD_PATH(context: WitnessContext, roleCommitment: Uint8Array): [ShieldedAccessControlPrivateState, MerkleTreePath] { + const defaultPath: MerkleTreePath = { + leaf: new Uint8Array(32), + path: Array.from({ length: 10 }, () => ({ + sibling: { field: 0n }, + goes_left: false, + })) + }; + return [context.privateState, defaultPath]; +} + describe('ShieldedAccessControl', () => { beforeEach(() => { // Create private state object and generate nonce @@ -81,7 +91,7 @@ describe('ShieldedAccessControl', () => { }); }); - describe.only('should fail when incorrect witness values provided', () => { + describe('should fail with bad witness values', () => { beforeEach(() => { shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); }); @@ -108,6 +118,13 @@ describe('ShieldedAccessControl', () => { (shieldedAccessControl[circuitName] as (...args: unknown[]) => unknown)(...args); }).toThrow('ShieldedAccessControl: unauthorized account'); }); + + it.each(protectedCircuits)('%s should fail with bad role path', (circuitName, args) => { + shieldedAccessControl.overrideWitness("wit_getRoleCommitmentPath", RETURN_BAD_PATH); + expect(() => { + (shieldedAccessControl[circuitName] as (...args: unknown[]) => unknown)(...args); + }).toThrow('ShieldedAccessControl: unauthorized account'); + }); }); describe('hasRole', () => { @@ -136,9 +153,25 @@ describe('ShieldedAccessControl', () => { it('should return true when admin has role', () => { const role = shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); - expect(role.hasRole).toEqual(true); + expect(role.isApproved).toEqual(true); }); + it('should return false when unauthorized', () => { + const role = shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_UNAUTHORIZED); + expect(role.isApproved).toEqual(false); + }) + + it('should return false when role does not exist', () => { + shieldedAccessControl.privateState.injectSecretNonce(Buffer.from(UNINITIALIZED_ROLE), Buffer.alloc(32)); + const role = shieldedAccessControl.hasRole(UNINITIALIZED_ROLE, Z_UNAUTHORIZED); + expect(role.isApproved).toBe(false); + }); + it('should fail when role access has been revoked', () => { + shieldedAccessControl._revokeRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + expect(() => { + shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + }).toThrow("ShieldedAccessControl: role access has been revoked"); + }); }) }); From 7ae6407b606c15ab2b16f1bf38ed8cd79dcdb624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Mon, 8 Sep 2025 19:39:23 -0400 Subject: [PATCH 182/202] Remove _unsafeGrantRole --- .../src/access/ShieldedAccessControl.compact | 35 ------------------- .../mocks/MockShieldedAccessControl.compact | 4 --- .../ShieldedAccessControlSimulator.ts | 9 ----- 3 files changed, 48 deletions(-) diff --git a/contracts/src/access/ShieldedAccessControl.compact b/contracts/src/access/ShieldedAccessControl.compact index 116af121..17a9f746 100644 --- a/contracts/src/access/ShieldedAccessControl.compact +++ b/contracts/src/access/ShieldedAccessControl.compact @@ -395,41 +395,6 @@ module ShieldedAccessControl { * @return {Boolean} roleGranted - A boolean indicating if `roleId` was granted. */ export circuit _grantRole(roleId: Bytes<32>, account: Either): Boolean { - return _unsafeGrantRole(roleId, account); - } - - /** - * @description Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted. - * Internal circuit without access restriction. It does NOT check if the role is granted to a ContractAddress. - * - * @circuitInfo k=17, rows=109162 - * - * @notice External smart contracts cannot call the token contract at this time, so granting a role to an ContractAddress may - * render a circuit permanently inaccessible. - * - * Requirements: - * - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) - * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce) - * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256(roleId | account | nonce) must - * exist at `index` in the `_operatorRoles` Merkle tree. - * - * Disclosures: - * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256(roleId | account | nonce). - * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` - * Merkle tree. - * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. - * - * @param {Bytes<32>} roleId - The role identifier. - * @param {Either} account - A ZswapCoinPublicKey or ContractAddress. - * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) - * @return {Boolean} roleGranted - A boolean indicating if `role` was granted. - */ - export circuit _unsafeGrantRole(roleId: Bytes<32>, account: Either): Boolean { const role = hasRole(roleId, account); if (role.isApproved) { return false; diff --git a/contracts/src/access/test/mocks/MockShieldedAccessControl.compact b/contracts/src/access/test/mocks/MockShieldedAccessControl.compact index 361bab4a..12c4faa3 100644 --- a/contracts/src/access/test/mocks/MockShieldedAccessControl.compact +++ b/contracts/src/access/test/mocks/MockShieldedAccessControl.compact @@ -54,10 +54,6 @@ export circuit _grantRole(roleId: Bytes<32>, account: Either, account: Either): Boolean { - return ShieldedAccessControl__unsafeGrantRole(roleId, account); -} - export circuit _revokeRole(roleId: Bytes<32>, account: Either): Boolean { return ShieldedAccessControl__revokeRole(roleId, account); } \ No newline at end of file diff --git a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts index 6666df91..2c22cbe6 100644 --- a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts +++ b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts @@ -285,15 +285,6 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< return this.circuits.impure._grantRole(roleId, account); } - /** - * @description Transfers ownership to owner id `newOwnerId` without - * enforcing permission checks on the caller. - * @param newOwnerId - The unique identifier of the new owner calculated by `SHA256(pk, nonce)`. - */ - public _unsafeGrantRole(roleId: Uint8Array, account: Either): Boolean { - return this.circuits.impure._unsafeGrantRole(roleId, account); - } - /** * @description Transfers ownership to owner id `newOwnerId` without * enforcing permission checks on the caller. From 5a1b0b4e839fbb6a0a4fb1f4cb2ac5eb41abbb31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Mon, 8 Sep 2025 19:54:46 -0400 Subject: [PATCH 183/202] Improve tests --- .../access/test/ShieldedAccessControl.test.ts | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/contracts/src/access/test/ShieldedAccessControl.test.ts b/contracts/src/access/test/ShieldedAccessControl.test.ts index f95381a9..44c685e2 100644 --- a/contracts/src/access/test/ShieldedAccessControl.test.ts +++ b/contracts/src/access/test/ShieldedAccessControl.test.ts @@ -94,6 +94,7 @@ describe('ShieldedAccessControl', () => { describe('should fail with bad witness values', () => { beforeEach(() => { shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + shieldedAccessControl.callerCtx.setCaller(ADMIN); }); type FailingCircuits = [method: keyof ShieldedAccessControlSimulator, args: unknown[]]; @@ -107,20 +108,44 @@ describe('ShieldedAccessControl', () => { it.each(protectedCircuits)('%s should fail with bad nonce', (circuitName, args) => { shieldedAccessControl.privateState.injectSecretNonce(Buffer.from(DEFAULT_ADMIN_ROLE), BAD_NONCE); + // Check nonce does not match + expect(shieldedAccessControl.privateState.getCurrentSecretNonce(Buffer.from(DEFAULT_ADMIN_ROLE))).not.toEqual( + secretNonce, + ); expect(() => { (shieldedAccessControl[circuitName] as (...args: unknown[]) => unknown)(...args); }).toThrow('ShieldedAccessControl: unauthorized account'); }); it.each(protectedCircuits)('%s should fail with bad index', (circuitName, args) => { + const [, trueIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); shieldedAccessControl.overrideWitness("wit_getRoleIndex", RETURN_BAD_INDEX); + const [, badIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); + + // Check index does not match + expect(trueIndex).not.toBe( + badIndex + ); expect(() => { (shieldedAccessControl[circuitName] as (...args: unknown[]) => unknown)(...args); }).toThrow('ShieldedAccessControl: unauthorized account'); }); it.each(protectedCircuits)('%s should fail with bad role path', (circuitName, args) => { + const expCommitment = buildCommitment( + DEFAULT_ADMIN_ROLE, + Z_ADMIN, + secretNonce, + INIT_COUNTER, + ); + const [, truePath] = shieldedAccessControl.witnesses.wit_getRoleCommitmentPath(shieldedAccessControl.getWitnessContext(), expCommitment); shieldedAccessControl.overrideWitness("wit_getRoleCommitmentPath", RETURN_BAD_PATH); + const [, badPath] = shieldedAccessControl.witnesses.wit_getRoleCommitmentPath(shieldedAccessControl.getWitnessContext(), expCommitment); + + // Check path does not match + expect(truePath).not.toEqual( + badPath + ); expect(() => { (shieldedAccessControl[circuitName] as (...args: unknown[]) => unknown)(...args); }).toThrow('ShieldedAccessControl: unauthorized account'); @@ -173,5 +198,25 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); }).toThrow("ShieldedAccessControl: role access has been revoked"); }); - }) + }); + + describe('assertOnlyRole', () => { + beforeEach(() => { + shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + shieldedAccessControl.callerCtx.setCaller(ADMIN); + }); + + it('should allow authorized caller with correct nonce to call', () => { + expect(() => + shieldedAccessControl.assertOnlyRole(DEFAULT_ADMIN_ROLE), + ).not.toThrow(); + }); + + it('should throw if caller is unauthorized', () => { + shieldedAccessControl.callerCtx.setCaller(UNAUTHORIZED); + expect(() => + shieldedAccessControl.assertOnlyRole(DEFAULT_ADMIN_ROLE), + ).toThrow('ShieldedAccessControl: unauthorized account'); + }); + }); }); From 923d779862031bd510d8ce555024886074d58e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Mon, 8 Sep 2025 19:54:54 -0400 Subject: [PATCH 184/202] Add helper method --- .../test/simulators/ShieldedAccessControlSimulator.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts index 2c22cbe6..40344d2f 100644 --- a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts +++ b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts @@ -2,6 +2,7 @@ import { type CircuitContext, type CoinPublicKey, emptyZswapLocalState, + WitnessContext, } from '@midnight-ntwrk/compact-runtime'; import { sampleContractAddress } from '@midnight-ntwrk/zswap'; import { @@ -95,6 +96,14 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< return ledger(this.circuitContext.transactionContext.state); } + getWitnessContext(): WitnessContext { + return { + ledger: this.getPublicState(), + privateState: this.getPrivateState(), + contractAddress: this.contractAddress + } + } + /** * @description Constructs a caller-specific circuit context. * If a caller override is present, it replaces the current Zswap local state with an empty one From 07dd40bdb4c86773b8c90552b750f14bb53f31d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 9 Sep 2025 19:18:51 -0400 Subject: [PATCH 185/202] Change privateState fn signatures --- .../test/simulators/ShieldedAccessControlSimulator.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts index 40344d2f..707f0bc7 100644 --- a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts +++ b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts @@ -310,12 +310,12 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< * @returns The ZOwnablePK private state after setting the new nonce. */ injectSecretNonce: ( - roleId: Buffer, + roleId: Uint8Array, newNonce: Buffer, ): ShieldedAccessControlPrivateState => { const currentState = this.stateManager.getContext().currentPrivateState; const updatedState = { ...currentState, roles: { ...currentState.roles } } - const roleString = roleId.toString('hex'); + const roleString = Buffer.from(roleId).toString('hex'); updatedState.roles[roleString] = newNonce; this.stateManager.updatePrivateState(updatedState); return updatedState; @@ -325,8 +325,8 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< * @description Returns the secret nonce given the context. * @returns The secret nonce. */ - getCurrentSecretNonce: (roleId: Buffer): Uint8Array => { - const roleString = roleId.toString('hex'); + getCurrentSecretNonce: (roleId: Uint8Array): Uint8Array => { + const roleString = Buffer.from(roleId).toString('hex'); return this.stateManager.getContext().currentPrivateState.roles[roleString]; }, }; From fdcf13cc0717cc0f89cc64b2f8600f3ad596eba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 9 Sep 2025 19:18:59 -0400 Subject: [PATCH 186/202] Add tests --- .../access/test/ShieldedAccessControl.test.ts | 368 +++++++++++++++--- 1 file changed, 314 insertions(+), 54 deletions(-) diff --git a/contracts/src/access/test/ShieldedAccessControl.test.ts b/contracts/src/access/test/ShieldedAccessControl.test.ts index 44c685e2..29af7e81 100644 --- a/contracts/src/access/test/ShieldedAccessControl.test.ts +++ b/contracts/src/access/test/ShieldedAccessControl.test.ts @@ -6,7 +6,7 @@ import { WitnessContext, } from '@midnight-ntwrk/compact-runtime'; import { beforeEach, describe, expect, it } from 'vitest'; -import type { ZswapCoinPublicKey, Either, ContractAddress, Ledger, MerkleTreePath } from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; +import type { ZswapCoinPublicKey, Either, ContractAddress, Ledger, MerkleTreePath, ShieldedAccessControl_Role as Role } from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; import { ShieldedAccessControlPrivateState } from '../witnesses/ShieldedAccessControlWitnesses.js'; import { ShieldedAccessControlSimulator } from './simulators/ShieldedAccessControlSimulator.js'; import * as utils from './utils/address.js'; @@ -22,6 +22,7 @@ const [OPERATOR_CONTRACT, Z_OPERATOR_CONTRACT] = utils.generateEitherPubKeyPair( const BAD_NONCE = Buffer.alloc(32, 'BAD_NONCE'); const DOMAIN = 'ShieldedAccessControl:shield:'; const INIT_COUNTER = 0n; + const EMPTY_ROOT = { field: 0n }; // Roles @@ -40,7 +41,10 @@ const operatorTypes = [ // Role to string const DEFAULT_ADMIN_ROLE_TO_STRING = Buffer.from(DEFAULT_ADMIN_ROLE).toString('hex'); -const secretNonce = Buffer.alloc(32, "secretNonce"); +const ADMIN_SECRET_NONCE = Buffer.alloc(32, "ADMIN_SECRET_NONCE"); +const OPERATOR_ROLE_1_SECRET_NONCE = Buffer.alloc(32, "OPERATOR_ROLE_1_SECRET_NONCE"); +const OPERATOR_ROLE_2_SECRET_NONCE = Buffer.alloc(32, "OPERATOR_ROLE_2_SECRET_NONCE"); +const OPERATOR_ROLE_3_SECRET_NONCE = Buffer.alloc(32, "OPERATOR_ROLE_3_SECRET_NONCE"); let shieldedAccessControl: ShieldedAccessControlSimulator; // Helpers @@ -66,6 +70,13 @@ const buildCommitment = ( return commitment; }; +const EXP_DEFAULT_ADMIN_COMMITMENT = buildCommitment( + DEFAULT_ADMIN_ROLE, + Z_ADMIN, + ADMIN_SECRET_NONCE, + INIT_COUNTER, +); + function RETURN_BAD_INDEX(context: WitnessContext, roleId: Uint8Array): [ShieldedAccessControlPrivateState, bigint] { return [context.privateState, 1023n] } @@ -81,74 +92,197 @@ function RETURN_BAD_PATH(context: WitnessContext { beforeEach(() => { // Create private state object and generate nonce - const PS = ShieldedAccessControlPrivateState.withRoleAndNonce(Z_ADMIN, Buffer.from(DEFAULT_ADMIN_ROLE), secretNonce); + const PS = ShieldedAccessControlPrivateState.withRoleAndNonce(Z_ADMIN, Buffer.from(DEFAULT_ADMIN_ROLE), ADMIN_SECRET_NONCE); // Init contract for user with PS shieldedAccessControl = new ShieldedAccessControlSimulator(Z_ADMIN, { privateState: PS, }); }); - describe('should fail with bad witness values', () => { + describe('checked circuits should fail for authorized caller with invalid witness values', () => { beforeEach(() => { shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); shieldedAccessControl.callerCtx.setCaller(ADMIN); }); - type FailingCircuits = [method: keyof ShieldedAccessControlSimulator, args: unknown[]]; - const protectedCircuits: FailingCircuits[] = [ - ['assertOnlyRole', [DEFAULT_ADMIN_ROLE]], - ['_checkRole', [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['grantRole', [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['revokeRole', [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + type FailingCircuits = [method: keyof ShieldedAccessControlSimulator, isValidNonce: boolean, isValidIndex: boolean, isValidPath: boolean, args: unknown[]]; + const checkedCircuits: FailingCircuits[] = [ + ['assertOnlyRole', false, true, true, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', true, false, true, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', true, true, false, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', false, false, true, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', true, false, false, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', false, true, false, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', false, false, false, [DEFAULT_ADMIN_ROLE]], + ['grantRole', false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', true, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', true, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], ]; - it.each(protectedCircuits)('%s should fail with bad nonce', (circuitName, args) => { - shieldedAccessControl.privateState.injectSecretNonce(Buffer.from(DEFAULT_ADMIN_ROLE), BAD_NONCE); + it.each(checkedCircuits)('%s should fail with isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', (circuitName, isValidNonce, isValidIndex, isValidPath, args) => { + if (isValidNonce) { + // Check nonce matches + expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).toEqual( + ADMIN_SECRET_NONCE, + ); + } else { + // Check nonce does not match + shieldedAccessControl.privateState.injectSecretNonce(DEFAULT_ADMIN_ROLE, BAD_NONCE); + expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).not.toEqual( + ADMIN_SECRET_NONCE, + ); + } - // Check nonce does not match - expect(shieldedAccessControl.privateState.getCurrentSecretNonce(Buffer.from(DEFAULT_ADMIN_ROLE))).not.toEqual( - secretNonce, - ); + if (isValidIndex) { + // Check index matches + const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); + expect(witnessCalculatedIndex).toBe(INIT_COUNTER); + } else { + // Check index does not match + shieldedAccessControl.overrideWitness('wit_getRoleIndex', RETURN_BAD_INDEX); + const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); + expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); + } + + if (isValidPath) { + // Check path matches + const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); + const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleCommitmentPath(shieldedAccessControl.getWitnessContext(), EXP_DEFAULT_ADMIN_COMMITMENT); + expect(witnessCalculatedPath).toEqual(truePath); + } else { + // Check path does not match + const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); + shieldedAccessControl.overrideWitness('wit_getRoleCommitmentPath', RETURN_BAD_PATH); + const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); + expect(witnessCalculatedPath).not.toEqual(truePath); + } + + // Test protected circuit expect(() => { (shieldedAccessControl[circuitName] as (...args: unknown[]) => unknown)(...args); }).toThrow('ShieldedAccessControl: unauthorized account'); }); + }); - it.each(protectedCircuits)('%s should fail with bad index', (circuitName, args) => { - const [, trueIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); - shieldedAccessControl.overrideWitness("wit_getRoleIndex", RETURN_BAD_INDEX); - const [, badIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); + describe('checked circuits should fail for unauthorized caller with any witness value', () => { + beforeEach(() => { + shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + shieldedAccessControl.callerCtx.setCaller(UNAUTHORIZED); + }); - // Check index does not match - expect(trueIndex).not.toBe( - badIndex - ); + type FailingCircuits = [method: keyof ShieldedAccessControlSimulator, isValidNonce: boolean, isValidIndex: boolean, isValidPath: boolean, args: unknown[]]; + const checkedCircuits: FailingCircuits[] = [ + ['assertOnlyRole', false, true, true, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', true, false, true, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', true, true, false, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', false, false, true, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', true, false, false, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', false, true, false, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', false, false, false, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', true, true, true, [DEFAULT_ADMIN_ROLE]], + ['grantRole', false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', true, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', true, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', true, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', true, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ]; + + it.each(checkedCircuits)('%s should fail with isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', (circuitName, isValidNonce, isValidIndex, isValidPath, args) => { + if (isValidNonce) { + // Check nonce matches + expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).toEqual( + ADMIN_SECRET_NONCE, + ); + } else { + // Check nonce does not match + shieldedAccessControl.privateState.injectSecretNonce(DEFAULT_ADMIN_ROLE, BAD_NONCE); + expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).not.toEqual( + ADMIN_SECRET_NONCE, + ); + } + + if (isValidIndex) { + // Check index matches + const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); + expect(witnessCalculatedIndex).toBe(INIT_COUNTER); + } else { + // Check index does not match + shieldedAccessControl.overrideWitness('wit_getRoleIndex', RETURN_BAD_INDEX); + const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); + expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); + } + + if (isValidPath) { + // Check path matches + const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); + const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleCommitmentPath(shieldedAccessControl.getWitnessContext(), EXP_DEFAULT_ADMIN_COMMITMENT); + expect(witnessCalculatedPath).toEqual(truePath); + } else { + // Check path does not match + const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); + shieldedAccessControl.overrideWitness('wit_getRoleCommitmentPath', RETURN_BAD_PATH); + const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); + expect(witnessCalculatedPath).not.toEqual(truePath); + } + + // Test protected circuit expect(() => { (shieldedAccessControl[circuitName] as (...args: unknown[]) => unknown)(...args); }).toThrow('ShieldedAccessControl: unauthorized account'); }); + }); - it.each(protectedCircuits)('%s should fail with bad role path', (circuitName, args) => { - const expCommitment = buildCommitment( - DEFAULT_ADMIN_ROLE, - Z_ADMIN, - secretNonce, - INIT_COUNTER, - ); - const [, truePath] = shieldedAccessControl.witnesses.wit_getRoleCommitmentPath(shieldedAccessControl.getWitnessContext(), expCommitment); - shieldedAccessControl.overrideWitness("wit_getRoleCommitmentPath", RETURN_BAD_PATH); - const [, badPath] = shieldedAccessControl.witnesses.wit_getRoleCommitmentPath(shieldedAccessControl.getWitnessContext(), expCommitment); + describe('unsupported contract address failure cases', () => { + beforeEach(() => { + shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + shieldedAccessControl.callerCtx.setCaller(ADMIN); + }); - // Check path does not match - expect(truePath).not.toEqual( - badPath - ); + type FailingCircuits = [method: keyof ShieldedAccessControlSimulator, args: unknown[]]; + const circuitsWithContractAddressCheck: FailingCircuits[] = [ + ['hasRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], + ['_checkRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], + ['grantRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], + ['revokeRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], + ['_grantRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], + ['_revokeRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], + ]; + + it.each(circuitsWithContractAddressCheck)('%s fails if contract address is queried', (circuitName, args) => { + // Test protected circuit expect(() => { (shieldedAccessControl[circuitName] as (...args: unknown[]) => unknown)(...args); - }).toThrow('ShieldedAccessControl: unauthorized account'); + }).toThrow('ShieldedAccessControl: contract address roles are not yet supported'); }); }); @@ -157,6 +291,26 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); }); + type HasRoleTest = [isValidNonce: boolean, isValidIndex: boolean, isValidPath: boolean, args: unknown[]]; + const falseCases: HasRoleTest[] = [ + [false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [true, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ]; + + const commitmentDoesNotMatchCases: HasRoleTest[] = [ + [false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ]; + it('should throw if caller is contract address', () => { shieldedAccessControl.callerCtx.setCaller(OPERATOR_CONTRACT); expect(() => { @@ -164,11 +318,18 @@ describe('ShieldedAccessControl', () => { }).toThrow('ShieldedAccessControl: contract address roles are not yet supported'); }); + it('should throw if role has been revoked', () => { + shieldedAccessControl._revokeRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + expect(() => { + shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + }).toThrow("ShieldedAccessControl: role access has been revoked"); + }); + it('should return correct role commitment', () => { const expCommitment = buildCommitment( DEFAULT_ADMIN_ROLE, Z_ADMIN, - secretNonce, + ADMIN_SECRET_NONCE, INIT_COUNTER, ); @@ -181,22 +342,101 @@ describe('ShieldedAccessControl', () => { expect(role.isApproved).toEqual(true); }); - it('should return false when unauthorized', () => { + it('should return false when unauthorized does not have role', () => { const role = shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_UNAUTHORIZED); expect(role.isApproved).toEqual(false); - }) + }); it('should return false when role does not exist', () => { - shieldedAccessControl.privateState.injectSecretNonce(Buffer.from(UNINITIALIZED_ROLE), Buffer.alloc(32)); + shieldedAccessControl.privateState.injectSecretNonce(UNINITIALIZED_ROLE, Buffer.alloc(32)); const role = shieldedAccessControl.hasRole(UNINITIALIZED_ROLE, Z_UNAUTHORIZED); expect(role.isApproved).toBe(false); }); - it('should fail when role access has been revoked', () => { - shieldedAccessControl._revokeRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); - expect(() => { - shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); - }).toThrow("ShieldedAccessControl: role access has been revoked"); + it.each(falseCases)('should return false with any invalid witness value - isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', (isValidNonce, isValidIndex, isValidPath, args) => { + if (isValidNonce) { + // Check nonce matches + expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).toEqual( + ADMIN_SECRET_NONCE, + ); + } else { + // Check nonce does not match + shieldedAccessControl.privateState.injectSecretNonce(DEFAULT_ADMIN_ROLE, BAD_NONCE); + expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).not.toEqual( + ADMIN_SECRET_NONCE, + ); + } + + if (isValidIndex) { + // Check index matches + const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); + expect(witnessCalculatedIndex).toBe(INIT_COUNTER); + } else { + // Check index does not match + shieldedAccessControl.overrideWitness('wit_getRoleIndex', RETURN_BAD_INDEX); + const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); + expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); + } + + if (isValidPath) { + // Check path matches + const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); + const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleCommitmentPath(shieldedAccessControl.getWitnessContext(), EXP_DEFAULT_ADMIN_COMMITMENT); + expect(witnessCalculatedPath).toEqual(truePath); + } else { + // Check path does not match + const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); + shieldedAccessControl.overrideWitness('wit_getRoleCommitmentPath', RETURN_BAD_PATH); + const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); + expect(witnessCalculatedPath).not.toEqual(truePath); + } + + // Test false case circuit + const role = (shieldedAccessControl['hasRole'] as (...args: unknown[]) => Role)(...args); + expect(role.isApproved).toBe(false); + }); + + it.each(commitmentDoesNotMatchCases)('commitment should not match with invalid nonce or index - isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', (isValidNonce, isValidIndex, isValidPath, args) => { + if (isValidNonce) { + // Check nonce matches + expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).toEqual( + ADMIN_SECRET_NONCE, + ); + } else { + // Check nonce does not match + shieldedAccessControl.privateState.injectSecretNonce(DEFAULT_ADMIN_ROLE, BAD_NONCE); + expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).not.toEqual( + ADMIN_SECRET_NONCE, + ); + } + + if (isValidIndex) { + // Check index matches + const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); + expect(witnessCalculatedIndex).toBe(INIT_COUNTER); + } else { + // Check index does not match + shieldedAccessControl.overrideWitness('wit_getRoleIndex', RETURN_BAD_INDEX); + const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); + expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); + } + + if (isValidPath) { + // Check path matches + const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); + const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleCommitmentPath(shieldedAccessControl.getWitnessContext(), EXP_DEFAULT_ADMIN_COMMITMENT); + expect(witnessCalculatedPath).toEqual(truePath); + } else { + // Check path does not match + const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); + shieldedAccessControl.overrideWitness('wit_getRoleCommitmentPath', RETURN_BAD_PATH); + const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); + expect(witnessCalculatedPath).not.toEqual(truePath); + } + + // Test false case circuit + const role = (shieldedAccessControl['hasRole'] as (...args: unknown[]) => Role)(...args); + expect(role.roleCommitment).not.toEqual(EXP_DEFAULT_ADMIN_COMMITMENT); }); }); @@ -206,17 +446,37 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.callerCtx.setCaller(ADMIN); }); - it('should allow authorized caller with correct nonce to call', () => { + it('should not fail when authorized caller has correct nonce, index, and path', () => { + // Check nonce is correct + expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).toBe(ADMIN_SECRET_NONCE); + + // Check index matches + const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); + expect(witnessCalculatedIndex).toBe(INIT_COUNTER); + + // Check path matches + const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); + const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleCommitmentPath(shieldedAccessControl.getWitnessContext(), EXP_DEFAULT_ADMIN_COMMITMENT); + expect(witnessCalculatedPath).toEqual(truePath); + expect(() => shieldedAccessControl.assertOnlyRole(DEFAULT_ADMIN_ROLE), ).not.toThrow(); }); - it('should throw if caller is unauthorized', () => { - shieldedAccessControl.callerCtx.setCaller(UNAUTHORIZED); - expect(() => - shieldedAccessControl.assertOnlyRole(DEFAULT_ADMIN_ROLE), - ).toThrow('ShieldedAccessControl: unauthorized account'); + it('should not fail for admin with multiple roles', () => { + shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_1, OPERATOR_ROLE_1_SECRET_NONCE); + shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_2, OPERATOR_ROLE_2_SECRET_NONCE); + shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_3, OPERATOR_ROLE_3_SECRET_NONCE); + shieldedAccessControl._grantRole(OPERATOR_ROLE_1, Z_ADMIN); + shieldedAccessControl._grantRole(OPERATOR_ROLE_2, Z_ADMIN); + shieldedAccessControl._grantRole(OPERATOR_ROLE_3, Z_ADMIN); + expect(() => { + shieldedAccessControl.assertOnlyRole(DEFAULT_ADMIN_ROLE); + shieldedAccessControl.assertOnlyRole(OPERATOR_ROLE_1); + shieldedAccessControl.assertOnlyRole(OPERATOR_ROLE_2); + shieldedAccessControl.assertOnlyRole(OPERATOR_ROLE_3); + }).not.toThrow(); }); }); }); From 7e6a6a0610ba25e0749c6b152f1c1c9e7b977446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Thu, 11 Sep 2025 14:39:14 -0400 Subject: [PATCH 187/202] Should not throw if commitment in nullifer set --- contracts/src/access/ShieldedAccessControl.compact | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/contracts/src/access/ShieldedAccessControl.compact b/contracts/src/access/ShieldedAccessControl.compact index 17a9f746..38b3fe94 100644 --- a/contracts/src/access/ShieldedAccessControl.compact +++ b/contracts/src/access/ShieldedAccessControl.compact @@ -156,12 +156,16 @@ module ShieldedAccessControl { const nonce = wit_secretNonce(roleId); const index = wit_getRoleIndex(roleId); const roleCommitment = persistentHash>>([roleId, account.left.bytes, nonce, index as Field as Bytes<32>, pad(32, "ShieldedAccessControl:shield:")]); - assert(!_roleCommitmentNullifiers.member(disclose(roleCommitment)), "ShieldedAccessControl: role access has been revoked"); const authPath = wit_getRoleCommitmentPath(roleCommitment); - const isApproved = _operatorRoles + const rootMatches = _operatorRoles .checkRoot(merkleTreePathRoot<10, Bytes<32>>(disclose(authPath))); - return Role {isApproved, disclose(roleCommitment)}; + + if(!_roleCommitmentNullifiers.member(roleCommitment) && rootMatches) { + return Role {isApproved: true, disclose(roleCommitment)}; + } else { + return Role {isApproved: false, disclose(roleCommitment)} + } } /** From 8e5c26f7976bcc0758c9fbd4264ba0ae30c2fee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Thu, 11 Sep 2025 19:44:29 -0400 Subject: [PATCH 188/202] Export nullifiers for testing --- .../src/access/test/mocks/MockShieldedAccessControl.compact | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/src/access/test/mocks/MockShieldedAccessControl.compact b/contracts/src/access/test/mocks/MockShieldedAccessControl.compact index 12c4faa3..59c24a52 100644 --- a/contracts/src/access/test/mocks/MockShieldedAccessControl.compact +++ b/contracts/src/access/test/mocks/MockShieldedAccessControl.compact @@ -15,6 +15,7 @@ export { ShieldedAccessControl_DEFAULT_ADMIN_ROLE, ShieldedAccessControl__operatorRoles, ShieldedAccessControl__currentMerkleTreeIndex, + ShieldedAccessControl__roleCommitmentNullifiers, ShieldedAccessControl_Role }; From dd0cd82d369d8aa1e943091f878079836e399eda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Thu, 11 Sep 2025 19:45:13 -0400 Subject: [PATCH 189/202] Rename var and change return behavior --- contracts/src/access/ShieldedAccessControl.compact | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/src/access/ShieldedAccessControl.compact b/contracts/src/access/ShieldedAccessControl.compact index 38b3fe94..95abf9d2 100644 --- a/contracts/src/access/ShieldedAccessControl.compact +++ b/contracts/src/access/ShieldedAccessControl.compact @@ -155,16 +155,16 @@ module ShieldedAccessControl { const nonce = wit_secretNonce(roleId); const index = wit_getRoleIndex(roleId); - const roleCommitment = persistentHash>>([roleId, account.left.bytes, nonce, index as Field as Bytes<32>, pad(32, "ShieldedAccessControl:shield:")]); + const computedCommitment = persistentHash>>([roleId, account.left.bytes, nonce, index as Field as Bytes<32>, pad(32, "ShieldedAccessControl:shield:")]); - const authPath = wit_getRoleCommitmentPath(roleCommitment); + const authPath = wit_getRoleCommitmentPath(computedCommitment); const rootMatches = _operatorRoles .checkRoot(merkleTreePathRoot<10, Bytes<32>>(disclose(authPath))); - if(!_roleCommitmentNullifiers.member(roleCommitment) && rootMatches) { - return Role {isApproved: true, disclose(roleCommitment)}; + if(!_roleCommitmentNullifiers.member(disclose(computedCommitment)) && rootMatches) { + return Role {isApproved: true, roleCommitment: disclose(computedCommitment)}; } else { - return Role {isApproved: false, disclose(roleCommitment)} + return Role {isApproved: false, roleCommitment: disclose(computedCommitment)}; } } From f24eeb0f2bc95db537fd712ade8eb51fd623f158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Thu, 11 Sep 2025 19:45:37 -0400 Subject: [PATCH 190/202] Add _checkRole, grantRole tests --- .../access/test/ShieldedAccessControl.test.ts | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/contracts/src/access/test/ShieldedAccessControl.test.ts b/contracts/src/access/test/ShieldedAccessControl.test.ts index 29af7e81..abf9bd35 100644 --- a/contracts/src/access/test/ShieldedAccessControl.test.ts +++ b/contracts/src/access/test/ShieldedAccessControl.test.ts @@ -16,7 +16,10 @@ const [ADMIN, Z_ADMIN] = utils.generateEitherPubKeyPair('ADMIN'); const [UNAUTHORIZED, Z_UNAUTHORIZED] = utils.generateEitherPubKeyPair('UNAUTHORIZED'); const [CUSTOM_ADMIN, Z_CUSTOM_ADMIN] = utils.generateEitherPubKeyPair('CUSTOM_ADMIN'); const [OPERATOR_1, Z_OPERATOR_1] = utils.generateEitherPubKeyPair('OPERATOR_1'); +const [OPERATOR_2, Z_OPERATOR_2] = utils.generateEitherPubKeyPair('OPERATOR_2'); +const [OPERATOR_3, Z_OPERATOR_3] = utils.generateEitherPubKeyPair('OPERATOR_3'); const [OPERATOR_CONTRACT, Z_OPERATOR_CONTRACT] = utils.generateEitherPubKeyPair('OPERATOR_CONTRACT', false); +const Z_OPERATOR_LIST = [Z_OPERATOR_1, Z_OPERATOR_2, Z_OPERATOR_3]; // Constants const BAD_NONCE = Buffer.alloc(32, 'BAD_NONCE'); @@ -32,6 +35,7 @@ const OPERATOR_ROLE_2 = convert_bigint_to_Uint8Array(32, 2n); const OPERATOR_ROLE_3 = convert_bigint_to_Uint8Array(32, 3n); const CUSTOM_ADMIN_ROLE = convert_bigint_to_Uint8Array(32, 4n); const UNINITIALIZED_ROLE = convert_bigint_to_Uint8Array(32, 5n); +const OPERATOR_ROLE_LIST = [OPERATOR_ROLE_1, OPERATOR_ROLE_2, OPERATOR_ROLE_3]; const operatorTypes = [ ['contract', Z_OPERATOR_CONTRACT], @@ -45,6 +49,7 @@ const ADMIN_SECRET_NONCE = Buffer.alloc(32, "ADMIN_SECRET_NONCE"); const OPERATOR_ROLE_1_SECRET_NONCE = Buffer.alloc(32, "OPERATOR_ROLE_1_SECRET_NONCE"); const OPERATOR_ROLE_2_SECRET_NONCE = Buffer.alloc(32, "OPERATOR_ROLE_2_SECRET_NONCE"); const OPERATOR_ROLE_3_SECRET_NONCE = Buffer.alloc(32, "OPERATOR_ROLE_3_SECRET_NONCE"); +const OPERATOR_ROLE_SECRET_NONCES = [OPERATOR_ROLE_1_SECRET_NONCE, OPERATOR_ROLE_2_SECRET_NONCE, OPERATOR_ROLE_3_SECRET_NONCE]; let shieldedAccessControl: ShieldedAccessControlSimulator; // Helpers @@ -479,4 +484,117 @@ describe('ShieldedAccessControl', () => { }).not.toThrow(); }); }); + + describe('_checkRole', () => { + beforeEach(() => { + shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + shieldedAccessControl.callerCtx.setCaller(ADMIN); + }); + + it('should not throw if admin has role', () => { + expect(() => + shieldedAccessControl._checkRole(DEFAULT_ADMIN_ROLE, Z_ADMIN), + ).not.toThrow(); + }); + + it('should throw if unauthorized does not have role', () => { + expect(() => + shieldedAccessControl._checkRole(DEFAULT_ADMIN_ROLE, Z_UNAUTHORIZED), + ).toThrow('ShieldedAccessControl: unauthorized account'); + }); + }); + + describe('getRoleAdmin', () => { + it('should return default admin role if admin role not set', () => { + expect(shieldedAccessControl.getRoleAdmin(OPERATOR_ROLE_1)).toEqual( + DEFAULT_ADMIN_ROLE, + ); + }); + + it('should return custom admin role if set', () => { + shieldedAccessControl._setRoleAdmin(OPERATOR_ROLE_1, CUSTOM_ADMIN_ROLE); + expect(shieldedAccessControl.getRoleAdmin(OPERATOR_ROLE_1)).toEqual( + CUSTOM_ADMIN_ROLE, + ); + }); + }); + + describe('grantRole', () => { + beforeEach(() => { + shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + shieldedAccessControl.callerCtx.setCaller(ADMIN); + }); + + it('admin should grant role', () => { + shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_1, OPERATOR_ROLE_1_SECRET_NONCE); + shieldedAccessControl.grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); + const role: Role = shieldedAccessControl.hasRole(OPERATOR_ROLE_1, Z_OPERATOR_1); + expect(role.isApproved).toBe( + true, + ); + }); + + it('path for role should exist in Merkle tree', () => { + expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT)).toBeDefined(); + }); + + it('should update Merkle tree root', () => { + expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.root().field).toBeGreaterThan(0n); + }); + + it('_currentMerkleTreeIndex should increment', () => { + // Starts at 1 because we grant role to self in beforeEach + expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex).toBe(1n); + + shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_1, OPERATOR_ROLE_1_SECRET_NONCE); + shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_2, OPERATOR_ROLE_2_SECRET_NONCE); + shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_3, OPERATOR_ROLE_3_SECRET_NONCE); + + shieldedAccessControl.grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); + expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex).toBe(2n); + + shieldedAccessControl.grantRole(OPERATOR_ROLE_2, Z_OPERATOR_2); + expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex).toBe(3n); + + shieldedAccessControl.grantRole(OPERATOR_ROLE_3, Z_OPERATOR_3); + expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex).toBe(4n); + }); + + + + it('admin should grant multiple roles', () => { + for (let i = 0; i < OPERATOR_ROLE_LIST.length; i++) { + shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_LIST[i], OPERATOR_ROLE_SECRET_NONCES[i]); + for (let j = 0; j < Z_OPERATOR_LIST.length; j++) { + shieldedAccessControl.grantRole(OPERATOR_ROLE_LIST[i], Z_OPERATOR_LIST[j]); + const role: Role = shieldedAccessControl.hasRole(OPERATOR_ROLE_LIST[i], Z_OPERATOR_LIST[j]) + expect(role.isApproved).toBe( + true, + ); + } + } + }); + + it('should throw if non-admin operator grants role', () => { + shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_1, OPERATOR_ROLE_1_SECRET_NONCE); + shieldedAccessControl._grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); + + shieldedAccessControl.callerCtx.setCaller(OPERATOR_1); + expect(() => { + shieldedAccessControl.grantRole(OPERATOR_ROLE_1, Z_UNAUTHORIZED); + }).toThrow('ShieldedAccessControl: unauthorized account'); + }); + }); + + describe('revokeRole', () => { + beforeEach(() => { + shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_1, OPERATOR_ROLE_1_SECRET_NONCE); + shieldedAccessControl.callerCtx.setCaller(ADMIN); + }); + + it.todo('admin should revoke role', () => { + + }); + }); }); From 2cb227e66bd687f025628e0b26d2bdecb26f62e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Thu, 11 Sep 2025 19:47:15 -0400 Subject: [PATCH 191/202] fmt files --- .../access/test/ShieldedAccessControl.test.ts | 767 +++++++++++++----- .../ShieldedAccessControlSimulator.ts | 96 ++- contracts/src/access/test/utils/address.ts | 32 +- .../ShieldedAccessControlWitnesses.ts | 132 ++- 4 files changed, 733 insertions(+), 294 deletions(-) diff --git a/contracts/src/access/test/ShieldedAccessControl.test.ts b/contracts/src/access/test/ShieldedAccessControl.test.ts index abf9bd35..73b5e4e3 100644 --- a/contracts/src/access/test/ShieldedAccessControl.test.ts +++ b/contracts/src/access/test/ShieldedAccessControl.test.ts @@ -3,22 +3,34 @@ import { CompactTypeVector, convert_bigint_to_Uint8Array, persistentHash, - WitnessContext, + type WitnessContext, } from '@midnight-ntwrk/compact-runtime'; import { beforeEach, describe, expect, it } from 'vitest'; -import type { ZswapCoinPublicKey, Either, ContractAddress, Ledger, MerkleTreePath, ShieldedAccessControl_Role as Role } from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; +import type { + ContractAddress, + Either, + Ledger, + MerkleTreePath, + ShieldedAccessControl_Role as Role, + ZswapCoinPublicKey, +} from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; import { ShieldedAccessControlPrivateState } from '../witnesses/ShieldedAccessControlWitnesses.js'; import { ShieldedAccessControlSimulator } from './simulators/ShieldedAccessControlSimulator.js'; import * as utils from './utils/address.js'; // PKs const [ADMIN, Z_ADMIN] = utils.generateEitherPubKeyPair('ADMIN'); -const [UNAUTHORIZED, Z_UNAUTHORIZED] = utils.generateEitherPubKeyPair('UNAUTHORIZED'); -const [CUSTOM_ADMIN, Z_CUSTOM_ADMIN] = utils.generateEitherPubKeyPair('CUSTOM_ADMIN'); +const [UNAUTHORIZED, Z_UNAUTHORIZED] = + utils.generateEitherPubKeyPair('UNAUTHORIZED'); +const [CUSTOM_ADMIN, Z_CUSTOM_ADMIN] = + utils.generateEitherPubKeyPair('CUSTOM_ADMIN'); const [OPERATOR_1, Z_OPERATOR_1] = utils.generateEitherPubKeyPair('OPERATOR_1'); const [OPERATOR_2, Z_OPERATOR_2] = utils.generateEitherPubKeyPair('OPERATOR_2'); const [OPERATOR_3, Z_OPERATOR_3] = utils.generateEitherPubKeyPair('OPERATOR_3'); -const [OPERATOR_CONTRACT, Z_OPERATOR_CONTRACT] = utils.generateEitherPubKeyPair('OPERATOR_CONTRACT', false); +const [OPERATOR_CONTRACT, Z_OPERATOR_CONTRACT] = utils.generateEitherPubKeyPair( + 'OPERATOR_CONTRACT', + false, +); const Z_OPERATOR_LIST = [Z_OPERATOR_1, Z_OPERATOR_2, Z_OPERATOR_3]; // Constants @@ -43,13 +55,27 @@ const operatorTypes = [ ] as const; // Role to string -const DEFAULT_ADMIN_ROLE_TO_STRING = Buffer.from(DEFAULT_ADMIN_ROLE).toString('hex'); +const DEFAULT_ADMIN_ROLE_TO_STRING = + Buffer.from(DEFAULT_ADMIN_ROLE).toString('hex'); -const ADMIN_SECRET_NONCE = Buffer.alloc(32, "ADMIN_SECRET_NONCE"); -const OPERATOR_ROLE_1_SECRET_NONCE = Buffer.alloc(32, "OPERATOR_ROLE_1_SECRET_NONCE"); -const OPERATOR_ROLE_2_SECRET_NONCE = Buffer.alloc(32, "OPERATOR_ROLE_2_SECRET_NONCE"); -const OPERATOR_ROLE_3_SECRET_NONCE = Buffer.alloc(32, "OPERATOR_ROLE_3_SECRET_NONCE"); -const OPERATOR_ROLE_SECRET_NONCES = [OPERATOR_ROLE_1_SECRET_NONCE, OPERATOR_ROLE_2_SECRET_NONCE, OPERATOR_ROLE_3_SECRET_NONCE]; +const ADMIN_SECRET_NONCE = Buffer.alloc(32, 'ADMIN_SECRET_NONCE'); +const OPERATOR_ROLE_1_SECRET_NONCE = Buffer.alloc( + 32, + 'OPERATOR_ROLE_1_SECRET_NONCE', +); +const OPERATOR_ROLE_2_SECRET_NONCE = Buffer.alloc( + 32, + 'OPERATOR_ROLE_2_SECRET_NONCE', +); +const OPERATOR_ROLE_3_SECRET_NONCE = Buffer.alloc( + 32, + 'OPERATOR_ROLE_3_SECRET_NONCE', +); +const OPERATOR_ROLE_SECRET_NONCES = [ + OPERATOR_ROLE_1_SECRET_NONCE, + OPERATOR_ROLE_2_SECRET_NONCE, + OPERATOR_ROLE_3_SECRET_NONCE, +]; let shieldedAccessControl: ShieldedAccessControlSimulator; // Helpers @@ -82,17 +108,23 @@ const EXP_DEFAULT_ADMIN_COMMITMENT = buildCommitment( INIT_COUNTER, ); -function RETURN_BAD_INDEX(context: WitnessContext, roleId: Uint8Array): [ShieldedAccessControlPrivateState, bigint] { - return [context.privateState, 1023n] +function RETURN_BAD_INDEX( + context: WitnessContext, + roleId: Uint8Array, +): [ShieldedAccessControlPrivateState, bigint] { + return [context.privateState, 1023n]; } -function RETURN_BAD_PATH(context: WitnessContext, roleCommitment: Uint8Array): [ShieldedAccessControlPrivateState, MerkleTreePath] { +function RETURN_BAD_PATH( + context: WitnessContext, + roleCommitment: Uint8Array, +): [ShieldedAccessControlPrivateState, MerkleTreePath] { const defaultPath: MerkleTreePath = { leaf: new Uint8Array(32), path: Array.from({ length: 10 }, () => ({ sibling: { field: 0n }, goes_left: false, - })) + })), }; return [context.privateState, defaultPath]; } @@ -100,12 +132,16 @@ function RETURN_BAD_PATH(context: WitnessContext { beforeEach(() => { // Create private state object and generate nonce - const PS = ShieldedAccessControlPrivateState.withRoleAndNonce(Z_ADMIN, Buffer.from(DEFAULT_ADMIN_ROLE), ADMIN_SECRET_NONCE); + const PS = ShieldedAccessControlPrivateState.withRoleAndNonce( + Z_ADMIN, + Buffer.from(DEFAULT_ADMIN_ROLE), + ADMIN_SECRET_NONCE, + ); // Init contract for user with PS shieldedAccessControl = new ShieldedAccessControlSimulator(Z_ADMIN, { privateState: PS, @@ -118,7 +154,13 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.callerCtx.setCaller(ADMIN); }); - type FailingCircuits = [method: keyof ShieldedAccessControlSimulator, isValidNonce: boolean, isValidIndex: boolean, isValidPath: boolean, args: unknown[]]; + type FailingCircuits = [ + method: keyof ShieldedAccessControlSimulator, + isValidNonce: boolean, + isValidIndex: boolean, + isValidPath: boolean, + args: unknown[], + ]; const checkedCircuits: FailingCircuits[] = [ ['assertOnlyRole', false, true, true, [DEFAULT_ADMIN_ROLE]], ['assertOnlyRole', true, false, true, [DEFAULT_ADMIN_ROLE]], @@ -143,49 +185,93 @@ describe('ShieldedAccessControl', () => { ['revokeRole', false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], ]; - it.each(checkedCircuits)('%s should fail with isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', (circuitName, isValidNonce, isValidIndex, isValidPath, args) => { - if (isValidNonce) { - // Check nonce matches - expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).toEqual( - ADMIN_SECRET_NONCE, - ); - } else { - // Check nonce does not match - shieldedAccessControl.privateState.injectSecretNonce(DEFAULT_ADMIN_ROLE, BAD_NONCE); - expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).not.toEqual( - ADMIN_SECRET_NONCE, - ); - } + it.each(checkedCircuits)( + '%s should fail with isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', + (circuitName, isValidNonce, isValidIndex, isValidPath, args) => { + if (isValidNonce) { + // Check nonce matches + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).toEqual(ADMIN_SECRET_NONCE); + } else { + // Check nonce does not match + shieldedAccessControl.privateState.injectSecretNonce( + DEFAULT_ADMIN_ROLE, + BAD_NONCE, + ); + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).not.toEqual(ADMIN_SECRET_NONCE); + } - if (isValidIndex) { - // Check index matches - const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); - expect(witnessCalculatedIndex).toBe(INIT_COUNTER); - } else { - // Check index does not match - shieldedAccessControl.overrideWitness('wit_getRoleIndex', RETURN_BAD_INDEX); - const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); - expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); - } + if (isValidIndex) { + // Check index matches + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + ); + expect(witnessCalculatedIndex).toBe(INIT_COUNTER); + } else { + // Check index does not match + shieldedAccessControl.overrideWitness( + 'wit_getRoleIndex', + RETURN_BAD_INDEX, + ); + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + ); + expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); + } - if (isValidPath) { - // Check path matches - const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); - const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleCommitmentPath(shieldedAccessControl.getWitnessContext(), EXP_DEFAULT_ADMIN_COMMITMENT); - expect(witnessCalculatedPath).toEqual(truePath); - } else { - // Check path does not match - const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); - shieldedAccessControl.overrideWitness('wit_getRoleCommitmentPath', RETURN_BAD_PATH); - const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); - expect(witnessCalculatedPath).not.toEqual(truePath); - } + if (isValidPath) { + // Check path matches + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleCommitmentPath( + shieldedAccessControl.getWitnessContext(), + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + expect(witnessCalculatedPath).toEqual(truePath); + } else { + // Check path does not match + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + shieldedAccessControl.overrideWitness( + 'wit_getRoleCommitmentPath', + RETURN_BAD_PATH, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + ); + expect(witnessCalculatedPath).not.toEqual(truePath); + } - // Test protected circuit - expect(() => { - (shieldedAccessControl[circuitName] as (...args: unknown[]) => unknown)(...args); - }).toThrow('ShieldedAccessControl: unauthorized account'); - }); + // Test protected circuit + expect(() => { + ( + shieldedAccessControl[circuitName] as ( + ...args: unknown[] + ) => unknown + )(...args); + }).toThrow('ShieldedAccessControl: unauthorized account'); + }, + ); }); describe('checked circuits should fail for unauthorized caller with any witness value', () => { @@ -194,7 +280,13 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.callerCtx.setCaller(UNAUTHORIZED); }); - type FailingCircuits = [method: keyof ShieldedAccessControlSimulator, isValidNonce: boolean, isValidIndex: boolean, isValidPath: boolean, args: unknown[]]; + type FailingCircuits = [ + method: keyof ShieldedAccessControlSimulator, + isValidNonce: boolean, + isValidIndex: boolean, + isValidPath: boolean, + args: unknown[], + ]; const checkedCircuits: FailingCircuits[] = [ ['assertOnlyRole', false, true, true, [DEFAULT_ADMIN_ROLE]], ['assertOnlyRole', true, false, true, [DEFAULT_ADMIN_ROLE]], @@ -222,49 +314,93 @@ describe('ShieldedAccessControl', () => { ['revokeRole', true, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], ]; - it.each(checkedCircuits)('%s should fail with isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', (circuitName, isValidNonce, isValidIndex, isValidPath, args) => { - if (isValidNonce) { - // Check nonce matches - expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).toEqual( - ADMIN_SECRET_NONCE, - ); - } else { - // Check nonce does not match - shieldedAccessControl.privateState.injectSecretNonce(DEFAULT_ADMIN_ROLE, BAD_NONCE); - expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).not.toEqual( - ADMIN_SECRET_NONCE, - ); - } + it.each(checkedCircuits)( + '%s should fail with isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', + (circuitName, isValidNonce, isValidIndex, isValidPath, args) => { + if (isValidNonce) { + // Check nonce matches + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).toEqual(ADMIN_SECRET_NONCE); + } else { + // Check nonce does not match + shieldedAccessControl.privateState.injectSecretNonce( + DEFAULT_ADMIN_ROLE, + BAD_NONCE, + ); + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).not.toEqual(ADMIN_SECRET_NONCE); + } - if (isValidIndex) { - // Check index matches - const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); - expect(witnessCalculatedIndex).toBe(INIT_COUNTER); - } else { - // Check index does not match - shieldedAccessControl.overrideWitness('wit_getRoleIndex', RETURN_BAD_INDEX); - const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); - expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); - } + if (isValidIndex) { + // Check index matches + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + ); + expect(witnessCalculatedIndex).toBe(INIT_COUNTER); + } else { + // Check index does not match + shieldedAccessControl.overrideWitness( + 'wit_getRoleIndex', + RETURN_BAD_INDEX, + ); + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + ); + expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); + } - if (isValidPath) { - // Check path matches - const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); - const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleCommitmentPath(shieldedAccessControl.getWitnessContext(), EXP_DEFAULT_ADMIN_COMMITMENT); - expect(witnessCalculatedPath).toEqual(truePath); - } else { - // Check path does not match - const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); - shieldedAccessControl.overrideWitness('wit_getRoleCommitmentPath', RETURN_BAD_PATH); - const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); - expect(witnessCalculatedPath).not.toEqual(truePath); - } + if (isValidPath) { + // Check path matches + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleCommitmentPath( + shieldedAccessControl.getWitnessContext(), + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + expect(witnessCalculatedPath).toEqual(truePath); + } else { + // Check path does not match + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + shieldedAccessControl.overrideWitness( + 'wit_getRoleCommitmentPath', + RETURN_BAD_PATH, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + ); + expect(witnessCalculatedPath).not.toEqual(truePath); + } - // Test protected circuit - expect(() => { - (shieldedAccessControl[circuitName] as (...args: unknown[]) => unknown)(...args); - }).toThrow('ShieldedAccessControl: unauthorized account'); - }); + // Test protected circuit + expect(() => { + ( + shieldedAccessControl[circuitName] as ( + ...args: unknown[] + ) => unknown + )(...args); + }).toThrow('ShieldedAccessControl: unauthorized account'); + }, + ); }); describe('unsupported contract address failure cases', () => { @@ -273,7 +409,10 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.callerCtx.setCaller(ADMIN); }); - type FailingCircuits = [method: keyof ShieldedAccessControlSimulator, args: unknown[]]; + type FailingCircuits = [ + method: keyof ShieldedAccessControlSimulator, + args: unknown[], + ]; const circuitsWithContractAddressCheck: FailingCircuits[] = [ ['hasRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], ['_checkRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], @@ -283,12 +422,21 @@ describe('ShieldedAccessControl', () => { ['_revokeRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], ]; - it.each(circuitsWithContractAddressCheck)('%s fails if contract address is queried', (circuitName, args) => { - // Test protected circuit - expect(() => { - (shieldedAccessControl[circuitName] as (...args: unknown[]) => unknown)(...args); - }).toThrow('ShieldedAccessControl: contract address roles are not yet supported'); - }); + it.each(circuitsWithContractAddressCheck)( + '%s fails if contract address is queried', + (circuitName, args) => { + // Test protected circuit + expect(() => { + ( + shieldedAccessControl[circuitName] as ( + ...args: unknown[] + ) => unknown + )(...args); + }).toThrow( + 'ShieldedAccessControl: contract address roles are not yet supported', + ); + }, + ); }); describe('hasRole', () => { @@ -296,7 +444,12 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); }); - type HasRoleTest = [isValidNonce: boolean, isValidIndex: boolean, isValidPath: boolean, args: unknown[]]; + type HasRoleTest = [ + isValidNonce: boolean, + isValidIndex: boolean, + isValidPath: boolean, + args: unknown[], + ]; const falseCases: HasRoleTest[] = [ [false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], [true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], @@ -319,15 +472,17 @@ describe('ShieldedAccessControl', () => { it('should throw if caller is contract address', () => { shieldedAccessControl.callerCtx.setCaller(OPERATOR_CONTRACT); expect(() => { - shieldedAccessControl.hasRole(UNINITIALIZED_ROLE, Z_OPERATOR_CONTRACT) - }).toThrow('ShieldedAccessControl: contract address roles are not yet supported'); + shieldedAccessControl.hasRole(UNINITIALIZED_ROLE, Z_OPERATOR_CONTRACT); + }).toThrow( + 'ShieldedAccessControl: contract address roles are not yet supported', + ); }); it('should throw if role has been revoked', () => { shieldedAccessControl._revokeRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); expect(() => { shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); - }).toThrow("ShieldedAccessControl: role access has been revoked"); + }).toThrow('ShieldedAccessControl: role access has been revoked'); }); it('should return correct role commitment', () => { @@ -348,101 +503,194 @@ describe('ShieldedAccessControl', () => { }); it('should return false when unauthorized does not have role', () => { - const role = shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_UNAUTHORIZED); + const role = shieldedAccessControl.hasRole( + DEFAULT_ADMIN_ROLE, + Z_UNAUTHORIZED, + ); expect(role.isApproved).toEqual(false); }); it('should return false when role does not exist', () => { - shieldedAccessControl.privateState.injectSecretNonce(UNINITIALIZED_ROLE, Buffer.alloc(32)); - const role = shieldedAccessControl.hasRole(UNINITIALIZED_ROLE, Z_UNAUTHORIZED); + shieldedAccessControl.privateState.injectSecretNonce( + UNINITIALIZED_ROLE, + Buffer.alloc(32), + ); + const role = shieldedAccessControl.hasRole( + UNINITIALIZED_ROLE, + Z_UNAUTHORIZED, + ); expect(role.isApproved).toBe(false); }); - it.each(falseCases)('should return false with any invalid witness value - isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', (isValidNonce, isValidIndex, isValidPath, args) => { - if (isValidNonce) { - // Check nonce matches - expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).toEqual( - ADMIN_SECRET_NONCE, - ); - } else { - // Check nonce does not match - shieldedAccessControl.privateState.injectSecretNonce(DEFAULT_ADMIN_ROLE, BAD_NONCE); - expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).not.toEqual( - ADMIN_SECRET_NONCE, - ); - } - - if (isValidIndex) { - // Check index matches - const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); - expect(witnessCalculatedIndex).toBe(INIT_COUNTER); - } else { - // Check index does not match - shieldedAccessControl.overrideWitness('wit_getRoleIndex', RETURN_BAD_INDEX); - const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); - expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); - } + it.each(falseCases)( + 'should return false with any invalid witness value - isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', + (isValidNonce, isValidIndex, isValidPath, args) => { + if (isValidNonce) { + // Check nonce matches + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).toEqual(ADMIN_SECRET_NONCE); + } else { + // Check nonce does not match + shieldedAccessControl.privateState.injectSecretNonce( + DEFAULT_ADMIN_ROLE, + BAD_NONCE, + ); + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).not.toEqual(ADMIN_SECRET_NONCE); + } - if (isValidPath) { - // Check path matches - const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); - const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleCommitmentPath(shieldedAccessControl.getWitnessContext(), EXP_DEFAULT_ADMIN_COMMITMENT); - expect(witnessCalculatedPath).toEqual(truePath); - } else { - // Check path does not match - const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); - shieldedAccessControl.overrideWitness('wit_getRoleCommitmentPath', RETURN_BAD_PATH); - const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); - expect(witnessCalculatedPath).not.toEqual(truePath); - } + if (isValidIndex) { + // Check index matches + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + ); + expect(witnessCalculatedIndex).toBe(INIT_COUNTER); + } else { + // Check index does not match + shieldedAccessControl.overrideWitness( + 'wit_getRoleIndex', + RETURN_BAD_INDEX, + ); + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + ); + expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); + } - // Test false case circuit - const role = (shieldedAccessControl['hasRole'] as (...args: unknown[]) => Role)(...args); - expect(role.isApproved).toBe(false); - }); + if (isValidPath) { + // Check path matches + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleCommitmentPath( + shieldedAccessControl.getWitnessContext(), + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + expect(witnessCalculatedPath).toEqual(truePath); + } else { + // Check path does not match + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + shieldedAccessControl.overrideWitness( + 'wit_getRoleCommitmentPath', + RETURN_BAD_PATH, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + ); + expect(witnessCalculatedPath).not.toEqual(truePath); + } - it.each(commitmentDoesNotMatchCases)('commitment should not match with invalid nonce or index - isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', (isValidNonce, isValidIndex, isValidPath, args) => { - if (isValidNonce) { - // Check nonce matches - expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).toEqual( - ADMIN_SECRET_NONCE, - ); - } else { - // Check nonce does not match - shieldedAccessControl.privateState.injectSecretNonce(DEFAULT_ADMIN_ROLE, BAD_NONCE); - expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).not.toEqual( - ADMIN_SECRET_NONCE, - ); - } + // Test false case circuit + const role = ( + shieldedAccessControl['hasRole'] as (...args: unknown[]) => Role + )(...args); + expect(role.isApproved).toBe(false); + }, + ); + + it.each(commitmentDoesNotMatchCases)( + 'commitment should not match with invalid nonce or index - isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', + (isValidNonce, isValidIndex, isValidPath, args) => { + if (isValidNonce) { + // Check nonce matches + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).toEqual(ADMIN_SECRET_NONCE); + } else { + // Check nonce does not match + shieldedAccessControl.privateState.injectSecretNonce( + DEFAULT_ADMIN_ROLE, + BAD_NONCE, + ); + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).not.toEqual(ADMIN_SECRET_NONCE); + } - if (isValidIndex) { - // Check index matches - const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); - expect(witnessCalculatedIndex).toBe(INIT_COUNTER); - } else { - // Check index does not match - shieldedAccessControl.overrideWitness('wit_getRoleIndex', RETURN_BAD_INDEX); - const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); - expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); - } + if (isValidIndex) { + // Check index matches + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + ); + expect(witnessCalculatedIndex).toBe(INIT_COUNTER); + } else { + // Check index does not match + shieldedAccessControl.overrideWitness( + 'wit_getRoleIndex', + RETURN_BAD_INDEX, + ); + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + ); + expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); + } - if (isValidPath) { - // Check path matches - const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); - const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleCommitmentPath(shieldedAccessControl.getWitnessContext(), EXP_DEFAULT_ADMIN_COMMITMENT); - expect(witnessCalculatedPath).toEqual(truePath); - } else { - // Check path does not match - const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); - shieldedAccessControl.overrideWitness('wit_getRoleCommitmentPath', RETURN_BAD_PATH); - const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); - expect(witnessCalculatedPath).not.toEqual(truePath); - } + if (isValidPath) { + // Check path matches + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleCommitmentPath( + shieldedAccessControl.getWitnessContext(), + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + expect(witnessCalculatedPath).toEqual(truePath); + } else { + // Check path does not match + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + shieldedAccessControl.overrideWitness( + 'wit_getRoleCommitmentPath', + RETURN_BAD_PATH, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + ); + expect(witnessCalculatedPath).not.toEqual(truePath); + } - // Test false case circuit - const role = (shieldedAccessControl['hasRole'] as (...args: unknown[]) => Role)(...args); - expect(role.roleCommitment).not.toEqual(EXP_DEFAULT_ADMIN_COMMITMENT); - }); + // Test false case circuit + const role = ( + shieldedAccessControl['hasRole'] as (...args: unknown[]) => Role + )(...args); + expect(role.roleCommitment).not.toEqual(EXP_DEFAULT_ADMIN_COMMITMENT); + }, + ); }); describe('assertOnlyRole', () => { @@ -453,15 +701,31 @@ describe('ShieldedAccessControl', () => { it('should not fail when authorized caller has correct nonce, index, and path', () => { // Check nonce is correct - expect(shieldedAccessControl.privateState.getCurrentSecretNonce(DEFAULT_ADMIN_ROLE)).toBe(ADMIN_SECRET_NONCE); + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).toBe(ADMIN_SECRET_NONCE); // Check index matches - const [, witnessCalculatedIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE); + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + ); expect(witnessCalculatedIndex).toBe(INIT_COUNTER); // Check path matches - const truePath = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); - const [, witnessCalculatedPath] = shieldedAccessControl.witnesses.wit_getRoleCommitmentPath(shieldedAccessControl.getWitnessContext(), EXP_DEFAULT_ADMIN_COMMITMENT); + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleCommitmentPath( + shieldedAccessControl.getWitnessContext(), + EXP_DEFAULT_ADMIN_COMMITMENT, + ); expect(witnessCalculatedPath).toEqual(truePath); expect(() => @@ -470,9 +734,18 @@ describe('ShieldedAccessControl', () => { }); it('should not fail for admin with multiple roles', () => { - shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_1, OPERATOR_ROLE_1_SECRET_NONCE); - shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_2, OPERATOR_ROLE_2_SECRET_NONCE); - shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_3, OPERATOR_ROLE_3_SECRET_NONCE); + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_1, + OPERATOR_ROLE_1_SECRET_NONCE, + ); + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_2, + OPERATOR_ROLE_2_SECRET_NONCE, + ); + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_3, + OPERATOR_ROLE_3_SECRET_NONCE, + ); shieldedAccessControl._grantRole(OPERATOR_ROLE_1, Z_ADMIN); shieldedAccessControl._grantRole(OPERATOR_ROLE_2, Z_ADMIN); shieldedAccessControl._grantRole(OPERATOR_ROLE_3, Z_ADMIN); @@ -526,57 +799,100 @@ describe('ShieldedAccessControl', () => { }); it('admin should grant role', () => { - shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_1, OPERATOR_ROLE_1_SECRET_NONCE); + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_1, + OPERATOR_ROLE_1_SECRET_NONCE, + ); shieldedAccessControl.grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); - const role: Role = shieldedAccessControl.hasRole(OPERATOR_ROLE_1, Z_OPERATOR_1); - expect(role.isApproved).toBe( - true, + const role: Role = shieldedAccessControl.hasRole( + OPERATOR_ROLE_1, + Z_OPERATOR_1, ); + expect(role.isApproved).toBe(true); }); it('path for role should exist in Merkle tree', () => { - expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT)).toBeDefined(); + expect( + shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ), + ).toBeDefined(); }); it('should update Merkle tree root', () => { - expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.root().field).toBeGreaterThan(0n); + expect( + shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.root().field, + ).toBeGreaterThan(0n); }); it('_currentMerkleTreeIndex should increment', () => { // Starts at 1 because we grant role to self in beforeEach - expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex).toBe(1n); - - shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_1, OPERATOR_ROLE_1_SECRET_NONCE); - shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_2, OPERATOR_ROLE_2_SECRET_NONCE); - shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_3, OPERATOR_ROLE_3_SECRET_NONCE); + expect( + shieldedAccessControl.getPublicState() + .ShieldedAccessControl__currentMerkleTreeIndex, + ).toBe(1n); + + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_1, + OPERATOR_ROLE_1_SECRET_NONCE, + ); + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_2, + OPERATOR_ROLE_2_SECRET_NONCE, + ); + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_3, + OPERATOR_ROLE_3_SECRET_NONCE, + ); shieldedAccessControl.grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); - expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex).toBe(2n); + expect( + shieldedAccessControl.getPublicState() + .ShieldedAccessControl__currentMerkleTreeIndex, + ).toBe(2n); shieldedAccessControl.grantRole(OPERATOR_ROLE_2, Z_OPERATOR_2); - expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex).toBe(3n); + expect( + shieldedAccessControl.getPublicState() + .ShieldedAccessControl__currentMerkleTreeIndex, + ).toBe(3n); shieldedAccessControl.grantRole(OPERATOR_ROLE_3, Z_OPERATOR_3); - expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex).toBe(4n); + expect( + shieldedAccessControl.getPublicState() + .ShieldedAccessControl__currentMerkleTreeIndex, + ).toBe(4n); }); - - it('admin should grant multiple roles', () => { for (let i = 0; i < OPERATOR_ROLE_LIST.length; i++) { - shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_LIST[i], OPERATOR_ROLE_SECRET_NONCES[i]); + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_LIST[i], + OPERATOR_ROLE_SECRET_NONCES[i], + ); for (let j = 0; j < Z_OPERATOR_LIST.length; j++) { - shieldedAccessControl.grantRole(OPERATOR_ROLE_LIST[i], Z_OPERATOR_LIST[j]); - const role: Role = shieldedAccessControl.hasRole(OPERATOR_ROLE_LIST[i], Z_OPERATOR_LIST[j]) - expect(role.isApproved).toBe( - true, + shieldedAccessControl.grantRole( + OPERATOR_ROLE_LIST[i], + Z_OPERATOR_LIST[j], ); + const role: Role = shieldedAccessControl.hasRole( + OPERATOR_ROLE_LIST[i], + Z_OPERATOR_LIST[j], + ); + expect(role.isApproved).toBe(true); } } }); it('should throw if non-admin operator grants role', () => { - shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_1, OPERATOR_ROLE_1_SECRET_NONCE); + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_1, + OPERATOR_ROLE_1_SECRET_NONCE, + ); shieldedAccessControl._grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); shieldedAccessControl.callerCtx.setCaller(OPERATOR_1); @@ -589,12 +905,13 @@ describe('ShieldedAccessControl', () => { describe('revokeRole', () => { beforeEach(() => { shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); - shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_ROLE_1, OPERATOR_ROLE_1_SECRET_NONCE); + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_1, + OPERATOR_ROLE_1_SECRET_NONCE, + ); shieldedAccessControl.callerCtx.setCaller(ADMIN); }); - it.todo('admin should revoke role', () => { - - }); + it.todo('admin should revoke role', () => {}); }); }); diff --git a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts index 707f0bc7..69e25938 100644 --- a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts +++ b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts @@ -2,7 +2,7 @@ import { type CircuitContext, type CoinPublicKey, emptyZswapLocalState, - WitnessContext, + type WitnessContext, } from '@midnight-ntwrk/compact-runtime'; import { sampleContractAddress } from '@midnight-ntwrk/zswap'; import { @@ -11,8 +11,8 @@ import { type Ledger, ledger, Contract as MockShieldedAccessControl, + type ShieldedAccessControl_Role as Role, type ZswapCoinPublicKey, - type ShieldedAccessControl_Role as Role } from '../../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; import { ShieldedAccessControlPrivateState, @@ -48,12 +48,16 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< private _witnesses: ReturnType; private _pureCircuitProxy?: ContextlessCircuits< - ExtractPureCircuits>, + ExtractPureCircuits< + MockShieldedAccessControl + >, ShieldedAccessControlPrivateState >; private _impureCircuitProxy?: ContextlessCircuits< - ExtractImpureCircuits>, + ExtractImpureCircuits< + MockShieldedAccessControl + >, ShieldedAccessControlPrivateState >; @@ -65,13 +69,18 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< // Setup initial state const { - privateState = options.privateState ? options.privateState : ShieldedAccessControlPrivateState.generate(initUser), + privateState = options.privateState + ? options.privateState + : ShieldedAccessControlPrivateState.generate(initUser), witnesses = ShieldedAccessControlWitnesses(), coinPK = '0'.repeat(64), address = sampleContractAddress(), } = options; - this.contract = new MockShieldedAccessControl(witnesses); + this.contract = + new MockShieldedAccessControl( + witnesses, + ); this.stateManager = new SimulatorStateManager( this.contract, @@ -81,7 +90,10 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< ); this.contractAddress = this.circuitContext.transactionContext.address; this._witnesses = witnesses; - this.contract = new MockShieldedAccessControl(this._witnesses); + this.contract = + new MockShieldedAccessControl( + this._witnesses, + ); } get circuitContext() { @@ -96,12 +108,15 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< return ledger(this.circuitContext.transactionContext.state); } - getWitnessContext(): WitnessContext { + getWitnessContext(): WitnessContext< + Ledger, + ShieldedAccessControlPrivateState + > { return { ledger: this.getPublicState(), privateState: this.getPrivateState(), - contractAddress: this.contractAddress - } + contractAddress: this.contractAddress, + }; } /** @@ -129,7 +144,9 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< * @returns A proxy object exposing pure circuit functions without requiring explicit context. */ protected get pureCircuit(): ContextlessCircuits< - ExtractPureCircuits>, + ExtractPureCircuits< + MockShieldedAccessControl + >, ShieldedAccessControlPrivateState > { if (!this._pureCircuitProxy) { @@ -150,7 +167,9 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< * @returns A proxy object exposing impure circuit functions without requiring explicit context management. */ protected get impureCircuit(): ContextlessCircuits< - ExtractImpureCircuits>, + ExtractImpureCircuits< + MockShieldedAccessControl + >, ShieldedAccessControlPrivateState > { if (!this._impureCircuitProxy) { @@ -193,10 +212,15 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< return this._witnesses; } - public set witnesses(newWitnesses: ReturnType) { + public set witnesses(newWitnesses: ReturnType< + typeof ShieldedAccessControlWitnesses + >) { this.resetCircuitProxies(); this._witnesses = newWitnesses; - this.contract = new MockShieldedAccessControl(this._witnesses); + this.contract = + new MockShieldedAccessControl( + this._witnesses, + ); } public overrideWitness( @@ -214,7 +238,10 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< * The full commitment is: `SHA256(SHA256(pk, nonce), instanceSalt, counter, domain)`. * @returns The current owner's commitment. */ - public hasRole(roleId: Uint8Array, account: Either): Role { + public hasRole( + roleId: Uint8Array, + account: Either, + ): Role { return this.circuits.impure.hasRole(roleId, account); } @@ -232,7 +259,10 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< * It will not be possible to call `assertOnlyOnwer` circuits anymore. * Can only be called by the current owner. */ - public _checkRole(roleId: Uint8Array, account: Either) { + public _checkRole( + roleId: Uint8Array, + account: Either, + ) { this.circuits.impure._checkRole(roleId, account); } @@ -254,7 +284,10 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< * @param nonce - A private nonce to scope the commitment. * @returns The computed owner ID. */ - public grantRole(roleId: Uint8Array, account: Either) { + public grantRole( + roleId: Uint8Array, + account: Either, + ) { this.circuits.impure.grantRole(roleId, account); } @@ -263,7 +296,10 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< * enforcing permission checks on the caller. * @param newOwnerId - The unique identifier of the new owner calculated by `SHA256(pk, nonce)`. */ - public revokeRole(roleId: Uint8Array, account: Either) { + public revokeRole( + roleId: Uint8Array, + account: Either, + ) { this.circuits.impure.revokeRole(roleId, account); } @@ -272,7 +308,10 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< * enforcing permission checks on the caller. * @param newOwnerId - The unique identifier of the new owner calculated by `SHA256(pk, nonce)`. */ - public renounceRole(roleId: Uint8Array, callerConfirmation: Either) { + public renounceRole( + roleId: Uint8Array, + callerConfirmation: Either, + ) { this.circuits.impure.renounceRole(roleId, callerConfirmation); } @@ -290,7 +329,10 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< * enforcing permission checks on the caller. * @param newOwnerId - The unique identifier of the new owner calculated by `SHA256(pk, nonce)`. */ - public _grantRole(roleId: Uint8Array, account: Either): Boolean { + public _grantRole( + roleId: Uint8Array, + account: Either, + ): boolean { return this.circuits.impure._grantRole(roleId, account); } @@ -299,7 +341,10 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< * enforcing permission checks on the caller. * @param newOwnerId - The unique identifier of the new owner calculated by `SHA256(pk, nonce)`. */ - public _revokeRole(roleId: Uint8Array, account: Either): Boolean { + public _revokeRole( + roleId: Uint8Array, + account: Either, + ): boolean { return this.circuits.impure._revokeRole(roleId, account); } @@ -314,7 +359,10 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< newNonce: Buffer, ): ShieldedAccessControlPrivateState => { const currentState = this.stateManager.getContext().currentPrivateState; - const updatedState = { ...currentState, roles: { ...currentState.roles } } + const updatedState = { + ...currentState, + roles: { ...currentState.roles }, + }; const roleString = Buffer.from(roleId).toString('hex'); updatedState.roles[roleString] = newNonce; this.stateManager.updatePrivateState(updatedState); @@ -327,7 +375,9 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< */ getCurrentSecretNonce: (roleId: Uint8Array): Uint8Array => { const roleString = Buffer.from(roleId).toString('hex'); - return this.stateManager.getContext().currentPrivateState.roles[roleString]; + return this.stateManager.getContext().currentPrivateState.roles[ + roleString + ]; }, }; diff --git a/contracts/src/access/test/utils/address.ts b/contracts/src/access/test/utils/address.ts index 0cd6f6a2..b5f184f4 100644 --- a/contracts/src/access/test/utils/address.ts +++ b/contracts/src/access/test/utils/address.ts @@ -64,27 +64,31 @@ export const createEitherTestContractAddress = (str: string) => ({ const baseGeneratePubKeyPair = ( str: string, asEither: boolean, - asPK: boolean + asPK: boolean, ): [ - string, - ( - | Compact.ZswapCoinPublicKey - | Compact.Either - ), - ] => { + string, + ( + | Compact.ZswapCoinPublicKey + | Compact.Either + ), +] => { const pk = toHexPadded(str); if (asEither && asPK) { - return [pk, createEitherTestUser(str)] - } else if (asEither && !asPK) { - return [pk, createEitherTestContractAddress(str)] + return [pk, createEitherTestUser(str)]; + } + if (asEither && !asPK) { + return [pk, createEitherTestContractAddress(str)]; } return [pk, encodeToPK(str)]; }; export const generatePubKeyPair = (str: string) => - baseGeneratePubKeyPair(str, false, false) as [string, Compact.ZswapCoinPublicKey]; + baseGeneratePubKeyPair(str, false, false) as [ + string, + Compact.ZswapCoinPublicKey, + ]; export const generateEitherPubKeyPair = (str: string, asPK = true) => baseGeneratePubKeyPair(str, true, asPK) as [ @@ -107,10 +111,12 @@ export const ZERO_ADDRESS = { right: { bytes: zeroUint8Array() }, }; -export const eitherToBytes = (account: Compact.Either) => { +export const eitherToBytes = ( + account: Compact.Either, +) => { if (account.is_left) { return account.left.bytes; } return account.right.bytes; -} \ No newline at end of file +}; diff --git a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts index 7de12adb..f36a1303 100644 --- a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts @@ -1,18 +1,29 @@ import { getRandomValues } from 'node:crypto'; -import { CompactTypeVector, CompactTypeBytes, persistentHash, type WitnessContext, convert_bigint_to_Uint8Array } from '@midnight-ntwrk/compact-runtime'; -import type { Ledger, MerkleTreePath, Either, ZswapCoinPublicKey, ContractAddress } from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; +import { + CompactTypeBytes, + CompactTypeVector, + convert_bigint_to_Uint8Array, + persistentHash, + type WitnessContext, +} from '@midnight-ntwrk/compact-runtime'; +import type { + ContractAddress, + Either, + Ledger, + MerkleTreePath, + ZswapCoinPublicKey, +} from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; import { eitherToBytes } from '../test/utils/address'; const MERKLE_TREE_DEPTH = 2 ** 10; -const DOMAIN = new TextEncoder().encode("ShieldedAccessControl:shield:"); +const DOMAIN = new TextEncoder().encode('ShieldedAccessControl:shield:'); -export function fmtHexString(bytes: String | Uint8Array): string { +export function fmtHexString(bytes: string | Uint8Array): string { if (bytes instanceof String) { - return `${bytes.slice(0, 4)}...${bytes.slice(-4)}` - } else { - const buffStr = Buffer.from(bytes).toString('hex'); - return `${buffStr.slice(0, 4)}...${buffStr.slice(-4)}`; + return `${bytes.slice(0, 4)}...${bytes.slice(-4)}`; } + const buffStr = Buffer.from(bytes).toString('hex'); + return `${buffStr.slice(0, 4)}...${buffStr.slice(-4)}`; } /** @@ -25,9 +36,18 @@ export interface IShieldedAccessControlWitnesses

{ * @param context - The witness context containing the private state. * @returns A tuple of the private state and the secret nonce as a Uint8Array. */ - wit_secretNonce(context: WitnessContext, roleId: Uint8Array): [P, Uint8Array]; - wit_getRoleCommitmentPath(context: WitnessContext, roleCommitment: Uint8Array): [P, MerkleTreePath]; - wit_getRoleIndex(context: WitnessContext, roleId: Uint8Array): [P, bigint]; + wit_secretNonce( + context: WitnessContext, + roleId: Uint8Array, + ): [P, Uint8Array]; + wit_getRoleCommitmentPath( + context: WitnessContext, + roleCommitment: Uint8Array, + ): [P, MerkleTreePath]; + wit_getRoleIndex( + context: WitnessContext, + roleId: Uint8Array, + ): [P, bigint]; } type RoleId = string; @@ -38,8 +58,8 @@ type SecretNonce = Uint8Array; */ export type ShieldedAccessControlPrivateState = { /** @description A 32-byte secret nonce used as a privacy additive. */ - roles: Record, - account: Either + roles: Record; + account: Either; }; /** @@ -50,9 +70,14 @@ export const ShieldedAccessControlPrivateState = { * @description Generates a new private state with a random secret nonce and a default roleId of 0. * @returns A fresh ShieldedAccessControlPrivateState instance. */ - generate: (account: Either): ShieldedAccessControlPrivateState => { + generate: ( + account: Either, + ): ShieldedAccessControlPrivateState => { const defaultRoleId: string = Buffer.alloc(32).toString('hex'); - const privateState: ShieldedAccessControlPrivateState = { roles: {}, account }; + const privateState: ShieldedAccessControlPrivateState = { + roles: {}, + account, + }; privateState.roles[defaultRoleId] = getRandomValues(Buffer.alloc(32)); return privateState; }, @@ -71,33 +96,56 @@ export const ShieldedAccessControlPrivateState = { * const privateState = ShieldedAccessControlPrivateState.withNonce(deterministicNonce); * ``` */ - withRoleAndNonce: (account: Either, roleId: Buffer, nonce: Buffer): ShieldedAccessControlPrivateState => { + withRoleAndNonce: ( + account: Either, + roleId: Buffer, + nonce: Buffer, + ): ShieldedAccessControlPrivateState => { const roleString = roleId.toString('hex'); - const privateState: ShieldedAccessControlPrivateState = { roles: {}, account }; + const privateState: ShieldedAccessControlPrivateState = { + roles: {}, + account, + }; privateState.roles[roleString] = nonce; return privateState; }, - setRole: (privateState: ShieldedAccessControlPrivateState, roleId: Buffer, nonce: Buffer): ShieldedAccessControlPrivateState => { + setRole: ( + privateState: ShieldedAccessControlPrivateState, + roleId: Buffer, + nonce: Buffer, + ): ShieldedAccessControlPrivateState => { const roleString = roleId.toString('hex'); privateState.roles[roleString] = nonce; return privateState; }, - getRoleCommitmentPath: (ledger: Ledger, roleCommitment: Uint8Array): MerkleTreePath => { - const path = ledger.ShieldedAccessControl__operatorRoles.findPathForLeaf(roleCommitment); + getRoleCommitmentPath: ( + ledger: Ledger, + roleCommitment: Uint8Array, + ): MerkleTreePath => { + const path = + ledger.ShieldedAccessControl__operatorRoles.findPathForLeaf( + roleCommitment, + ); const defaultPath: MerkleTreePath = { leaf: new Uint8Array(32), path: Array.from({ length: 10 }, () => ({ sibling: { field: 0n }, goes_left: false, - })) - } + })), + }; return path ? path : defaultPath; }, // If index cannot be found in MT return _currentMTIndex - getRoleIndex: ({ ledger, privateState }: WitnessContext, roleId: Uint8Array): bigint => { + getRoleIndex: ( + { + ledger, + privateState, + }: WitnessContext, + roleId: Uint8Array, + ): bigint => { const roleIdString = Buffer.from(roleId).toString('hex'); // Iterate over each MT to determine if commitment exists for (let i = 0; i < MERKLE_TREE_DEPTH; i++) { @@ -105,14 +153,23 @@ export const ShieldedAccessControlPrivateState = { const bIndex = convert_bigint_to_Uint8Array(32, BigInt(i)); const bAccount = eitherToBytes(privateState.account); const bNonce = privateState.roles[roleIdString]; - const commitment = persistentHash(rt_type, [roleId, bAccount, bNonce, bIndex, DOMAIN]); + const commitment = persistentHash(rt_type, [ + roleId, + bAccount, + bNonce, + bIndex, + DOMAIN, + ]); try { - ledger.ShieldedAccessControl__operatorRoles.pathForLeaf(BigInt(i), commitment); + ledger.ShieldedAccessControl__operatorRoles.pathForLeaf( + BigInt(i), + commitment, + ); return BigInt(i); } catch (e: unknown) { if (e instanceof Error) { - const [msg, index] = e.message.split(":"); - if (msg === "invalid index into sparse merkle tree") { + const [msg, index] = e.message.split(':'); + if (msg === 'invalid index into sparse merkle tree') { // console.log(`role ${fmtHexString(roleIdString)} with commitment ${fmtHexString(commitment)} not found at index ${index}`); } else { throw e; @@ -135,21 +192,30 @@ export const ShieldedAccessControlWitnesses = (): IShieldedAccessControlWitnesses => ({ wit_secretNonce( context: WitnessContext, - roleId: Uint8Array + roleId: Uint8Array, ): [ShieldedAccessControlPrivateState, Uint8Array] { const roleString = Buffer.from(roleId).toString('hex'); return [context.privateState, context.privateState.roles[roleString]]; }, wit_getRoleCommitmentPath( context: WitnessContext, - roleCommitment: Uint8Array + roleCommitment: Uint8Array, ): [ShieldedAccessControlPrivateState, MerkleTreePath] { - return [context.privateState, ShieldedAccessControlPrivateState.getRoleCommitmentPath(context.ledger, roleCommitment)]; + return [ + context.privateState, + ShieldedAccessControlPrivateState.getRoleCommitmentPath( + context.ledger, + roleCommitment, + ), + ]; }, wit_getRoleIndex( context: WitnessContext, - roleId: Uint8Array + roleId: Uint8Array, ): [ShieldedAccessControlPrivateState, bigint] { - return [context.privateState, ShieldedAccessControlPrivateState.getRoleIndex(context, roleId)]; + return [ + context.privateState, + ShieldedAccessControlPrivateState.getRoleIndex(context, roleId), + ]; }, - }); \ No newline at end of file + }); From 061ac4a124393a129c3df50f0b2adc152419f99e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Fri, 12 Sep 2025 16:00:40 -0400 Subject: [PATCH 192/202] WIP --- .../src/access/ShieldedAccessControl.compact | 4 +- .../access/test/ShieldedAccessControl.test.ts | 108 +++++++++++++++--- .../ShieldedAccessControlWitnesses.ts | 21 ++-- 3 files changed, 107 insertions(+), 26 deletions(-) diff --git a/contracts/src/access/ShieldedAccessControl.compact b/contracts/src/access/ShieldedAccessControl.compact index 95abf9d2..a5a4c732 100644 --- a/contracts/src/access/ShieldedAccessControl.compact +++ b/contracts/src/access/ShieldedAccessControl.compact @@ -116,7 +116,7 @@ module ShieldedAccessControl { witness wit_secretNonce(roleId: Bytes<32>): Bytes<32>; - witness wit_getRoleIndex(roleId: Bytes<32>): Uint<64>; + witness wit_getRoleIndex(roleId: Bytes<32> , account: Either): Uint<64>; export struct Role { isApproved: Boolean; @@ -154,7 +154,7 @@ module ShieldedAccessControl { assert(!Utils_isContractAddress(account), "ShieldedAccessControl: contract address roles are not yet supported"); const nonce = wit_secretNonce(roleId); - const index = wit_getRoleIndex(roleId); + const index = wit_getRoleIndex(roleId, account); const computedCommitment = persistentHash>>([roleId, account.left.bytes, nonce, index as Field as Bytes<32>, pad(32, "ShieldedAccessControl:shield:")]); const authPath = wit_getRoleCommitmentPath(computedCommitment); diff --git a/contracts/src/access/test/ShieldedAccessControl.test.ts b/contracts/src/access/test/ShieldedAccessControl.test.ts index 73b5e4e3..ac20d8f1 100644 --- a/contracts/src/access/test/ShieldedAccessControl.test.ts +++ b/contracts/src/access/test/ShieldedAccessControl.test.ts @@ -35,7 +35,8 @@ const Z_OPERATOR_LIST = [Z_OPERATOR_1, Z_OPERATOR_2, Z_OPERATOR_3]; // Constants const BAD_NONCE = Buffer.alloc(32, 'BAD_NONCE'); -const DOMAIN = 'ShieldedAccessControl:shield:'; +const DOMAIN = new Uint8Array(32); +new TextEncoder().encodeInto('ShieldedAccessControl:shield:', DOMAIN); const INIT_COUNTER = 0n; const EMPTY_ROOT = { field: 0n }; @@ -49,11 +50,6 @@ const CUSTOM_ADMIN_ROLE = convert_bigint_to_Uint8Array(32, 4n); const UNINITIALIZED_ROLE = convert_bigint_to_Uint8Array(32, 5n); const OPERATOR_ROLE_LIST = [OPERATOR_ROLE_1, OPERATOR_ROLE_2, OPERATOR_ROLE_3]; -const operatorTypes = [ - ['contract', Z_OPERATOR_CONTRACT], - ['pubkey', Z_OPERATOR_1], -] as const; - // Role to string const DEFAULT_ADMIN_ROLE_TO_STRING = Buffer.from(DEFAULT_ADMIN_ROLE).toString('hex'); @@ -88,14 +84,13 @@ const buildCommitment = ( const rt_type = new CompactTypeVector(5, new CompactTypeBytes(32)); const bAccount = utils.eitherToBytes(account); const bIndex = convert_bigint_to_Uint8Array(32, index); - const bDomain = new TextEncoder().encode(DOMAIN); const commitment = persistentHash(rt_type, [ roleId, bAccount, nonce, bIndex, - bDomain, + DOMAIN, ]); return commitment; @@ -214,6 +209,7 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.witnesses.wit_getRoleIndex( shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, + Z_ADMIN ); expect(witnessCalculatedIndex).toBe(INIT_COUNTER); } else { @@ -226,6 +222,7 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.witnesses.wit_getRoleIndex( shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, + Z_ADMIN ); expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); } @@ -258,6 +255,7 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.witnesses.wit_getRoleIndex( shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, + Z_ADMIN ); expect(witnessCalculatedPath).not.toEqual(truePath); } @@ -343,6 +341,7 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.witnesses.wit_getRoleIndex( shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, + Z_ADMIN ); expect(witnessCalculatedIndex).toBe(INIT_COUNTER); } else { @@ -355,6 +354,7 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.witnesses.wit_getRoleIndex( shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, + Z_ADMIN ); expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); } @@ -387,6 +387,7 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.witnesses.wit_getRoleIndex( shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, + Z_ADMIN ); expect(witnessCalculatedPath).not.toEqual(truePath); } @@ -478,13 +479,6 @@ describe('ShieldedAccessControl', () => { ); }); - it('should throw if role has been revoked', () => { - shieldedAccessControl._revokeRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); - expect(() => { - shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); - }).toThrow('ShieldedAccessControl: role access has been revoked'); - }); - it('should return correct role commitment', () => { const expCommitment = buildCommitment( DEFAULT_ADMIN_ROLE, @@ -551,6 +545,7 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.witnesses.wit_getRoleIndex( shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, + Z_ADMIN ); expect(witnessCalculatedIndex).toBe(INIT_COUNTER); } else { @@ -563,6 +558,7 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.witnesses.wit_getRoleIndex( shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, + Z_ADMIN ); expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); } @@ -595,6 +591,7 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.witnesses.wit_getRoleIndex( shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, + Z_ADMIN ); expect(witnessCalculatedPath).not.toEqual(truePath); } @@ -636,6 +633,7 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.witnesses.wit_getRoleIndex( shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, + Z_ADMIN ); expect(witnessCalculatedIndex).toBe(INIT_COUNTER); } else { @@ -648,6 +646,7 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.witnesses.wit_getRoleIndex( shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, + Z_ADMIN ); expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); } @@ -680,6 +679,7 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.witnesses.wit_getRoleIndex( shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, + Z_ADMIN ); expect(witnessCalculatedPath).not.toEqual(truePath); } @@ -712,6 +712,7 @@ describe('ShieldedAccessControl', () => { shieldedAccessControl.witnesses.wit_getRoleIndex( shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, + Z_ADMIN ); expect(witnessCalculatedIndex).toBe(INIT_COUNTER); @@ -884,6 +885,15 @@ describe('ShieldedAccessControl', () => { Z_OPERATOR_LIST[j], ); expect(role.isApproved).toBe(true); + + + expect( + shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ), + ).toBeDefined(); } } }); @@ -902,16 +912,80 @@ describe('ShieldedAccessControl', () => { }); }); - describe('revokeRole', () => { + describe.only('revokeRole', () => { beforeEach(() => { + console.log("TEST - Current MT Index", shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex.toString()); shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + console.log("TEST - Current MT Index", shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex.toString()); shieldedAccessControl.privateState.injectSecretNonce( OPERATOR_ROLE_1, OPERATOR_ROLE_1_SECRET_NONCE, ); + shieldedAccessControl._grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); + console.log("TEST - Current MT Index", shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex.toString()); shieldedAccessControl.callerCtx.setCaller(ADMIN); }); - it.todo('admin should revoke role', () => {}); + it('admin should revoke role', () => { + expect(shieldedAccessControl.hasRole(OPERATOR_ROLE_1, Z_OPERATOR_1).isApproved).toBe(true); + shieldedAccessControl.revokeRole(OPERATOR_ROLE_1, Z_OPERATOR_1); + expect(shieldedAccessControl.hasRole(OPERATOR_ROLE_1, Z_OPERATOR_1).isApproved).toBe(false); + }); + + it.only('commitment should be in nullifier set', () => { + console.log("TEST - Current MT Index", shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex.toString()); + const [, opRoleIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), OPERATOR_ROLE_1, Z_OPERATOR_1); + const [, adminRoleIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, Z_ADMIN); + console.log("OPERATOR INDEX ", opRoleIndex.toString(10)); + console.log("ADMIN INDEX ", adminRoleIndex.toString(10)); + const expCommitmentOp = buildCommitment(OPERATOR_ROLE_1, Z_OPERATOR_1, OPERATOR_ROLE_1_SECRET_NONCE, opRoleIndex); + const pathToOp = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(expCommitmentOp); + const pathToAdmin = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); + //console.log("PATH TO OP ", pathToOp); + //console.log("PATH TO ADMIN ", pathToAdmin); + + //console.log("EXPECTED COMMITMENT ", expCommitmentOp); + const contractCommit = shieldedAccessControl.hasRole(OPERATOR_ROLE_1, Z_OPERATOR_1).roleCommitment; + //console.log("CONTRACT COMMITMENT ", contractCommit); + + shieldedAccessControl.revokeRole(OPERATOR_ROLE_1, Z_OPERATOR_1); + expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__roleCommitmentNullifiers.isEmpty()).toBe(false); + expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__roleCommitmentNullifiers.member(expCommitmentOp)).toBe(true); + }); + + it('admin should revoke multiple roles', () => { + const expCommitment = buildCommitment(OPERATOR_ROLE_1, Z_OPERATOR_1, OPERATOR_ROLE_1_SECRET_NONCE, 1n); + shieldedAccessControl.revokeRole(OPERATOR_ROLE_1, Z_OPERATOR_1); + expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__roleCommitmentNullifiers.member(expCommitment)).toBe(true); + + for (let i = 1; i < OPERATOR_ROLE_LIST.length; i++) { + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_LIST[i], + OPERATOR_ROLE_SECRET_NONCES[i], + ); + for (let j = 1; j < Z_OPERATOR_LIST.length; j++) { + shieldedAccessControl._grantRole( + OPERATOR_ROLE_LIST[i], + Z_OPERATOR_LIST[j], + ); + const expCommitment = buildCommitment(OPERATOR_ROLE_LIST[i], Z_OPERATOR_LIST[j], OPERATOR_ROLE_SECRET_NONCES[i], BigInt(1 + i)); + shieldedAccessControl.revokeRole(OPERATOR_ROLE_LIST[i], Z_OPERATOR_LIST[j]); + expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__roleCommitmentNullifiers.member(expCommitment)).toBe(true); + } + } + }); + + it('should throw if non-admin operator revokes role', () => { + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_1, + OPERATOR_ROLE_1_SECRET_NONCE, + ); + shieldedAccessControl._grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); + + shieldedAccessControl.callerCtx.setCaller(OPERATOR_1); + expect(() => { + shieldedAccessControl.revokeRole(OPERATOR_ROLE_1, Z_UNAUTHORIZED); + }).toThrow('ShieldedAccessControl: unauthorized account'); + }); }); }); diff --git a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts index f36a1303..425f3e29 100644 --- a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts @@ -16,14 +16,16 @@ import type { import { eitherToBytes } from '../test/utils/address'; const MERKLE_TREE_DEPTH = 2 ** 10; -const DOMAIN = new TextEncoder().encode('ShieldedAccessControl:shield:'); +const DOMAIN = new Uint8Array(32); +new TextEncoder().encodeInto('ShieldedAccessControl:shield:', DOMAIN); export function fmtHexString(bytes: string | Uint8Array): string { if (bytes instanceof String) { return `${bytes.slice(0, 4)}...${bytes.slice(-4)}`; + } else { + const buffStr = Buffer.from(bytes as Uint8Array).toString('hex'); + return `${buffStr.slice(0, 4)}...${buffStr.slice(-4)}`; } - const buffStr = Buffer.from(bytes).toString('hex'); - return `${buffStr.slice(0, 4)}...${buffStr.slice(-4)}`; } /** @@ -47,6 +49,7 @@ export interface IShieldedAccessControlWitnesses

{ wit_getRoleIndex( context: WitnessContext, roleId: Uint8Array, + account: Either ): [P, bigint]; } @@ -145,13 +148,14 @@ export const ShieldedAccessControlPrivateState = { privateState, }: WitnessContext, roleId: Uint8Array, + account: Either ): bigint => { const roleIdString = Buffer.from(roleId).toString('hex'); - // Iterate over each MT to determine if commitment exists + // Iterate over each MT index to determine if commitment exists for (let i = 0; i < MERKLE_TREE_DEPTH; i++) { const rt_type = new CompactTypeVector(5, new CompactTypeBytes(32)); const bIndex = convert_bigint_to_Uint8Array(32, BigInt(i)); - const bAccount = eitherToBytes(privateState.account); + const bAccount = eitherToBytes(account); const bNonce = privateState.roles[roleIdString]; const commitment = persistentHash(rt_type, [ roleId, @@ -170,7 +174,7 @@ export const ShieldedAccessControlPrivateState = { if (e instanceof Error) { const [msg, index] = e.message.split(':'); if (msg === 'invalid index into sparse merkle tree') { - // console.log(`role ${fmtHexString(roleIdString)} with commitment ${fmtHexString(commitment)} not found at index ${index}`); + //console.log(`role ${fmtHexString(roleIdString)} with commitment ${fmtHexString(commitment)} not found at index ${index}`); } else { throw e; } @@ -178,6 +182,8 @@ export const ShieldedAccessControlPrivateState = { } } + console.log("WIT - Commitment DNE, returing MT index ", ledger.ShieldedAccessControl__currentMerkleTreeIndex.toString()); + // If commitment doesn't exist return currentMTIndex // Used for adding roles return ledger.ShieldedAccessControl__currentMerkleTreeIndex; @@ -212,10 +218,11 @@ export const ShieldedAccessControlWitnesses = wit_getRoleIndex( context: WitnessContext, roleId: Uint8Array, + account: Either ): [ShieldedAccessControlPrivateState, bigint] { return [ context.privateState, - ShieldedAccessControlPrivateState.getRoleIndex(context, roleId), + ShieldedAccessControlPrivateState.getRoleIndex(context, roleId, account), ]; }, }); From a98144ef8d1ec992efa662609bdbb5cf5563e438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:50:23 -0400 Subject: [PATCH 193/202] Update contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andrew Fleming Signed-off-by: ⟣ €₥ℵ∪ℓ ⟢ <34749913+emnul@users.noreply.github.com> --- .../src/access/witnesses/ShieldedAccessControlWitnesses.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts index 425f3e29..119ed504 100644 --- a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts @@ -152,7 +152,7 @@ export const ShieldedAccessControlPrivateState = { ): bigint => { const roleIdString = Buffer.from(roleId).toString('hex'); // Iterate over each MT index to determine if commitment exists - for (let i = 0; i < MERKLE_TREE_DEPTH; i++) { + for (let i = 0; i <= ledger.ShieldedAccessControl__currentMerkleTreeIndex; i++) { const rt_type = new CompactTypeVector(5, new CompactTypeBytes(32)); const bIndex = convert_bigint_to_Uint8Array(32, BigInt(i)); const bAccount = eitherToBytes(account); From 9d9c2565dba81eb162ed731c1b3b22228b03af3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Mon, 15 Sep 2025 14:04:26 -0400 Subject: [PATCH 194/202] Optimize loop --- .../access/witnesses/ShieldedAccessControlWitnesses.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts index 425f3e29..6a2a4f14 100644 --- a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts @@ -15,7 +15,7 @@ import type { } from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; import { eitherToBytes } from '../test/utils/address'; -const MERKLE_TREE_DEPTH = 2 ** 10; +const MERKLE_TREE_DEPTH = 2 ** 11 - 1; const DOMAIN = new Uint8Array(32); new TextEncoder().encodeInto('ShieldedAccessControl:shield:', DOMAIN); @@ -151,12 +151,12 @@ export const ShieldedAccessControlPrivateState = { account: Either ): bigint => { const roleIdString = Buffer.from(roleId).toString('hex'); + const bNonce = privateState.roles[roleIdString]; + const rt_type = new CompactTypeVector(5, new CompactTypeBytes(32)); + const bAccount = eitherToBytes(account); // Iterate over each MT index to determine if commitment exists for (let i = 0; i < MERKLE_TREE_DEPTH; i++) { - const rt_type = new CompactTypeVector(5, new CompactTypeBytes(32)); const bIndex = convert_bigint_to_Uint8Array(32, BigInt(i)); - const bAccount = eitherToBytes(account); - const bNonce = privateState.roles[roleIdString]; const commitment = persistentHash(rt_type, [ roleId, bAccount, @@ -174,7 +174,7 @@ export const ShieldedAccessControlPrivateState = { if (e instanceof Error) { const [msg, index] = e.message.split(':'); if (msg === 'invalid index into sparse merkle tree') { - //console.log(`role ${fmtHexString(roleIdString)} with commitment ${fmtHexString(commitment)} not found at index ${index}`); + // console.log(`role ${fmtHexString(roleIdString)} with commitment ${fmtHexString(commitment)} not found at index ${index}`); } else { throw e; } From 5d2da061c36c98f2bc2202a4c888cb858549f765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 16 Sep 2025 22:37:17 -0400 Subject: [PATCH 195/202] Refactor Shielded Design --- .../src/access/ShieldedAccessControl.compact | 181 ++++++++++++------ 1 file changed, 123 insertions(+), 58 deletions(-) diff --git a/contracts/src/access/ShieldedAccessControl.compact b/contracts/src/access/ShieldedAccessControl.compact index a5a4c732..7700e0b0 100644 --- a/contracts/src/access/ShieldedAccessControl.compact +++ b/contracts/src/access/ShieldedAccessControl.compact @@ -116,13 +116,101 @@ module ShieldedAccessControl { witness wit_secretNonce(roleId: Bytes<32>): Bytes<32>; - witness wit_getRoleIndex(roleId: Bytes<32> , account: Either): Uint<64>; + witness wit_getRoleIndex(roleId: Bytes<32> , accountId: Bytes<32>): Uint<64>; export struct Role { isApproved: Boolean; roleCommitment: Bytes<32>; } + /** + * @description Computes the owner commitment from the given `id` and `counter`. + * + * ## Owner ID (`id`) + * The `id` is expected to be computed off-chain as: + * `id = SHA256(pk, nonce)` + * + * - `pk`: The owner's public key. + * - `nonce`: A secret nonce scoped to the instance, ideally rotated with each transfer. + * + * ## Commitment Derivation + * `commitment = SHA256(id, instanceSalt, counter, domain)` + * + * - `id`: See above. + * - `instanceSalt`: A unique per-deployment salt, stored during initialization. + * This prevents commitment collisions across deployments. + * - `counter`: Incremented with each ownership transfer, ensuring uniqueness + * even with repeated `id` values. Cast to `Field` then `Bytes<32>` for hashing. + * - `domain`: Domain separator `"ZOwnablePK:shield:"` (padded to 32 bytes) to prevent + * hash collisions when extending the module or using similar commitment schemes. + * + * @circuitInfo k=14, rows=14853 + * + * Requirements: + * + * - Contract is initialized. + * + * @param {Bytes<32>} id - The unique identifier of the owner calculated by `SHA256(pk, nonce)`. + * @param {Uint<64>} counter - The current counter or round. This increments by `1` + * after every transfer to prevent duplicate commitments given the same `id`. + * @returns {Bytes<32>} The commitment derived from `id` and `counter`. + */ + export circuit _computeRoleCommitment( + accountId: Bytes<32>, + roleId: Bytes<32>, + index: Uint<64>, + ): Bytes<32> { + return persistentHash>>( + [ + accountId, + roleId, + index as Field as Bytes<32>, + pad(32, "ShieldedAccessControl:shield:") + ] + ); + } + + /** + * @description Computes the unique identifier (`id`) of the owner from their + * public key and a secret nonce. + * + * ## ID Derivation + * `id = SHA256(pk, nonce)` + * + * - `pk`: The public key of the caller. This is passed explicitly to allow + * for off-chain derivation, testing, or scenarios where the caller is + * different from the subject of the computation. + * We recommend using an Air-Gapped Public Key. + * - `nonce`: A secret nonce tied to the identity. The generation strategy is + * left to the user, offering different security/convenience trade-offs. + * + * The result is a 32-byte commitment that uniquely identifies the owner. + * This value is later used in owner commitment hashing, + * and acts as a privacy-preserving alternative to a raw public key. + * + * @notice This module allows ownership to be tied to an identity commitment derived + * from a public key and secret nonce. + * While typically used with user public keys, this mechanism may also + * support contract addresses as identifiers in future contract-to-contract + * interactions. Both are treated as 32-byte values (`Bytes<32>`). + * + * Requirements: + * + * - `pk` is not a ContractAddress. + * + * @param {Either} pk - The public key of the identity being committed. + * @param {Bytes<32>} nonce - A private nonce to scope the commitment. + * @returns {Bytes<32>} The computed owner ID. + */ + export pure circuit _computeRoleId( + pk: Either, + nonce: Bytes<32> + ): Bytes<32> { + assert(pk.is_left, "ShieldedAccessControl: contract address owners are not yet supported"); + + return persistentHash>>([pk.left.bytes, nonce]); + } + /** * @description Returns `true` if `account` has been granted `roleId`. * @@ -150,22 +238,17 @@ module ShieldedAccessControl { * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK). * @return {Boolean} - A boolean determining if the account has the specified role.  */ - export circuit hasRole(roleId: Bytes<32>, account: Either): Role { - assert(!Utils_isContractAddress(account), "ShieldedAccessControl: contract address roles are not yet supported"); + export circuit callerHasRole(roleId: Bytes<32>): Role { + const account = ownPublicKey(); const nonce = wit_secretNonce(roleId); - const index = wit_getRoleIndex(roleId, account); - const computedCommitment = persistentHash>>([roleId, account.left.bytes, nonce, index as Field as Bytes<32>, pad(32, "ShieldedAccessControl:shield:")]); - - const authPath = wit_getRoleCommitmentPath(computedCommitment); - const rootMatches = _operatorRoles - .checkRoot(merkleTreePathRoot<10, Bytes<32>>(disclose(authPath))); + const accountId = _computeRoleId(account, nonce); + return getRole(roleId, accountId); + } - if(!_roleCommitmentNullifiers.member(disclose(computedCommitment)) && rootMatches) { - return Role {isApproved: true, roleCommitment: disclose(computedCommitment)}; - } else { - return Role {isApproved: false, roleCommitment: disclose(computedCommitment)}; - } + export hasRole(roleId: Bytes<32>, accountId: Bytes<32>): Boolean { + const roleInfo = getRoleInfo(); + return roleInfo.isApproved; } /** @@ -196,41 +279,22 @@ module ShieldedAccessControl { * @return {[]} - Empty tuple. */ export circuit assertOnlyRole(roleId: Bytes<32>): [] { - _checkRole( - roleId, - left(ownPublicKey()) - ); + const role = callerHasRole(roleId); + assert(role.isApproved, "ShieldedAccessControl: unauthorized account"); } - /** - * @description Reverts if `account` is missing `roleId`. - * - * @circuitInfo k=16, rows=60129 - * - * Requirements: - * - * - An index for the intermediate role commitment produced by SHA256(roleId | account | nonce) - * must exist in the `_roleCommitmentIndex` map. - * - A nullifier for the role commitment produced by SHA256(roleId | account | nonce) - * must not exist in the `_roleCommitmentNullifiers` set. - * - A path for the role commitment produced by SHA256(roleId | account | nonce) must - * exist at `index` in the `_operatorRoles` Merkle tree. - * - * Disclosures: - * - * - The intermediate role commitment produced by SHA256(roleId | account | nonce). - * - The role commitment produced by SHA256(roleId | account | nonce). - * - The Merkle tree path for the role commitment stored at `index` in the `_operatorRoles` - * Merkle tree. - * - The type data of `account` - a ZswapCoinPublicKey or ContractAddress. - * - * @param {Bytes<32>} roleId - The role identifier. - * @param {Either} account - The account to check. - * @return {[]} - Empty tuple. - */ - export circuit _checkRole(roleId: Bytes<32>, account: Either): [] { - const role = hasRole(roleId, account); - assert(role.isApproved, "ShieldedAccessControl: unauthorized account"); + export circuit getRole(roleId: Bytes<32>, accountId: Bytes<32>): Role { + const index = wit_getRoleIndex(roleId, accountId); + const commitment = _computeRoleCommitment(accountId, roleId, index); + const authPath = wit_getRoleCommitmentPath(commitment); + const rootMatches = _operatorRoles + .checkRoot(merkleTreePathRoot<10, Bytes<32>>(disclose(authPath))); + + if(!_roleCommitmentNullifiers.member(disclose(commitment)) && rootMatches) { + return Role {isApproved: true, roleCommitment: disclose(commitment)}; + } else { + return Role {isApproved: false, roleCommitment: disclose(commitment)}; + } } /** @@ -279,9 +343,9 @@ module ShieldedAccessControl { * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {[]} - Empty tuple. */ - export circuit grantRole(roleId: Bytes<32>, account: Either): [] { + export circuit grantRole(roleId: Bytes<32>, accountId: Bytes<32>): [] { assertOnlyRole(getRoleAdmin(roleId)); - _grantRole(roleId, account); + _grantRole(roleId, accountId); } /** @@ -312,9 +376,9 @@ module ShieldedAccessControl { * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {[]} - Empty tuple. */ - export circuit revokeRole(roleId: Bytes<32>, account: Either): [] { + export circuit revokeRole(roleId: Bytes<32>, accountId: Bytes<32>): [] { assertOnlyRole(getRoleAdmin(roleId)); - _revokeRole(roleId, account); + _revokeRole(roleId, accountId); } /** @@ -350,10 +414,11 @@ module ShieldedAccessControl { * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {[]} - Empty tuple. */ - export circuit renounceRole(roleId: Bytes<32>, callerConfirmation: Either): [] { - assert(callerConfirmation == left(ownPublicKey()), "ShieldedAccessControl: bad confirmation"); + export circuit renounceRole(roleId: Bytes<32>, accountId: Bytes<32>): [] { + const nonce = wit_secretNonce(roleId); + assert(accountId, _computeRoleId(ownPublicKey(), nonce), "ShieldedAccessControl: bad confirmation"); - _revokeRole(roleId, callerConfirmation); + _revokeRole(roleId, accountId); } /** @@ -398,8 +463,8 @@ module ShieldedAccessControl { * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {Boolean} roleGranted - A boolean indicating if `roleId` was granted. */ - export circuit _grantRole(roleId: Bytes<32>, account: Either): Boolean { - const role = hasRole(roleId, account); + export circuit _grantRole(roleId: Bytes<32>, accountId: Bytes<32>): Boolean { + const role = getRole(roleId, accountId); if (role.isApproved) { return false; } @@ -438,8 +503,8 @@ module ShieldedAccessControl { * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. */ - export circuit _revokeRole(roleId: Bytes<32>, account: Either): Boolean { - const role = hasRole(roleId, account); + export circuit _revokeRole(roleId: Bytes<32>, accountId: Bytes<32>): Boolean { + const role = getRole(roleId, account); if (!role.isApproved) { return false; } From 713d3d426d20bd531ff479c02d8915127006d7ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 16 Sep 2025 22:38:25 -0400 Subject: [PATCH 196/202] Move resetProxy call to end of fn --- .../access/test/simulators/ShieldedAccessControlSimulator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts index 69e25938..170c41ec 100644 --- a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts +++ b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts @@ -215,12 +215,12 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< public set witnesses(newWitnesses: ReturnType< typeof ShieldedAccessControlWitnesses >) { - this.resetCircuitProxies(); this._witnesses = newWitnesses; this.contract = new MockShieldedAccessControl( this._witnesses, ); + this.resetCircuitProxies(); } public overrideWitness( @@ -388,6 +388,6 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< */ setCaller: (caller: CoinPublicKey) => { this.callerOverride = caller; - }, + } }; } From 8a5fbba71e0d1f6ad7e448d3e36e3caf32b86537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 16 Sep 2025 22:38:52 -0400 Subject: [PATCH 197/202] Fixes incorrect indexing bug --- .../witnesses/ShieldedAccessControlWitnesses.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts index 8f557a4c..36bf6ac2 100644 --- a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts @@ -15,7 +15,6 @@ import type { } from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; import { eitherToBytes } from '../test/utils/address'; -const MERKLE_TREE_DEPTH = 2 ** 11 - 1; const DOMAIN = new Uint8Array(32); new TextEncoder().encodeInto('ShieldedAccessControl:shield:', DOMAIN); @@ -156,7 +155,6 @@ export const ShieldedAccessControlPrivateState = { const bAccount = eitherToBytes(account); // Iterate over each MT index to determine if commitment exists for (let i = 0; i <= ledger.ShieldedAccessControl__currentMerkleTreeIndex; i++) { - const rt_type = new CompactTypeVector(5, new CompactTypeBytes(32)); const bIndex = convert_bigint_to_Uint8Array(32, BigInt(i)); const commitment = persistentHash(rt_type, [ roleId, @@ -166,11 +164,14 @@ export const ShieldedAccessControlPrivateState = { DOMAIN, ]); try { - ledger.ShieldedAccessControl__operatorRoles.pathForLeaf( - BigInt(i), + const index = BigInt(i); + const pathForLeaf = ledger.ShieldedAccessControl__operatorRoles.pathForLeaf( + index, commitment, ); - return BigInt(i); + if (pathForLeaf.leaf === commitment) { + return index; + } } catch (e: unknown) { if (e instanceof Error) { const [msg, index] = e.message.split(':'); From 2fce76c8c73350dc8d492bb3869df1b51ff59a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Tue, 16 Sep 2025 22:39:20 -0400 Subject: [PATCH 198/202] WIP refactor tests --- .../access/test/ShieldedAccessControl.test.ts | 991 ---------------- .../access/test/ShieldedAccessControl_OLD.ts | 1053 +++++++++++++++++ 2 files changed, 1053 insertions(+), 991 deletions(-) create mode 100644 contracts/src/access/test/ShieldedAccessControl_OLD.ts diff --git a/contracts/src/access/test/ShieldedAccessControl.test.ts b/contracts/src/access/test/ShieldedAccessControl.test.ts index ac20d8f1..e69de29b 100644 --- a/contracts/src/access/test/ShieldedAccessControl.test.ts +++ b/contracts/src/access/test/ShieldedAccessControl.test.ts @@ -1,991 +0,0 @@ -import { - CompactTypeBytes, - CompactTypeVector, - convert_bigint_to_Uint8Array, - persistentHash, - type WitnessContext, -} from '@midnight-ntwrk/compact-runtime'; -import { beforeEach, describe, expect, it } from 'vitest'; -import type { - ContractAddress, - Either, - Ledger, - MerkleTreePath, - ShieldedAccessControl_Role as Role, - ZswapCoinPublicKey, -} from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; -import { ShieldedAccessControlPrivateState } from '../witnesses/ShieldedAccessControlWitnesses.js'; -import { ShieldedAccessControlSimulator } from './simulators/ShieldedAccessControlSimulator.js'; -import * as utils from './utils/address.js'; - -// PKs -const [ADMIN, Z_ADMIN] = utils.generateEitherPubKeyPair('ADMIN'); -const [UNAUTHORIZED, Z_UNAUTHORIZED] = - utils.generateEitherPubKeyPair('UNAUTHORIZED'); -const [CUSTOM_ADMIN, Z_CUSTOM_ADMIN] = - utils.generateEitherPubKeyPair('CUSTOM_ADMIN'); -const [OPERATOR_1, Z_OPERATOR_1] = utils.generateEitherPubKeyPair('OPERATOR_1'); -const [OPERATOR_2, Z_OPERATOR_2] = utils.generateEitherPubKeyPair('OPERATOR_2'); -const [OPERATOR_3, Z_OPERATOR_3] = utils.generateEitherPubKeyPair('OPERATOR_3'); -const [OPERATOR_CONTRACT, Z_OPERATOR_CONTRACT] = utils.generateEitherPubKeyPair( - 'OPERATOR_CONTRACT', - false, -); -const Z_OPERATOR_LIST = [Z_OPERATOR_1, Z_OPERATOR_2, Z_OPERATOR_3]; - -// Constants -const BAD_NONCE = Buffer.alloc(32, 'BAD_NONCE'); -const DOMAIN = new Uint8Array(32); -new TextEncoder().encodeInto('ShieldedAccessControl:shield:', DOMAIN); -const INIT_COUNTER = 0n; - -const EMPTY_ROOT = { field: 0n }; - -// Roles -const DEFAULT_ADMIN_ROLE = utils.zeroUint8Array(); -const OPERATOR_ROLE_1 = convert_bigint_to_Uint8Array(32, 1n); -const OPERATOR_ROLE_2 = convert_bigint_to_Uint8Array(32, 2n); -const OPERATOR_ROLE_3 = convert_bigint_to_Uint8Array(32, 3n); -const CUSTOM_ADMIN_ROLE = convert_bigint_to_Uint8Array(32, 4n); -const UNINITIALIZED_ROLE = convert_bigint_to_Uint8Array(32, 5n); -const OPERATOR_ROLE_LIST = [OPERATOR_ROLE_1, OPERATOR_ROLE_2, OPERATOR_ROLE_3]; - -// Role to string -const DEFAULT_ADMIN_ROLE_TO_STRING = - Buffer.from(DEFAULT_ADMIN_ROLE).toString('hex'); - -const ADMIN_SECRET_NONCE = Buffer.alloc(32, 'ADMIN_SECRET_NONCE'); -const OPERATOR_ROLE_1_SECRET_NONCE = Buffer.alloc( - 32, - 'OPERATOR_ROLE_1_SECRET_NONCE', -); -const OPERATOR_ROLE_2_SECRET_NONCE = Buffer.alloc( - 32, - 'OPERATOR_ROLE_2_SECRET_NONCE', -); -const OPERATOR_ROLE_3_SECRET_NONCE = Buffer.alloc( - 32, - 'OPERATOR_ROLE_3_SECRET_NONCE', -); -const OPERATOR_ROLE_SECRET_NONCES = [ - OPERATOR_ROLE_1_SECRET_NONCE, - OPERATOR_ROLE_2_SECRET_NONCE, - OPERATOR_ROLE_3_SECRET_NONCE, -]; -let shieldedAccessControl: ShieldedAccessControlSimulator; - -// Helpers -const buildCommitment = ( - roleId: Uint8Array, - account: Either, - nonce: Uint8Array, - index: bigint, -): Uint8Array => { - const rt_type = new CompactTypeVector(5, new CompactTypeBytes(32)); - const bAccount = utils.eitherToBytes(account); - const bIndex = convert_bigint_to_Uint8Array(32, index); - - const commitment = persistentHash(rt_type, [ - roleId, - bAccount, - nonce, - bIndex, - DOMAIN, - ]); - - return commitment; -}; - -const EXP_DEFAULT_ADMIN_COMMITMENT = buildCommitment( - DEFAULT_ADMIN_ROLE, - Z_ADMIN, - ADMIN_SECRET_NONCE, - INIT_COUNTER, -); - -function RETURN_BAD_INDEX( - context: WitnessContext, - roleId: Uint8Array, -): [ShieldedAccessControlPrivateState, bigint] { - return [context.privateState, 1023n]; -} - -function RETURN_BAD_PATH( - context: WitnessContext, - roleCommitment: Uint8Array, -): [ShieldedAccessControlPrivateState, MerkleTreePath] { - const defaultPath: MerkleTreePath = { - leaf: new Uint8Array(32), - path: Array.from({ length: 10 }, () => ({ - sibling: { field: 0n }, - goes_left: false, - })), - }; - return [context.privateState, defaultPath]; -} - -type RoleAndNonce = { - roleId: string; - nonce: Buffer; -}; - -describe('ShieldedAccessControl', () => { - beforeEach(() => { - // Create private state object and generate nonce - const PS = ShieldedAccessControlPrivateState.withRoleAndNonce( - Z_ADMIN, - Buffer.from(DEFAULT_ADMIN_ROLE), - ADMIN_SECRET_NONCE, - ); - // Init contract for user with PS - shieldedAccessControl = new ShieldedAccessControlSimulator(Z_ADMIN, { - privateState: PS, - }); - }); - - describe('checked circuits should fail for authorized caller with invalid witness values', () => { - beforeEach(() => { - shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); - shieldedAccessControl.callerCtx.setCaller(ADMIN); - }); - - type FailingCircuits = [ - method: keyof ShieldedAccessControlSimulator, - isValidNonce: boolean, - isValidIndex: boolean, - isValidPath: boolean, - args: unknown[], - ]; - const checkedCircuits: FailingCircuits[] = [ - ['assertOnlyRole', false, true, true, [DEFAULT_ADMIN_ROLE]], - ['assertOnlyRole', true, false, true, [DEFAULT_ADMIN_ROLE]], - ['assertOnlyRole', true, true, false, [DEFAULT_ADMIN_ROLE]], - ['assertOnlyRole', false, false, true, [DEFAULT_ADMIN_ROLE]], - ['assertOnlyRole', true, false, false, [DEFAULT_ADMIN_ROLE]], - ['assertOnlyRole', false, true, false, [DEFAULT_ADMIN_ROLE]], - ['assertOnlyRole', false, false, false, [DEFAULT_ADMIN_ROLE]], - ['grantRole', false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['grantRole', true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['grantRole', true, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['grantRole', false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['grantRole', true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['grantRole', false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['grantRole', false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['revokeRole', false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['revokeRole', true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['revokeRole', true, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['revokeRole', false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['revokeRole', true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['revokeRole', false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['revokeRole', false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ]; - - it.each(checkedCircuits)( - '%s should fail with isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', - (circuitName, isValidNonce, isValidIndex, isValidPath, args) => { - if (isValidNonce) { - // Check nonce matches - expect( - shieldedAccessControl.privateState.getCurrentSecretNonce( - DEFAULT_ADMIN_ROLE, - ), - ).toEqual(ADMIN_SECRET_NONCE); - } else { - // Check nonce does not match - shieldedAccessControl.privateState.injectSecretNonce( - DEFAULT_ADMIN_ROLE, - BAD_NONCE, - ); - expect( - shieldedAccessControl.privateState.getCurrentSecretNonce( - DEFAULT_ADMIN_ROLE, - ), - ).not.toEqual(ADMIN_SECRET_NONCE); - } - - if (isValidIndex) { - // Check index matches - const [, witnessCalculatedIndex] = - shieldedAccessControl.witnesses.wit_getRoleIndex( - shieldedAccessControl.getWitnessContext(), - DEFAULT_ADMIN_ROLE, - Z_ADMIN - ); - expect(witnessCalculatedIndex).toBe(INIT_COUNTER); - } else { - // Check index does not match - shieldedAccessControl.overrideWitness( - 'wit_getRoleIndex', - RETURN_BAD_INDEX, - ); - const [, witnessCalculatedIndex] = - shieldedAccessControl.witnesses.wit_getRoleIndex( - shieldedAccessControl.getWitnessContext(), - DEFAULT_ADMIN_ROLE, - Z_ADMIN - ); - expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); - } - - if (isValidPath) { - // Check path matches - const truePath = shieldedAccessControl - .getPublicState() - .ShieldedAccessControl__operatorRoles.findPathForLeaf( - EXP_DEFAULT_ADMIN_COMMITMENT, - ); - const [, witnessCalculatedPath] = - shieldedAccessControl.witnesses.wit_getRoleCommitmentPath( - shieldedAccessControl.getWitnessContext(), - EXP_DEFAULT_ADMIN_COMMITMENT, - ); - expect(witnessCalculatedPath).toEqual(truePath); - } else { - // Check path does not match - const truePath = shieldedAccessControl - .getPublicState() - .ShieldedAccessControl__operatorRoles.findPathForLeaf( - EXP_DEFAULT_ADMIN_COMMITMENT, - ); - shieldedAccessControl.overrideWitness( - 'wit_getRoleCommitmentPath', - RETURN_BAD_PATH, - ); - const [, witnessCalculatedPath] = - shieldedAccessControl.witnesses.wit_getRoleIndex( - shieldedAccessControl.getWitnessContext(), - DEFAULT_ADMIN_ROLE, - Z_ADMIN - ); - expect(witnessCalculatedPath).not.toEqual(truePath); - } - - // Test protected circuit - expect(() => { - ( - shieldedAccessControl[circuitName] as ( - ...args: unknown[] - ) => unknown - )(...args); - }).toThrow('ShieldedAccessControl: unauthorized account'); - }, - ); - }); - - describe('checked circuits should fail for unauthorized caller with any witness value', () => { - beforeEach(() => { - shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); - shieldedAccessControl.callerCtx.setCaller(UNAUTHORIZED); - }); - - type FailingCircuits = [ - method: keyof ShieldedAccessControlSimulator, - isValidNonce: boolean, - isValidIndex: boolean, - isValidPath: boolean, - args: unknown[], - ]; - const checkedCircuits: FailingCircuits[] = [ - ['assertOnlyRole', false, true, true, [DEFAULT_ADMIN_ROLE]], - ['assertOnlyRole', true, false, true, [DEFAULT_ADMIN_ROLE]], - ['assertOnlyRole', true, true, false, [DEFAULT_ADMIN_ROLE]], - ['assertOnlyRole', false, false, true, [DEFAULT_ADMIN_ROLE]], - ['assertOnlyRole', true, false, false, [DEFAULT_ADMIN_ROLE]], - ['assertOnlyRole', false, true, false, [DEFAULT_ADMIN_ROLE]], - ['assertOnlyRole', false, false, false, [DEFAULT_ADMIN_ROLE]], - ['assertOnlyRole', true, true, true, [DEFAULT_ADMIN_ROLE]], - ['grantRole', false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['grantRole', true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['grantRole', true, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['grantRole', false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['grantRole', true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['grantRole', false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['grantRole', false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['grantRole', true, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['revokeRole', false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['revokeRole', true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['revokeRole', true, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['revokeRole', false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['revokeRole', true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['revokeRole', false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['revokeRole', false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ['revokeRole', true, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ]; - - it.each(checkedCircuits)( - '%s should fail with isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', - (circuitName, isValidNonce, isValidIndex, isValidPath, args) => { - if (isValidNonce) { - // Check nonce matches - expect( - shieldedAccessControl.privateState.getCurrentSecretNonce( - DEFAULT_ADMIN_ROLE, - ), - ).toEqual(ADMIN_SECRET_NONCE); - } else { - // Check nonce does not match - shieldedAccessControl.privateState.injectSecretNonce( - DEFAULT_ADMIN_ROLE, - BAD_NONCE, - ); - expect( - shieldedAccessControl.privateState.getCurrentSecretNonce( - DEFAULT_ADMIN_ROLE, - ), - ).not.toEqual(ADMIN_SECRET_NONCE); - } - - if (isValidIndex) { - // Check index matches - const [, witnessCalculatedIndex] = - shieldedAccessControl.witnesses.wit_getRoleIndex( - shieldedAccessControl.getWitnessContext(), - DEFAULT_ADMIN_ROLE, - Z_ADMIN - ); - expect(witnessCalculatedIndex).toBe(INIT_COUNTER); - } else { - // Check index does not match - shieldedAccessControl.overrideWitness( - 'wit_getRoleIndex', - RETURN_BAD_INDEX, - ); - const [, witnessCalculatedIndex] = - shieldedAccessControl.witnesses.wit_getRoleIndex( - shieldedAccessControl.getWitnessContext(), - DEFAULT_ADMIN_ROLE, - Z_ADMIN - ); - expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); - } - - if (isValidPath) { - // Check path matches - const truePath = shieldedAccessControl - .getPublicState() - .ShieldedAccessControl__operatorRoles.findPathForLeaf( - EXP_DEFAULT_ADMIN_COMMITMENT, - ); - const [, witnessCalculatedPath] = - shieldedAccessControl.witnesses.wit_getRoleCommitmentPath( - shieldedAccessControl.getWitnessContext(), - EXP_DEFAULT_ADMIN_COMMITMENT, - ); - expect(witnessCalculatedPath).toEqual(truePath); - } else { - // Check path does not match - const truePath = shieldedAccessControl - .getPublicState() - .ShieldedAccessControl__operatorRoles.findPathForLeaf( - EXP_DEFAULT_ADMIN_COMMITMENT, - ); - shieldedAccessControl.overrideWitness( - 'wit_getRoleCommitmentPath', - RETURN_BAD_PATH, - ); - const [, witnessCalculatedPath] = - shieldedAccessControl.witnesses.wit_getRoleIndex( - shieldedAccessControl.getWitnessContext(), - DEFAULT_ADMIN_ROLE, - Z_ADMIN - ); - expect(witnessCalculatedPath).not.toEqual(truePath); - } - - // Test protected circuit - expect(() => { - ( - shieldedAccessControl[circuitName] as ( - ...args: unknown[] - ) => unknown - )(...args); - }).toThrow('ShieldedAccessControl: unauthorized account'); - }, - ); - }); - - describe('unsupported contract address failure cases', () => { - beforeEach(() => { - shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); - shieldedAccessControl.callerCtx.setCaller(ADMIN); - }); - - type FailingCircuits = [ - method: keyof ShieldedAccessControlSimulator, - args: unknown[], - ]; - const circuitsWithContractAddressCheck: FailingCircuits[] = [ - ['hasRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], - ['_checkRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], - ['grantRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], - ['revokeRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], - ['_grantRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], - ['_revokeRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], - ]; - - it.each(circuitsWithContractAddressCheck)( - '%s fails if contract address is queried', - (circuitName, args) => { - // Test protected circuit - expect(() => { - ( - shieldedAccessControl[circuitName] as ( - ...args: unknown[] - ) => unknown - )(...args); - }).toThrow( - 'ShieldedAccessControl: contract address roles are not yet supported', - ); - }, - ); - }); - - describe('hasRole', () => { - beforeEach(() => { - shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); - }); - - type HasRoleTest = [ - isValidNonce: boolean, - isValidIndex: boolean, - isValidPath: boolean, - args: unknown[], - ]; - const falseCases: HasRoleTest[] = [ - [false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - [true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - [true, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - [false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - [true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - [false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - [false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ]; - - const commitmentDoesNotMatchCases: HasRoleTest[] = [ - [false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - [true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - [false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - [true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - [false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - [false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], - ]; - - it('should throw if caller is contract address', () => { - shieldedAccessControl.callerCtx.setCaller(OPERATOR_CONTRACT); - expect(() => { - shieldedAccessControl.hasRole(UNINITIALIZED_ROLE, Z_OPERATOR_CONTRACT); - }).toThrow( - 'ShieldedAccessControl: contract address roles are not yet supported', - ); - }); - - it('should return correct role commitment', () => { - const expCommitment = buildCommitment( - DEFAULT_ADMIN_ROLE, - Z_ADMIN, - ADMIN_SECRET_NONCE, - INIT_COUNTER, - ); - - const role = shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); - expect(role.roleCommitment).toEqual(expCommitment); - }); - - it('should return true when admin has role', () => { - const role = shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); - expect(role.isApproved).toEqual(true); - }); - - it('should return false when unauthorized does not have role', () => { - const role = shieldedAccessControl.hasRole( - DEFAULT_ADMIN_ROLE, - Z_UNAUTHORIZED, - ); - expect(role.isApproved).toEqual(false); - }); - - it('should return false when role does not exist', () => { - shieldedAccessControl.privateState.injectSecretNonce( - UNINITIALIZED_ROLE, - Buffer.alloc(32), - ); - const role = shieldedAccessControl.hasRole( - UNINITIALIZED_ROLE, - Z_UNAUTHORIZED, - ); - expect(role.isApproved).toBe(false); - }); - - it.each(falseCases)( - 'should return false with any invalid witness value - isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', - (isValidNonce, isValidIndex, isValidPath, args) => { - if (isValidNonce) { - // Check nonce matches - expect( - shieldedAccessControl.privateState.getCurrentSecretNonce( - DEFAULT_ADMIN_ROLE, - ), - ).toEqual(ADMIN_SECRET_NONCE); - } else { - // Check nonce does not match - shieldedAccessControl.privateState.injectSecretNonce( - DEFAULT_ADMIN_ROLE, - BAD_NONCE, - ); - expect( - shieldedAccessControl.privateState.getCurrentSecretNonce( - DEFAULT_ADMIN_ROLE, - ), - ).not.toEqual(ADMIN_SECRET_NONCE); - } - - if (isValidIndex) { - // Check index matches - const [, witnessCalculatedIndex] = - shieldedAccessControl.witnesses.wit_getRoleIndex( - shieldedAccessControl.getWitnessContext(), - DEFAULT_ADMIN_ROLE, - Z_ADMIN - ); - expect(witnessCalculatedIndex).toBe(INIT_COUNTER); - } else { - // Check index does not match - shieldedAccessControl.overrideWitness( - 'wit_getRoleIndex', - RETURN_BAD_INDEX, - ); - const [, witnessCalculatedIndex] = - shieldedAccessControl.witnesses.wit_getRoleIndex( - shieldedAccessControl.getWitnessContext(), - DEFAULT_ADMIN_ROLE, - Z_ADMIN - ); - expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); - } - - if (isValidPath) { - // Check path matches - const truePath = shieldedAccessControl - .getPublicState() - .ShieldedAccessControl__operatorRoles.findPathForLeaf( - EXP_DEFAULT_ADMIN_COMMITMENT, - ); - const [, witnessCalculatedPath] = - shieldedAccessControl.witnesses.wit_getRoleCommitmentPath( - shieldedAccessControl.getWitnessContext(), - EXP_DEFAULT_ADMIN_COMMITMENT, - ); - expect(witnessCalculatedPath).toEqual(truePath); - } else { - // Check path does not match - const truePath = shieldedAccessControl - .getPublicState() - .ShieldedAccessControl__operatorRoles.findPathForLeaf( - EXP_DEFAULT_ADMIN_COMMITMENT, - ); - shieldedAccessControl.overrideWitness( - 'wit_getRoleCommitmentPath', - RETURN_BAD_PATH, - ); - const [, witnessCalculatedPath] = - shieldedAccessControl.witnesses.wit_getRoleIndex( - shieldedAccessControl.getWitnessContext(), - DEFAULT_ADMIN_ROLE, - Z_ADMIN - ); - expect(witnessCalculatedPath).not.toEqual(truePath); - } - - // Test false case circuit - const role = ( - shieldedAccessControl['hasRole'] as (...args: unknown[]) => Role - )(...args); - expect(role.isApproved).toBe(false); - }, - ); - - it.each(commitmentDoesNotMatchCases)( - 'commitment should not match with invalid nonce or index - isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', - (isValidNonce, isValidIndex, isValidPath, args) => { - if (isValidNonce) { - // Check nonce matches - expect( - shieldedAccessControl.privateState.getCurrentSecretNonce( - DEFAULT_ADMIN_ROLE, - ), - ).toEqual(ADMIN_SECRET_NONCE); - } else { - // Check nonce does not match - shieldedAccessControl.privateState.injectSecretNonce( - DEFAULT_ADMIN_ROLE, - BAD_NONCE, - ); - expect( - shieldedAccessControl.privateState.getCurrentSecretNonce( - DEFAULT_ADMIN_ROLE, - ), - ).not.toEqual(ADMIN_SECRET_NONCE); - } - - if (isValidIndex) { - // Check index matches - const [, witnessCalculatedIndex] = - shieldedAccessControl.witnesses.wit_getRoleIndex( - shieldedAccessControl.getWitnessContext(), - DEFAULT_ADMIN_ROLE, - Z_ADMIN - ); - expect(witnessCalculatedIndex).toBe(INIT_COUNTER); - } else { - // Check index does not match - shieldedAccessControl.overrideWitness( - 'wit_getRoleIndex', - RETURN_BAD_INDEX, - ); - const [, witnessCalculatedIndex] = - shieldedAccessControl.witnesses.wit_getRoleIndex( - shieldedAccessControl.getWitnessContext(), - DEFAULT_ADMIN_ROLE, - Z_ADMIN - ); - expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); - } - - if (isValidPath) { - // Check path matches - const truePath = shieldedAccessControl - .getPublicState() - .ShieldedAccessControl__operatorRoles.findPathForLeaf( - EXP_DEFAULT_ADMIN_COMMITMENT, - ); - const [, witnessCalculatedPath] = - shieldedAccessControl.witnesses.wit_getRoleCommitmentPath( - shieldedAccessControl.getWitnessContext(), - EXP_DEFAULT_ADMIN_COMMITMENT, - ); - expect(witnessCalculatedPath).toEqual(truePath); - } else { - // Check path does not match - const truePath = shieldedAccessControl - .getPublicState() - .ShieldedAccessControl__operatorRoles.findPathForLeaf( - EXP_DEFAULT_ADMIN_COMMITMENT, - ); - shieldedAccessControl.overrideWitness( - 'wit_getRoleCommitmentPath', - RETURN_BAD_PATH, - ); - const [, witnessCalculatedPath] = - shieldedAccessControl.witnesses.wit_getRoleIndex( - shieldedAccessControl.getWitnessContext(), - DEFAULT_ADMIN_ROLE, - Z_ADMIN - ); - expect(witnessCalculatedPath).not.toEqual(truePath); - } - - // Test false case circuit - const role = ( - shieldedAccessControl['hasRole'] as (...args: unknown[]) => Role - )(...args); - expect(role.roleCommitment).not.toEqual(EXP_DEFAULT_ADMIN_COMMITMENT); - }, - ); - }); - - describe('assertOnlyRole', () => { - beforeEach(() => { - shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); - shieldedAccessControl.callerCtx.setCaller(ADMIN); - }); - - it('should not fail when authorized caller has correct nonce, index, and path', () => { - // Check nonce is correct - expect( - shieldedAccessControl.privateState.getCurrentSecretNonce( - DEFAULT_ADMIN_ROLE, - ), - ).toBe(ADMIN_SECRET_NONCE); - - // Check index matches - const [, witnessCalculatedIndex] = - shieldedAccessControl.witnesses.wit_getRoleIndex( - shieldedAccessControl.getWitnessContext(), - DEFAULT_ADMIN_ROLE, - Z_ADMIN - ); - expect(witnessCalculatedIndex).toBe(INIT_COUNTER); - - // Check path matches - const truePath = shieldedAccessControl - .getPublicState() - .ShieldedAccessControl__operatorRoles.findPathForLeaf( - EXP_DEFAULT_ADMIN_COMMITMENT, - ); - const [, witnessCalculatedPath] = - shieldedAccessControl.witnesses.wit_getRoleCommitmentPath( - shieldedAccessControl.getWitnessContext(), - EXP_DEFAULT_ADMIN_COMMITMENT, - ); - expect(witnessCalculatedPath).toEqual(truePath); - - expect(() => - shieldedAccessControl.assertOnlyRole(DEFAULT_ADMIN_ROLE), - ).not.toThrow(); - }); - - it('should not fail for admin with multiple roles', () => { - shieldedAccessControl.privateState.injectSecretNonce( - OPERATOR_ROLE_1, - OPERATOR_ROLE_1_SECRET_NONCE, - ); - shieldedAccessControl.privateState.injectSecretNonce( - OPERATOR_ROLE_2, - OPERATOR_ROLE_2_SECRET_NONCE, - ); - shieldedAccessControl.privateState.injectSecretNonce( - OPERATOR_ROLE_3, - OPERATOR_ROLE_3_SECRET_NONCE, - ); - shieldedAccessControl._grantRole(OPERATOR_ROLE_1, Z_ADMIN); - shieldedAccessControl._grantRole(OPERATOR_ROLE_2, Z_ADMIN); - shieldedAccessControl._grantRole(OPERATOR_ROLE_3, Z_ADMIN); - expect(() => { - shieldedAccessControl.assertOnlyRole(DEFAULT_ADMIN_ROLE); - shieldedAccessControl.assertOnlyRole(OPERATOR_ROLE_1); - shieldedAccessControl.assertOnlyRole(OPERATOR_ROLE_2); - shieldedAccessControl.assertOnlyRole(OPERATOR_ROLE_3); - }).not.toThrow(); - }); - }); - - describe('_checkRole', () => { - beforeEach(() => { - shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); - shieldedAccessControl.callerCtx.setCaller(ADMIN); - }); - - it('should not throw if admin has role', () => { - expect(() => - shieldedAccessControl._checkRole(DEFAULT_ADMIN_ROLE, Z_ADMIN), - ).not.toThrow(); - }); - - it('should throw if unauthorized does not have role', () => { - expect(() => - shieldedAccessControl._checkRole(DEFAULT_ADMIN_ROLE, Z_UNAUTHORIZED), - ).toThrow('ShieldedAccessControl: unauthorized account'); - }); - }); - - describe('getRoleAdmin', () => { - it('should return default admin role if admin role not set', () => { - expect(shieldedAccessControl.getRoleAdmin(OPERATOR_ROLE_1)).toEqual( - DEFAULT_ADMIN_ROLE, - ); - }); - - it('should return custom admin role if set', () => { - shieldedAccessControl._setRoleAdmin(OPERATOR_ROLE_1, CUSTOM_ADMIN_ROLE); - expect(shieldedAccessControl.getRoleAdmin(OPERATOR_ROLE_1)).toEqual( - CUSTOM_ADMIN_ROLE, - ); - }); - }); - - describe('grantRole', () => { - beforeEach(() => { - shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); - shieldedAccessControl.callerCtx.setCaller(ADMIN); - }); - - it('admin should grant role', () => { - shieldedAccessControl.privateState.injectSecretNonce( - OPERATOR_ROLE_1, - OPERATOR_ROLE_1_SECRET_NONCE, - ); - shieldedAccessControl.grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); - const role: Role = shieldedAccessControl.hasRole( - OPERATOR_ROLE_1, - Z_OPERATOR_1, - ); - expect(role.isApproved).toBe(true); - }); - - it('path for role should exist in Merkle tree', () => { - expect( - shieldedAccessControl - .getPublicState() - .ShieldedAccessControl__operatorRoles.findPathForLeaf( - EXP_DEFAULT_ADMIN_COMMITMENT, - ), - ).toBeDefined(); - }); - - it('should update Merkle tree root', () => { - expect( - shieldedAccessControl - .getPublicState() - .ShieldedAccessControl__operatorRoles.root().field, - ).toBeGreaterThan(0n); - }); - - it('_currentMerkleTreeIndex should increment', () => { - // Starts at 1 because we grant role to self in beforeEach - expect( - shieldedAccessControl.getPublicState() - .ShieldedAccessControl__currentMerkleTreeIndex, - ).toBe(1n); - - shieldedAccessControl.privateState.injectSecretNonce( - OPERATOR_ROLE_1, - OPERATOR_ROLE_1_SECRET_NONCE, - ); - shieldedAccessControl.privateState.injectSecretNonce( - OPERATOR_ROLE_2, - OPERATOR_ROLE_2_SECRET_NONCE, - ); - shieldedAccessControl.privateState.injectSecretNonce( - OPERATOR_ROLE_3, - OPERATOR_ROLE_3_SECRET_NONCE, - ); - - shieldedAccessControl.grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); - expect( - shieldedAccessControl.getPublicState() - .ShieldedAccessControl__currentMerkleTreeIndex, - ).toBe(2n); - - shieldedAccessControl.grantRole(OPERATOR_ROLE_2, Z_OPERATOR_2); - expect( - shieldedAccessControl.getPublicState() - .ShieldedAccessControl__currentMerkleTreeIndex, - ).toBe(3n); - - shieldedAccessControl.grantRole(OPERATOR_ROLE_3, Z_OPERATOR_3); - expect( - shieldedAccessControl.getPublicState() - .ShieldedAccessControl__currentMerkleTreeIndex, - ).toBe(4n); - }); - - it('admin should grant multiple roles', () => { - for (let i = 0; i < OPERATOR_ROLE_LIST.length; i++) { - shieldedAccessControl.privateState.injectSecretNonce( - OPERATOR_ROLE_LIST[i], - OPERATOR_ROLE_SECRET_NONCES[i], - ); - for (let j = 0; j < Z_OPERATOR_LIST.length; j++) { - shieldedAccessControl.grantRole( - OPERATOR_ROLE_LIST[i], - Z_OPERATOR_LIST[j], - ); - const role: Role = shieldedAccessControl.hasRole( - OPERATOR_ROLE_LIST[i], - Z_OPERATOR_LIST[j], - ); - expect(role.isApproved).toBe(true); - - - expect( - shieldedAccessControl - .getPublicState() - .ShieldedAccessControl__operatorRoles.findPathForLeaf( - EXP_DEFAULT_ADMIN_COMMITMENT, - ), - ).toBeDefined(); - } - } - }); - - it('should throw if non-admin operator grants role', () => { - shieldedAccessControl.privateState.injectSecretNonce( - OPERATOR_ROLE_1, - OPERATOR_ROLE_1_SECRET_NONCE, - ); - shieldedAccessControl._grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); - - shieldedAccessControl.callerCtx.setCaller(OPERATOR_1); - expect(() => { - shieldedAccessControl.grantRole(OPERATOR_ROLE_1, Z_UNAUTHORIZED); - }).toThrow('ShieldedAccessControl: unauthorized account'); - }); - }); - - describe.only('revokeRole', () => { - beforeEach(() => { - console.log("TEST - Current MT Index", shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex.toString()); - shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); - console.log("TEST - Current MT Index", shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex.toString()); - shieldedAccessControl.privateState.injectSecretNonce( - OPERATOR_ROLE_1, - OPERATOR_ROLE_1_SECRET_NONCE, - ); - shieldedAccessControl._grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); - console.log("TEST - Current MT Index", shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex.toString()); - shieldedAccessControl.callerCtx.setCaller(ADMIN); - }); - - it('admin should revoke role', () => { - expect(shieldedAccessControl.hasRole(OPERATOR_ROLE_1, Z_OPERATOR_1).isApproved).toBe(true); - shieldedAccessControl.revokeRole(OPERATOR_ROLE_1, Z_OPERATOR_1); - expect(shieldedAccessControl.hasRole(OPERATOR_ROLE_1, Z_OPERATOR_1).isApproved).toBe(false); - }); - - it.only('commitment should be in nullifier set', () => { - console.log("TEST - Current MT Index", shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex.toString()); - const [, opRoleIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), OPERATOR_ROLE_1, Z_OPERATOR_1); - const [, adminRoleIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, Z_ADMIN); - console.log("OPERATOR INDEX ", opRoleIndex.toString(10)); - console.log("ADMIN INDEX ", adminRoleIndex.toString(10)); - const expCommitmentOp = buildCommitment(OPERATOR_ROLE_1, Z_OPERATOR_1, OPERATOR_ROLE_1_SECRET_NONCE, opRoleIndex); - const pathToOp = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(expCommitmentOp); - const pathToAdmin = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); - //console.log("PATH TO OP ", pathToOp); - //console.log("PATH TO ADMIN ", pathToAdmin); - - //console.log("EXPECTED COMMITMENT ", expCommitmentOp); - const contractCommit = shieldedAccessControl.hasRole(OPERATOR_ROLE_1, Z_OPERATOR_1).roleCommitment; - //console.log("CONTRACT COMMITMENT ", contractCommit); - - shieldedAccessControl.revokeRole(OPERATOR_ROLE_1, Z_OPERATOR_1); - expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__roleCommitmentNullifiers.isEmpty()).toBe(false); - expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__roleCommitmentNullifiers.member(expCommitmentOp)).toBe(true); - }); - - it('admin should revoke multiple roles', () => { - const expCommitment = buildCommitment(OPERATOR_ROLE_1, Z_OPERATOR_1, OPERATOR_ROLE_1_SECRET_NONCE, 1n); - shieldedAccessControl.revokeRole(OPERATOR_ROLE_1, Z_OPERATOR_1); - expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__roleCommitmentNullifiers.member(expCommitment)).toBe(true); - - for (let i = 1; i < OPERATOR_ROLE_LIST.length; i++) { - shieldedAccessControl.privateState.injectSecretNonce( - OPERATOR_ROLE_LIST[i], - OPERATOR_ROLE_SECRET_NONCES[i], - ); - for (let j = 1; j < Z_OPERATOR_LIST.length; j++) { - shieldedAccessControl._grantRole( - OPERATOR_ROLE_LIST[i], - Z_OPERATOR_LIST[j], - ); - const expCommitment = buildCommitment(OPERATOR_ROLE_LIST[i], Z_OPERATOR_LIST[j], OPERATOR_ROLE_SECRET_NONCES[i], BigInt(1 + i)); - shieldedAccessControl.revokeRole(OPERATOR_ROLE_LIST[i], Z_OPERATOR_LIST[j]); - expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__roleCommitmentNullifiers.member(expCommitment)).toBe(true); - } - } - }); - - it('should throw if non-admin operator revokes role', () => { - shieldedAccessControl.privateState.injectSecretNonce( - OPERATOR_ROLE_1, - OPERATOR_ROLE_1_SECRET_NONCE, - ); - shieldedAccessControl._grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); - - shieldedAccessControl.callerCtx.setCaller(OPERATOR_1); - expect(() => { - shieldedAccessControl.revokeRole(OPERATOR_ROLE_1, Z_UNAUTHORIZED); - }).toThrow('ShieldedAccessControl: unauthorized account'); - }); - }); -}); diff --git a/contracts/src/access/test/ShieldedAccessControl_OLD.ts b/contracts/src/access/test/ShieldedAccessControl_OLD.ts new file mode 100644 index 00000000..ececb9aa --- /dev/null +++ b/contracts/src/access/test/ShieldedAccessControl_OLD.ts @@ -0,0 +1,1053 @@ +import { + CompactTypeBytes, + CompactTypeVector, + convert_bigint_to_Uint8Array, + persistentHash, + type WitnessContext, +} from '@midnight-ntwrk/compact-runtime'; +import { beforeEach, describe, expect, it } from 'vitest'; +import { + ContractAddress, + Either, + Ledger, + MerkleTreePath, + ShieldedAccessControl_Role as Role, + ZswapCoinPublicKey, + Contract as MyContract +} from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; +import { fmtHexString, ShieldedAccessControlPrivateState, ShieldedAccessControlWitnesses } from '../witnesses/ShieldedAccessControlWitnesses.js'; +import { ShieldedAccessControlSimulator } from './simulators/ShieldedAccessControlSimulator.js'; +import * as utils from './utils/address.js'; + +// PKs +const [ADMIN, Z_ADMIN] = utils.generateEitherPubKeyPair('ADMIN'); +const [UNAUTHORIZED, Z_UNAUTHORIZED] = + utils.generateEitherPubKeyPair('UNAUTHORIZED'); +const [CUSTOM_ADMIN, Z_CUSTOM_ADMIN] = + utils.generateEitherPubKeyPair('CUSTOM_ADMIN'); +const [OPERATOR_1, Z_OPERATOR_1] = utils.generateEitherPubKeyPair('OPERATOR_1'); +const [OPERATOR_2, Z_OPERATOR_2] = utils.generateEitherPubKeyPair('OPERATOR_2'); +const [OPERATOR_3, Z_OPERATOR_3] = utils.generateEitherPubKeyPair('OPERATOR_3'); +const [OPERATOR_CONTRACT, Z_OPERATOR_CONTRACT] = utils.generateEitherPubKeyPair( + 'OPERATOR_CONTRACT', + false, +); +const Z_OPERATOR_LIST = [Z_OPERATOR_1, Z_OPERATOR_2, Z_OPERATOR_3]; + +// Constants +const BAD_NONCE = Buffer.alloc(32, 'BAD_NONCE'); +const DOMAIN = new Uint8Array(32); +new TextEncoder().encodeInto('ShieldedAccessControl:shield:', DOMAIN); +const INIT_COUNTER = 0n; + +const EMPTY_ROOT = { field: 0n }; +const getRoleIndex = ( + { + ledger, + privateState, + }: WitnessContext, + roleId: Uint8Array, + account: Either +): bigint => { + const roleIdString = Buffer.from(roleId).toString('hex'); + const bNonce = privateState.roles[roleIdString]; + const rt_type = new CompactTypeVector(5, new CompactTypeBytes(32)); + const bAccount = utils.eitherToBytes(account); + // Iterate over each MT index to determine if commitment exists + for (let i = 0; i < (2 ** 11 - 1); i++) { + const bIndex = convert_bigint_to_Uint8Array(32, BigInt(i)); + const commitment = persistentHash(rt_type, [ + roleId, + bAccount, + bNonce, + bIndex, + DOMAIN, + ]); + try { + ledger.ShieldedAccessControl__operatorRoles.pathForLeaf( + BigInt(i), + commitment, + ); + return BigInt(i); + } catch (e: unknown) { + if (e instanceof Error) { + const [msg, index] = e.message.split(':'); + if (msg === 'invalid index into sparse merkle tree') { + // console.log(`role ${fmtHexString(roleIdString)} with commitment ${fmtHexString(commitment)} not found at index ${index}`); + } else { + throw e; + } + } + } + } + + console.log("WIT - Commitment DNE, returing MT index ", ledger.ShieldedAccessControl__currentMerkleTreeIndex.toString()); + + // If commitment doesn't exist return currentMTIndex + // Used for adding roles + return ledger.ShieldedAccessControl__currentMerkleTreeIndex; +} + +// Roles +const DEFAULT_ADMIN_ROLE = utils.zeroUint8Array(); +const OPERATOR_ROLE_1 = convert_bigint_to_Uint8Array(32, 1n); +const OPERATOR_ROLE_2 = convert_bigint_to_Uint8Array(32, 2n); +const OPERATOR_ROLE_3 = convert_bigint_to_Uint8Array(32, 3n); +const CUSTOM_ADMIN_ROLE = convert_bigint_to_Uint8Array(32, 4n); +const UNINITIALIZED_ROLE = convert_bigint_to_Uint8Array(32, 5n); +const OPERATOR_ROLE_LIST = [OPERATOR_ROLE_1, OPERATOR_ROLE_2, OPERATOR_ROLE_3]; + +// Role to string +const DEFAULT_ADMIN_ROLE_TO_STRING = + Buffer.from(DEFAULT_ADMIN_ROLE).toString('hex'); + +const ADMIN_SECRET_NONCE = Buffer.alloc(32, 'ADMIN_SECRET_NONCE'); +const OPERATOR_ROLE_1_SECRET_NONCE = Buffer.alloc( + 32, + 'OPERATOR_ROLE_1_SECRET_NONCE', +); +const OPERATOR_ROLE_2_SECRET_NONCE = Buffer.alloc( + 32, + 'OPERATOR_ROLE_2_SECRET_NONCE', +); +const OPERATOR_ROLE_3_SECRET_NONCE = Buffer.alloc( + 32, + 'OPERATOR_ROLE_3_SECRET_NONCE', +); +const OPERATOR_ROLE_SECRET_NONCES = [ + OPERATOR_ROLE_1_SECRET_NONCE, + OPERATOR_ROLE_2_SECRET_NONCE, + OPERATOR_ROLE_3_SECRET_NONCE, +]; +let shieldedAccessControl: ShieldedAccessControlSimulator; + +// Helpers +const buildCommitment = ( + roleId: Uint8Array, + account: Either, + nonce: Uint8Array, + index: bigint, +): Uint8Array => { + const rt_type = new CompactTypeVector(5, new CompactTypeBytes(32)); + const bAccount = utils.eitherToBytes(account); + const bIndex = convert_bigint_to_Uint8Array(32, index); + + const commitment = persistentHash(rt_type, [ + roleId, + bAccount, + nonce, + bIndex, + DOMAIN, + ]); + + return commitment; +}; + +const EXP_DEFAULT_ADMIN_COMMITMENT = buildCommitment( + DEFAULT_ADMIN_ROLE, + Z_ADMIN, + ADMIN_SECRET_NONCE, + INIT_COUNTER, +); + +function RETURN_BAD_INDEX( + context: WitnessContext, + roleId: Uint8Array, +): [ShieldedAccessControlPrivateState, bigint] { + return [context.privateState, 1023n]; +} + +function RETURN_BAD_PATH( + context: WitnessContext, + roleCommitment: Uint8Array, +): [ShieldedAccessControlPrivateState, MerkleTreePath] { + const defaultPath: MerkleTreePath = { + leaf: new Uint8Array(32), + path: Array.from({ length: 10 }, () => ({ + sibling: { field: 0n }, + goes_left: false, + })), + }; + return [context.privateState, defaultPath]; +} + +type RoleAndNonce = { + roleId: string; + nonce: Buffer; +}; + +describe('ShieldedAccessControl', () => { + beforeEach(() => { + // Create private state object and generate nonce + const PS = ShieldedAccessControlPrivateState.withRoleAndNonce( + Z_ADMIN, + Buffer.from(DEFAULT_ADMIN_ROLE), + ADMIN_SECRET_NONCE, + ); + // Init contract for user with PS + shieldedAccessControl = new ShieldedAccessControlSimulator(Z_ADMIN, { + privateState: PS, + }); + }); + + describe('checked circuits should fail for authorized caller with invalid witness values', () => { + beforeEach(() => { + shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + shieldedAccessControl.callerCtx.setCaller(ADMIN); + }); + + type FailingCircuits = [ + method: keyof ShieldedAccessControlSimulator, + isValidNonce: boolean, + isValidIndex: boolean, + isValidPath: boolean, + args: unknown[], + ]; + const checkedCircuits: FailingCircuits[] = [ + ['assertOnlyRole', false, true, true, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', true, false, true, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', true, true, false, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', false, false, true, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', true, false, false, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', false, true, false, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', false, false, false, [DEFAULT_ADMIN_ROLE]], + ['grantRole', false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', true, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', true, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ]; + + it.each(checkedCircuits)( + '%s should fail with isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', + (circuitName, isValidNonce, isValidIndex, isValidPath, args) => { + if (isValidNonce) { + // Check nonce matches + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).toEqual(ADMIN_SECRET_NONCE); + } else { + // Check nonce does not match + shieldedAccessControl.privateState.injectSecretNonce( + DEFAULT_ADMIN_ROLE, + BAD_NONCE, + ); + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).not.toEqual(ADMIN_SECRET_NONCE); + } + + if (isValidIndex) { + // Check index matches + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + Z_ADMIN + ); + expect(witnessCalculatedIndex).toBe(INIT_COUNTER); + } else { + // Check index does not match + shieldedAccessControl.overrideWitness( + 'wit_getRoleIndex', + RETURN_BAD_INDEX, + ); + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + Z_ADMIN + ); + expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); + } + + if (isValidPath) { + // Check path matches + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleCommitmentPath( + shieldedAccessControl.getWitnessContext(), + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + expect(witnessCalculatedPath).toEqual(truePath); + } else { + // Check path does not match + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + shieldedAccessControl.overrideWitness( + 'wit_getRoleCommitmentPath', + RETURN_BAD_PATH, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + Z_ADMIN + ); + expect(witnessCalculatedPath).not.toEqual(truePath); + } + + // Test protected circuit + expect(() => { + ( + shieldedAccessControl[circuitName] as ( + ...args: unknown[] + ) => unknown + )(...args); + }).toThrow('ShieldedAccessControl: unauthorized account'); + }, + ); + }); + + describe('checked circuits should fail for unauthorized caller with any witness value', () => { + beforeEach(() => { + shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + shieldedAccessControl.callerCtx.setCaller(UNAUTHORIZED); + }); + + type FailingCircuits = [ + method: keyof ShieldedAccessControlSimulator, + isValidNonce: boolean, + isValidIndex: boolean, + isValidPath: boolean, + args: unknown[], + ]; + const checkedCircuits: FailingCircuits[] = [ + ['assertOnlyRole', false, true, true, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', true, false, true, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', true, true, false, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', false, false, true, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', true, false, false, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', false, true, false, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', false, false, false, [DEFAULT_ADMIN_ROLE]], + ['assertOnlyRole', true, true, true, [DEFAULT_ADMIN_ROLE]], + ['grantRole', false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', true, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['grantRole', true, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', true, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ['revokeRole', true, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ]; + + it.each(checkedCircuits)( + '%s should fail with isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', + (circuitName, isValidNonce, isValidIndex, isValidPath, args) => { + if (isValidNonce) { + // Check nonce matches + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).toEqual(ADMIN_SECRET_NONCE); + } else { + // Check nonce does not match + shieldedAccessControl.privateState.injectSecretNonce( + DEFAULT_ADMIN_ROLE, + BAD_NONCE, + ); + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).not.toEqual(ADMIN_SECRET_NONCE); + } + + if (isValidIndex) { + // Check index matches + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + Z_ADMIN + ); + expect(witnessCalculatedIndex).toBe(INIT_COUNTER); + } else { + // Check index does not match + shieldedAccessControl.overrideWitness( + 'wit_getRoleIndex', + RETURN_BAD_INDEX, + ); + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + Z_ADMIN + ); + expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); + } + + if (isValidPath) { + // Check path matches + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleCommitmentPath( + shieldedAccessControl.getWitnessContext(), + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + expect(witnessCalculatedPath).toEqual(truePath); + } else { + // Check path does not match + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + shieldedAccessControl.overrideWitness( + 'wit_getRoleCommitmentPath', + RETURN_BAD_PATH, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + Z_ADMIN + ); + expect(witnessCalculatedPath).not.toEqual(truePath); + } + + // Test protected circuit + expect(() => { + ( + shieldedAccessControl[circuitName] as ( + ...args: unknown[] + ) => unknown + )(...args); + }).toThrow('ShieldedAccessControl: unauthorized account'); + }, + ); + }); + + describe('unsupported contract address failure cases', () => { + beforeEach(() => { + shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + shieldedAccessControl.callerCtx.setCaller(ADMIN); + }); + + type FailingCircuits = [ + method: keyof ShieldedAccessControlSimulator, + args: unknown[], + ]; + const circuitsWithContractAddressCheck: FailingCircuits[] = [ + ['hasRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], + ['_checkRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], + ['grantRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], + ['revokeRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], + ['_grantRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], + ['_revokeRole', [DEFAULT_ADMIN_ROLE, Z_OPERATOR_CONTRACT]], + ]; + + it.each(circuitsWithContractAddressCheck)( + '%s fails if contract address is queried', + (circuitName, args) => { + // Test protected circuit + expect(() => { + ( + shieldedAccessControl[circuitName] as ( + ...args: unknown[] + ) => unknown + )(...args); + }).toThrow( + 'ShieldedAccessControl: contract address roles are not yet supported', + ); + }, + ); + }); + + describe('hasRole', () => { + beforeEach(() => { + shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + }); + + type HasRoleTest = [ + isValidNonce: boolean, + isValidIndex: boolean, + isValidPath: boolean, + args: unknown[], + ]; + const falseCases: HasRoleTest[] = [ + [false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [true, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ]; + + const commitmentDoesNotMatchCases: HasRoleTest[] = [ + [false, true, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [true, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [false, false, true, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [true, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [false, true, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + [false, false, false, [DEFAULT_ADMIN_ROLE, Z_ADMIN]], + ]; + + it('should throw if caller is contract address', () => { + shieldedAccessControl.callerCtx.setCaller(OPERATOR_CONTRACT); + expect(() => { + shieldedAccessControl.hasRole(UNINITIALIZED_ROLE, Z_OPERATOR_CONTRACT); + }).toThrow( + 'ShieldedAccessControl: contract address roles are not yet supported', + ); + }); + + it('should return correct role commitment', () => { + const expCommitment = buildCommitment( + DEFAULT_ADMIN_ROLE, + Z_ADMIN, + ADMIN_SECRET_NONCE, + INIT_COUNTER, + ); + + const role = shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + expect(role.roleCommitment).toEqual(expCommitment); + }); + + it('should return true when admin has role', () => { + const role = shieldedAccessControl.hasRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + expect(role.isApproved).toEqual(true); + }); + + it('should return false when unauthorized does not have role', () => { + const role = shieldedAccessControl.hasRole( + DEFAULT_ADMIN_ROLE, + Z_UNAUTHORIZED, + ); + expect(role.isApproved).toEqual(false); + }); + + it('should return false when role does not exist', () => { + shieldedAccessControl.privateState.injectSecretNonce( + UNINITIALIZED_ROLE, + Buffer.alloc(32), + ); + const role = shieldedAccessControl.hasRole( + UNINITIALIZED_ROLE, + Z_UNAUTHORIZED, + ); + expect(role.isApproved).toBe(false); + }); + + it.each(falseCases)( + 'should return false with any invalid witness value - isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', + (isValidNonce, isValidIndex, isValidPath, args) => { + if (isValidNonce) { + // Check nonce matches + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).toEqual(ADMIN_SECRET_NONCE); + } else { + // Check nonce does not match + shieldedAccessControl.privateState.injectSecretNonce( + DEFAULT_ADMIN_ROLE, + BAD_NONCE, + ); + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).not.toEqual(ADMIN_SECRET_NONCE); + } + + if (isValidIndex) { + // Check index matches + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + Z_ADMIN + ); + expect(witnessCalculatedIndex).toBe(INIT_COUNTER); + } else { + // Check index does not match + shieldedAccessControl.overrideWitness( + 'wit_getRoleIndex', + RETURN_BAD_INDEX, + ); + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + Z_ADMIN + ); + expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); + } + + if (isValidPath) { + // Check path matches + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleCommitmentPath( + shieldedAccessControl.getWitnessContext(), + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + expect(witnessCalculatedPath).toEqual(truePath); + } else { + // Check path does not match + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + shieldedAccessControl.overrideWitness( + 'wit_getRoleCommitmentPath', + RETURN_BAD_PATH, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + Z_ADMIN + ); + expect(witnessCalculatedPath).not.toEqual(truePath); + } + + // Test false case circuit + const role = ( + shieldedAccessControl['hasRole'] as (...args: unknown[]) => Role + )(...args); + expect(role.isApproved).toBe(false); + }, + ); + + it.each(commitmentDoesNotMatchCases)( + 'commitment should not match with invalid nonce or index - isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', + (isValidNonce, isValidIndex, isValidPath, args) => { + if (isValidNonce) { + // Check nonce matches + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).toEqual(ADMIN_SECRET_NONCE); + } else { + // Check nonce does not match + shieldedAccessControl.privateState.injectSecretNonce( + DEFAULT_ADMIN_ROLE, + BAD_NONCE, + ); + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).not.toEqual(ADMIN_SECRET_NONCE); + } + + if (isValidIndex) { + // Check index matches + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + Z_ADMIN + ); + expect(witnessCalculatedIndex).toBe(INIT_COUNTER); + } else { + // Check index does not match + shieldedAccessControl.overrideWitness( + 'wit_getRoleIndex', + RETURN_BAD_INDEX, + ); + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + Z_ADMIN + ); + expect(witnessCalculatedIndex).not.toBe(INIT_COUNTER); + } + + if (isValidPath) { + // Check path matches + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleCommitmentPath( + shieldedAccessControl.getWitnessContext(), + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + expect(witnessCalculatedPath).toEqual(truePath); + } else { + // Check path does not match + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + shieldedAccessControl.overrideWitness( + 'wit_getRoleCommitmentPath', + RETURN_BAD_PATH, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + Z_ADMIN + ); + expect(witnessCalculatedPath).not.toEqual(truePath); + } + + // Test false case circuit + const role = ( + shieldedAccessControl['hasRole'] as (...args: unknown[]) => Role + )(...args); + expect(role.roleCommitment).not.toEqual(EXP_DEFAULT_ADMIN_COMMITMENT); + }, + ); + }); + + describe('assertOnlyRole', () => { + beforeEach(() => { + shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + shieldedAccessControl.callerCtx.setCaller(ADMIN); + }); + + it('should not fail when authorized caller has correct nonce, index, and path', () => { + shieldedAccessControl.callerCtx.setCaller(OPERATOR_1); + shieldedAccessControl.assertOnlyRole(new Uint8Array(32).fill(1)); + // Check nonce is correct + expect( + shieldedAccessControl.privateState.getCurrentSecretNonce( + DEFAULT_ADMIN_ROLE, + ), + ).toBe(ADMIN_SECRET_NONCE); + + // Check index matches + const [, witnessCalculatedIndex] = + shieldedAccessControl.witnesses.wit_getRoleIndex( + shieldedAccessControl.getWitnessContext(), + DEFAULT_ADMIN_ROLE, + Z_ADMIN + ); + expect(witnessCalculatedIndex).toBe(INIT_COUNTER); + + // Check path matches + const truePath = shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + const [, witnessCalculatedPath] = + shieldedAccessControl.witnesses.wit_getRoleCommitmentPath( + shieldedAccessControl.getWitnessContext(), + EXP_DEFAULT_ADMIN_COMMITMENT, + ); + expect(witnessCalculatedPath).toEqual(truePath); + + expect(() => + shieldedAccessControl.assertOnlyRole(DEFAULT_ADMIN_ROLE), + ).not.toThrow(); + }); + + it('should not fail for admin with multiple roles', () => { + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_1, + OPERATOR_ROLE_1_SECRET_NONCE, + ); + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_2, + OPERATOR_ROLE_2_SECRET_NONCE, + ); + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_3, + OPERATOR_ROLE_3_SECRET_NONCE, + ); + shieldedAccessControl._grantRole(OPERATOR_ROLE_1, Z_ADMIN); + shieldedAccessControl._grantRole(OPERATOR_ROLE_2, Z_ADMIN); + shieldedAccessControl._grantRole(OPERATOR_ROLE_3, Z_ADMIN); + expect(() => { + shieldedAccessControl.assertOnlyRole(DEFAULT_ADMIN_ROLE); + shieldedAccessControl.assertOnlyRole(OPERATOR_ROLE_1); + shieldedAccessControl.assertOnlyRole(OPERATOR_ROLE_2); + shieldedAccessControl.assertOnlyRole(OPERATOR_ROLE_3); + }).not.toThrow(); + }); + }); + + describe('_checkRole', () => { + beforeEach(() => { + shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + }); + + it('should not throw if admin has role', () => { + shieldedAccessControl.callerCtx.setCaller(OPERATOR_1); + console.log("ZswapState", shieldedAccessControl.circuitContext.currentZswapLocalState) + expect(() => + shieldedAccessControl._checkRole(DEFAULT_ADMIN_ROLE, Z_ADMIN), + ).not.toThrow(); + }); + + it('should throw if unauthorized does not have role', () => { + expect(() => + shieldedAccessControl._checkRole(DEFAULT_ADMIN_ROLE, Z_UNAUTHORIZED), + ).toThrow('ShieldedAccessControl: unauthorized account'); + }); + }); + + describe('getRoleAdmin', () => { + it('should return default admin role if admin role not set', () => { + expect(shieldedAccessControl.getRoleAdmin(OPERATOR_ROLE_1)).toEqual( + DEFAULT_ADMIN_ROLE, + ); + }); + + it('should return custom admin role if set', () => { + shieldedAccessControl._setRoleAdmin(OPERATOR_ROLE_1, CUSTOM_ADMIN_ROLE); + expect(shieldedAccessControl.getRoleAdmin(OPERATOR_ROLE_1)).toEqual( + CUSTOM_ADMIN_ROLE, + ); + }); + }); + + describe('grantRole', () => { + beforeEach(() => { + shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN); + shieldedAccessControl.callerCtx.setCaller(ADMIN); + }); + + it('admin should grant role', () => { + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_1, + OPERATOR_ROLE_1_SECRET_NONCE, + ); + shieldedAccessControl.grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); + const role: Role = shieldedAccessControl.hasRole( + OPERATOR_ROLE_1, + Z_OPERATOR_1, + ); + expect(role.isApproved).toBe(true); + }); + + it('path for role should exist in Merkle tree', () => { + expect( + shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ), + ).toBeDefined(); + }); + + it('should update Merkle tree root', () => { + expect( + shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.root().field, + ).toBeGreaterThan(0n); + }); + + it('_currentMerkleTreeIndex should increment', () => { + // Starts at 1 because we grant role to self in beforeEach + expect( + shieldedAccessControl.getPublicState() + .ShieldedAccessControl__currentMerkleTreeIndex, + ).toBe(1n); + + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_1, + OPERATOR_ROLE_1_SECRET_NONCE, + ); + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_2, + OPERATOR_ROLE_2_SECRET_NONCE, + ); + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_3, + OPERATOR_ROLE_3_SECRET_NONCE, + ); + + shieldedAccessControl.grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); + expect( + shieldedAccessControl.getPublicState() + .ShieldedAccessControl__currentMerkleTreeIndex, + ).toBe(2n); + + shieldedAccessControl.grantRole(OPERATOR_ROLE_2, Z_OPERATOR_2); + expect( + shieldedAccessControl.getPublicState() + .ShieldedAccessControl__currentMerkleTreeIndex, + ).toBe(3n); + + shieldedAccessControl.grantRole(OPERATOR_ROLE_3, Z_OPERATOR_3); + expect( + shieldedAccessControl.getPublicState() + .ShieldedAccessControl__currentMerkleTreeIndex, + ).toBe(4n); + }); + + it('admin should grant multiple roles', () => { + for (let i = 0; i < OPERATOR_ROLE_LIST.length; i++) { + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_LIST[i], + OPERATOR_ROLE_SECRET_NONCES[i], + ); + for (let j = 0; j < Z_OPERATOR_LIST.length; j++) { + shieldedAccessControl.grantRole( + OPERATOR_ROLE_LIST[i], + Z_OPERATOR_LIST[j], + ); + const role: Role = shieldedAccessControl.hasRole( + OPERATOR_ROLE_LIST[i], + Z_OPERATOR_LIST[j], + ); + expect(role.isApproved).toBe(true); + + + expect( + shieldedAccessControl + .getPublicState() + .ShieldedAccessControl__operatorRoles.findPathForLeaf( + EXP_DEFAULT_ADMIN_COMMITMENT, + ), + ).toBeDefined(); + } + } + }); + + it('should throw if non-admin operator grants role', () => { + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_1, + OPERATOR_ROLE_1_SECRET_NONCE, + ); + shieldedAccessControl._grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); + + shieldedAccessControl.callerCtx.setCaller(OPERATOR_1); + expect(() => { + shieldedAccessControl.grantRole(OPERATOR_ROLE_1, Z_UNAUTHORIZED); + }).toThrow('ShieldedAccessControl: unauthorized account'); + }); + }); + + describe('revokeRole', () => { + beforeEach(() => { + shieldedAccessControl.callerCtx.setCaller(ADMIN); + console.log("TEST - Current MT Index", shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex.toString()); + console.log(shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN)); + console.log(shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, Z_ADMIN)); + console.log("TEST - ADMIN NONCE ", fmtHexString(ADMIN_SECRET_NONCE)); + console.log("TEST - OP NONCE ", fmtHexString(OPERATOR_ROLE_1_SECRET_NONCE)); + console.log("TEST - Current MT Index", shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex.toString()); + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_1, + OPERATOR_ROLE_1_SECRET_NONCE, + ); + shieldedAccessControl.grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); + console.log("TEST - Current MT Index", shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex.toString()); + + }); + + it('admin should revoke role', () => { + expect(shieldedAccessControl.hasRole(OPERATOR_ROLE_1, Z_OPERATOR_1).isApproved).toBe(true); + shieldedAccessControl.revokeRole(OPERATOR_ROLE_1, Z_OPERATOR_1); + expect(shieldedAccessControl.hasRole(OPERATOR_ROLE_1, Z_OPERATOR_1).isApproved).toBe(false); + }); + + it('commitment should be in nullifier set', () => { + console.log("TEST - Current MT Index", shieldedAccessControl.getPublicState().ShieldedAccessControl__currentMerkleTreeIndex.toString()); + const opRoleIndex = getRoleIndex({ ledger: shieldedAccessControl.getPublicState(), privateState: shieldedAccessControl.getPrivateState(), contractAddress: shieldedAccessControl.contractAddress }, OPERATOR_ROLE_1, Z_OPERATOR_1); + const adminRoleIndex = getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, Z_ADMIN); + console.log("OPERATOR INDEX ", opRoleIndex.toString(10)); + console.log("ADMIN INDEX ", adminRoleIndex.toString(10)); + const expCommitmentOp = buildCommitment(OPERATOR_ROLE_1, Z_OPERATOR_1, OPERATOR_ROLE_1_SECRET_NONCE, 0n); + const expCommitmentOp2 = buildCommitment(OPERATOR_ROLE_1, Z_OPERATOR_1, OPERATOR_ROLE_1_SECRET_NONCE, 0n); + const pathToOp = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(expCommitmentOp); + const pathToAdmin = shieldedAccessControl.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf(EXP_DEFAULT_ADMIN_COMMITMENT); + //console.log("PATH TO OP ", pathToOp); + //console.log("PATH TO ADMIN ", pathToAdmin); + + //console.log("EXPECTED COMMITMENT ", expCommitmentOp); + const contractCommit = shieldedAccessControl.hasRole(OPERATOR_ROLE_1, Z_OPERATOR_1).roleCommitment; + //console.log("CONTRACT COMMITMENT ", contractCommit); + + shieldedAccessControl.revokeRole(OPERATOR_ROLE_1, Z_OPERATOR_1); + const it = shieldedAccessControl.getPublicState().ShieldedAccessControl_sanity[Symbol.iterator](); + console.log(EXP_DEFAULT_ADMIN_COMMITMENT); + console.log(it.next()); + console.log(shieldedAccessControl.getPublicState().ShieldedAccessControl_sanity.member(EXP_DEFAULT_ADMIN_COMMITMENT)); + console.log(expCommitmentOp) + console.log(it.next()); + console.log(shieldedAccessControl.getPublicState().ShieldedAccessControl_sanity.member(expCommitmentOp)); + expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__roleCommitmentNullifiers.isEmpty()).toBe(false); + expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__roleCommitmentNullifiers.member(expCommitmentOp)).toBe(true); + }); + + it('admin should revoke multiple roles', () => { + const expCommitment = buildCommitment(OPERATOR_ROLE_1, Z_OPERATOR_1, OPERATOR_ROLE_1_SECRET_NONCE, 1n); + shieldedAccessControl.revokeRole(OPERATOR_ROLE_1, Z_OPERATOR_1); + expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__roleCommitmentNullifiers.member(expCommitment)).toBe(true); + + for (let i = 1; i < OPERATOR_ROLE_LIST.length; i++) { + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_LIST[i], + OPERATOR_ROLE_SECRET_NONCES[i], + ); + for (let j = 1; j < Z_OPERATOR_LIST.length; j++) { + shieldedAccessControl._grantRole( + OPERATOR_ROLE_LIST[i], + Z_OPERATOR_LIST[j], + ); + const expCommitment = buildCommitment(OPERATOR_ROLE_LIST[i], Z_OPERATOR_LIST[j], OPERATOR_ROLE_SECRET_NONCES[i], BigInt(1 + i)); + shieldedAccessControl.revokeRole(OPERATOR_ROLE_LIST[i], Z_OPERATOR_LIST[j]); + expect(shieldedAccessControl.getPublicState().ShieldedAccessControl__roleCommitmentNullifiers.member(expCommitment)).toBe(true); + } + } + }); + + it('should throw if non-admin operator revokes role', () => { + shieldedAccessControl.privateState.injectSecretNonce( + OPERATOR_ROLE_1, + OPERATOR_ROLE_1_SECRET_NONCE, + ); + shieldedAccessControl._grantRole(OPERATOR_ROLE_1, Z_OPERATOR_1); + + shieldedAccessControl.callerCtx.setCaller(OPERATOR_1); + expect(() => { + shieldedAccessControl.revokeRole(OPERATOR_ROLE_1, Z_UNAUTHORIZED); + }).toThrow('ShieldedAccessControl: unauthorized account'); + }); + }); +}); From 2fb86e7d930d9ec09392a6659c7ca63a3a4e373a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:24:27 -0400 Subject: [PATCH 199/202] Fix compiler errors, refactor mock and witnesses --- .../src/access/ShieldedAccessControl.compact | 52 +++++++++++++------ .../mocks/MockShieldedAccessControl.compact | 49 ++++++++++++----- .../ShieldedAccessControlWitnesses.ts | 31 ++++++----- 3 files changed, 88 insertions(+), 44 deletions(-) diff --git a/contracts/src/access/ShieldedAccessControl.compact b/contracts/src/access/ShieldedAccessControl.compact index 7700e0b0..fff6831c 100644 --- a/contracts/src/access/ShieldedAccessControl.compact +++ b/contracts/src/access/ShieldedAccessControl.compact @@ -121,6 +121,7 @@ module ShieldedAccessControl { export struct Role { isApproved: Boolean; roleCommitment: Bytes<32>; + commitmentNullifier: Bytes<32>; } /** @@ -155,7 +156,7 @@ module ShieldedAccessControl { * after every transfer to prevent duplicate commitments given the same `id`. * @returns {Bytes<32>} The commitment derived from `id` and `counter`. */ - export circuit _computeRoleCommitment( + export pure circuit _computeRoleCommitment( accountId: Bytes<32>, roleId: Bytes<32>, index: Uint<64>, @@ -165,11 +166,15 @@ module ShieldedAccessControl { accountId, roleId, index as Field as Bytes<32>, - pad(32, "ShieldedAccessControl:shield:") + pad(32, "ShieldedAccessControl:commitment") ] ); } + export pure circuit _computeNullifier(roleCommitment: Bytes<32>): Bytes<32> { + return persistentHash>>([roleCommitment, pad(32, "ShieldedAccessControl:nullifier")]); + } + /** * @description Computes the unique identifier (`id`) of the owner from their * public key and a secret nonce. @@ -239,15 +244,18 @@ module ShieldedAccessControl { * @return {Boolean} - A boolean determining if the account has the specified role.  */ export circuit callerHasRole(roleId: Bytes<32>): Role { - const account = ownPublicKey(); - + const callerAsEither = Either { + is_left: true, + left: ownPublicKey(), + right: ContractAddress { bytes: pad(32, "") } + }; const nonce = wit_secretNonce(roleId); - const accountId = _computeRoleId(account, nonce); + const accountId = _computeRoleId(callerAsEither, nonce); return getRole(roleId, accountId); } - export hasRole(roleId: Bytes<32>, accountId: Bytes<32>): Boolean { - const roleInfo = getRoleInfo(); + export circuit hasRole(roleId: Bytes<32>, accountId: Bytes<32>): Boolean { + const roleInfo = getRole(roleId, accountId); return roleInfo.isApproved; } @@ -286,14 +294,23 @@ module ShieldedAccessControl { export circuit getRole(roleId: Bytes<32>, accountId: Bytes<32>): Role { const index = wit_getRoleIndex(roleId, accountId); const commitment = _computeRoleCommitment(accountId, roleId, index); + const commitmentNullifier = _computeNullifier(commitment); const authPath = wit_getRoleCommitmentPath(commitment); const rootMatches = _operatorRoles .checkRoot(merkleTreePathRoot<10, Bytes<32>>(disclose(authPath))); - if(!_roleCommitmentNullifiers.member(disclose(commitment)) && rootMatches) { - return Role {isApproved: true, roleCommitment: disclose(commitment)}; + if(!_roleCommitmentNullifiers.member(disclose(commitmentNullifier)) && rootMatches) { + return Role { + isApproved: true, + roleCommitment: disclose(commitment), + commitmentNullifier: disclose(commitmentNullifier) + }; } else { - return Role {isApproved: false, roleCommitment: disclose(commitment)}; + return Role { + isApproved: false, + roleCommitment: disclose(commitment), + commitmentNullifier: disclose(commitmentNullifier) + }; } } @@ -414,11 +431,16 @@ module ShieldedAccessControl { * @param {Bytes<32>} nonce - A nonce created using SHA256(SK | "role-nonce" | role | PK) * @return {[]} - Empty tuple. */ - export circuit renounceRole(roleId: Bytes<32>, accountId: Bytes<32>): [] { + export circuit renounceRole(roleId: Bytes<32>, callerConfirmation: Bytes<32>): [] { const nonce = wit_secretNonce(roleId); - assert(accountId, _computeRoleId(ownPublicKey(), nonce), "ShieldedAccessControl: bad confirmation"); + const callerAsEither = Either { + is_left: true, + left: ownPublicKey(), + right: ContractAddress { bytes: pad(32, "") } + }; + assert(callerConfirmation == _computeRoleId(callerAsEither, nonce), "ShieldedAccessControl: bad confirmation"); - _revokeRole(roleId, accountId); + _revokeRole(roleId, callerConfirmation); } /** @@ -504,12 +526,12 @@ module ShieldedAccessControl { * @return {Boolean} roleRevoked - A boolean indicating if `roleId` was revoked. */ export circuit _revokeRole(roleId: Bytes<32>, accountId: Bytes<32>): Boolean { - const role = getRole(roleId, account); + const role = getRole(roleId, accountId); if (!role.isApproved) { return false; } - _roleCommitmentNullifiers.insert(disclose(role.roleCommitment)); + _roleCommitmentNullifiers.insert(disclose(role.commitmentNullifier)); return true; } } diff --git a/contracts/src/access/test/mocks/MockShieldedAccessControl.compact b/contracts/src/access/test/mocks/MockShieldedAccessControl.compact index 59c24a52..05c17395 100644 --- a/contracts/src/access/test/mocks/MockShieldedAccessControl.compact +++ b/contracts/src/access/test/mocks/MockShieldedAccessControl.compact @@ -19,31 +19,54 @@ export { ShieldedAccessControl_Role }; -export circuit hasRole(roleId: Bytes<32>, account: Either): ShieldedAccessControl_Role { - return ShieldedAccessControl_hasRole(roleId, account); +export pure circuit _computeRoleCommitment( + accountId: Bytes<32>, + roleId: Bytes<32>, + index: Uint<64>, +): Bytes<32> { + return ShieldedAccessControl__computeRoleCommitment(accountId, roleId, index); +} + +export pure circuit _computeRoleId( + pk: Either, + nonce: Bytes<32> +): Bytes<32> { + return ShieldedAccessControl__computeRoleId(pk, nonce); +} + +export pure circuit _computeNullifier(commitment: Bytes<32>): Bytes<32> { + return ShieldedAccessControl__computeNullifier(commitment); +} + +export circuit callerHasRole(roleId: Bytes<32>): ShieldedAccessControl_Role { + return ShieldedAccessControl_callerHasRole(roleId); +} + +export circuit hasRole(roleId: Bytes<32>, accountId: Bytes<32>): Boolean { + return ShieldedAccessControl_hasRole(roleId, accountId); } export circuit assertOnlyRole(roleId: Bytes<32>): [] { ShieldedAccessControl_assertOnlyRole(roleId); } -export circuit _checkRole(roleId: Bytes<32>, account: Either): [] { - ShieldedAccessControl__checkRole(roleId, account); +export circuit getRole(roleId: Bytes<32>, accountId: Bytes<32>): ShieldedAccessControl_Role { + return ShieldedAccessControl_getRole(roleId, accountId); } export circuit getRoleAdmin(roleId: Bytes<32>): Bytes<32> { return ShieldedAccessControl_getRoleAdmin(roleId); } -export circuit grantRole(roleId: Bytes<32>, account: Either): [] { - ShieldedAccessControl_grantRole(roleId, account); +export circuit grantRole(roleId: Bytes<32>, accountId: Bytes<32>): [] { + ShieldedAccessControl_grantRole(roleId, accountId); } -export circuit revokeRole(roleId: Bytes<32>, account: Either): [] { - ShieldedAccessControl_revokeRole(roleId, account); +export circuit revokeRole(roleId: Bytes<32>, accountId: Bytes<32>): [] { + ShieldedAccessControl_revokeRole(roleId, accountId); } -export circuit renounceRole(roleId: Bytes<32>, callerConfirmation: Either): [] { +export circuit renounceRole(roleId: Bytes<32>, callerConfirmation: Bytes<32>): [] { ShieldedAccessControl_renounceRole(roleId, callerConfirmation); } @@ -51,10 +74,10 @@ export circuit _setRoleAdmin(roleId: Bytes<32>, adminRole: Bytes<32>): [] { ShieldedAccessControl__setRoleAdmin(roleId, adminRole); } -export circuit _grantRole(roleId: Bytes<32>, account: Either): Boolean { - return ShieldedAccessControl__grantRole(roleId, account); +export circuit _grantRole(roleId: Bytes<32>, accountId: Bytes<32>): Boolean { + return ShieldedAccessControl__grantRole(roleId, accountId); } -export circuit _revokeRole(roleId: Bytes<32>, account: Either): Boolean { - return ShieldedAccessControl__revokeRole(roleId, account); +export circuit _revokeRole(roleId: Bytes<32>, accountId: Bytes<32>): Boolean { + return ShieldedAccessControl__revokeRole(roleId, accountId); } \ No newline at end of file diff --git a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts index 36bf6ac2..f5266101 100644 --- a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts @@ -27,6 +27,12 @@ export function fmtHexString(bytes: string | Uint8Array): string { } } +export function createAccountId(account: Either, secretNonce: Uint8Array): Uint8Array { + const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32)); + const bAccount = eitherToBytes(account); + return persistentHash(rt_type, [secretNonce, bAccount]); +} + /** * @description Interface defining the witness methods for ShieldedAccessControl operations. * @template P - The private state type. @@ -48,7 +54,7 @@ export interface IShieldedAccessControlWitnesses

{ wit_getRoleIndex( context: WitnessContext, roleId: Uint8Array, - account: Either + accountId: Uint8Array ): [P, bigint]; } @@ -61,7 +67,6 @@ type SecretNonce = Uint8Array; export type ShieldedAccessControlPrivateState = { /** @description A 32-byte secret nonce used as a privacy additive. */ roles: Record; - account: Either; }; /** @@ -73,14 +78,14 @@ export const ShieldedAccessControlPrivateState = { * @returns A fresh ShieldedAccessControlPrivateState instance. */ generate: ( - account: Either, ): ShieldedAccessControlPrivateState => { const defaultRoleId: string = Buffer.alloc(32).toString('hex'); + const secretNonce = new Uint8Array(getRandomValues(Buffer.alloc(32))); + const privateState: ShieldedAccessControlPrivateState = { roles: {}, - account, }; - privateState.roles[defaultRoleId] = getRandomValues(Buffer.alloc(32)); + privateState.roles[defaultRoleId] = secretNonce; return privateState; }, @@ -99,14 +104,12 @@ export const ShieldedAccessControlPrivateState = { * ``` */ withRoleAndNonce: ( - account: Either, roleId: Buffer, nonce: Buffer, ): ShieldedAccessControlPrivateState => { const roleString = roleId.toString('hex'); const privateState: ShieldedAccessControlPrivateState = { roles: {}, - account, }; privateState.roles[roleString] = nonce; return privateState; @@ -147,19 +150,15 @@ export const ShieldedAccessControlPrivateState = { privateState, }: WitnessContext, roleId: Uint8Array, - account: Either + accountId: Uint8Array ): bigint => { - const roleIdString = Buffer.from(roleId).toString('hex'); - const bNonce = privateState.roles[roleIdString]; - const rt_type = new CompactTypeVector(5, new CompactTypeBytes(32)); - const bAccount = eitherToBytes(account); + const rt_type = new CompactTypeVector(4, new CompactTypeBytes(32)); // Iterate over each MT index to determine if commitment exists for (let i = 0; i <= ledger.ShieldedAccessControl__currentMerkleTreeIndex; i++) { const bIndex = convert_bigint_to_Uint8Array(32, BigInt(i)); const commitment = persistentHash(rt_type, [ + accountId, roleId, - bAccount, - bNonce, bIndex, DOMAIN, ]); @@ -220,11 +219,11 @@ export const ShieldedAccessControlWitnesses = wit_getRoleIndex( context: WitnessContext, roleId: Uint8Array, - account: Either + accountId: Uint8Array ): [ShieldedAccessControlPrivateState, bigint] { return [ context.privateState, - ShieldedAccessControlPrivateState.getRoleIndex(context, roleId, account), + ShieldedAccessControlPrivateState.getRoleIndex(context, roleId, accountId), ]; }, }); From a2e644d99d8803d0f6ec2833923633889a52dc6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Thu, 18 Sep 2025 22:49:13 -0400 Subject: [PATCH 200/202] Refactor test suite --- .../access/test/ShieldedAccessControl.test.ts | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) diff --git a/contracts/src/access/test/ShieldedAccessControl.test.ts b/contracts/src/access/test/ShieldedAccessControl.test.ts index e69de29b..232fb37d 100644 --- a/contracts/src/access/test/ShieldedAccessControl.test.ts +++ b/contracts/src/access/test/ShieldedAccessControl.test.ts @@ -0,0 +1,199 @@ +import { + CompactTypeBytes, + CompactTypeVector, + convert_bigint_to_Uint8Array, + persistentHash, + type WitnessContext, +} from '@midnight-ntwrk/compact-runtime'; +import { beforeEach, describe, expect, it } from 'vitest'; +import { + ContractAddress, + Either, + Ledger, + MerkleTreePath, + ShieldedAccessControl_Role as Role, + ZswapCoinPublicKey, + Contract as MyContract +} from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; +import { fmtHexString, ShieldedAccessControlPrivateState, ShieldedAccessControlWitnesses } from '../witnesses/ShieldedAccessControlWitnesses.js'; +import { ShieldedAccessControlSimulator } from './simulators/ShieldedAccessControlSimulator.js'; +import * as utils from './utils/address.js'; + +// Helpers +const buildCommitment = ( + accountId: Uint8Array, + roleId: Uint8Array, + index: bigint, +): Uint8Array => { + const rt_type = new CompactTypeVector(4, new CompactTypeBytes(32)); + const bIndex = convert_bigint_to_Uint8Array(32, index); + + const commitment = persistentHash(rt_type, [ + accountId, + roleId, + bIndex, + COMMITMENT_DOMAIN, + ]); + + return commitment; +}; + +const buildNullifier = ( + roleCommitment: Uint8Array, +): Uint8Array => { + const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32)); + + const nullifier = persistentHash(rt_type, [ + roleCommitment, + NULLIFIER_DOMAIN, + ]); + + return nullifier; +}; + +const createIdHash = ( + pk: ZswapCoinPublicKey, + nonce: Uint8Array, +): Uint8Array => { + const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32)); + + const bPK = pk.bytes; + return persistentHash(rt_type, [bPK, nonce]); +}; + +// PKs +const [ADMIN, Z_ADMIN] = utils.generatePubKeyPair('ADMIN'); +const [UNAUTHORIZED, Z_UNAUTHORIZED] = utils.generatePubKeyPair('UNAUTHORIZED'); + +// Roles +const DEFAULT_ADMIN_ROLE = utils.zeroUint8Array(); +const BAD_ROLE = convert_bigint_to_Uint8Array(32, 99999999n); + +// Nonces +const ADMIN_SECRET_NONCE = Buffer.alloc(32, 'ADMIN_SECRET_NONCE'); +const BAD_NONCE = Buffer.alloc(32, 'BAD_NONCE'); + +// Constants +const COMMITMENT_DOMAIN = new Uint8Array(32); +new TextEncoder().encodeInto('ShieldedAccessControl:commitment', COMMITMENT_DOMAIN); +const NULLIFIER_DOMAIN = new Uint8Array(32); +new TextEncoder().encodeInto('ShieldedAccessControl:nullifier', NULLIFIER_DOMAIN); + +const ADMIN_ID = createIdHash(Z_ADMIN, ADMIN_SECRET_NONCE); +const ADMIN_COMMITMENT = buildCommitment(ADMIN_ID, DEFAULT_ADMIN_ROLE, 0n); +const ADMIN_NULLIFIER = buildNullifier(ADMIN_COMMITMENT); + +const BAD_ID = createIdHash(Z_UNAUTHORIZED, new Uint8Array(32)); +const BAD_INDEX = 99999999n; +const BAD_COMMITMENT = buildCommitment(BAD_ID, BAD_ROLE, BAD_INDEX); + +let shieldedAccessControl: ShieldedAccessControlSimulator; + + +describe('ShieldedAccessControl', () => { + beforeEach(() => { + // Create private state object and generate nonce + const PS = ShieldedAccessControlPrivateState.withRoleAndNonce( + Buffer.from(DEFAULT_ADMIN_ROLE), + ADMIN_SECRET_NONCE, + ); + // Init contract for user with PS + shieldedAccessControl = new ShieldedAccessControlSimulator({ + privateState: PS, + coinPK: ADMIN + }); + }); + + describe('_computeRoleCommitment', () => { + it('computed commitment should match', () => { + expect(shieldedAccessControl._computeRoleCommitment(ADMIN_ID, DEFAULT_ADMIN_ROLE, 0n)).toEqual(ADMIN_COMMITMENT); + }); + + type ComputeRoleCommitmentCases = [ + method: keyof ShieldedAccessControlSimulator, + isValidId: boolean, + isValidRole: boolean, + isValidIndex: boolean, + args: unknown[], + ]; + + const checkedCircuits: ComputeRoleCommitmentCases[] = [ + ['_computeRoleCommitment', false, true, true, [BAD_ID, DEFAULT_ADMIN_ROLE, 0n]], + ['_computeRoleCommitment', true, false, true, [ADMIN_ID, BAD_ROLE, 0n]], + ['_computeRoleCommitment', true, true, false, [ADMIN_ID, DEFAULT_ADMIN_ROLE, BAD_INDEX]], + ['_computeRoleCommitment', false, true, false, [BAD_ID, DEFAULT_ADMIN_ROLE, BAD_INDEX]], + ['_computeRoleCommitment', false, false, false, [BAD_ID, BAD_ROLE, BAD_INDEX]], + ['_computeRoleCommitment', true, false, false, [ADMIN_ID, BAD_ROLE, BAD_INDEX]], + ['_computeRoleCommitment', false, false, true, [BAD_ID, BAD_ROLE, 0n]], + ] + + it.each(checkedCircuits)( + '%s should not match with isValidNonce(%s), isValidIndex(%s), isValidPath(%s)', + (circuitName, isValidId, isValidRole, isValidIndex, args) => { + // Test protected circuit + expect(() => { + ( + shieldedAccessControl[circuitName] as ( + ...args: unknown[] + ) => unknown + )(...args); + }).not.toEqual(ADMIN); + } + ) + }); + + describe('_computeNullifier', () => { + it('should match nullifier', () => { + expect(shieldedAccessControl._computeNullifier(ADMIN_COMMITMENT)).toEqual(ADMIN_NULLIFIER); + }); + + it('should not match with bad commitment', () => { + expect(shieldedAccessControl._computeNullifier(BAD_COMMITMENT)).not.toEqual(ADMIN_NULLIFIER); + }); + }); + + describe('_computeRoleId', () => { + const eitherAdmin = utils.createEitherTestUser('ADMIN'); + const eitherUnauthorized = utils.createEitherTestUser('UNAUTHORIZED'); + + it('should match role id', () => { + expect(shieldedAccessControl._computeRoleId(eitherAdmin, ADMIN_SECRET_NONCE)).toEqual(ADMIN_ID); + }); + + it('should fail for contract address', () => { + const eitherContract = utils.createEitherTestContractAddress('CONTRACT') + expect(() => { + shieldedAccessControl._computeRoleId(eitherContract, ADMIN_SECRET_NONCE); + }).toThrow('ShieldedAccessControl: contract address owners are not yet supported'); + }); + + type ComputeRoleIdCases = [ + method: keyof ShieldedAccessControlSimulator, + isValidAccount: boolean, + isValidNonce: boolean, + args: unknown[], + ]; + + const checkedCircuits: ComputeRoleIdCases[] = [ + ['_computeRoleId', true, false, [eitherAdmin, BAD_NONCE]], + ['_computeRoleId', false, true, [eitherUnauthorized, ADMIN_SECRET_NONCE]], + ['_computeRoleId', false, false, [eitherUnauthorized, BAD_NONCE]], + ]; + + it.each(checkedCircuits)( + '%s should not match role id with invalidAccount=%s or invalidNonce=%s', + (circuitName, isValidAccount, isValidNonce, args) => { + // Test circuit + expect(() => { + ( + shieldedAccessControl[circuitName] as ( + ...args: unknown[] + ) => unknown + )(...args); + }).not.toEqual(ADMIN_ID); + } + ) + }); + + +}); \ No newline at end of file From 16928c308c92f4758abfd398a043bfe1ab1d2d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Thu, 18 Sep 2025 22:49:50 -0400 Subject: [PATCH 201/202] Refactor simulator for new design --- .../ShieldedAccessControlSimulator.ts | 67 +++++++++++-------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts index 170c41ec..79df3512 100644 --- a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts +++ b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts @@ -62,7 +62,6 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< >; constructor( - initUser: Either, options: ShieldedAccessControlSimOptions = {}, ) { super(); @@ -71,9 +70,9 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< const { privateState = options.privateState ? options.privateState - : ShieldedAccessControlPrivateState.generate(initUser), + : ShieldedAccessControlPrivateState.generate(), witnesses = ShieldedAccessControlWitnesses(), - coinPK = '0'.repeat(64), + coinPK = options.coinPK ? options.coinPK : '0'.repeat(64), address = sampleContractAddress(), } = options; @@ -233,6 +232,29 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< }; } + public _computeRoleCommitment( + accountId: Uint8Array, + roleId: Uint8Array, + index: bigint, + ): Uint8Array { + return this.circuits.pure._computeRoleCommitment(accountId, roleId, index); + } + + public _computeRoleId( + pk: Either, + nonce: Uint8Array + ): Uint8Array { + return this.circuits.pure._computeRoleId(pk, nonce); + } + + public _computeNullifier(commitment: Uint8Array): Uint8Array { + return this.circuits.pure._computeNullifier(commitment); + } + + public callerHasRole(roleId: Uint8Array): Role { + return this.circuits.impure.callerHasRole(roleId); + } + /** * @description Returns the current commitment representing the contract owner. * The full commitment is: `SHA256(SHA256(pk, nonce), instanceSalt, counter, domain)`. @@ -240,9 +262,9 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< */ public hasRole( roleId: Uint8Array, - account: Either, - ): Role { - return this.circuits.impure.hasRole(roleId, account); + accountId: Uint8Array, + ): Boolean { + return this.circuits.impure.hasRole(roleId, accountId); } /** @@ -254,16 +276,8 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< this.circuits.impure.assertOnlyRole(roleId); } - /** - * @description Leaves the contract without an owner. - * It will not be possible to call `assertOnlyOnwer` circuits anymore. - * Can only be called by the current owner. - */ - public _checkRole( - roleId: Uint8Array, - account: Either, - ) { - this.circuits.impure._checkRole(roleId, account); + public getRole(roleId: Uint8Array, accountId: Uint8Array): Role { + return this.circuits.impure.getRole(roleId, accountId); } /** @@ -286,9 +300,9 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< */ public grantRole( roleId: Uint8Array, - account: Either, + accountId: Uint8Array ) { - this.circuits.impure.grantRole(roleId, account); + this.circuits.impure.grantRole(roleId, accountId); } /** @@ -298,9 +312,9 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< */ public revokeRole( roleId: Uint8Array, - account: Either, + accountId: Uint8Array ) { - this.circuits.impure.revokeRole(roleId, account); + this.circuits.impure.revokeRole(roleId, accountId); } /** @@ -310,7 +324,7 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< */ public renounceRole( roleId: Uint8Array, - callerConfirmation: Either, + callerConfirmation: Uint8Array ) { this.circuits.impure.renounceRole(roleId, callerConfirmation); } @@ -331,9 +345,9 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< */ public _grantRole( roleId: Uint8Array, - account: Either, + accountId: Uint8Array ): boolean { - return this.circuits.impure._grantRole(roleId, account); + return this.circuits.impure._grantRole(roleId, accountId); } /** @@ -343,9 +357,9 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< */ public _revokeRole( roleId: Uint8Array, - account: Either, + accountId: Uint8Array ): boolean { - return this.circuits.impure._revokeRole(roleId, account); + return this.circuits.impure._revokeRole(roleId, accountId); } public readonly privateState = { @@ -360,7 +374,6 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< ): ShieldedAccessControlPrivateState => { const currentState = this.stateManager.getContext().currentPrivateState; const updatedState = { - ...currentState, roles: { ...currentState.roles }, }; const roleString = Buffer.from(roleId).toString('hex'); @@ -370,7 +383,7 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< }, /** - * @description Returns the secret nonce given the context. + * @description Returns the secret nonce for a given roleId. * @returns The secret nonce. */ getCurrentSecretNonce: (roleId: Uint8Array): Uint8Array => { From 740bb831439d2a3a4329607193c2df33bdc6499b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9F=A3=20=E2=82=AC=E2=82=A5=E2=84=B5=E2=88=AA=E2=84=93?= =?UTF-8?q?=20=E2=9F=A2?= <34749913+emnul@users.noreply.github.com> Date: Thu, 25 Sep 2025 13:56:37 -0400 Subject: [PATCH 202/202] WIP --- .../src/access/ShieldedAccessControl.compact | 4 +- .../access/test/ShieldedAccessControl.test.ts | 93 ++++++++++++++++++- .../ShieldedAccessControlSimulator.ts | 7 +- .../ShieldedAccessControlWitnesses.ts | 17 ++-- 4 files changed, 104 insertions(+), 17 deletions(-) diff --git a/contracts/src/access/ShieldedAccessControl.compact b/contracts/src/access/ShieldedAccessControl.compact index fff6831c..2986cda6 100644 --- a/contracts/src/access/ShieldedAccessControl.compact +++ b/contracts/src/access/ShieldedAccessControl.compact @@ -297,7 +297,7 @@ module ShieldedAccessControl { const commitmentNullifier = _computeNullifier(commitment); const authPath = wit_getRoleCommitmentPath(commitment); const rootMatches = _operatorRoles - .checkRoot(merkleTreePathRoot<10, Bytes<32>>(disclose(authPath))); + .checkRoot(merkleTreePathRootNoLeafHash<10>(disclose(authPath))); if(!_roleCommitmentNullifiers.member(disclose(commitmentNullifier)) && rootMatches) { return Role { @@ -492,7 +492,7 @@ module ShieldedAccessControl { } // Use ledger index as source of truth - _operatorRoles.insertIndex(disclose(role.roleCommitment), _currentMerkleTreeIndex); + _operatorRoles.insertHashIndex(role.roleCommitment, _currentMerkleTreeIndex); _currentMerkleTreeIndex.increment(1); return true; } diff --git a/contracts/src/access/test/ShieldedAccessControl.test.ts b/contracts/src/access/test/ShieldedAccessControl.test.ts index 232fb37d..c6dc0891 100644 --- a/contracts/src/access/test/ShieldedAccessControl.test.ts +++ b/contracts/src/access/test/ShieldedAccessControl.test.ts @@ -5,7 +5,7 @@ import { persistentHash, type WitnessContext, } from '@midnight-ntwrk/compact-runtime'; -import { beforeEach, describe, expect, it } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { ContractAddress, Either, @@ -13,7 +13,7 @@ import { MerkleTreePath, ShieldedAccessControl_Role as Role, ZswapCoinPublicKey, - Contract as MyContract + Contract as MockShieldedAccessControl } from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; import { fmtHexString, ShieldedAccessControlPrivateState, ShieldedAccessControlWitnesses } from '../witnesses/ShieldedAccessControlWitnesses.js'; import { ShieldedAccessControlSimulator } from './simulators/ShieldedAccessControlSimulator.js'; @@ -63,14 +63,24 @@ const createIdHash = ( // PKs const [ADMIN, Z_ADMIN] = utils.generatePubKeyPair('ADMIN'); +const [OPERATOR_1, Z_OPERATOR_1] = utils.generatePubKeyPair('OPERATOR_1'); +const [OPERATOR_2, Z_OPERATOR_2] = utils.generatePubKeyPair('OPERATOR_2'); +const [OPERATOR_3, Z_OPERATOR_3] = utils.generatePubKeyPair('OPERATOR_3'); const [UNAUTHORIZED, Z_UNAUTHORIZED] = utils.generatePubKeyPair('UNAUTHORIZED'); // Roles const DEFAULT_ADMIN_ROLE = utils.zeroUint8Array(); +const OPERATOR_1_ROLE = convert_bigint_to_Uint8Array(32, 1n); +const OPERATOR_2_ROLE = convert_bigint_to_Uint8Array(32, 2n); +const OPERATOR_3_ROLE = convert_bigint_to_Uint8Array(32, 3n); +const UNINITIALIZED_ROLE = convert_bigint_to_Uint8Array(32, 555n); const BAD_ROLE = convert_bigint_to_Uint8Array(32, 99999999n); // Nonces const ADMIN_SECRET_NONCE = Buffer.alloc(32, 'ADMIN_SECRET_NONCE'); +const OPERATOR_1_SECRET_NONCE = Buffer.alloc(32, 'OPERATOR_1_NONCE'); +const OPERATOR_2_SECRET_NONCE = Buffer.alloc(32, 'OPERATOR_2_NONCE'); +const OPERATOR_3_SECRET_NONCE = Buffer.alloc(32, 'OPERATOR_3_NONCE'); const BAD_NONCE = Buffer.alloc(32, 'BAD_NONCE'); // Constants @@ -83,6 +93,10 @@ const ADMIN_ID = createIdHash(Z_ADMIN, ADMIN_SECRET_NONCE); const ADMIN_COMMITMENT = buildCommitment(ADMIN_ID, DEFAULT_ADMIN_ROLE, 0n); const ADMIN_NULLIFIER = buildNullifier(ADMIN_COMMITMENT); +const OPERATOR_1_ID = createIdHash(Z_OPERATOR_1, OPERATOR_1_SECRET_NONCE); +const OPERATOR_2_ID = createIdHash(Z_OPERATOR_2, OPERATOR_2_SECRET_NONCE); +const OPERATOR_3_ID = createIdHash(Z_OPERATOR_3, OPERATOR_3_SECRET_NONCE); + const BAD_ID = createIdHash(Z_UNAUTHORIZED, new Uint8Array(32)); const BAD_INDEX = 99999999n; const BAD_COMMITMENT = buildCommitment(BAD_ID, BAD_ROLE, BAD_INDEX); @@ -195,5 +209,80 @@ describe('ShieldedAccessControl', () => { ) }); + // Complete testing once issue with pathForLeaf is resolved + describe.todo('wit_getRoleIndex', () => { + it.todo('should return 0 if no roles granted', () => { + const [_, index] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), UNINITIALIZED_ROLE, ADMIN_ID); + expect(index).toBe(0n); + }); + + it.todo('should return correct index', () => { + let granted = shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, ADMIN_ID); + expect(granted).toBe(true); + let [, adminIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, ADMIN_ID); + expect(adminIndex).toBe(0n); + + shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_1_ROLE, OPERATOR_1_SECRET_NONCE); + granted = shieldedAccessControl._grantRole(OPERATOR_1_ROLE, OPERATOR_1_ID); + expect(granted).toBe(true); + const [, operatorIndex] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), OPERATOR_1_ROLE, OPERATOR_1_ID); + expect(operatorIndex).toBe(1n); + + shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_2_ROLE, OPERATOR_2_SECRET_NONCE); + granted = shieldedAccessControl._grantRole(OPERATOR_2_ROLE, OPERATOR_2_ID); + expect(granted).toBe(true); + shieldedAccessControl._grantRole(OPERATOR_2_ROLE, OPERATOR_2_ID); + const [, operatorIndex2] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), OPERATOR_2_ROLE, OPERATOR_2_ID); + expect(operatorIndex2).toBe(2n); + + shieldedAccessControl.privateState.injectSecretNonce(OPERATOR_3_ROLE, OPERATOR_3_SECRET_NONCE); + shieldedAccessControl._grantRole(OPERATOR_3_ROLE, OPERATOR_3_ID); + const [_, operatorIndex3] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), OPERATOR_3_ROLE, OPERATOR_3_ID); + expect(operatorIndex3).toBe(3n); + + let [, adminIndex2] = shieldedAccessControl.witnesses.wit_getRoleIndex(shieldedAccessControl.getWitnessContext(), DEFAULT_ADMIN_ROLE, ADMIN_ID); + expect(adminIndex2).toBe(0n); + }); + + it.todo('should return current Merkle tree index if role does not exist') + }); + + describe('wit_getRoleCommitmentPath', () => { + it('should return a Merkle tree path if one exists', () => { + + }); + }); + + describe('getRole', () => { + it('should return unapproved if role does not exist', () => { + expect(shieldedAccessControl.getRole(UNINITIALIZED_ROLE, ADMIN_ID).isApproved).toBe(false); + }); + + it('should return correct commitment', () => { + expect(shieldedAccessControl.getRole(DEFAULT_ADMIN_ROLE, ADMIN_ID).roleCommitment).toEqual(ADMIN_COMMITMENT); + }); + + it('should return correct nullifier', () => { + expect(shieldedAccessControl.getRole(DEFAULT_ADMIN_ROLE, ADMIN_ID).commitmentNullifier).toEqual(ADMIN_NULLIFIER); + }); + + it('should return approved role', () => { + shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, ADMIN_ID); + expect(shieldedAccessControl.getRole(DEFAULT_ADMIN_ROLE, ADMIN_ID).isApproved).toBe(true); + }); + }); + + describe('_grantRole', () => { + it('should return true for new role', () => { + expect(shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, ADMIN_ID)).toBe(true); + }); + + it('should return false if role already granted', () => { + shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, ADMIN_ID); + expect(shieldedAccessControl._grantRole(DEFAULT_ADMIN_ROLE, ADMIN_ID)).toBe(false); + }); + }); + + describe('') }); \ No newline at end of file diff --git a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts index 79df3512..2118e91e 100644 --- a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts +++ b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts @@ -2,6 +2,7 @@ import { type CircuitContext, type CoinPublicKey, emptyZswapLocalState, + witnessContext, type WitnessContext, } from '@midnight-ntwrk/compact-runtime'; import { sampleContractAddress } from '@midnight-ntwrk/zswap'; @@ -111,11 +112,7 @@ export class ShieldedAccessControlSimulator extends AbstractContractSimulator< Ledger, ShieldedAccessControlPrivateState > { - return { - ledger: this.getPublicState(), - privateState: this.getPrivateState(), - contractAddress: this.contractAddress, - }; + return witnessContext(this.getPublicState(), this.getPrivateState(), this.contractAddress); } /** diff --git a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts index f5266101..cd905647 100644 --- a/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts +++ b/contracts/src/access/witnesses/ShieldedAccessControlWitnesses.ts @@ -15,8 +15,8 @@ import type { } from '../../../artifacts/MockShieldedAccessControl/contract/index.cjs'; import { eitherToBytes } from '../test/utils/address'; -const DOMAIN = new Uint8Array(32); -new TextEncoder().encodeInto('ShieldedAccessControl:shield:', DOMAIN); +const COMMITMENT_DOMAIN = new Uint8Array(32); +new TextEncoder().encodeInto('ShieldedAccessControl:commitment', COMMITMENT_DOMAIN); export function fmtHexString(bytes: string | Uint8Array): string { if (bytes instanceof String) { @@ -154,28 +154,29 @@ export const ShieldedAccessControlPrivateState = { ): bigint => { const rt_type = new CompactTypeVector(4, new CompactTypeBytes(32)); // Iterate over each MT index to determine if commitment exists + console.log("current MT index ", ledger.ShieldedAccessControl__currentMerkleTreeIndex); for (let i = 0; i <= ledger.ShieldedAccessControl__currentMerkleTreeIndex; i++) { - const bIndex = convert_bigint_to_Uint8Array(32, BigInt(i)); + const index = BigInt(i); + const bIndex = convert_bigint_to_Uint8Array(32, index); const commitment = persistentHash(rt_type, [ accountId, roleId, bIndex, - DOMAIN, + COMMITMENT_DOMAIN, ]); try { - const index = BigInt(i); const pathForLeaf = ledger.ShieldedAccessControl__operatorRoles.pathForLeaf( index, commitment, ); - if (pathForLeaf.leaf === commitment) { + if (Buffer.from(pathForLeaf.leaf).compare(Buffer.from(commitment)) === 0) { return index; } } catch (e: unknown) { if (e instanceof Error) { const [msg, index] = e.message.split(':'); if (msg === 'invalid index into sparse merkle tree') { - // console.log(`role ${fmtHexString(roleIdString)} with commitment ${fmtHexString(commitment)} not found at index ${index}`); + // console.log(`accountId ${fmtHexString(accountId)} with commitment ${fmtHexString(commitment)} not found at index ${index}`); } else { throw e; } @@ -183,7 +184,7 @@ export const ShieldedAccessControlPrivateState = { } } - console.log("WIT - Commitment DNE, returing MT index ", ledger.ShieldedAccessControl__currentMerkleTreeIndex.toString()); + console.log("WIT - Commitment DNE, returning MT index ", ledger.ShieldedAccessControl__currentMerkleTreeIndex.toString()); // If commitment doesn't exist return currentMTIndex // Used for adding roles