diff --git a/README.md b/README.md index 4b60692..ade94d8 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,28 @@ # Compact native scripts -CLI-based application to generate compact code that checks commitments based on Cardano native scripts. Accepts JSON inputs structured around native script patterns, supporting nested conditions (`any`, `all`, `atLeast`). +CLI-based application to generate compact code that checks commitments and time-lock conditions based on multisignature scripts. Accepts JSON inputs structured around Cardano native script patterns, supporting nested conditions (`any`, `all`, `atLeast`). -The idea is that the CLI generates a Compact contract module that can be used to grant access to circuits based on a set of commitments. The contract has two exported circuits: `commit` and `verify`: +The idea is that the CLI generates a Compact contract module that can be used to grant access to circuits based on a set of commitments. The contract has three exported circuits: `init`, `commit` and `verify`: +- `init` is used to initalize the contract's state - `commit` is used by a user when it wants to add its commitment to the set to authorize a certain circuit running. - `verify` is used within a circuit to ensure that it will be run if and only if the set of commitments present satisfies the predefined assertions. The predefined assertions are constructed based on Cardano native scripts. +## Table of Contents + +- [Prerequisites](#prerequisites) +- [Setup](#setup) +- [Documentation](#documentation) +- [Authorization workflow](#authorization-workflow) +- [Commands](#commands) + - [Commitment generator](#commitment-generator) + - [Script wizard](#script-wizard) + - [Compact code generator](#compact-code-generator) + - [Compile](#compile) + - [Test](#test) +- [E2E example](#e2e-example) + ## Prerequisites - Compact Devtools 0.4.0 (check with `compact --version`) @@ -22,24 +37,99 @@ Install dependencies pnpm install ``` -## Usage +## Documentation -### Compact code generator +See [docs/schema.md](docs/schema.md) for the complete schema design documentation, and [docs/design.md](docs/design.md) for a description of the Compact code that the CLI generates and usage instructions. + +## Authorization workflow + +This tool involves two distinct roles: + +### Participant + +Each person who needs to authorize operations runs `make-commitment` to generate their **secret pair** (`secret` + `randomness`) and the corresponding **commitment** which will be present in the Compact code. ```bash -pnpm generate-code -i -pnpm generate-code -i -o -pnpm generate-code -i -o -t +pnpm make-commitment -o my-pair.json ``` -Generates a Compact contract module (`Warden.compact`) from a JSON input file. -The input file defines the script tree (commitment hashes, composite conditions, -time locks) following the schema documented below. +This produces a file like: -Optional parameters are: +```json +{ + "secret": "ed192a825b79d3602cd82cdf0c15ad7accfdc06f32bc0062f89a6c09edaf964b", + "randomness": "1a0a174c772ee1556bf1b980475eaffe2b4bb8112e82f5348e612b93804405da", + "commitment": "21f314e3679165f234fe8a986f60d2a0308e4b846415fe68f4bf9d3d194ea74c" +} +``` -- `-o, --output ` Directory to write the generated Compact code (default: `generated/`) -- `-t, --test` Include import/export boilerplate for unit testing +> **Keep your `secret` and `randomness` private** — they prove your identity at circuit execution time. +> **Share only the `commitment` hex** with the script author so it can be included in the authorization policy. + +### Script author + +The person in charge of building the authorization policy collects the commitments from all participants and runs `generate-code` to produce the Compact contract. + +``` +Commitments from participants Native script definition + │ │ + ▼ ▼ + ┌──────────────────────────────────────────┐ + │ pnpm generate-code │ + │ -i script.json │ + └──────────────────────────────────────────┘ + │ + ▼ + ┌──────────────────┐ + │ Warden.compact │ + │ (authorization │ + │ contract) │ + └──────────────────┘ +``` + +**Step 1 — Collect commitments** + +Ask each participant to run `make-commitment` and share the `commitment` hex with you. + +**Step 2 — Build the native script** + +Write a JSON file describing the authorization conditions. You can either: + +- Use the interactive wizard: + ```bash + pnpm script-wizard + ``` +- Write the JSON manually (see [the schema docs](docs/schema.md) and the [examples directory](examples/inputs/)). + +The script references each commitment by its hex hash: + +```json +{ + "type": "all", + "scripts": [ + { "type": "cmt", "hash": "21f314e3679165f234fe8a986f60d2a0308e4b846415fe68f4bf9d3d194ea74c" }, + { "type": "cmt", "hash": "9517dcb986da18099688e88b6fe1fce568fd0eeb7ddbba86a8263dc489f85783" } + ] +} +``` + +**Step 3 — Generate the contract** + +```bash +pnpm generate-code -i script.json -o generated/ +``` + +This creates `generated/Warden.compact` — a Compact module with the authorization logic baked in. + +**Step 4 — Import into your project** + +```compact +import "generated/Warden"; +``` + +Use `verify()` as a guard before any protected operation. + +## Commands ### Commitment generator @@ -53,6 +143,36 @@ Optional parameters are: - -s, --seed 64-character hex seed for the secret - -o, --output Path to write the result as JSON +### Script wizard + +```bash +pnpm script-wizard +``` + +Launches an interactive wizard that builds a native script schema JSON file +that can be used as input to the `generate-code` command. +Walks through script node types — commitment (`cmt`), time locks (`after`/`before`), +and composites (`any`/`all`/`atLeast`) — and writes the result to a JSON file +(defaults to `script.json`). +Ensure all commitments required have been gathered prior to running this command. + +### Compact code generator + +```bash +pnpm generate-code -i +pnpm generate-code -i -o +pnpm generate-code -i -o -t +``` + +Generates a Compact contract module (`Warden.compact`) from a JSON input file. +The input file defines the script tree (commitment hashes, composite conditions, +time locks) following the schema documented below. + +Optional parameters are: + +- `-o, --output ` Directory to write the generated Compact code (default: `generated/`) +- `-t, --test` Include import/export boilerplate for unit testing + ### Compile ```bash @@ -75,6 +195,11 @@ To use another example, the command must be run like: TEST_INPUT=examples/inputs/ pnpm test ``` -## Documentation +## E2E example + +A complete [end-to-end example](e2e/) demonstrating Warden in action — a token +supply contract that uses Warden to gate mint and burn operations by an +authorization policy (any, all, atLeast, time locks, or combinations). -See [docs/schema.md](docs/schema.md) for the complete schema design documentation, and [docs/design.md](docs/design.md) for a description of the Compact code that the CLI generates. +The flow: **deploy** → participants **commit** → **mint/burn** guarded by +`verify()`. See the [e2e README](e2e/README.md) for setup and usage. diff --git a/package.json b/package.json index fe0920c..0b451e1 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "scripts": { "generate-code": "tsx src/cli.ts generate-code", "make-commitment": "tsx src/cli.ts make-commitment", + "script-wizard": "tsx src/cli.ts script-wizard", "build": "tsc && turbo run build", "compact": "compact compile generated/Warden.compact ./generated/managed/warden", "clean": "pnpm --filter @e2e/* run clean && rm -rf dist generated tsconfig.tsbuildinfo .turbo", @@ -36,6 +37,7 @@ "vitest": "^4.1.6" }, "dependencies": { + "@inquirer/prompts": "^8.5.2", "@midnight-ntwrk/compact-runtime": "0.16.0", "commander": "^14.0.3", "zod": "^4.3.6" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e6f175a..3e69d26 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: .: dependencies: + '@inquirer/prompts': + specifier: ^8.5.2 + version: 8.5.2(@types/node@25.6.0) '@midnight-ntwrk/compact-runtime': specifier: 0.16.0 version: 0.16.0 @@ -437,6 +440,140 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@inquirer/ansi@2.0.7': + resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/checkbox@5.2.1': + resolution: {integrity: sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@6.1.1': + resolution: {integrity: sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@11.2.1': + resolution: {integrity: sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@5.2.2': + resolution: {integrity: sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@5.1.1': + resolution: {integrity: sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@3.0.3': + resolution: {integrity: sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@2.0.7': + resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/input@5.1.2': + resolution: {integrity: sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@4.1.1': + resolution: {integrity: sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@5.1.1': + resolution: {integrity: sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@8.5.2': + resolution: {integrity: sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@5.3.1': + resolution: {integrity: sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@4.2.1': + resolution: {integrity: sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@5.2.1': + resolution: {integrity: sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@4.0.7': + resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} @@ -1121,10 +1258,17 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + classic-level@3.0.0: resolution: {integrity: sha512-yGy8j8LjPbN0Bh3+ygmyYvrmskVita92pD/zCoalfcC9XxZj6iDtZTAnz+ot7GG8p9KLTG+MZ84tSA4AhkgVZQ==} engines: {node: '>=18'} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + commander@14.0.3: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} @@ -1249,6 +1393,15 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1335,6 +1488,10 @@ packages: resolution: {integrity: sha512-cQOsSMS/IrDz82PVyRDvf/Q1F/bRbBVjJlh+xYOkI1qw2bWRvWGiWc+m2O0d6l4Bt1fyY+8kzJ8JFWGJqNeDBg==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -1515,6 +1672,10 @@ packages: multipasta@0.2.7: resolution: {integrity: sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==} + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + nanoid@3.3.12: resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -1662,6 +1823,9 @@ packages: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scale-ts@1.6.1: resolution: {integrity: sha512-PBMc2AWc6wSEqJYBDPcyCLUj9/tMKnLX70jLOSndMtcUoLQucP/DM0vnQo1wJAYjTrQiq8iG9rD0q6wFzgjH7g==} @@ -1681,6 +1845,10 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + smoldot@2.0.26: resolution: {integrity: sha512-F+qYmH4z2s2FK+CxGj8moYcd1ekSIKH8ywkdqlOz88Dat35iB1DIYL11aILN46YSGMzQW/lbJNS307zBSDN5Ig==} @@ -2078,6 +2246,125 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@inquirer/ansi@2.0.7': {} + + '@inquirer/checkbox@5.2.1(@types/node@25.6.0)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/confirm@6.1.1(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/core@11.2.1(@types/node@25.6.0)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@25.6.0) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.2 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/editor@5.2.2(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/external-editor': 3.0.3(@types/node@25.6.0) + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/expand@5.1.1(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/external-editor@3.0.3(@types/node@25.6.0)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/figures@2.0.7': {} + + '@inquirer/input@5.1.2(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/number@4.1.1(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/password@5.1.1(@types/node@25.6.0)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/prompts@8.5.2(@types/node@25.6.0)': + dependencies: + '@inquirer/checkbox': 5.2.1(@types/node@25.6.0) + '@inquirer/confirm': 6.1.1(@types/node@25.6.0) + '@inquirer/editor': 5.2.2(@types/node@25.6.0) + '@inquirer/expand': 5.1.1(@types/node@25.6.0) + '@inquirer/input': 5.1.2(@types/node@25.6.0) + '@inquirer/number': 4.1.1(@types/node@25.6.0) + '@inquirer/password': 5.1.1(@types/node@25.6.0) + '@inquirer/rawlist': 5.3.1(@types/node@25.6.0) + '@inquirer/search': 4.2.1(@types/node@25.6.0) + '@inquirer/select': 5.2.1(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/rawlist@5.3.1(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/search@4.2.1(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/select@5.2.1(@types/node@25.6.0)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/core': 11.2.1(@types/node@25.6.0) + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/type@4.0.7(@types/node@25.6.0)': + optionalDependencies: + '@types/node': 25.6.0 + '@jridgewell/sourcemap-codec@1.5.5': {} '@midnight-ntwrk/compact-js@2.5.1': @@ -3113,6 +3400,8 @@ snapshots: chai@6.2.2: {} + chardet@2.1.1: {} + classic-level@3.0.0: dependencies: abstract-level: 3.1.1 @@ -3120,6 +3409,8 @@ snapshots: napi-macros: 2.2.2 node-gyp-build: 4.8.4 + cli-width@4.1.0: {} + commander@14.0.3: {} convert-source-map@2.0.0: {} @@ -3274,6 +3565,16 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 @@ -3337,6 +3638,10 @@ snapshots: graphql@16.14.1: {} + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} ignore@5.3.2: {} @@ -3478,6 +3783,8 @@ snapshots: multipasta@0.2.7: {} + mute-stream@3.0.0: {} + nanoid@3.3.12: {} napi-macros@2.2.2: {} @@ -3624,6 +3931,8 @@ snapshots: safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} + scale-ts@1.6.1: optional: true @@ -3637,6 +3946,8 @@ snapshots: siginfo@2.0.0: {} + signal-exit@4.1.0: {} + smoldot@2.0.26: dependencies: ws: 8.21.0 diff --git a/src/cli.ts b/src/cli.ts index 02f78d2..15ba919 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -5,14 +5,17 @@ import { fileURLToPath } from 'url'; import { NativeScriptSchema } from './schema.js'; import { generateCompact } from './generator/index.js'; import { generateSecretPair, generateCommitment } from './pair.js'; +import { scriptWizard } from './script-wizard.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -program.description('CLI-based tool to generate Compact code and commitments.'); +program.description('CLI-based tool to generate Compact code, commitments and JSON inputs.'); program .command('generate-code') - .description('Generate Compact code from a JSON schema') + .description( + 'Generate Compact module that represents an authorization policy, from a JSON schema' + ) .requiredOption('-i, --input ', 'Path to input JSON file') .option( '-o, --output ', @@ -40,7 +43,8 @@ program const outputPath = path.join(outputDir, 'Warden.compact'); writeFileSync(outputPath, compactCode, 'utf-8'); - console.log(`✅ Valid script. Output written to: ${outputPath}`); + console.log(`✅ Authorization contract generated at ${outputPath}`); + console.log(` Import this module into your Compact program to enforce the policy.`); }); program @@ -72,12 +76,25 @@ program ), 'utf-8' ); - console.log(`Written to: ${options.output}`); + console.log(`✅ Secret pair and commitment written to ${options.output}`); + console.log(` Share this commitment with the script author: ${commitmentHex}`); + console.log(` ⚠️ Keep the secret and randomness private — they prove your identity.`); + console.log(` They'll include this commitment in the authorization policy.`); } else { console.log(`Secret (hex): ${secretHex}`); console.log(`Randomness (hex): ${randomnessHex}`); console.log(`Commitment (hex): ${commitmentHex}`); + console.log(`--- Share the "commitment" hex with the script author ---`); + console.log(`⚠️ Keep the secret and randomness private — they prove your identity.`); + console.log(`They'll include this commitment in the authorization policy.`); } }); +program + .command('script-wizard') + .description('Interactively build a native script schema JSON file') + .action(async () => { + await scriptWizard(); + }); + program.parse(); diff --git a/src/script-wizard.ts b/src/script-wizard.ts new file mode 100644 index 0000000..d4101e0 --- /dev/null +++ b/src/script-wizard.ts @@ -0,0 +1,114 @@ +import { select, input, number } from '@inquirer/prompts'; +import { writeFileSync } from 'fs'; + +type ScriptNode = Record; + +const SCRIPT_TYPES = [ + { + name: 'cmt', + value: 'cmt', + description: 'Commitment verification clause (signature-equivalent)', + }, + { + name: 'after', + value: 'after', + description: 'Transaction must be at or after the specified block', + }, + { + name: 'before', + value: 'before', + description: 'Transaction must be before the specified block', + }, + { + name: 'any', + value: 'any', + description: 'At least one sub-script must be satisfied', + }, + { + name: 'all', + value: 'all', + description: 'All sub-scripts must be satisfied', + }, + { + name: 'atLeast', + value: 'atLeast', + description: 'At least N of the sub-scripts must be satisfied', + }, + { + name: 'done', + value: 'done', + description: 'Finish building the script', + }, +] as const; + +async function promptForScript(): Promise { + const type = await select<'cmt' | 'after' | 'before' | 'any' | 'all' | 'atLeast' | 'done'>({ + message: 'Select script type:', + choices: SCRIPT_TYPES, + }); + + switch (type) { + case 'done': + return null; + case 'cmt': { + const hash = await input({ + message: 'Enter commitment hash (64 hex characters):', + validate: (v: string) => /^[0-9a-f]{64}$/i.test(v) || 'Must be 64 hex characters', + }); + return { type: 'cmt', hash: hash.toLowerCase() }; + } + case 'after': { + const block = await number({ + message: 'Enter block number:', + required: true, + }); + return { type: 'after', block }; + } + case 'before': { + const block = await number({ + message: 'Enter block number:', + required: true, + }); + return { type: 'before', block }; + } + case 'any': + case 'all': { + const scripts = await collectScripts(); + return { type, scripts }; + } + case 'atLeast': { + const required = await number({ + message: 'Minimum number of scripts that must be satisfied:', + required: true, + }); + const scripts = await collectScripts(); + return { type: 'atLeast', required, scripts }; + } + } +} + +async function collectScripts(): Promise { + const scripts: ScriptNode[] = []; + while (true) { + const node = await promptForScript(); + if (node === null) break; + scripts.push(node); + } + return scripts; +} + +export async function scriptWizard(): Promise { + console.log( + 'This wizard helps you interactively build a script JSON file. The output can be used as input to `pnpm generate-code -i ` to generate a Compact contract that checks commitments and/or time-lock conditions to authorize an operation.\n' + ); + console.log( + 'Before running this command, ensure each participant has generated their commitment via `pnpm make-commitment` and shared the commitment hex with you.\n' + ); + const schema = await promptForScript(); + const outputPath = await input({ message: 'Output file path:', default: 'script.json' }); + writeFileSync(outputPath, JSON.stringify(schema, null, 2) + '\n', 'utf-8'); + console.log(`✅ Script written to: ${outputPath}`); + console.log( + `You can now run pnpm generate-code -i ${outputPath} to generate the Compact contract.` + ); +}