feat(multisig): add MultisigNativeShieldedToken mint/burn preset#632
Draft
0xisk wants to merge 18 commits into
Draft
feat(multisig): add MultisigNativeShieldedToken mint/burn preset#6320xisk wants to merge 18 commits into
0xisk wants to merge 18 commits into
Conversation
Start a clean branch off main carrying only the Native Shielded Token Standard MIP and its reference Compact modules, so the standard can be compiled, deployed, and integration-tested against local infra in isolation from the broader exploration branch. Includes: * mip-xxxx-native-shielded-token.md (concise rewrite) * NativeShieldedToken / NativeShieldedTokenFamily core modules * NativeShieldedTokenDerivedNonce optional extension * Ownable + AccessControl presets for both profiles * Mocks for the simulator/test harness Custody, conversion, and unshielded siblings are intentionally left out; each is tracked by a separate companion MIP.
The native shielded token module set did not compile and lagged the library's current conventions. This lands it cleanly: * extensions: shorten the derived-nonce domain tag to "NativeShieldedToken:nonce" so pad(32, ...) fits 32 bytes. The prior 37-byte tag was a hard compile error, and the shorter tag matches the MIP. * token: drop the Initializable import and track _isInitialized inline per module, following FungibleToken and the per-module init change in the library. The shared Initializable flag collapses across modules in one directory, which the inline flag avoids. * build: raise the language_version pragma to >= 0.23.0 across the native files to match the 0.31.0 toolchain. * remove the four presets; they are out of scope for now. * mip: re-justify the Family-profile requirement via converter composition and drop the stale shared-Initializable-flag rationale.
Move the flat multisig modules into concern-based subdirectories so the package reads as composable building blocks rather than a flat pile: * signer/ — Signer, SignerManager * proposal/ — ProposalManager * treasury/ — ShieldedTreasury, ShieldedTreasuryStateless, UnshieldedTreasury * forwarder/ — ForwarderPrivate, ForwarderShielded, ForwarderUnshielded Presets and mocks keep their locations; only their import paths change. Mocks stay flat, so compiled artifact names (flat by basename) and every TS test import are untouched. The five moved modules that reach into utils/ gain an extra ../ in their import path. Also mark V3's inlined mint/burn with a TODO pointing at the future ShieldedToken module. Pure move plus import-path change, no logic change. compact:multisig compiles 24/24; the multisig suite passes 273/273. Refs: #619
SignerManager<T> is an older generation of the same module as Signer<T>: identical signer-set/threshold state, but without the init-safety guards (assertInitialized / re-init protection) and the custom-setup _setThreshold path that Signer<T> adds. Remove the legacy module together with its mock, simulator, witnesses, and test. The next commit renames Signer<T> into its place; splitting the removal out first lets git record that as a rename (preserving Signer's history) rather than a rewrite of this file. Refs: #619
Rename the hardened Signer<T> into the SignerManager name freed by the previous commit, keeping the more descriptive name on the init-safe implementation. Git records this as a rename, so the module's history, blame, and log --follow carry over. * signer/Signer.compact -> signer/SignerManager.compact (module decl + assert prefix Signer: -> SignerManager:) * mock, simulator, witnesses, and test renamed to match * repoint the ShieldedMultiSigV3 import and migrate the preset tests to the new module name and messages (the init-safe module reports "threshold must not be zero" rather than the legacy "threshold must be > 0") compact:multisig compiles 22/22; multisig suite passes 249/249. Refs: #619
Move supply accounting out of the native shielded token core modules into standalone extensions, because tracking it is a privacy trade-off, not just modularity. A contract-mediated burn is amount-private on its own: the coin ops (receiveShielded / sendImmediateShielded / sendShielded) emit only commitments and nullifiers, and the disclose() wrappers they require are compiler permission markers, not disclosure sinks. The sole thing that puts a burned amount into the public transcript is the supply-counter ledger write. So a bare _burn / _burnFromContract hides the amount; composing supply trades that privacy for on-chain auditability. Mint amounts stay public regardless, via the protocol shieldedMints effect. * token: strip _totalMinted / _totalBurned, the totalMinted / totalBurned / totalSupply getters, and _addMinted / _addBurned from NativeShieldedToken and NativeShieldedTokenFamily; mint/burn keep their coin ops and disclose wrappers. * extensions: add NativeShieldedTokenSupply (scalar) and NativeShieldedTokenFamilySupply (per-domain), standalone modules mirroring NativeShieldedTokenDerivedNonce. The consumer pairs _addMinted / _addBurned with the matching mint/burn op. Supply getters no longer gate on init (a counter reading 0 pre-init is correct). * tests/mocks: wire the supply extension into the unit + integration deployables and add unit and integration coverage for the standard.
Reshape the multisig package per the team layout review: reusable primitives sit at the package root, the composable behaviour modules become example contracts, and presets are named for what they do instead of a version number. * Root primitives: move SignerManager to the package root and add the extracted SignatureVerifier (commitment signer registry + threshold ECDSA-commitment verification), the single owner of the registry the signature presets share. * examples/: house the behaviour modules (SignatureTreasury, SignatureMintBurn, ProposalTreasury) and the three forwarder example contracts. * Presets named for behaviour: ShieldedMultiSigV2 becomes NativeShieldedStatelessTreasury, ShieldedMultiSigV3 becomes NativeShieldedMintBurn, ShieldedMultiSig becomes NativeShieldedProposal; add NativeShieldedTokenVault (mint/burn plus treasury under one signer set, the no-C2C composition). * Rename treasury and forwarder modules to NativeShielded / Private naming and repoint every import path, including the test mocks. Compiles green under SKIP_ZK (28/28 multisig contracts). The .ts test stacks (specs, simulators, witnesses) are renamed on disk but their rewiring to the new names is deferred to a follow-up.
Make signature verification a swappable per-scheme module, since Compact cannot make `verify` generic over a signature scheme. * SignerManager<T> stays the general signer registry (signer set, threshold, membership, add/remove), generic over the identity type: <Bytes<32>> commitments for the signature path, <Either<...>> for caller-authorized governance. * EcdsaSignerManager (formerly SignatureVerifier) wraps SignerManager<Bytes<32>> and adds threshold ECDSA-commitment verification (instance salt, commitment derivation, verify). The cryptographic check is stubbed until the Compact ECDSA primitive lands. It is the single entrance the signature examples import; a future SchnorrSignerManager is a sibling of the same shape. * SignatureTreasury, SignatureMintBurn, and the NativeShieldedTokenVault preset import EcdsaSignerManager. ProposalTreasury (caller-auth V1) keeps using SignerManager<Either> directly. The signer count stays a single source of truth: one SignerManager registry under EcdsaSignerManager, shared across the Vault's mint/burn/execute via the same import path. Compiles green under SKIP_ZK (28/28 multisig contracts). The .ts test stacks remain renamed-on-disk and deferred to a follow-up.
The commitment domain separator padded the full descriptive string "PrivateNativeShieldedForwarder:commitment" (41 bytes) to 32, which exceeds the pad width and fails to compile. Use the module name alone (30 bytes) as the unique domain tag. This unblocks MockForwarderPrivate and the private-forwarder suite, which the compiler wrapper had been masking as a false success.
The deployable forwarder example contracts (examples/*Forwarder) are thin top-level wrappers in the same category as the presets, and their basenames collide with the forwarder modules in the shared flat artifact namespace. Remove them (and their now-stale preset tests and simulators) from this branch; they return with the presets in a follow-up branch. The forwarder modules and their Mock-based tests stay and remain the coverage for forwarder behavior.
…esses Every multisig contract has empty private state and declares no witnesses, so each per-contract *Witnesses.ts was byte-identical to the shared EmptyWitnesses.ts (the forwarder simulators already used it). Delete them all and repoint the remaining simulators (SignerManager, ProposalManager, ShieldedTreasury) at EmptyWitnesses, matching where main is heading and shrinking the rebase surface.
…nerManager The example modules (SignatureTreasury, SignatureMintBurn, ProposalTreasury) are composable behaviors with no constructor, so each gets a Mock top-level wrapper plus a simulator and spec, matching how every other module in the package is tested. The specs carry over from the old ShieldedMultiSigV2/V3/(V1) suites (rename-preserved) and target the modules directly via EmptyWitnesses. Also rename the signature primitive's test stack to match the renamed module: MockSignatureVerifier -> MockEcdsaSignerManager, with a new EcdsaSignerManager spec/simulator and the corrected assert prefix. The example modules' duplicate-signer assertion now reads "EcdsaSignerManager: duplicate signer".
The forwarder modules were renamed (ForwarderShielded -> NativeShieldedForwarder, etc.) and their assert messages updated, but the kept module tests still expected the old prefixes. Update the toThrow expectations to the current "Native*Forwarder:" / "PrivateNativeShieldedForwarder:" messages.
The deployable preset contracts (NativeShieldedMintBurn, NativeShieldedProposal, NativeShieldedStatelessTreasury, NativeShieldedTokenVault) need rework and move to a separate branch. Remove them here and drop the now-dangling `presets/...` references from the SignatureTreasury and SignatureMintBurn module docs, describing the thin-wrapper composition pattern in prose instead.
Wire the previously-orphan MockShieldedTreasuryStateless to a simulator and spec covering deposit, full/partial send (with change accounting), and the over-send rejection. Uses the shared EmptyWitnesses.
…elded-token-multisig
Add MultisigNativeShieldedToken: a deployable preset composing the NativeShieldedToken standard (core + derived-nonce extension) with EcdsaSignerManager for institutional 2-of-3 ECDSA-authorized mint/burn of a single shielded token. * Coins are contract-owned (mint targets kernel.self()), so there is no coin secret key and no single-sig hop; the mint is constant-shape. * Authorization is by circuit parameters (public keys + signatures), not msg.sender; the preset declares no witnesses. * A dedicated replay nonce is consumed into each op's message hash, and the burn hash binds the spent coin's nonce so a signed burn cannot be redirected to another coin. * Burns are amount-private: the supply extension is intentionally not composed, so no burned-amount cell is published. ECDSA verification remains stubbed and the message hash uses persistentHash pending the Compact ECDSA primitive; not for production deployment until that lands.
Move the multisig examples (SignatureMintBurn, SignatureTreasury, ProposalTreasury) out of src/multisig/examples/ into test/integration/_examples/, converting each from a module (+ test mock) into a self-contained top-level contract. They stay as usage examples and now sit where they can double as integration-test fixtures (composing production modules into one deployable contract). Remove the now-redundant unit tests, simulators, and mocks for the three. Integration tests are not added here; they are tracked in #630. Also repoint the preset's doc cross-reference to the moved example. Refs: #628, #630
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Types of changes
Put an
xin the boxes that applyNo linked issue — design is tracked separately.
What this adds
MultisigNativeShieldedTokenpreset — a single omnibus contract that mintsand burns one shielded (Zswap) native token under an M-of-N ECDSA signer set.
Mint targets contract-owned coins only (
kernel.self()); the token ismint/burn-only (non-transferable). Composes the native-token core + supply +
derived-nonce extensions with
EcdsaSignerManager, rather than reimplementingmint/burn. Supersedes the bespoke
examples/SignatureMintBurn.Supporting multisig refactor (prerequisite for the preset)
Signer→SignerManager; remove the legacy moduleEcdsaSignerManagerforwarder/,proposal/,treasury/,presets/)Signature*,ProposalTreasury) into integration_mocksPR Checklist
Further comments
The preset is the institutional omnibus shape: one contract, one token color
(bound to the minter via
tokenType(domain, self())), M-of-N ECDSA approval,contract-held coins (no coin secret key, no single-sig hop), and per-customer
balances tracked off-chain. The multisig refactor is the prerequisite that turns
the bespoke example contracts into reusable, composable modules. ECDSA / Keccak is
an upstream dependency; the verifier stays stubbed until it lands.