-
Notifications
You must be signed in to change notification settings - Fork 49
feat(wasm-sdk): add shielded pool WASM bindings and query methods#3235
feat(wasm-sdk): add shielded pool WASM bindings and query methods#3235QuantumExplorer wants to merge 18 commits intov3.1-devfrom
Conversation
Issue being fixed or feature implemented
Adds the WASM/JS layer for shielded pool functionality in wasm-dpp2 and wasm-sdk.
What was done?
wasm-dpp2: Shielded state transition wrappers
ShieldTransitionWasm-- shield (deposit) with inputs, actions, amount, proof, binding signatureShieldFromAssetLockTransitionWasm-- shield from asset lock with proof verificationShieldedTransferTransitionWasm-- private transfer between shielded addressesUnshieldTransitionWasm-- withdraw from shielded pool to transparent addressShieldedWithdrawalTransitionWasm-- withdraw from shielded pool to core chaincomputePlatformSighashutility function exposed to JS- All wrappers include
toBytes/fromBytes/toJSON/toObject/toStateTransition/getModifiedDataIds - Replaced
todo!()panics inStateTransitionWasmfor shielded variants with proper handling
wasm-sdk: Shielded pool query methods
getShieldedPoolState-- total shielded pool balance (BigInt)getShieldedEncryptedNotes(startIndex, count)-- paginated encrypted notesgetShieldedAnchors-- valid anchors for Orchard spend proofsgetMostRecentShieldedAnchor-- latest 32-byte anchorgetShieldedNullifiers(nullifiers)-- check nullifier spent/unspent status- All 5 queries have
WithProofInfovariants returningProofMetadataResponse ShieldedEncryptedNoteWasmandShieldedNullifierStatusWasmwrapper types with TypeScript interfaces
How Has This Been Tested?
cargo check -p wasm-dpp2cargo check -p wasm-sdkcargo clippy -p wasm-dpp2(no warnings)cargo clippy -p wasm-sdk(no warnings)cargo fmt
Breaking Changes
None. All additions are new public API.
Checklist
- I have performed a self-review of my own code
- I have commented my code, particularly in hard-to-understand areas
- I have added or updated relevant unit/integration/functional tests
- I have made corresponding changes to the documentation
- I have added the PR to a milestone (if applicable)
Generated with Claude Code
Summary by CodeRabbit
-
New Features
- Added full support for shielded transitions (shield, unshield, shielded transfer, shielded withdrawal) with JS/WASM bindings and serialization.
- Added shielded pool queries (pool state, encrypted notes, anchors, nullifier status) and proof-info variants.
- Expanded proof-result types for shielded proof verification and SDK exports for shielded query types.
-
Bug Fixes
- Improved error handling for shielded transition operations.
Add WASM bindings for all 5 shielded state transition types in wasm-dpp2:
- ShieldTransitionWasm
- ShieldedTransferTransitionWasm
- UnshieldTransitionWasm
- ShieldFromAssetLockTransitionWasm
- ShieldedWithdrawalTransitionWasm
Each wrapper exposes type-specific getters (anchor, proof, binding signature,
actions, etc.), serialization (toBytes/fromBytes/toJSON/toObject), and
toStateTransition conversion following wasm-dpp2 patterns.
Also adds computePlatformSighash utility for constructing the platform sighash
used by Orchard binding signatures.
Replaces all shielded todo!() panics in wasm-dpp2's StateTransitionWasm with
proper None returns or error messages for identityContractNonce, identityNonce,
setOwnerId, setIdentityContractNonce, and setIdentityNonce.
Updates wasm-dpp to:
- Register shielded types (15-19) in StateTransitionTypeWasm enum
- Handle shielded transitions in createFromBuffer factory via serde
Adds JS SDK shielded methods (platform.shielded.*):
- shield, shieldedTransfer, unshield, shieldFromAssetLock, shieldedWithdrawal
All accept pre-serialized transition bytes and broadcast with skipValidation.
Co-Authored-By: Claude Opus 4.6
WalkthroughWalkthroughAdds a new shielded module and WASM bindings: five shielded state-transition wrappers, shielded proof-result types, helper functions, and wallet/query APIs to expose shielded pool state, anchors, encrypted notes, and nullifier statuses (with proof-info variants) to JavaScript via wasm-bindgen. Changes
Sequence Diagram(s)
sequenceDiagram
actor JS as JavaScript Client participant WASM as WASM Lib (wasm-dpp2) participant DPP as DPP Core (Rust) participant Crypto as Crypto Libs JS->>WASM: construct ShieldTransitionWasm / call getters WASM->>DPP: build ShieldTransition::V0 (fields, actions) WASM->>Crypto: validate proof/anchor/binding_signature Crypto-->>WASM: validation result WASM->>DPP: serialize via PlatformSerializable -> bytes WASM-->>JS: return bytes / StateTransitionWasm
sequenceDiagram
actor JS as JavaScript Client participant SDK as WASM SDK (wasm-sdk) participant Drive as Drive Backend participant Verifier as Proof Verifier JS->>SDK: getShieldedNullifiersWithProofInfo(nullifiers) SDK->>Drive: request nullifier statuses + proof metadata Drive-->>SDK: statuses + proof SDK->>Verifier: verify proof Verifier-->>SDK: verification result SDK->>SDK: build nullifier_map, convert to ShieldedNullifierStatusWasm, assemble ProofMetadataResponseWasm SDK-->>JS: return ProofMetadataResponseWasm Estimated code review effort4 (Complex) | ~50 minutes Poem
Pre-merge checks | 3Passed checks (3 passed)
Tip: You can configure your own custom pre-merge checks in the settings. Finishing Touches
Generate unit tests (beta)
Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Nitpick comments (3)
packages/wasm-dpp2/src/shielded/mod.rs (1)
28-33: Consider consistent validation style.The length validation and conversion on lines 28-34 is correct, but
expect("checked length above")could be replaced withunwrap()for consistency, since the length is already validated. This is a minor style preference.Optional: use unwrap() after validation
if bundle_commitment.len() != 32 {
return Err(crate::error::WasmDppError::invalid_argument(&format!(
"bundleCommitment must be exactly 32 bytes, got {}",
bundle_commitment.len()
)));
}
- let commitment: &[u8; 32] = bundle_commitment.try_into().expect("checked length above");
+ let commitment: &[u8; 32] = bundle_commitment.try_into().unwrap();Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/wasm-dpp2/src/shielded/mod.rs` around lines 28 - 33, Replace the
post-validation call that uses expect("checked length above") with unwrap() for
stylistic consistency: after you validate bundle_commitment.len() != 32 and
return an error, use unwrap() when converting/creating the fixed-size array (the
same spot currently calling expect) so the code relies on the prior check and
matches the project's validation style (refer to the bundle_commitment length
check and the conversion site where expect("checked length above") is used).
packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs (1)
88-116: Add a round-trip test for this wasm-facing ABI.
toBytes/fromBytesplus the JS-visible getters are new public surface, and this PR explicitly ships without test updates. A small round-trip/golden test here would catch wire-format drift and wrong-variant regressions before they reach the SDK.Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/wasm-dpp2/src/shielded/shielded_transfer_transiti on.rs` around lines
88 - 116, Add a round-trip unit test exercising the wasm ABI: construct a
ShieldedTransferTransitionWasm instance, call to_bytes(), pass the bytes into
from_bytes() and assert the resulting ShieldedTransferTransitionWasm equals the
original; also include a JSON round-trip by calling to_json() and verifying the
serialized value round-trips (or matches expected golden JSON), and verify
to_state_transition() produces the expected StateTransition variant. Target the
wasm-facing symbols to_bytes, from_bytes, to_json, to_state_transition and
assert equality of the core inner StateTransition::ShieldedTransfer data to
catch wire-format or variant regressions.
packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts (1)
202-208: Consider a shared helper for the shielded broadcast path.These five bindings fan out to near-identical modules today. Once you add the missing decoded-type guard or any future validation/logging tweak, you'll need to keep five copies in sync. A small helper that takes
{ expectedType, logPrefix }would make this surface much harder to drift.Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts` around lines 202 -
208, The five near-identical bindings under this.shielded (shield,
shieldedTransfer, unshield, shieldFromAssetLock, shieldedWithdrawal) duplicate
broadcast/validation logic; extract a small helper (e.g.,
createShieldedBinder(expectedType, logPrefix)) that returns a bound function
given a method reference and metadata, then replace the direct .bind(this) calls
with calls to that helper (pass the original method function like shieldMethod,
shieldedTransferMethod, unshieldMethod, shieldFromAssetLockMethod,
shieldedWithdrawalMethod and the appropriate expectedType/logPrefix) so shared
decoded-type guards, validation and logging live in one place.
Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shield.ts`:
- Around line 24-31: After deserializing the transition with
dpp.stateTransition.createFromBuffer in shield(), add a type guard that checks
transition.getType() and ensure it matches the expected StateTransition type for
shield (e.g., ShieldedTransfer/Shield/ShieldFromAssetLock/ShieldedWithdraw al as
appropriate) before calling broadcastStateTransition(this, await transition,
...); if the type does not match, throw or return a clear error. Apply the same
guard/check (use .getType() on the result of
dpp.stateTransition.createFromBuffer) to all other shielded entrypoints so each
only accepts its intended StateTransition variant prior to calling
broadcastStateTransition.
In `@packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts`:
- Around line 37-41: The three new shielded module filenames use mixed/camel
casing; rename the files shieldFromAssetLock.ts, shieldedTransfer.ts, and
shieldedWithdrawal.ts to kebab-case (shield-from-asset-lock.ts,
shielded-transfer.ts, shielded-withdrawal.ts) and update all imports/exports
referencing them (e.g., the imports in Platform.ts: shieldFromAssetLockMethod,
shieldedTransferMethod, shieldedWithdrawalMethod) to use the new kebab-case
paths; also update any barrel/index exports and tests or references across the
repo to the new filenames so imports remain consistent.
In `@packages/wasm-dpp/src/state_transition/state_transition_factory.rs`:
- Around line 82-96: The Shield-related state transition variants
(StateTransition::Shield, ::ShieldedTransfer, ::Unshield, ::ShieldFromAssetLock,
::ShieldedWithdrawal) call serde_wasm_bindgen::to_value(&st) but fail to compile
because the Serialize impls are behind the rs-dpp "serde-conversion" feature;
fix this by enabling that feature for the dpp dependency in wasm-dpp's
Cargo.toml (add "serde-conversion" to the features list for dpp) so those types
derive Serialize and serde_wasm_bindgen::to_value(&st) will compile.
In `@packages/wasm-dpp2/src/shielded/mod.rs`:
- Around line 16-36: The doc comment for compute_platform_sighash_wasm
incorrectly states that extraData includes an amount for unshield and shielded
withdrawal transitions; update the comment to match the rs-dpp implementation by
removing references to amount and describing extraData as just the output
address bytes for unshield transitions and the output script bytes for shielded
withdrawal transitions (see the extra_sighash_data construction in unshield.rs
and shielded_withdrawal.rs for reference).
---
Nitpick comments:
In `@packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts`:
- Around line 202-208: The five near-identical bindings under this.shielded
(shield, shieldedTransfer, unshield, shieldFromAssetLock, shieldedWithdrawal)
duplicate broadcast/validation logic; extract a small helper (e.g.,
createShieldedBinder(expectedType, logPrefix)) that returns a bound function
given a method reference and metadata, then replace the direct .bind(this) calls
with calls to that helper (pass the original method function like shieldMethod,
shieldedTransferMethod, unshieldMethod, shieldFromAssetLockMethod,
shieldedWithdrawalMethod and the appropriate expectedType/logPrefix) so shared
decoded-type guards, validation and logging live in one place.
In `@packages/wasm-dpp2/src/shielded/mod.rs`:
- Around line 28-33: Replace the post-validation call that uses expect("checked
length above") with unwrap() for stylistic consistency: after you validate
bundle_commitment.len() != 32 and return an error, use unwrap() when
converting/creating the fixed-size array (the same spot currently calling
expect) so the code relies on the prior check and matches the project's
validation style (refer to the bundle_commitment length check and the conversion
site where expect("checked length above") is used).
In `@packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs`:
- Around line 88-116: Add a round-trip unit test exercising the wasm ABI:
construct a ShieldedTransferTransitionWasm instance, call to_bytes(), pass the
bytes into from_bytes() and assert the resulting ShieldedTransferTransitionWasm
equals the original; also include a JSON round-trip by calling to_json() and
verifying the serialized value round-trips (or matches expected golden JSON),
and verify to_state_transition() produces the expected StateTransition variant.
Target the wasm-facing symbols to_bytes, from_bytes, to_json,
to_state_transition and assert equality of the core inner
StateTransition::ShieldedTransfer data to catch wire-format or variant
regressions.
i Review info
Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2bdbcf86-45ca-47c6-ba00-fc5f4eeab635
Files selected for processing (16)
packages/js-dash-sdk/src/SDK/Client/Platform/Platform.tspackages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shield.tspackages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldFromAssetLock.tspackages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedTransfer.tspackages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedWithdrawal.tspackages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/unshield.tspackages/wasm-dpp/src/identity/state_transition/transition_types.rspackages/wasm-dpp/src/state_transition/state_transition_factory.rspackages/wasm-dpp2/src/lib.rspackages/wasm-dpp2/src/shielded/mod.rspackages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rspackages/wasm-dpp2/src/shielded/shield_transition.rspackages/wasm-dpp2/src/shielded/shielded_transfer_transition.rspackages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rspackages/wasm-dpp2/src/shielded/unshield_transition.rspackages/wasm-dpp2/src/state_transitions/base/state_transition.rs
packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shield.ts
Outdated
Show resolved
Hide resolved
packages/wasm-dpp/src/state_transition/state_transition_factory.rs
Outdated
Show resolved
Hide resolved
belong in a separate effort. This PR focuses on wasm-sdk + wasm-dpp2 only.
Co-Authored-By: Claude Opus 4.6
- getShieldedPoolState / WithProofInfo -- total shielded balance
- getShieldedEncryptedNotes / WithProofInfo -- paginated encrypted notes
- getShieldedAnchors / WithProofInfo -- valid spend anchors
- getMostRecentShieldedAnchor / WithProofInfo -- latest anchor
- getShieldedNullifiers / WithProofInfo -- nullifier spent status
Also adds ShieldedEncryptedNoteWasm and ShieldedNullifierStatusWasm
wrapper types with TypeScript interface declarations.
Co-Authored-By: Claude Opus 4.6
WASM wrapper types:
- VerifiedAssetLockConsumed (status + credit values)
- VerifiedShieldedNullifiers (nullifier - isSpent map)
- VerifiedShieldedNullifiersWithAddressInfos
- VerifiedShieldedNullifiersWithWithdrawalDocument
This enables broadcasting shielded state transitions through wasm-sdk
without panicking on proof verification results.
Co-Authored-By: Claude Opus 4.6
unshield and shielded withdrawal transitions. The actual rs-dpp
implementation only uses the address/script bytes without amount.
Co-Authored-By: Claude Opus 4.6
Co-Authored-By: Claude Opus 4.6
packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs
Outdated
Show resolved
Hide resolved
packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs
Outdated
Show resolved
Hide resolved
- Add unchecked_return_type to getMostRecentShieldedAnchor
- Add constructor, TypeScript types, and impl_wasm_conversions_serde
macro to ShieldFromAssetLockTransitionWasm
- Extract shielded proof result types into separate module
Co-Authored-By: Claude Opus 4.6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/wasm-sdk/src/queries/shielded.rs`:
- Around line 175-185: The current nullifier_arrays mapping in functions using
`nullifiers.iter()` and `Uint8Array::new(&n)` (the `nullifier_arrays`
construction and the similar block at the second occurrence) silently truncates
or zero-pads inputs; change this to validate that `Uint8Array::length()` (or the
vector length produced by `to_vec()`) is exactly 32 and return an error
(propagate an Err/JsValue back to the caller) when length != 32 instead of using
`len().min(32)` and copying/padding; update both the `nullifier_arrays` mapping
and the analogous block at the other occurrence so invalid-length inputs are
rejected with a clear error message identifying the offending nullifier.
i Review info
Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 482ab228-743d-48c4-a268-2e18dfc07964
Files selected for processing (8)
packages/wasm-dpp2/src/shielded/mod.rspackages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rspackages/wasm-dpp2/src/state_transitions/mod.rspackages/wasm-dpp2/src/state_transitions/proof_result.rspackages/wasm-dpp2/src/state_transitions/proof_result_shielded.rspackages/wasm-sdk/src/lib.rspackages/wasm-sdk/src/queries/mod.rspackages/wasm-sdk/src/queries/shielded.rs
Files skipped from review as they are similar to previous changes (2)
- packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs
- packages/wasm-dpp2/src/shielded/mod.rs
error when a nullifier Uint8Array is not exactly 32 bytes.
Co-Authored-By: Claude Opus 4.6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/wasm-sdk/src/queries/shielded.rs`:
- Around line 175-190: The code uses Uint8Array::new(&n) which silently coerces
numeric JS values into zero-filled arrays; update the nullifier parsing (the
nullifiers -> nullifier_arrays mapping where Uint8Array::new(&n) is called) to
first dyn_into::() the JsValue `n` and return a
WasmSdkError::invalid_argument if the cast fails, then call to_vec(), check
length == 32, copy into [u8;32], and propagate errors; apply the same
dyn_into-based validation to the second nullifier-parsing block referenced in
the review so both locations reject non-Uint8Array inputs instead of coercing
numbers.
i Review info
Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a0485101-3553-4505-8ff4-25940161772c
Files selected for processing (1)
packages/wasm-sdk/src/queries/shielded.rs
thepastaclaw
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This PR adds WASM/JS bindings for shielded pool state transitions and query methods. The code follows established wrapper patterns and is structurally sound. The main concern is an inconsistency where getShieldedPoolState silently maps a missing pool to BigInt(0) while its WithProofInfo variant correctly returns null, which would cause different observable behavior depending on which API a consumer uses. Test coverage for this security-critical surface is also absent.
1 blocking | 3 suggestion(s)
Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/wasm-sdk/src/queries/shielded.rs`:
- [BLOCKING] lines 99-100: Inconsistent None-handling: getShieldedPoolState returns 0 but WithProofInfo variant returns null
`get_shielded_pool_state` coerces `None` to `BigInt(0)` via `unwrap_or(0)` (line 99), while `get_shielded_pool_state_with_proof_info` (line 222) maps `None` to `JsValue::NULL`. This means the non-proof variant silently conflates "pool doesn't exist" with "pool has zero balance", while the proof variant correctly distinguishes the two. A JS consumer switching between APIs would observe different semantics for the same underlying state. The `WithProofInfo` behavior (returning null) is correct -- the non-proof variant should match.
- [SUGGESTION] lines 1-362: No tests for any of the new shielded query methods or transition wrappers
This PR adds 5 state transition wrapper types, 1 utility function (`computePlatformSighash`), 5 query methods, 5 `WithProofInfo` query variants, and 4 new proof result types -- all without corresponding tests. The inconsistency flagged above (None - 0 vs None - null) is exactly the kind of behavioral bug that a test comparing both API variants would catch. Given that this is security-critical shielded pool functionality, at minimum serialization roundtrip tests for the transition wrappers and mock-based tests for the query methods would provide important coverage.
- [SUGGESTION] lines 175-189: Duplicated nullifier parsing and validation logic
The nullifier parsing block (convert JS `Array` to `Vec<[u8; 32]>` with 32-byte validation) is copy-pasted identically between `get_shielded_nullifiers` (lines 175-189) and `get_shielded_nullifiers_with_proof_info` (lines 327-341). Extract to a shared helper to avoid the two copies drifting apart.
Additionally, `Uint8Array::new(&n)` doesn't type-check its argument -- if a non-Uint8Array value is passed, it may silently produce a zero-length array rather than erroring. Using `n.dyn_into::()` would provide a clearer error message.
In `packages/wasm-dpp2/src/shielded/shield_transition.rs`:
- [SUGGESTION] lines 1-156: Four shielded transition wrappers lack JS constructors (intentional?)
`ShieldFromAssetLockTransitionWasm` has a `#[wasm_bindgen(constructor)]` plus `impl_wasm_conversions_serde!` for `fromObject`/`fromJSON`, but `ShieldTransitionWasm`, `ShieldedTransferTransitionWasm`, `UnshieldTransitionWasm`, and `ShieldedWithdrawalTransitionWasm` can only be constructed via `fromBytes()`. This is likely intentional since ZK-proof-based transitions are built in Rust (where the proof is generated), not from plain JS objects. If so, a brief doc comment noting that `fromBytes` is the intended construction path would prevent confusion. If JS construction is eventually needed, the `Serialize`/`Deserialize` derives are missing from these four types (they'd need to be added for serde-based constructors to work).
Shielded transition wrappers (wasm-dpp2):
- Add Serialize/Deserialize derives, TypeScript interface declarations,
extern "C" typed JS/JSON types, constructors, and impl_wasm_conversions_serde
macro to ShieldTransition, ShieldedTransferTransition,
ShieldedWithdrawalTransition, and UnshieldTransition (matching the
pattern already used by ShieldFromAssetLockTransition)
Shielded queries (wasm-sdk):
- Change JsValue::NULL to JsValue::UNDEFINED for missing values (JS
convention: missing = undefined, explicit empty = null)
- Fix getShieldedPoolState to return undefined for None instead of
BigInt(0), making None-handling consistent with WithProofInfo variant
- Add unchecked_return_type annotations for TypeScript ("bigint | undefined",
"Uint8Array | undefined")
- Extract duplicated nullifier parsing into parse_nullifiers() helper
Co-Authored-By: Claude Opus 4.6 (1M context)
packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs
Outdated
Show resolved
Hide resolved
packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs
Outdated
Show resolved
Hide resolved
packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs
Outdated
Show resolved
Hide resolved
packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs
Outdated
Show resolved
Hide resolved
- Change $version to $formatVersion in all interfaces
- Use AssetLockProofObject/JSON instead of generic object
- Define SerializedOrchardAction interface with typed fields
- Define FeeStrategyStepObject, AddressWitnessObject types
- Use proper types for inputs, feeStrategy, inputWitnesses
- Fix JSON byte fields: number[] - string (base64-encoded)
- Use typed arrays for actions instead of Array
Codecov Report All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## v3.1-dev #3235 +/- ## ========================================= Coverage 75.87% 75.87% ========================================= Files 2912 2912 Lines 283860 283860 ========================================= Hits 215375 215375 Misses 68485 68485
New features to boost your workflow:
|
be string (base64-encoded) to match the JSON serialization convention.
Co-Authored-By: Claude Opus 4.6 (1M context)
packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs
Outdated
Show resolved
Hide resolved
numbers (when they fit in f64) or strings (when they exceed 2^53).
Update all JSON TypeScript interfaces to use `number | string` for
u64 fields: valueBalance, amount, unshieldingAmount.
The Object interfaces keep `bigint` since wasm_bindgen maps u64 to
BigInt directly. The u64 getters are already correct -- wasm_bindgen
automatically returns BigInt to JS.
Co-Authored-By: Claude Opus 4.6 (1M context)
thepastaclaw
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This incremental push addresses all four prior findings: None-undefined consistency, duplicated nullifier parsing, missing constructors, and macro usage. The changes are well-structured. Two remaining issues: the TypeScript interfaces declare $formatVersion but the Rust shielded enums use serde(tag = "$version"), creating a mismatch; and the FeeStrategyStepObject interface shape doesn't match the actual externally-tagged serde output.
Reviewed commit: 7f216ec
1 blocking | 2 suggestion(s)
Prompt for all review comments with AI agents
packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs
Outdated
Show resolved
Hide resolved
*ObjectJs extern type in all 5 shielded transition constructors.
This provides TypeScript type checking at the call site.
Co-Authored-By: Claude Opus 4.6 (1M context)
- Fix FeeStrategyStepObject to externally-tagged union type matching
serde's camelCase enum output: { deductFromInput: n } | { reduceOutput: n }
- Fix inputs from Map<> to Record
emits plain JS objects, not Maps
Co-Authored-By: Claude Opus 4.6 (1M context)
thepastaclaw
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
Incremental push with 4 commits fixing TypeScript type definitions to match actual serde output. All changes are in wasm-dpp2 TS custom sections: $formatVersion - $version (matching Rust serde(tag = "$version")), byte fields in JSON variants corrected from number[] to base64 string, u64 values widened to number | string, Map
Reviewed commit: 2db3796
packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs
Outdated
Show resolved
Hide resolved
packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs
Outdated
Show resolved
Hide resolved
- Change $version to $formatVersion in all 10 TS interfaces (Object
and JSON) across all 5 shielded transition files
- Add ShieldFromAssetLockTransitionOptions interface that accepts
AssetLockProof as a WASM instance type (not a plain JS object)
- Constructor now extracts assetLockProof via try_from_options and
deserializes remaining fields via serde
Co-Authored-By: Claude Opus 4.6 (1M context)
thepastaclaw
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
The incremental push (a87c6ad - c6a2b3f) addresses all 4 prior review findings well: constructors added for all 5 shielded transitions, undefined replaces inconsistent 0/null, and nullifier parsing was deduplicated. However, the TS interfaces now declare $formatVersion while the Rust serde tags still use $version -- this will break every constructor and make toObject/toJSON output mismatched. The ShieldFromAssetLock constructor also silently accepts missing required fields via #[serde(default)].
Reviewed commit: c6a2b3f
1 blocking | 2 suggestion(s) | 1 nitpick(s)
Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/wasm-dpp2/src/shielded/shield_transition.rs`:
- [BLOCKING] line 68: TS interfaces declare `$formatVersion` but Rust serde tag is `$version` -- breaks constructors and output types
All 5 shielded transition Rust enums (`ShieldTransition`, `ShieldFromAssetLockTransition`, `ShieldedTransferTransition`, `ShieldedWithdrawalTransition`, `UnshieldTransition`) use `#[serde(tag = "$version")]`, but the TS Object/JSON interfaces in this delta were changed to `$formatVersion`.
**Impact on constructors:** The 4 constructors using `serde_wasm_bindgen::from_value()` require `{"$version": "0", ...}` as the serde discriminant. A JS caller following the TS types will pass `{"$formatVersion": "0", ...}` - deserialization fails with a missing-tag error.
**Impact on output:** `toObject()`/`toJSON()` serialize through serde and produce `{"$version": "0", ...}`, but the TS return types declare `$formatVersion`. TypeScript consumers accessing `obj.$formatVersion` will get `undefined`.
The rest of the codebase (Identity, DataContract, address-based transitions) already uses `serde(tag = "$formatVersion")`. The 5 shielded enums are inconsistent -- update the Rust serde tags in `packages/rs-dpp/src/state_transition/state_transitions/shie lded/*/mod.rs` to `serde(tag = "$formatVersion")`.
In `packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs`:
- [SUGGESTION] lines 14-29: `#[serde(default)]` on all fields silently accepts missing required cryptographic fields
Every field in `ShieldFromAssetLockTransitionSimpleFields` has `#[serde(default)]`, which means the constructor silently succeeds with empty vectors and zero values if the JS caller omits `actions`, `proof`, `bindingSignature`, etc. While `anchor` and `binding_signature` are validated for correct length downstream (32 and 64 bytes respectively), the other fields -- `actions`, `proof`, `value_balance`, `signature` -- will silently default to empty/zero.
This creates a confusing failure mode: instead of getting a clear "missing field" error at the WASM boundary, a JS caller omitting required fields gets a structurally valid but logically broken transition that will fail much later during validation.
Consider removing `#[serde(default)]` from fields that are truly required (actions, proof, binding_signature, signature), or at minimum adding explicit emptiness checks after deserialization.
In `packages/wasm-sdk/src/queries/shielded.rs`:
- [SUGGESTION] lines 92-93: `Uint8Array::new(&n)` polymorphic behavior may allocate attacker-chosen buffers
`parse_nullifiers()` calls `Uint8Array::new(&n)` on each array element. In JS, `new Uint8Array(number)` creates a *new* zero-filled buffer of that length. If a caller passes `[4294967295]` (a plain number instead of a Uint8Array), this would attempt to allocate a 4GB buffer.
The 32-byte length check on the next line catches this eventually, but only after the allocation completes. Consider using `Uint8Array::new(&n.dyn_into::().map_err(...)?)` or checking `n.is_instance_of::()` first to reject non-Uint8Array inputs early.
| * ShieldTransition serialized as a plain object. | ||
| */ | ||
| export interface ShieldTransitionObject { | ||
| $formatVersion: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blocking: TS interfaces declare $formatVersion but Rust serde tag is $version -- breaks constructors and output types
All 5 shielded transition Rust enums (ShieldTransition, ShieldFromAssetLockTransition, ShieldedTransferTransition, ShieldedWithdrawalTransition, UnshieldTransition) use #[serde(tag = "$version")], but the TS Object/JSON interfaces in this delta were changed to $formatVersion.
Impact on constructors: The 4 constructors using serde_wasm_bindgen::from_value() require {"$version": "0", ...} as the serde discriminant. A JS caller following the TS types will pass {"$formatVersion": "0", ...} - deserialization fails with a missing-tag error.
Impact on output: toObject()/toJSON() serialize through serde and produce {"$version": "0", ...}, but the TS return types declare $formatVersion. TypeScript consumers accessing obj.$formatVersion will get undefined.
The rest of the codebase (Identity, DataContract, address-based transitions) already uses serde(tag = "$formatVersion"). The 5 shielded enums are inconsistent -- update the Rust serde tags in packages/rs-dpp/src/state_transition/state_transitions/shielded/*/mod.rs to serde(tag = "$formatVersion").
source: ['claude', 'codex']
Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/wasm-dpp2/src/shielded/shield_transition.rs`:
- [BLOCKING] line 68: TS interfaces declare `$formatVersion` but Rust serde tag is `$version` -- breaks constructors and output types
All 5 shielded transition Rust enums (`ShieldTransition`, `ShieldFromAssetLockTransition`, `ShieldedTransferTransition`, `ShieldedWithdrawalTransition`, `UnshieldTransition`) use `#[serde(tag = "$version")]`, but the TS Object/JSON interfaces in this delta were changed to `$formatVersion`.
**Impact on constructors:** The 4 constructors using `serde_wasm_bindgen::from_value()` require `{"$version": "0", ...}` as the serde discriminant. A JS caller following the TS types will pass `{"$formatVersion": "0", ...}` - deserialization fails with a missing-tag error.
**Impact on output:** `toObject()`/`toJSON()` serialize through serde and produce `{"$version": "0", ...}`, but the TS return types declare `$formatVersion`. TypeScript consumers accessing `obj.$formatVersion` will get `undefined`.
The rest of the codebase (Identity, DataContract, address-based transitions) already uses `serde(tag = "$formatVersion")`. The 5 shielded enums are inconsistent -- update the Rust serde tags in `packages/rs-dpp/src/state_transition/state_transitions/shie lded/*/mod.rs` to `serde(tag = "$formatVersion")`.
| #[derive(Deserialize)] | ||
| #[serde(rename_all = "camelCase")] | ||
| struct ShieldFromAssetLockTransitionSimpleFields { | ||
| #[serde(default)] | ||
|
actions: Vec |
||
| #[serde(default)] | ||
| value_balance: u64, | ||
| #[serde(default)] | ||
|
anchor: Vec |
||
| #[serde(default)] | ||
|
proof: Vec |
||
| #[serde(default)] | ||
|
binding_signature: Vec |
||
| #[serde(default)] | ||
|
signature: Vec |
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: #[serde(default)] on all fields silently accepts missing required cryptographic fields
Every field in ShieldFromAssetLockTransitionSimpleFields has #[serde(default)], which means the constructor silently succeeds with empty vectors and zero values if the JS caller omits actions, proof, bindingSignature, etc. While anchor and binding_signature are validated for correct length downstream (32 and 64 bytes respectively), the other fields -- actions, proof, value_balance, signature -- will silently default to empty/zero.
This creates a confusing failure mode: instead of getting a clear "missing field" error at the WASM boundary, a JS caller omitting required fields gets a structurally valid but logically broken transition that will fail much later during validation.
Consider removing #[serde(default)] from fields that are truly required (actions, proof, binding_signature, signature), or at minimum adding explicit emptiness checks after deserialization.
source: ['claude', 'codex']
Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs`:
- [SUGGESTION] lines 14-29: `#[serde(default)]` on all fields silently accepts missing required cryptographic fields
Every field in `ShieldFromAssetLockTransitionSimpleFields` has `#[serde(default)]`, which means the constructor silently succeeds with empty vectors and zero values if the JS caller omits `actions`, `proof`, `bindingSignature`, etc. While `anchor` and `binding_signature` are validated for correct length downstream (32 and 64 bytes respectively), the other fields -- `actions`, `proof`, `value_balance`, `signature` -- will silently default to empty/zero.
This creates a confusing failure mode: instead of getting a clear "missing field" error at the WASM boundary, a JS caller omitting required fields gets a structurally valid but logically broken transition that will fail much later during validation.
Consider removing `#[serde(default)]` from fields that are truly required (actions, proof, binding_signature, signature), or at minimum adding explicit emptiness checks after deserialization.
| let uint8_arr = Uint8Array::new(&n); | ||
| let bytes = uint8_arr.to_vec(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Uint8Array::new(&n) polymorphic behavior may allocate attacker-chosen buffers
parse_nullifiers() calls Uint8Array::new(&n) on each array element. In JS, new Uint8Array(number) creates a new zero-filled buffer of that length. If a caller passes [4294967295] (a plain number instead of a Uint8Array), this would attempt to allocate a 4GB buffer.
The 32-byte length check on the next line catches this eventually, but only after the allocation completes. Consider using Uint8Array::new(&n.dyn_into:: or checking n.is_instance_of:: first to reject non-Uint8Array inputs early.
source: ['claude', 'codex']
Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/wasm-sdk/src/queries/shielded.rs`:
- [SUGGESTION] lines 92-93: `Uint8Array::new(&n)` polymorphic behavior may allocate attacker-chosen buffers
`parse_nullifiers()` calls `Uint8Array::new(&n)` on each array element. In JS, `new Uint8Array(number)` creates a *new* zero-filled buffer of that length. If a caller passes `[4294967295]` (a plain number instead of a Uint8Array), this would attempt to allocate a 4GB buffer.
The 32-byte length check on the next line catches this eventually, but only after the allocation completes. Consider using `Uint8Array::new(&n.dyn_into::().map_err(...)?)` or checking `n.is_instance_of::()` first to reject non-Uint8Array inputs early.
| export interface UnshieldTransitionObject { | ||
| $formatVersion: string; | ||
| outputAddress: object; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: outputAddress typed as bare object -- inconsistent with rest of PR
All other complex types in this iteration received properly typed TS interfaces (SerializedOrchardAction, AssetLockProofObject, FeeStrategyStepObject, AddressWitnessObject), but outputAddress in both UnshieldTransitionObject and UnshieldTransitionJSON is still object. Consider adding a PlatformAddressObject / PlatformAddressJSON interface for type safety consistency.
source: ['claude']