From f144831dcf81f24b7470ea6b465cfb9807c378f2 Mon Sep 17 00:00:00 2001 From: Tijesunimi004 Date: Wed, 1 Apr 2026 20:50:56 +0100 Subject: [PATCH] feat(commitment_nft): initialize-auth-model-document-single-deployer-assumption-an --- Cargo.lock | 204 ++++++++++++++++++++++++-- contracts/commitment_nft/src/lib.rs | 1 + contracts/commitment_nft/src/tests.rs | 14 ++ docs/DEPLOYMENT_CHECKLIST.md | 33 +++++ 4 files changed, 241 insertions(+), 11 deletions(-) create mode 100644 docs/DEPLOYMENT_CHECKLIST.md diff --git a/Cargo.lock b/Cargo.lock index 29eb590..257a9de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,6 +110,21 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "2.11.0" @@ -194,7 +209,6 @@ dependencies = [ name = "commitment_core" version = "0.1.0" dependencies = [ - "commitment_nft", "shared_utils", "soroban-sdk", ] @@ -212,6 +226,9 @@ name = "commitment_nft" version = "0.1.0" dependencies = [ "commitment_core", + "ed25519-dalek", + "rand 0.10.0", + "serde_json", "shared_utils", "soroban-sdk", ] @@ -516,6 +533,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "escape-bytes" version = "0.1.1" @@ -528,6 +555,12 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "ff" version = "0.13.1" @@ -586,6 +619,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + [[package]] name = "getrandom" version = "0.4.2" @@ -594,7 +639,7 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "rand_core 0.10.0", "wasip2", "wasip3", @@ -797,6 +842,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "log" version = "0.4.29" @@ -964,6 +1015,31 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.45" @@ -973,6 +1049,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" @@ -986,10 +1068,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + [[package]] name = "rand" version = "0.10.0" @@ -1011,6 +1103,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -1020,12 +1122,30 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "rand_core" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + [[package]] name = "ref-cast" version = "1.0.25" @@ -1046,6 +1166,12 @@ dependencies = [ "syn", ] +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + [[package]] name = "rfc6979" version = "0.4.0" @@ -1071,12 +1197,37 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "schemars" version = "0.9.0" @@ -1219,6 +1370,7 @@ dependencies = [ name = "shared_utils" version = "0.1.0" dependencies = [ + "proptest", "soroban-sdk", ] @@ -1306,7 +1458,7 @@ dependencies = [ "num-traits", "p256", "rand 0.8.5", - "rand_chacha", + "rand_chacha 0.3.1", "sec1", "sha2", "sha3", @@ -1502,6 +1654,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -1567,6 +1732,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -1586,19 +1757,21 @@ dependencies = [ "soroban-sdk", ] -[[package]] -name = "version-system" -version = "0.0.0" -dependencies = [ - "soroban-sdk", -] - [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1798,6 +1971,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/contracts/commitment_nft/src/lib.rs b/contracts/commitment_nft/src/lib.rs index d97fba9..c71a152 100644 --- a/contracts/commitment_nft/src/lib.rs +++ b/contracts/commitment_nft/src/lib.rs @@ -231,6 +231,7 @@ impl CommitmentNFTContract { /// /// The recommended deployment pattern is to invoke `initialize` in the same /// transaction as the contract upload, leaving no window for front-running. + /// See `docs/DEPLOYMENT_CHECKLIST.md` for the full operational checklist. /// /// This function may only be called once. Any subsequent call returns /// [`ContractError::AlreadyInitialized`]. diff --git a/contracts/commitment_nft/src/tests.rs b/contracts/commitment_nft/src/tests.rs index d123873..778df68 100644 --- a/contracts/commitment_nft/src/tests.rs +++ b/contracts/commitment_nft/src/tests.rs @@ -159,6 +159,20 @@ fn test_initialize_twice_fails() { client.initialize(&admin); // Should panic } +#[test] +#[should_panic(expected = "Error(Auth, #1)")] // Unauthenticated +fn test_initialize_requires_auth() { + // Intentionally DO NOT mock auths — proves admin.require_auth() is enforced. + // A third party cannot front-run deployment and hijack the admin role. + let e = Env::default(); + let contract_id = e.register_contract(None, CommitmentNFTContract); + let client = CommitmentNFTContractClient::new(&e, &contract_id); + let admin = SdkAddress::generate(&e); + + // Must fail because the admin has not authorized this call + client.initialize(&admin); +} + // ============================================ // Access control: whitelist and unauthorized mint // ============================================ diff --git a/docs/DEPLOYMENT_CHECKLIST.md b/docs/DEPLOYMENT_CHECKLIST.md new file mode 100644 index 0000000..2320907 --- /dev/null +++ b/docs/DEPLOYMENT_CHECKLIST.md @@ -0,0 +1,33 @@ +# Deployment Checklist & Operational Assumptions + +This document outlines critical operational and security assumptions required when deploying and integrating the `commitment_nft` and associated contracts. + +## Single Deployer Assumption + +The Commitment NFT enforces the **Single Deployer Assumption** during its initialization sequence. This means: +1. The contract is deployed with no intrinsic initial `admin` address inside the compiled WASM. +2. The `initialize(env, admin)` function is the only way to lock the `admin` slot. +3. **Critical Vulnerability if Unchecked:** Since `initialize` is an open endpoint, if it is invoked without authentication *in a separate transaction*, an attacker monitoring the network can front-run the deployment and assume the `admin` role by calling `initialize` before the legitimate deployer does. + +To remediate this, `initialize()` enforces `admin.require_auth()`. This means the provided `admin` account **must sign** the envelope that invokes the initialization call. + +## Operational Deployment Checklist + +When deploying this system into a production or live-testnet environment, operations must adhere to this checklist: + +### Stage 1: Contract Deployment +- [ ] Determine the account address that will serve as `admin`. +- [ ] Build the WASM and deploy it onto the network. +- [ ] Record the resulting Contract ID. + +### Stage 2: Synchronous Initialization +- [ ] Ensure `initialize(env, admin)` is called in the exact same transaction bundle (Host invocation) as the deployment operation OR immediately thereafter in a transaction signed by the `admin`. +- [ ] Confirm no malicious actor has front-run the `initialize` endpoint via checking the `admin` state. (Any unauthorized initialization attempt will fail because `admin.require_auth()` will reject signatures not matching the specified `admin`). + +### Stage 3: Feature Flags & Permissions +The `admin` must properly authorize other contracts before the system will allow minting / core protocol functionality. +- [ ] Unpause the contract using `unpause()` (if deployed paused). +- [ ] Explicitly map the `CoreContract` interface via `set_core_contract(core_address)`. +- [ ] Add any authorized minters/factory wrappers via `add_authorized_contract(minter_address)`. + +**Warning**: Do not ignore `admin.require_auth()` failures. If your initialization transaction fails because the admin signature is missing, do not bypass it. Provide the required Soroban auth envelope.