- 
                Notifications
    You must be signed in to change notification settings 
- Fork 0
feat: improve txmetadata features #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
b31d98e    to
    29d7618      
    Compare
  
    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.
Looks good
| merge with  | 
…form into feat/improve-txmetadata
…form into feat/improve-txmetadata
…n-platform into feat/improve-txmetadata
…n-platform into feat/improve-txmetadata
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.
Looks good
…form into feat/improve-txmetadata
| WalkthroughVersion and build bumps; large SWIG/FFI remap and ignore rule overhaul; added wallet-utils protobuf and migrated TxMetadata to versioned CBOR/Protobuf batching with encryption, publish, and decryption; updated platform app IDs and examples; expanded tests; minor Rust SDK error-handling and export visibility changes. Changes
 Sequence Diagram(s)sequenceDiagram
  autonumber
  actor App
  participant BI as BlockchainIdentity
  participant TM as TxMetadata
  participant Doc as Documents API
  App->>BI: publishTxMetaData(items, keyParam?, encKeyIndex, version, progress?)
  BI->>BI: createTxMetadata(items, keyParam?, encKeyIndex, version)
  note right of BI: Build per-document payloads (CBOR or Protobuf)<br/>Select encryption key by index and encrypt
  loop per document
    BI->>Doc: publish(Document for wallet-utils data contract)
    Doc-->>BI: ack Document
    BI-->>App: optional progress update
  end
  BI-->>App: List<Document> (published)
sequenceDiagram
  autonumber
  participant Caller
  participant TMD as TxMetadataDocument
  participant Dec as Decrypt
  Caller->>TMD: decryptTxMetadata(doc, keyParam?)
  TMD->>Dec: decrypt payload -> decryptedBytes
  alt first byte == VERSION_CBOR
    Note right of TMD: txMetadataVersion = CBOR
    TMD-->>Caller: items = Cbor.decodeList(decryptedBytes[1..])
  else first byte == VERSION_PROTOBUF
    Note right of TMD: txMetadataVersion = PROTOBUF
    TMD-->>Caller: items = TxMetadataBatch.parseFrom(decryptedBytes[1..]).items
  else
    TMD-->>Caller: error (unknown version)
  end
sequenceDiagram
  autonumber
  participant Caller
  participant FD as fetch_document.rs
  Caller->>FD: DocumentQuery::new(...)
  alt Ok(query)
    FD-->>Caller: proceed with query.with_where(...)
  else Err(e)
    Note right of FD: Return Err(String) instead of panicking
    FD-->>Caller: Err(e.to_string())
  end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120+ minutes Poem
 Pre-merge checks and finishing touches❌ Failed checks (1 warning)
 ✅ Passed checks (2 passed)
 ✨ Finishing touches
 🧪 Generate unit tests (beta)
 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: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️  Outside diff range comments (1)
README.md (1)
55-60: Fix README version to match published artifact.Line 55 advertises
dppVersion = "2.0.1", but the build currently ships as2.0.1-TX-SNAPSHOT(see build.gradle Line 2). Anyone following the README will pull a non-existent artifact and break their build. Please update the snippet (or release a 2.0.1 artifact) so the instructions resolve correctly.
🧹 Nitpick comments (3)
dash-sdk-java/src/test/java/org/dashj/platform/sdk/ValueTest.java (1)
42-47: Remove the commented dead code.The commented
//v2.delete()on line 43 should be removed entirely, as thev2variable no longer exists in this test.Apply this diff to clean up the commented code:
v1.delete(); - //v2.delete(); v3.delete();dpp/src/main/proto/wallet-utils.proto (1)
9-9: Consider fixed-point representation for financial fields.The use of
doubleforexchangeRate(line 9) andoriginalPrice(line 17) can lead to precision issues with financial calculations due to floating-point representation limitations.Consider using one of these alternatives:
- Option 1 (Recommended): Use string representation with a documented format (e.g., "123.45" for currency amounts), allowing the application layer to parse with appropriate decimal precision.
- Option 2: Use fixed-point representation with separate integer fields for the whole and fractional parts, or use an integer representing the smallest currency unit (e.g., cents).
Example for Option 1:
- optional double exchangeRate = 4; + optional string exchangeRate = 4; // decimal string, e.g., "1.2345"- optional double originalPrice = 12; + optional string originalPrice = 12; // decimal string, e.g., "99.99"Also applies to: 17-17
dpp/src/test/kotlin/org/dashj/platform/contracts/wallet/TxMetaDataTests.kt (1)
226-229: Remove the empty test stub.
publishToPlatform()contains no assertions or behavior. Keeping an empty test just adds noise and trips detekt’s empty-block rule. Drop the method or flesh it out with real coverage.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
- dash-sdk-android/src/main/rust/Cargo.lockis excluded by- !**/*.lock
- dash-sdk-bindings/Cargo.lockis excluded by- !**/*.lock
- platform-mobile/Cargo.lockis excluded by- !**/*.lock
📒 Files selected for processing (24)
- README.md(1 hunks)
- build.gradle(1 hunks)
- dash-sdk-java/build.gradle(1 hunks)
- dash-sdk-java/src/main/cpp/clone.h(2 hunks)
- dash-sdk-java/src/main/swig/generics/result.i(0 hunks)
- dash-sdk-java/src/main/swig/ignore.i(5 hunks)
- dash-sdk-java/src/test/java/org/dashj/platform/sdk/DataContractTest.java(3 hunks)
- dash-sdk-java/src/test/java/org/dashj/platform/sdk/ValueTest.java(1 hunks)
- dpp/src/main/java/org/dashj/platform/dapiclient/SystemIds.kt(1 hunks)
- dpp/src/main/java/org/dashj/platform/dashpay/BlockchainIdentity.kt(6 hunks)
- dpp/src/main/java/org/dashj/platform/sdk/platform/Platform.kt(1 hunks)
- dpp/src/main/java/org/dashj/platform/wallet/IdentityVerify.kt(1 hunks)
- dpp/src/main/java/org/dashj/platform/wallet/TxMetadata.kt(3 hunks)
- dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt(3 hunks)
- dpp/src/main/java/org/dashj/platform/wallet/TxMetadataItem.kt(4 hunks)
- dpp/src/main/proto/wallet-utils.proto(1 hunks)
- dpp/src/test/kotlin/org/dashj/platform/contracts/wallet/TxMetaDataTests.kt(5 hunks)
- dpp/src/test/kotlin/org/dashj/platform/contracts/wallet/TxMetadataWalletTest.kt(1 hunks)
- examples/src/main/kotlin/dashj/org/platform/CreateTxMetadata.kt(2 hunks)
- examples/src/main/kotlin/dashj/org/platform/CreateTxMetadataTest.kt(1 hunks)
- examples/src/main/kotlin/dashj/org/platform/DisplayTxMetadata.kt(2 hunks)
- examples/src/main/kotlin/dashj/org/platform/RegisteredNamesWithBalance.kt(1 hunks)
- platform-mobile/src/fetch_document.rs(1 hunks)
- platform-mobile/src/sdk.rs(2 hunks)
💤 Files with no reviewable changes (1)
- dash-sdk-java/src/main/swig/generics/result.i
🧰 Additional context used
🧬 Code graph analysis (1)
dpp/src/main/java/org/dashj/platform/wallet/TxMetadata.kt (2)
dpp/src/main/java/org/dashj/platform/dashpay/Profiles.kt (2)
get(166-168)
get(170-185)dpp/src/main/java/org/dashj/platform/sdk/platform/Documents.kt (2)
get(151-167)
get(169-187)
🪛 Buf (1.57.2)
dpp/src/main/proto/wallet-utils.proto
3-3: Files with package "org.dashj.platform.wallet" must be within a directory "org/dashj/platform/wallet" relative to root but were in directory "dpp/src/main/proto".
(PACKAGE_DIRECTORY_MATCH)
🪛 detekt (1.23.8)
examples/src/main/kotlin/dashj/org/platform/RegisteredNamesWithBalance.kt
[warning] 74-74: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
dpp/src/test/kotlin/org/dashj/platform/contracts/wallet/TxMetaDataTests.kt
[warning] 227-229: This empty block of code can be removed.
(detekt.empty-blocks.EmptyFunctionBlock)
🔇 Additional comments (20)
platform-mobile/src/fetch_document.rs (1)
106-109: LGTM! Improved error handling.The change from
.unwrap()to explicit error matching prevents potential panics and provides better error propagation, which is consistent with the function's error handling pattern.dash-sdk-java/src/test/java/org/dashj/platform/sdk/ValueTest.java (1)
23-48: LGTM! Redundant test code appropriately removed.The removal of the v2 test code for I16 is appropriate since I16 is already comprehensively tested in
createPlatformValuePrimitiveTest()at line 148. This cleanup reduces code duplication without affecting test coverage.dash-sdk-java/src/test/java/org/dashj/platform/sdk/DataContractTest.java (2)
20-22: LGTM: Improved null-safety.The defensive pattern of checking
isPresent()before callingget()prevents potentialNoSuchElementExceptionif theOptionalis empty.
34-36: LGTM: Improved null-safety.Consistent with the pattern above—checking
isPresent()before callingget()prevents potential runtime exceptions.platform-mobile/src/sdk.rs (2)
225-225: LGTM! Good observability improvement.Adding the SDK version log is consistent with the existing pattern in
create_dash_sdk_with_context(line 173) and improves observability.
62-92: No external FFI bindings referenceupdate_sdk_with_address_list; removing its export attribute is safe.dpp/src/main/java/org/dashj/platform/dapiclient/SystemIds.kt (1)
10-19: LGTM! Wallet-utils identifiers added consistently.The addition of wallet-utils identifiers and the refactoring of existing IDs to use
Sha256Hash.ZERO_HASHfollow a consistent pattern and align with the broader wallet-utils integration across the codebase.dpp/src/main/java/org/dashj/platform/sdk/platform/Platform.kt (1)
80-91: LGTM! App definitions updated to support wallet-utils and identity-verify.The addition of the wallet-utils app and the environment-specific identity-verify app definitions are consistent with the system-wide migration to wallet-utils identifiers introduced in SystemIds.kt.
examples/src/main/kotlin/dashj/org/platform/DisplayTxMetadata.kt (2)
29-38: LGTM! Improved argument handling with default identity support.The refactored argument parsing now supports both interactive phrase input and default identity seeds, which improves the utility's flexibility.
55-55: LGTM! Consistent with updated metadata ordering.The change from
createdAttoupdatedAtaligns with the broader shift in TxMetadata.kt where ordering and filtering now use$updatedAtinstead of$createdAt.dpp/src/main/java/org/dashj/platform/wallet/IdentityVerify.kt (1)
30-30: LGTM! Document constant updated to reflect identity-verify app.The DOCUMENT constant change from "dashwallet.identityVerify" to "identity-verify.identityVerify" is consistent with the Platform.kt updates where the identity-verify app definition replaces dashwallet in environment mappings.
examples/src/main/kotlin/dashj/org/platform/CreateTxMetadata.kt (2)
10-10: LGTM! Import updated to reflect TxMetadataItem package relocation.The import change from
org.dashj.platform.contracts.wallet.TxMetadataItemtoorg.dashj.platform.wallet.TxMetadataItemis consistent with the broader reorganization of wallet-related types into the wallet package.
47-47: LGTM! Updated to use versioned metadata publishing.The addition of the keyIndex parameter (1) and version parameter (TxMetadataDocument.VERSION_PROTOBUF) aligns with the new versioned metadata handling introduced in TxMetadataDocument.kt and the updated publishTxMetaData signature.
dpp/src/main/java/org/dashj/platform/wallet/TxMetadata.kt (4)
69-91: LGTM! New publish method follows established patterns.The new
publish()method correctly retrieves the HIGH security level key, calls the platform SDK with appropriate parameters, and returns a Document. The implementation is consistent with the existingcreate()method pattern.
113-121: LGTM! Version-aware buffer serialization implemented correctly.The
getBuffer()method properly handles both CBOR and Protobuf serialization formats based on the version parameter, with appropriate error handling for invalid versions.
37-37: LGTM! Document constant updated for wallet-utils migration.The DOCUMENT constant change from "dashwallet.tx_metadata" to "wallet-utils.txMetadata" is consistent with the broader migration to wallet-utils identifiers and data contracts.
130-133: ```bash
#!/bin/bashShow get() signature in TxMetadata.kt
rg -n 'fun get' -g 'dpp/src/main/java/org/dashj/platform/wallet/TxMetadata.kt' -C3
Find direct calls to TxMetadata(...).get(
rg -nP 'TxMetadata([^)]).get(' -g '.kt'
Find instantiations of TxMetadata
rg -nP 'val\s+\w+\s*=\sTxMetadata(' -g '.kt'
</blockquote></details> <details> <summary>dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt (1)</summary><blockquote> `27-39`: **LGTM! Version tracking added with good encapsulation.** The addition of version constants and the `txMetadataVersion` field with a private setter provides clean version tracking for metadata documents. The field is appropriately initialized to `VERSION_UNKNOWN` and updated during decryption. </blockquote></details> <details> <summary>dpp/src/main/java/org/dashj/platform/wallet/TxMetadataItem.kt (2)</summary><blockquote> `8-8`: **LGTM: Package reorganization and new imports.** The package move to `org.dashj.platform.wallet` and the addition of protobuf/formatting imports appropriately support the new proto conversion features. Also applies to: 11-12, 15-15 --- `281-281`: **Note: toString() now uses locale-dependent formatting.** The toString() method now delegates to `toJson()`, which means numeric fields (exchangeRate, originalPrice) will be displayed with locale-specific currency formatting. This is acceptable for debugging/logging but be aware that the string representation will vary across locales. </blockquote></details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
| assertEquals(3, dataContract.getDoc_types().size()); | ||
| assertTrue("didn't find txMetadata", dataContract.getDoc_types().stream().allMatch(type -> type.equals("txMetadata"))); | 
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.
Fix the assertion logic.
Line 39 uses allMatch(type -> type.equals("txMetadata")), which checks that every doc_type is named "txMetadata". With 3 doc_types, this assertion would only pass if all three have identical names, which violates the principle that data contract doc types should have distinct names.
Additionally, the error message "didn't find txMetadata" is misleading—it suggests checking for existence, but allMatch verifies that all types are "txMetadata".
Did you intend to:
- Verify that "txMetadata"exists among the doc_types? UseanyMatch:-assertTrue("didn't find txMetadata", dataContract.getDoc_types().stream().allMatch(type -> type.equals("txMetadata"))); +assertTrue("didn't find txMetadata", dataContract.getDoc_types().stream().anyMatch(type -> type.equals("txMetadata"))); 
- Or verify specific doc_type names (e.g., three distinct types)? Assert the exact set:
Set<String> expectedTypes = Set.of("txMetadata", "typeA", "typeB"); assertEquals(expectedTypes, new HashSet<>(dataContract.getDoc_types())); 
Please clarify the intended assertion and apply the appropriate fix.
🤖 Prompt for AI Agents
In dash-sdk-java/src/test/java/org/dashj/platform/sdk/DataContractTest.java
around lines 38-39, the test uses allMatch which wrongly asserts every doc_type
equals "txMetadata". Replace that with anyMatch to assert existence (and
keep/update the failure message to "didn't find txMetadata"), OR if the intent
is to verify all three specific distinct types, assert the exact set of names by
comparing a Set of expected names to new HashSet<>(dataContract.getDoc_types())
so the test validates the exact distinct types.
        
          
                dpp/src/test/kotlin/org/dashj/platform/contracts/wallet/TxMetadataWalletTest.kt
          
            Show resolved
            Hide resolved
        
              
          
                examples/src/main/kotlin/dashj/org/platform/CreateTxMetadataTest.kt
              
                Outdated
          
            Show resolved
            Hide resolved
        
              
          
                examples/src/main/kotlin/dashj/org/platform/RegisteredNamesWithBalance.kt
          
            Show resolved
            Hide resolved
        
              
          
                examples/src/main/kotlin/dashj/org/platform/RegisteredNamesWithBalance.kt
          
            Show resolved
            Hide resolved
        
      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: 6
♻️ Duplicate comments (6)
dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt (1)
77-78: Make the error explicit with expected versions.The message is better than before but still omits supported values. Include both constants.
- else -> error("unknown txmetadata version $version") + else -> error("Unknown tx metadata version $version. Expected $VERSION_CBOR (CBOR) or $VERSION_PROTOBUF (Protobuf)")dpp/src/main/java/org/dashj/platform/wallet/TxMetadataItem.kt (5)
80-97: Proto defaults vs presence — avoid sentinel checks.Using
!= 0/""to infer presence is brittle (floats, legit zeros, future defaults). PreferhasXxx()or wrapper/optional fields in the proto.Example:
constructor(p: WalletUtils.TxMetadataItem) : this( p.txId.toByteArray(), if (p.hasTimestamp()) p.timestamp else null, if (p.hasMemo()) p.memo else null, if (p.hasExchangeRate()) p.exchangeRate else null, // ... if (p.otherDataCount > 0) p.otherDataMap else null )If fields aren’t optional, consider switching to wrappers (e.g., google.protobuf.*Value).
103-106: Fix for timestamp map assignment looks good.
map["timestamp"] = itresolves the prior critical bug (usingto).
166-214: toJson should emit raw numbers and avoid locale-dependent formatting.Formatting with
DecimalFormat.getCurrencyInstance()produces locale-specific strings; JSON consumers typically expect numbers.- fun toJson(): Map<String, Any?> { - val map = hashMapOf<String, String?>( + fun toJson(): Map<String, Any?> { + val map = hashMapOf<String, Any?>( "txId" to Sha256Hash.wrap(txId).toString() ) - timestamp?.let { map["timestamp"] = it.toString() } + timestamp?.let { map["timestamp"] = it } memo?.let { map["memo"] = it } - exchangeRate?.let { - val format = DecimalFormat.getCurrencyInstance() - map["exchangeRate"] = format.format(it) - } + exchangeRate?.let { map["exchangeRate"] = it } currencyCode?.let { map["currencyCode"] = it } // ... - originalPrice?.let { - val format = DecimalFormat.getCurrencyInstance() - map["originalPrice"] = format.format(it) - } + originalPrice?.let { map["originalPrice"] = it }If display formatting is needed, provide a separate
toDisplayMap(Locale)helper.
263-269: hashCode byte order reversal — confirm intent or restore for stability.Using bytes [3,2,1,0] breaks compatibility with prior [0,1,2,3] ordering (affects HashMap/HashSet behavior).
If unintended, revert:
- return Ints.fromBytes( - txId[3], - txId[2], - txId[1], - txId[0] - ) + return Ints.fromBytes(txId[0], txId[1], txId[2], txId[3])If intentional, document the breaking change and add migration notes/tests.
272-292: customIconUrl mapping fixed.Correct field (
builder.customIconUrl) is now used; previous copy-paste bug resolved.
🧹 Nitpick comments (10)
examples/src/main/kotlin/dashj/org/platform/PlatformExplorer.kt (1)
556-571: Pagination logic is correct, but consider reducing duplication.The pagination implementation correctly fetches additional pages until fewer than 100 items are returned. However, the API call and printing logic are duplicated between the initial fetch and the loop body.
Consider refactoring to a do-while loop to eliminate duplication:
- var contestedResources = result.unwrap() - var list = contestedResources._0 - println("${list.size} contested resources: ") - for (item in list) { - println(item.value.text) - } - - while (list.size == 100) { - val result2 = dashsdk.platformMobileVotingGetContestedResources( - sdk, - "domain", - Identifier(dpnsContractId), - 100, - contestedResources._0.lastOrNull()?._0, - false - ) - contestedResources = result2.unwrap() - list = contestedResources._0 + var contestedResources = result.unwrap() + var startAfter: Any? = null + do { + val pageResult = dashsdk.platformMobileVotingGetContestedResources( + sdk, + "domain", + Identifier(dpnsContractId), + 100, + startAfter, + false + ) + contestedResources = pageResult.unwrap() + val list = contestedResources._0 println("${list.size} contested resources: ") for (item in list) { println(item.value.text) } - } + startAfter = list.lastOrNull()?._0 + } while (list.size == 100)examples/src/main/kotlin/dashj/org/platform/DisplayIdentityKeys.kt (1)
45-49: Consider adding a security warning comment.The additional output provides useful debugging information for this example program. However, the code prints sensitive data including private keys (lines 45-46) and addresses (line 49). While this is expected for a utility explicitly designed to display identity keys, consider adding a comment warning that this code should never be used in production environments.
Minor: Fix spacing in line 46.
Add a space after the comma for consistency:
- println("privateKey: ${DumpedPrivateKey(client.params,firstKey.privKeyBytes, true)}") + println("privateKey: ${DumpedPrivateKey(client.params, firstKey.privKeyBytes, true)}")examples/src/main/kotlin/dashj/org/platform/CreateTxMetadataTest.kt (2)
169-173: Consider removing commented code.The commented test block for
txMetadataItemFourshould either be uncommented if needed or removed to maintain code cleanliness.
193-193: Clarify magic number check.The condition
encryptedMetadata[0] != 0.toByte()uses a magic number without explanation. Consider extracting this into a named constant or adding a comment explaining what the first byte represents (e.g., encryption status flag).dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt (2)
58-66: Guard against short/invalid plaintext and add defensive parse handling.Accessing
decryptedData[0]will throw if the plaintext is too short. Add a minimal-length check and fail fast with a clear message.val decryptedData = cipher.decrypt(EncryptedData(iv, encryptedData), keyParameter) - val version = decryptedData.copyOfRange(0, 1)[0].toInt() and 0xFF + require(decryptedData.isNotEmpty()) { "Decrypted metadata is empty (no version byte present)" } + val version = (decryptedData[0].toInt() and 0xFF)
59-63: Consider adding integrity protection (AEAD/HMAC).AES-CBC provides confidentiality but not integrity. A wrong key or tampering can yield garbage that passes to decoders. If possible, switch to an AEAD mode (e.g., AES-GCM) or include an HMAC over version+payload and verify before parsing.
Would you like a follow-up PR sketching an authenticated format?
dpp/src/main/java/org/dashj/platform/wallet/TxMetadataItem.kt (1)
228-231: Don’t flatten otherData into top-level JSON.Merging
otherDatainto the root risks key collisions (e.g., “memo”) and diverges fromtoObject()which nests it.- otherData?.let { - map.putAll(it) - } + otherData?.let { + map["otherData"] = it + } ``` <!-- review_comment_end --> </blockquote></details> <details> <summary>dpp/src/test/kotlin/org/dashj/platform/dashpay/BlockchainIdentityTest.kt (3)</summary><blockquote> `155-157`: **Avoid magic slack in size check.** `+ 100` is arbitrary. Either compute exact overhead (IV + padding + version byte) or assert `<= MAX_ENCRYPTED_SIZE` on the plaintext before encryption. Example: ```kotlin // Check serialized payload length before encrypt assertTrue(serialized.size <= TxMetadataDocument.MAX_ENCRYPTED_SIZE, "Payload too large")Or derive a constant for IV(16) + max padding(16) + version(1).
98-103: Assert detected version on decrypt.Strengthen the round-trip by asserting the parsed version.
- val decryptedItems = blockchainIdentity.decryptTxMetadata(txMetadataDocument, null) + val decryptedItems = blockchainIdentity.decryptTxMetadata(txMetadataDocument, null) + assertEquals(TxMetadataDocument.VERSION_PROTOBUF, txMetadataDocument.txMetadataVersion)
60-108: Consider adding a CBOR round-trip test.Add a mirror of this test using
TxMetadataDocument.VERSION_CBORto validate both paths.I can draft the CBOR variant if helpful.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
- dash-sdk-android/src/main/rust/Cargo.lockis excluded by- !**/*.lock
- dash-sdk-bindings/Cargo.lockis excluded by- !**/*.lock
- platform-mobile/Cargo.lockis excluded by- !**/*.lock
📒 Files selected for processing (8)
- dash-sdk-java/src/test/java/org/dashj/platform/sdk/DataContractTest.java(2 hunks)
- dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt(3 hunks)
- dpp/src/main/java/org/dashj/platform/wallet/TxMetadataItem.kt(6 hunks)
- dpp/src/test/kotlin/org/dashj/platform/dashpay/BlockchainIdentityTest.kt(2 hunks)
- examples/src/main/kotlin/dashj/org/platform/CreateTxMetadataTest.kt(1 hunks)
- examples/src/main/kotlin/dashj/org/platform/DisplayIdentityKeys.kt(2 hunks)
- examples/src/main/kotlin/dashj/org/platform/PlatformExplorer.kt(1 hunks)
- examples/src/main/kotlin/dashj/org/platform/RegisteredNamesWithBalance.kt(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- dash-sdk-java/src/test/java/org/dashj/platform/sdk/DataContractTest.java
- examples/src/main/kotlin/dashj/org/platform/RegisteredNamesWithBalance.kt
🔇 Additional comments (3)
examples/src/main/kotlin/dashj/org/platform/PlatformExplorer.kt (1)
549-550: LGTM! Necessary mutability for pagination.The change from
valtovaris correct and required for reassignment within the pagination loop.examples/src/main/kotlin/dashj/org/platform/DisplayIdentityKeys.kt (1)
9-10: LGTM!The new imports are used appropriately in the enhanced output statements.
examples/src/main/kotlin/dashj/org/platform/CreateTxMetadataTest.kt (1)
142-144: Off-by-one issue resolved.The previous off-by-one error has been correctly fixed by replacing
IntRange(0, count)withrepeat(count), ensuring exactlycountitems are added.
| // 2^16 + 2 | ||
| val childNumber = ChildNumber(2 shl 15 + 1, true) | ||
| const val MAX_ENCRYPTED_SIZE = 4096 - 32 // leave room for a partially filled block and the IV | ||
|  | 
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.
Operator precedence bug in child number expression.
2 shl 15 + 1 is parsed as 2 shl (15 + 1) yielding 131072, not “2^16 + 2” as the comment states. Make the intent explicit.
Apply:
-        // 2^16 + 2
-        val childNumber = ChildNumber(2 shl 15 + 1, true)
+        // 2^16 + 2 = 65538
+        val childNumber = ChildNumber((1 shl 16) + 2, true)Also fix the same expression in tests (BlockchainIdentityTest.kt Lines 172–173).
🤖 Prompt for AI Agents
In dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt around
lines 23–26, the expression `2 shl 15 + 1` has wrong operator precedence and
does not match the comment "2^16 + 2"; replace it with an explicit calculation
such as `(1 shl 16) + 2` (or a literal 65538) to make the intent unambiguous,
and apply the same fix to the identical expression in tests at
BlockchainIdentityTest.kt lines 172–173.
| val version = decryptedData.copyOfRange(0, 1)[0].toInt() and 0xFF | ||
| return when (version) { | ||
| VERSION_CBOR -> { | ||
| val list = Cbor.decodeList(decryptedData) | ||
| this.txMetadataVersion = VERSION_CBOR | ||
| // use .map to convert to List<TxMetadataItem> | ||
| list.map { TxMetadataItem(it as Map<String, Any?>) } | ||
| } | 
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.
CBOR branch ignores version byte; parsing starts at offset 0.
You read a leading version byte (Line 64) but CBOR decoding uses the full buffer instead of payload = decryptedData[1..]. This will corrupt parsing.
-            VERSION_CBOR -> {
-                val list = Cbor.decodeList(decryptedData)
+            VERSION_CBOR -> {
+                val list = Cbor.decodeList(decryptedData.copyOfRange(1, decryptedData.size))
                 this.txMetadataVersion = VERSION_CBOR
                 // use .map to convert to List<TxMetadataItem>
                 list.map { TxMetadataItem(it as Map<String, Any?>) }
             }Additionally, ensure the serializer prepends the version byte (see verification script below).
🤖 Prompt for AI Agents
In dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt around
lines 64 to 71, the CBOR branch currently decodes the entire decryptedData
including the leading version byte, corrupting the CBOR parse; change the decode
call to use the payload excluding the first byte (copyOfRange(1,
decryptedData.size)) so parsing starts at offset 1, set txMetadataVersion as
before, and return the mapped List<TxMetadataItem>; also update/verify the
serializer to prepend the single-byte version header to the serialized payload
so round-trip encoding/decoding includes the version byte.
| val version = decryptedData.copyOfRange(0, 1)[0].toInt() and 0xFF | ||
| return when (version) { | ||
| VERSION_CBOR -> { | ||
| val list = Cbor.decodeList(decryptedData) | ||
| this.txMetadataVersion = VERSION_CBOR | ||
| // use .map to convert to List<TxMetadataItem> | ||
| list.map { TxMetadataItem(it as Map<String, Any?>) } | ||
| } | ||
| VERSION_PROTOBUF -> { | ||
| val batch = TxMetadataBatch.parser().parseFrom(decryptedData, 1, decryptedData.size - 1) | ||
| txMetadataVersion = VERSION_PROTOBUF | ||
| batch.itemsList.map { TxMetadataItem(it) } | ||
| } | 
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.
🧩 Analysis chain
Verify serializer prepends the version byte.
Decrypt expects a leading version byte (offset 0). Please confirm TxMetadata.getBuffer() returns byteArrayOf(version) + payload.
Run:
🏁 Script executed:
#!/bin/bash
# Verify that serializer prefixes a version byte
set -euo pipefail
echo "Searching for TxMetadata.getBuffer() and version usage..."
fd -a --hidden --fixed-strings "TxMetadata.kt" || true
rg -nP --type=kt -C3 'class\s+TxMetadata\b|fun\s+getBuffer\s*\(|VERSION_CBOR|VERSION_PROTOBUF|byteArrayOf\(\s*.*toByte\(\)\s*\)\s*\+' dpp/ || trueLength of output: 328
🏁 Script executed:
#!/bin/bash
# Search for TxMetadata.kt and inspect getBuffer() implementation
set -euo pipefail
# Use correct type or no type filter
echo "=== Searching for getBuffer() method ==="
rg -nP -C5 'fun\s+getBuffer\s*\(' dpp/src/main/java/org/dashj/platform/wallet/ || true
echo ""
echo "=== Searching for VERSION constants ==="
rg -nP 'VERSION_CBOR|VERSION_PROTOBUF' dpp/src/main/java/org/dashj/platform/wallet/ || true
echo ""
echo "=== Inspecting TxMetadata.kt directly ==="
cat dpp/src/main/java/org/dashj/platform/wallet/TxMetadata.kt || trueLength of output: 384
🏁 Script executed:
#!/bin/bash
# Dynamically find TxMetadata.kt and inspect it
set -euo pipefail
echo "=== Finding TxMetadata.kt files ==="
TX_METADATA_FILE=$(fd -type f "TxMetadata\.kt" | head -1)
echo "Found: $TX_METADATA_FILE"
echo ""
echo "=== Inspecting TxMetadata.kt ==="
cat "$TX_METADATA_FILE" || true
echo ""
echo "=== Searching for getBuffer() and VERSION constants in same directory ==="
DIR=$(dirname "$TX_METADATA_FILE")
rg -nP 'fun\s+getBuffer|VERSION_CBOR|VERSION_PROTOBUF|byteArrayOf' "$DIR" || trueLength of output: 206
🏁 Script executed:
#!/bin/bash
# Find and inspect TxMetadata.kt with corrected syntax
set -euo pipefail
echo "=== Finding TxMetadata.kt files ==="
TX_METADATA_FILE=$(fd "TxMetadata\.kt$" | head -1)
echo "Found: $TX_METADATA_FILE"
if [ -z "$TX_METADATA_FILE" ]; then
  echo "File not found, searching more broadly..."
  TX_METADATA_FILE=$(find . -name "TxMetadata.kt" 2>/dev/null | head -1)
  echo "Found: $TX_METADATA_FILE"
fi
echo ""
echo "=== Inspecting TxMetadata.kt ==="
cat "$TX_METADATA_FILE"Length of output: 5124
🏁 Script executed:
#!/bin/bash
# Find where getBuffer() is called and where version byte is added
set -euo pipefail
echo "=== Searching for getBuffer() calls ==="
rg -nP 'getBuffer\s*\(' --context 5 || true
echo ""
echo "=== Searching for encryption/serialization patterns ==="
rg -nP 'encrypt|byteArrayOf.*version|toByte\(\)\s*\+|VERSION_CBOR.*\+|VERSION_PROTOBUF.*\+' --context 3 || true
echo ""
echo "=== Looking at TxMetadataDocument.kt for encryption context ==="
fd "TxMetadataDocument\.kt$" -x cat {} || trueLength of output: 82012
Version byte serialization verified but CBOR decode path has a critical bug.
The version byte IS prepended to encrypted data, but not by TxMetadata.getBuffer(). Instead, it's prepended by the caller in BlockchainIdentity.createTxMetadata() (line 2326: allEncryptedData[0] = version.toByte()).
After decryption in TxMetadataDocument.decrypt(), the decrypted payload contains the version byte at offset 0. However, there's an asymmetry:
- PROTOBUF path (line 74): Correctly skips version byte with parseFrom(decryptedData, 1, decryptedData.size - 1)
- CBOR path (line 69): BUG — passes full decryptedDatatoCbor.decodeList()without skipping the version byte at offset 0
This will cause CBOR decoding to fail or produce corrupted data. Line 69 should be:
val list = Cbor.decodeList(decryptedData.copyOfRange(1, decryptedData.size))🤖 Prompt for AI Agents
In dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt around
lines 64 to 76, the CBOR decode path is incorrectly passing the entire
decryptedData (including the prepended version byte) to Cbor.decodeList; update
the CBOR branch to skip the first byte (same as the protobuf branch) by decoding
from decryptedData offset 1 to decryptedData.size so the version byte is not
included in the CBOR payload, then set txMetadataVersion and map the decoded
list to TxMetadataItem as before.
| val parentEncryptionKeyPath = org.bitcoinj.wallet.DerivationPathFactory.get(blockchainIdentity.params).blockchainIdentityECDSADerivationPath() | ||
| val parentEncryptionKey = authenticationGroupExtension.identityKeyChain.getKeyByPath(parentEncryptionKeyPath) | ||
| .deriveChildKey(ChildNumber(2, true)) | ||
| val expectedEncryptionKey = parentEncryptionKey | ||
| .deriveChildKey(ChildNumber(2 shl 15 + 1, true)) | ||
| .derive(1) | ||
| assertEquals(expectedEncryptionKey, actualEncryptionKey) | ||
| } | 
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.
Use the constant and fix precedence in derivation path.
Avoid duplicating the expression and fix the precedence bug.
-                val expectedEncryptionKey = parentEncryptionKey
-                    .deriveChildKey(ChildNumber(2 shl 15 + 1, true))
-                    .derive(1)
+                val expectedEncryptionKey = parentEncryptionKey
+                    .deriveChildKey(TxMetadataDocument.childNumber)
+                    .derive(1)🤖 Prompt for AI Agents
In dpp/src/test/kotlin/org/dashj/platform/dashpay/BlockchainIdentityTest.kt
around lines 168–175, you duplicated the derivation expression and used
incorrect operator precedence in ChildNumber(2 shl 15 + 1, true); fix by
introducing a named constant for the derived index (e.g. val
ENCRYPTION_DERIVATION_INDEX = (2 shl 15) + 1) and use that constant in the
deriveChildKey call, ensuring the shift is parenthesized as shown so the shift
happens before the addition.
| val seed = "caution kitchen month seven humble olympic author session dutch direct decrease moment" | ||
| // val seed = "half sample spirit edit dawn humor eight refuse hundred suit critic print" | 
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.
Avoid hardcoding seed phrases, even in test code.
Hardcoded mnemonic seeds pose a security risk if they control real funds or are accidentally reused in production. Consider loading test seeds from environment variables or a secure configuration file.
🤖 Prompt for AI Agents
In examples/src/main/kotlin/dashj/org/platform/CreateTxMetadataTest.kt around
lines 36-37, the test currently hardcodes a mnemonic seed; replace this by
reading the seed from an environment variable or secure test config (e.g.
System.getenv("TEST_MNEMONIC")) and fail the test with a clear error if the env
var is required but missing, or alternatively generate a random ephemeral
mnemonic at runtime for pure unit tests; ensure any fallback is explicitly
marked "test-only" and add a brief comment/documentation instructing CI/devs to
provide a test-only mnemonic via env var rather than committing real wallets.
| println("Tx Metadata: -----------------------------------") | ||
| for (doc in documents) { | ||
| val txDoc = TxMetadataDocument(doc) | ||
| if (txDoc.encryptedMetadata[0] != 0.toByte()) { | 
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.
Add bounds check before accessing array index.
Accessing txDoc.encryptedMetadata[0] without verifying the array is non-empty will throw IndexOutOfBoundsException if encryptedMetadata is empty.
Apply this diff to add a bounds check:
-            if (txDoc.encryptedMetadata[0] != 0.toByte()) {
+            if (txDoc.encryptedMetadata.isNotEmpty() && txDoc.encryptedMetadata[0] != 0.toByte()) {
                 println(JSONObject(doc.toJSON()).toString(2))
                 val txList = blockchainIdentity.decryptTxMetadata(txDoc, null)
                 println("  $txList")🤖 Prompt for AI Agents
In examples/src/main/kotlin/dashj/org/platform/CreateTxMetadataTest.kt around
line 193, the code accesses txDoc.encryptedMetadata[0] without ensuring the
array is non-empty; add a bounds check by evaluating that
txDoc.encryptedMetadata is not empty (e.g., txDoc.encryptedMetadata.isNotEmpty()
or size > 0) before accessing index 0, and adjust the conditional to only
inspect [0] when the check passes (handle the empty case accordingly).
Updates to support tx metadata changes
Summary by CodeRabbit
New Features
Improvements
Documentation
Chores