# Confidential Assets: Production Readiness - #2
Open
ganymedio wants to merge 58 commits into
Open
Conversation
7 tasks
ganymedio
force-pushed
the
confidential-asset-prod
branch
from
March 27, 2026 09:28
af8f478 to
ac22fc8
Compare
ganymedio
marked this pull request as ready for review
April 14, 2026 03:04
# Conflicts: # confidential-assets/src/api/confidentialAsset.ts # confidential-assets/src/crypto/derivation.ts
Implements the MIP-001 vault envelope layer in @moveindustries/confidential-assets: - vaultDecryptionKey(dkVault, multisigAddress, tokenMetaAddr): HKDF-SHA512 from the shared vault root to dk[Vault, token] (salt "movement-ca-vault/v1"). Shares a single HKDF core with keylessDecryptionKey so the two cannot drift. - sealVaultDk / openVaultDk: per-recipient AES-GCM-256 envelope, keyed by an X25519 ECDH (ephemeral <-> recipient's Ed25519 owner key via the RFC 7748 birational map) and HKDF-SHA256, with AAD binding tag/multisig/dealer/recipient/ ephemeral key. - encodeVaultDkRaw / decodeVaultDkRaw: mv-dk-vault-raw-v1: codec for the raw vault root (manual-recovery fallback). Coexists with the per-asset mv-dk-v1: codec (re-documented, not removed) per the MIP. - Adds @noble/ciphers for AES-GCM. Tests pin the vaultDecryptionKey vector, the Ed25519->X25519 agreement, a byte-exact envelope vector, and a decrypt-only vector, plus negative cases. Note: the envelope header carries dealerOwnerAddress, which the MIP's AAD/open require but its envelope diagram omits — see vault.ts for the reconciliation.
The MIP multisig model derives dk[Vault, token] from a shared random dk[Vault] (vaultDecryptionKey + sealVaultDk/openVaultDk), not from each proposer's own root material. Drop the now-superseded helpers, which are not part of the MIP's required derivation surface (spec line 519): multisigAccountIndexFromAddress, softwareDecryptionKeyDerivationPathForMultisig, hardwareDecryptionKeyDerivationMessageForMultisig. The per-asset mv-dk-v1: codec is unaffected and retained (MIP per-asset format).
…pers Per MIP §"Required SDK Changes" #1 (recommended option: delete). After the auto-rollover was stripped, these were just withdraw/transfer + an actual-balance pre-flight check, while the "…WithTotalBalance" name wrongly implied they spend available + pending. Callers use withdraw / transfer directly; to spend pending, rolloverPendingBalance first (explicit accept). Also drops the now-unused private assertSufficientActualBalance helper. No in-repo or wallet callers.
The withdraw/transfer proof builders already decrypt the available balance to range-prove the remainder, and already threw on an over-spend — but as a plain Error message string. Replace with a typed InsufficientBalanceError carrying a stable code 'INSUFFICIENT_BALANCE' (+ available/requested), so callers can branch programmatically per MIP §"Required SDK Changes" #1. Fail-fast, in the existing build path (no extra balance round-trip). Pending balance is still not counted — rolloverPendingBalance remains the explicit accept step.
…/vault-envelope-layer # Conflicts: # confidential-assets/src/crypto/index.ts
feat(ca): multisig vault dk[Vault] envelope layer (MIP-001 §1.4)
Follow-up to 51d1d7c, which replaced the plain "Insufficient balance" Error message with the typed InsufficientBalanceError (message prefixed "INSUFFICIENT_BALANCE: ..."). The withdraw/transfer over-balance e2e cases still asserted the old substring and broke. Match the new named token.
…ount The envelope's u16-LE recipientCount was decoded via readU16LE but encoded inline with hand-rolled bit ops. Add a matching writeU16LE helper and use it in sealVaultDk so the read/write sides are symmetric and self-documenting. Byte-identical output (the pinned envelope test vector is unchanged).
…elopes
The dk[Vault] envelope encrypted to the birational map of the recipient's
Ed25519 owner key, which opens only with the Ed25519 private scalar — a hardware
wallet never exposes that. Replace the recipient key with a per-owner
vault-envelope key (vek), an X25519 keypair whose private half every backing can
reconstruct locally and whose public half is published + ownership-authenticated
(MIP-001 §"Vault-envelope key").
- Add vaultEnvelopeKeyFromSeed / FromSignature (hardware) / FromPepper (keyless)
and vaultEnvelopeKeyDerivationPath (software m/44'/637'/{i}'/2'/0'), plus the
VAULT_ENVELOPE_KEY_DERIVATION_MESSAGE constant.
- Add vaultEnvelopeKeyOwnershipMessage + sign/verifyVaultEnvelopeKeyOwnership
(Ed25519 over DST ‖ vekPub) so a dealer verifies a published vek_pub against
the owner's on-chain key before sealing.
- Rework sealVaultDk/openVaultDk to take the X25519 vek directly (no Ed25519
birational map).
- Regenerate pinned envelope vectors; add vek + ownership tests (52 pass).
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.
Confidential Assets: Production Readiness
Summary
Upgrades Movement's confidential-asset TS SDK to production readiness by adding proof-replay and key-registration protections, restoring batch range proofs, and pointing the SDK at the framework module address.
Changes
src/crypto/fiatShamir.ts. Challenges are SHA2-512 over a DST prefix (MovementConfidentialAsset/<protocol>) followed bychainId,senderAddress, and the remaining public inputs (matching the on-chainnew_scalar_from_sha2_512). BindingchainIdand the addresses prevents proof replay across chains and transaction contexts. The oldgenFiatShamirChallengeinhelpers.ts(raw concatenation, no domain separation) is deprecated and unused.src/crypto/confidentialRegistration.ts. Registration now requires a Schnorr proof that the registrant controls the decryption key, preventing registration of an encryption key you don't own.genBatchRangeZKP, matching the on-chain verifier which expects a single batched Bulletproof per component.0x1) —DEFAULT_CONFIDENTIAL_COIN_MODULE_ADDRESSand the e2e test-helper default are both0x1; confidential asset now lives in the framework, not a standalone deploy address.@moveindustries/confidential-asset-wasm-bindings@0.0.5(package.json plus the WASM URLs inrangeProof.tsandtwistedElGamal.ts).CI & localnet fixes
APTOS_CORE_COMMITto the aptos-core commit whose framework + Bulletproof DST match the wasm-bindings version (older pins fail withERANGE_PROOF_VERIFICATION_FAILED). Sinceconfidential_assetnow lives at0x1, the old publish-address extraction was removed.src/cli/localNode.tspreviously setENABLE_KEYLESS_DEFAULT=1, which made the node fetch the keyless Groth16 VK from devnet at genesis — a devnet outage crashed every e2e localnet. It now seeds the VK from a checked-in fixture (src/cli/keylessGroth16Vk.json) viaINSTALL_KEYLESS_GROTH16_VK_FROM_PATH.api/staking.test.tscoercesnum_active_delegator(the indexer returns it as a string) before numeric comparison;client/get.test.tsdeterministically verifies request overrides against an unreachable host instead of relying on a live network.Test Plan
Requires a local Movement node from the aptos-core branch that ships the confidential asset framework and the localnet helper script (e.g.
movementlabsxyz/aptos-core#299). The script enables feature flag 87 (BULLETPROOFS_BATCH_NATIVES), publishes the framework, and sets the chain auditor (required for transfers under #328).1. Start the localnet + configure the chain auditor (in
aptos-core):2. Run the e2e tests against it (in this repo):
cd confidential-assets MOVEMENT_NETWORK=local pnpm jest tests/e2e --globalSetup= --globalTeardown=MOVEMENT_NETWORK=localtargets the external node;--globalSetup= --globalTeardown=disables the SDK's own localnet-spawning hooks so it doesn't start a second node. The module address defaults to0x1, so no extra env var is needed.Results: the full
pnpm jestrun is 102 passing, 23 skipped, 0 failing across 8 suites, including all confidential-asset e2e tests (43 acrossconfidentialAsset.test.tsandconfidentialAssetTxnBuilder.test.ts). The skipped tests are legacy from a previous version of confidential assets and can be ignored.Note: the contract now rejects self-transfers (
ESELF_TRANSFER), so the transfer e2e tests send to a distinct registered recipient, and there is a dedicated test asserting that a self-transfer aborts.